diff --git a/.eslintignore b/.eslintignore
index eddb5e16e44bf44a8e8be5ffac3da5aed8f70591..43f35ac5216e0577bbde544a4dd37c5cc04667b9 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -11,5 +11,6 @@ app/lib/dal/sqliteDAL/index/*.js
 app/lib/dal/fileDALs/*.js
 app/lib/dal/fileDAL.js
 app/service/*.js
+app/lib/rules/local_rules.js
 test/*.js
 test/**/*.js
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b0ce50ee330b11485ca8fa03fa767e722a70e2c5..21bbc63cf014d14df36e8b57d223f2e9411bfb05 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,7 @@ app/lib/dal/sqliteDAL/*.js*
 app/lib/dal/sqliteDAL/index/*.js*
 app/lib/dal/fileDALs/*.js*
 app/lib/dal/fileDAL.js*
+app/lib/rules/local_rules*.js*
 app/lib/logger*js*
 app/service/*.js*
 app/lib/wot.js*
\ No newline at end of file
diff --git a/app/lib/dto/TransactionDTO.ts b/app/lib/dto/TransactionDTO.ts
index 70ea23cba0c23db4fe66cec75131ef486a507286..478935e5d70e24e5959f05adf8437090ff5d912b 100644
--- a/app/lib/dto/TransactionDTO.ts
+++ b/app/lib/dto/TransactionDTO.ts
@@ -1,6 +1,10 @@
 import {hashf} from "../common"
 
-export class InputDTO {
+export interface BaseDTO {
+  base: number
+}
+
+export class InputDTO implements BaseDTO {
   constructor(
     public amount: number,
     public base: number,
@@ -11,7 +15,7 @@ export class InputDTO {
   ) {}
 }
 
-export class OutputDTO {
+export class OutputDTO implements BaseDTO {
   constructor(
     public amount: number,
     public base: number,
diff --git a/app/lib/rules/local_rules.js b/app/lib/rules/local_rules.js
index d60afac8fe53ec58427d38bbe7cd52883d59e4c7..3db085b94a4d3f4b44400dd4560d00c50ef16565 100644
--- a/app/lib/rules/local_rules.js
+++ b/app/lib/rules/local_rules.js
@@ -1,510 +1,475 @@
 "use strict";
-
-const co         = require('co');
-const _          = require('underscore');
-const common     = require('duniter-common');
-const indexer    = require('../indexer').Indexer;
-const BlockDTO   = require('../dto/BlockDTO').BlockDTO
-
-const constants       = common.constants
-const hashf           = common.hashf
-const keyring         = common.keyring
-const rawer           = common.rawer
-const Block           = common.document.Block
-const Identity        = common.document.Identity
-const Membership      = common.document.Membership
-const Transaction     = common.document.Transaction
-const maxAcceleration = require('./helpers').maxAcceleration
-
-let rules = {};
-
-// TODO: make a global variable 'index' instead of recomputing the index each time
-
-rules.FUNCTIONS = {
-
-  checkParameters: (block) => co(function *() {
-    if (block.number == 0 && !block.parameters) {
-      throw Error('Parameters must be provided for root block');
-    }
-    else if (block.number > 0 && block.parameters) {
-      throw Error('Parameters must not be provided for non-root block');
-    }
-    return true;
-  }),
-
-  checkProofOfWork: (block) => co(function *() {
-    let remainder = block.powMin % 16;
-    let nb_zeros = (block.powMin - remainder) / 16;
-    const powRegexp = new RegExp('^0{' + nb_zeros + '}');
-    if (!block.hash.match(powRegexp)) {
-      throw Error('Not a proof-of-work');
-    }
-    return true;
-  }),
-
-  checkInnerHash: (block) => co(function *() {
-    let inner_hash = hashf(block.getRawInnerPart()).toUpperCase();
-    if (block.inner_hash != inner_hash) {
-      throw Error('Wrong inner hash');
-    }
-    return true;
-  }),
-
-  checkPreviousHash: (block) => co(function *() {
-    if (block.number == 0 && block.previousHash) {
-      throw Error('PreviousHash must not be provided for root block');
-    }
-    else if (block.number > 0 && !block.previousHash) {
-      throw Error('PreviousHash must be provided for non-root block');
-    }
-    return true;
-  }),
-
-  checkPreviousIssuer: (block) => co(function *() {
-    if (block.number == 0 && block.previousIssuer)
-      throw Error('PreviousIssuer must not be provided for root block');
-    else if (block.number > 0 && !block.previousIssuer)
-      throw Error('PreviousIssuer must be provided for non-root block');
-    return true;
-  }),
-
-  checkUnitBase: (block) => co(function *() {
-    if (block.number == 0 && block.unitbase != 0) {
-      throw Error('UnitBase must equal 0 for root block');
-    }
-    return true;
-  }),
-
-  checkBlockSignature: (block) => co(function *() {
-    if (!keyring.verify(block.getSignedPart(), block.signature, block.issuer))
-      throw Error('Block\'s signature must match');
-    return true;
-  }),
-
-  checkBlockTimes: (block, conf) => co(function *() {
-    const time = parseInt(block.time);
-    const medianTime = parseInt(block.medianTime);
-    if (block.number > 0 && (time < medianTime || time > medianTime + maxAcceleration(conf)))
-      throw Error('A block must have its Time between MedianTime and MedianTime + ' + maxAcceleration(conf));
-    else if (block.number == 0 && time != medianTime)
-      throw Error('Root block must have Time equal MedianTime');
-    return true;
-  }),
-
-  checkIdentitiesSignature: (block) => co(function *() {
-    let i = 0;
-    let wrongSig = false;
-    while (!wrongSig && i < block.identities.length) {
-      const idty = Identity.fromInline(block.identities[i]);
-      idty.currency = block.currency;
-      wrongSig = !keyring.verify(idty.rawWithoutSig(), idty.sig, idty.pubkey);
-      if (wrongSig) {
-        throw Error('Identity\'s signature must match');
-      }
-      i++;
-    }
-    return true;
-  }),
-
-  checkIdentitiesUserIDConflict: (block, conf, index) => co(function *() {
-    const creates = indexer.iindexCreate(index);
-    const uids = _.chain(creates).pluck('uid').uniq().value();
-    if (creates.length !== uids.length) {
-      throw Error('Block must not contain twice same identity uid');
-    }
-    return true;
-  }),
-
-  checkIdentitiesPubkeyConflict: (block, conf, index) => co(function *() {
-    const creates = indexer.iindexCreate(index);
-    const pubkeys = _.chain(creates).pluck('pub').uniq().value();
-    if (creates.length !== pubkeys.length) {
-      throw Error('Block must not contain twice same identity pubkey');
-    }
-    return true;
-  }),
-
-  checkIdentitiesMatchJoin: (block, conf, index) => co(function *() {
-    const icreates = indexer.iindexCreate(index);
-    const mcreates = indexer.mindexCreate(index);
-    for (const icreate of icreates) {
-      const matching = _(mcreates).filter({ pub: icreate.pub });
-      if (matching.length == 0) {
-        throw Error('Each identity must match a newcomer line with same userid and certts');
-      }
-    }
-    return true;
-  }),
-
-  checkRevokedAreExcluded: (block, conf, index) => co(function *() {
-    const iindex = indexer.iindex(index);
-    const mindex = indexer.mindex(index);
-    const revocations = _.chain(mindex)
-      .filter((row) => row.op == constants.IDX_UPDATE && row.revoked_on !== null)
-      .pluck('pub')
-      .value();
-    for (const pub of revocations) {
-      const exclusions = _(iindex).where({ op: constants.IDX_UPDATE, member: false, pub });
-      if (exclusions.length == 0) {
-        throw Error('A revoked member must be excluded');
-      }
-    }
-    return true;
-  }),
-
-  checkRevokedUnicity: (block, conf, index) => co(function *() {
-    try {
-      yield rules.FUNCTIONS.checkMembershipUnicity(block, conf, index);
-    } catch (e) {
-      throw Error('A single revocation per member is allowed');
-    }
-    return true;
-  }),
-
-  checkMembershipUnicity: (block, conf, index) => co(function *() {
-    const mindex = indexer.mindex(index);
-    const pubkeys = _.chain(mindex).pluck('pub').uniq().value();
-    if (pubkeys.length !== mindex.length) {
-      throw Error('Unicity constraint PUBLIC_KEY on MINDEX is not respected');
-    }
-    return true;
-  }),
-
-  checkMembershipsSignature: (block) => co(function *() {
-    let i = 0;
-    let wrongSig = false, ms;
-    // Joiners
-    while (!wrongSig && i < block.joiners.length) {
-      ms = Membership.fromInline(block.joiners[i], 'IN', block.currency);
-      wrongSig = !checkSingleMembershipSignature(ms);
-      i++;
-    }
-    // Actives
-    i = 0;
-    while (!wrongSig && i < block.actives.length) {
-      ms = Membership.fromInline(block.actives[i], 'IN', block.currency);
-      wrongSig = !checkSingleMembershipSignature(ms);
-      i++;
-    }
-    // Leavers
-    i = 0;
-    while (!wrongSig && i < block.leavers.length) {
-      ms = Membership.fromInline(block.leavers[i], 'OUT', block.currency);
-      wrongSig = !checkSingleMembershipSignature(ms);
-      i++;
-    }
-    if (wrongSig) {
-      throw Error('Membership\'s signature must match');
-    }
-    return true;
-  }),
-
-  checkPubkeyUnicity: (block) => co(function *() {
-    const pubkeys = [];
-    let conflict = false;
-    let pubk;
-    // Joiners
-    let i = 0;
-    while (!conflict && i < block.joiners.length) {
-      pubk = block.joiners[i].split(':')[0];
-      conflict = ~pubkeys.indexOf(pubk);
-      pubkeys.push(pubk);
-      i++;
-    }
-    // Actives
-    i = 0;
-    while (!conflict && i < block.actives.length) {
-      pubk = block.actives[i].split(':')[0];
-      conflict = ~pubkeys.indexOf(pubk);
-      pubkeys.push(pubk);
-      i++;
-    }
-    // Leavers
-    i = 0;
-    while (!conflict && i < block.leavers.length) {
-      pubk = block.leavers[i].split(':')[0];
-      conflict = ~pubkeys.indexOf(pubk);
-      pubkeys.push(pubk);
-      i++;
-    }
-    // Excluded
-    i = 0;
-    while (!conflict && i < block.excluded.length) {
-      pubk = block.excluded[i].split(':')[0];
-      conflict = ~pubkeys.indexOf(pubk);
-      pubkeys.push(pubk);
-      i++;
-    }
-    if (conflict) {
-      throw Error('Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded');
-    }
-    return true;
-  }),
-
-  checkCertificationOneByIssuer: (block, conf, index) => co(function *() {
-    if (block.number > 0) {
-      const cindex = indexer.cindex(index);
-      const certFromA = _.uniq(cindex.map((row) => row.issuer));
-      if (certFromA.length !== cindex.length) {
-        throw Error('Block cannot contain two certifications from same issuer');
-      }
-    }
-    return true;
-  }),
-
-  checkCertificationUnicity: (block, conf, index) => co(function *() {
-    const cindex = indexer.cindex(index);
-    const certAtoB = _.uniq(cindex.map((row) => row.issuer + row.receiver));
-    if (certAtoB.length !== cindex.length) {
-      throw Error('Block cannot contain identical certifications (A -> B)');
-    }
-    return true;
-  }),
-
-  checkCertificationIsntForLeaverOrExcluded: (block, conf, index) => co(function *() {
-    const cindex = indexer.cindex(index);
-    const iindex = indexer.iindex(index);
-    const mindex = indexer.mindex(index);
-    const certified = cindex.map((row) => row.receiver);
-    for (const pub of certified) {
-      const exclusions = _(iindex).where({ op: constants.IDX_UPDATE, member: false, pub: pub });
-      const leavers    = _(mindex).where({ op: constants.IDX_UPDATE, leaving: true, pub: pub });
-      if (exclusions.length > 0 || leavers.length > 0) {
-        throw Error('Block cannot contain certifications concerning leavers or excluded members');
-      }
-    }
-    return true;
-  }),
-
-  checkTxVersion: (block) => co(function *() {
-    const txs = block.transactions
-    // Check rule against each transaction
-    for (const tx of txs) {
-      if (tx.version != 10) {
-        throw Error('A transaction must have the version 10');
-      }
-    }
-    return true;
-  }),
-
-  checkTxLen: (block) => co(function *() {
-    const txs = block.transactions
-    // Check rule against each transaction
-    for (const tx of txs) {
-      const txLen = Transaction.getLen(tx);
-      if (txLen > constants.MAXIMUM_LEN_OF_COMPACT_TX) {
-        throw constants.ERRORS.A_TRANSACTION_HAS_A_MAX_SIZE;
-      }
-    }
-    // Check rule against each output of each transaction
-    for (const tx of txs) {
-      for (const output of tx.outputs) {
-        const out = typeof output === 'string' ? output : Transaction.outputObj2Str(output)
-        if (out.length > constants.MAXIMUM_LEN_OF_OUTPUT) {
-          throw constants.ERRORS.MAXIMUM_LEN_OF_OUTPUT
-        }
-      }
-    }
-    // Check rule against each unlock of each transaction
-    for (const tx of txs) {
-      for (const unlock of tx.unlocks) {
-        if (unlock.length > constants.MAXIMUM_LEN_OF_UNLOCK) {
-          throw constants.ERRORS.MAXIMUM_LEN_OF_UNLOCK
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const BlockDTO_1 = require("../dto/BlockDTO");
+const _ = require('underscore');
+const common = require('duniter-common');
+const indexer = require('../indexer').Indexer;
+const constants = common.constants;
+const hashf = common.hashf;
+const keyring = common.keyring;
+const Block = common.document.Block;
+const Identity = common.document.Identity;
+const Membership = common.document.Membership;
+const Transaction = common.document.Transaction;
+const maxAcceleration = require('./helpers').maxAcceleration;
+exports.FUNCTIONS = {
+    checkParameters: (block) => __awaiter(this, void 0, void 0, function* () {
+        if (block.number == 0 && !block.parameters) {
+            throw Error('Parameters must be provided for root block');
         }
-      }
-    }
-    return true;
-  }),
-
-  checkTxIssuers: (block) => co(function *() {
-    const txs = block.transactions
-    // Check rule against each transaction
-    for (const tx of txs) {
-      if (tx.issuers.length == 0) {
-        throw Error('A transaction must have at least 1 issuer');
-      }
-    }
-    return true;
-  }),
-
-  checkTxSources: (block) => co(function *() {
-    const dto = BlockDTO.fromJSONObject(block)
-    for (const tx of dto.transactions) {
-      if (!tx.inputs || tx.inputs.length == 0) {
-        throw Error('A transaction must have at least 1 source');
-      }
-    }
-    const sindex = indexer.localSIndex(dto);
-    const inputs = _.filter(sindex, (row) => row.op == constants.IDX_UPDATE).map((row) => [row.op, row.identifier, row.pos].join('-'));
-    if (inputs.length !== _.uniq(inputs).length) {
-      throw Error('It cannot exist 2 identical sources for transactions inside a given block');
-    }
-    const outputs = _.filter(sindex, (row) => row.op == constants.IDX_CREATE).map((row) => [row.op, row.identifier, row.pos].join('-'));
-    if (outputs.length !== _.uniq(outputs).length) {
-      throw Error('It cannot exist 2 identical sources for transactions inside a given block');
-    }
-    return true;
-  }),
-
-  checkTxAmounts: (block) => co(function *() {
-    for (const tx of block.transactions) {
-      rules.HELPERS.checkTxAmountsValidity(tx);
-    }
-  }),
-
-  checkTxRecipients: (block) => co(function *() {
-    const txs = block.transactions
-    // Check rule against each transaction
-    for (const tx of txs) {
-      if (!tx.outputs || tx.outputs.length == 0) {
-        throw Error('A transaction must have at least 1 recipient');
-      }
-      else {
-        // Cannot have empty output condition
-        for (const output of tx.outputsAsObjects()) {
-          if (!output.conditions.match(/(SIG|XHX)/)) {
-            throw Error('Empty conditions are forbidden');
-          }
-        }
-      }
-    }
-    return true;
-  }),
-
-  checkTxSignature: (block) => co(function *() {
-    const txs = block.transactions
-    // Check rule against each transaction
-    for (const tx of txs) {
-      let sigResult = getSigResult(tx);
-      if (!sigResult.matching) {
-        throw Error('Signature from a transaction must match');
-      }
-    }
-    return true;
-  })
+        else if (block.number > 0 && block.parameters) {
+            throw Error('Parameters must not be provided for non-root block');
+        }
+        return true;
+    }),
+    checkProofOfWork: (block) => __awaiter(this, void 0, void 0, function* () {
+        let remainder = block.powMin % 16;
+        let nb_zeros = (block.powMin - remainder) / 16;
+        const powRegexp = new RegExp('^0{' + nb_zeros + '}');
+        if (!block.hash.match(powRegexp)) {
+            throw Error('Not a proof-of-work');
+        }
+        return true;
+    }),
+    checkInnerHash: (block) => __awaiter(this, void 0, void 0, function* () {
+        let inner_hash = hashf(block.getRawInnerPart()).toUpperCase();
+        if (block.inner_hash != inner_hash) {
+            throw Error('Wrong inner hash');
+        }
+        return true;
+    }),
+    checkPreviousHash: (block) => __awaiter(this, void 0, void 0, function* () {
+        if (block.number == 0 && block.previousHash) {
+            throw Error('PreviousHash must not be provided for root block');
+        }
+        else if (block.number > 0 && !block.previousHash) {
+            throw Error('PreviousHash must be provided for non-root block');
+        }
+        return true;
+    }),
+    checkPreviousIssuer: (block) => __awaiter(this, void 0, void 0, function* () {
+        if (block.number == 0 && block.previousIssuer)
+            throw Error('PreviousIssuer must not be provided for root block');
+        else if (block.number > 0 && !block.previousIssuer)
+            throw Error('PreviousIssuer must be provided for non-root block');
+        return true;
+    }),
+    checkUnitBase: (block) => __awaiter(this, void 0, void 0, function* () {
+        if (block.number == 0 && block.unitbase != 0) {
+            throw Error('UnitBase must equal 0 for root block');
+        }
+        return true;
+    }),
+    checkBlockSignature: (block) => __awaiter(this, void 0, void 0, function* () {
+        if (!keyring.verify(block.getSignedPart(), block.signature, block.issuer))
+            throw Error('Block\'s signature must match');
+        return true;
+    }),
+    checkBlockTimes: (block, conf) => __awaiter(this, void 0, void 0, function* () {
+        const time = block.time;
+        const medianTime = block.medianTime;
+        if (block.number > 0 && (time < medianTime || time > medianTime + maxAcceleration(conf)))
+            throw Error('A block must have its Time between MedianTime and MedianTime + ' + maxAcceleration(conf));
+        else if (block.number == 0 && time != medianTime)
+            throw Error('Root block must have Time equal MedianTime');
+        return true;
+    }),
+    checkIdentitiesSignature: (block) => __awaiter(this, void 0, void 0, function* () {
+        let i = 0;
+        let wrongSig = false;
+        while (!wrongSig && i < block.identities.length) {
+            const idty = Identity.fromInline(block.identities[i]);
+            idty.currency = block.currency;
+            wrongSig = !keyring.verify(idty.rawWithoutSig(), idty.sig, idty.pubkey);
+            if (wrongSig) {
+                throw Error('Identity\'s signature must match');
+            }
+            i++;
+        }
+        return true;
+    }),
+    checkIdentitiesUserIDConflict: (block, conf, index) => __awaiter(this, void 0, void 0, function* () {
+        const creates = indexer.iindexCreate(index);
+        const uids = _.chain(creates).pluck('uid').uniq().value();
+        if (creates.length !== uids.length) {
+            throw Error('Block must not contain twice same identity uid');
+        }
+        return true;
+    }),
+    checkIdentitiesPubkeyConflict: (block, conf, index) => __awaiter(this, void 0, void 0, function* () {
+        const creates = indexer.iindexCreate(index);
+        const pubkeys = _.chain(creates).pluck('pub').uniq().value();
+        if (creates.length !== pubkeys.length) {
+            throw Error('Block must not contain twice same identity pubkey');
+        }
+        return true;
+    }),
+    checkIdentitiesMatchJoin: (block, conf, index) => __awaiter(this, void 0, void 0, function* () {
+        const icreates = indexer.iindexCreate(index);
+        const mcreates = indexer.mindexCreate(index);
+        for (const icreate of icreates) {
+            const matching = _(mcreates).filter({ pub: icreate.pub });
+            if (matching.length == 0) {
+                throw Error('Each identity must match a newcomer line with same userid and certts');
+            }
+        }
+        return true;
+    }),
+    checkRevokedAreExcluded: (block, conf, index) => __awaiter(this, void 0, void 0, function* () {
+        const iindex = indexer.iindex(index);
+        const mindex = indexer.mindex(index);
+        const revocations = _.chain(mindex)
+            .filter((row) => row.op == constants.IDX_UPDATE && row.revoked_on !== null)
+            .pluck('pub')
+            .value();
+        for (const pub of revocations) {
+            const exclusions = _(iindex).where({ op: constants.IDX_UPDATE, member: false, pub });
+            if (exclusions.length == 0) {
+                throw Error('A revoked member must be excluded');
+            }
+        }
+        return true;
+    }),
+    checkRevokedUnicity: (block, conf, index) => __awaiter(this, void 0, void 0, function* () {
+        try {
+            yield exports.FUNCTIONS.checkMembershipUnicity(block, conf, index);
+        }
+        catch (e) {
+            throw Error('A single revocation per member is allowed');
+        }
+        return true;
+    }),
+    checkMembershipUnicity: (block, conf, index) => __awaiter(this, void 0, void 0, function* () {
+        const mindex = indexer.mindex(index);
+        const pubkeys = _.chain(mindex).pluck('pub').uniq().value();
+        if (pubkeys.length !== mindex.length) {
+            throw Error('Unicity constraint PUBLIC_KEY on MINDEX is not respected');
+        }
+        return true;
+    }),
+    checkMembershipsSignature: (block) => __awaiter(this, void 0, void 0, function* () {
+        let i = 0;
+        let wrongSig = false, ms;
+        // Joiners
+        while (!wrongSig && i < block.joiners.length) {
+            ms = Membership.fromInline(block.joiners[i], 'IN', block.currency);
+            wrongSig = !checkSingleMembershipSignature(ms);
+            i++;
+        }
+        // Actives
+        i = 0;
+        while (!wrongSig && i < block.actives.length) {
+            ms = Membership.fromInline(block.actives[i], 'IN', block.currency);
+            wrongSig = !checkSingleMembershipSignature(ms);
+            i++;
+        }
+        // Leavers
+        i = 0;
+        while (!wrongSig && i < block.leavers.length) {
+            ms = Membership.fromInline(block.leavers[i], 'OUT', block.currency);
+            wrongSig = !checkSingleMembershipSignature(ms);
+            i++;
+        }
+        if (wrongSig) {
+            throw Error('Membership\'s signature must match');
+        }
+        return true;
+    }),
+    checkPubkeyUnicity: (block) => __awaiter(this, void 0, void 0, function* () {
+        const pubkeys = [];
+        let conflict = false;
+        let pubk;
+        // Joiners
+        let i = 0;
+        while (!conflict && i < block.joiners.length) {
+            pubk = block.joiners[i].split(':')[0];
+            conflict = !!(~pubkeys.indexOf(pubk));
+            pubkeys.push(pubk);
+            i++;
+        }
+        // Actives
+        i = 0;
+        while (!conflict && i < block.actives.length) {
+            pubk = block.actives[i].split(':')[0];
+            conflict = !!(~pubkeys.indexOf(pubk));
+            pubkeys.push(pubk);
+            i++;
+        }
+        // Leavers
+        i = 0;
+        while (!conflict && i < block.leavers.length) {
+            pubk = block.leavers[i].split(':')[0];
+            conflict = !!(~pubkeys.indexOf(pubk));
+            pubkeys.push(pubk);
+            i++;
+        }
+        // Excluded
+        i = 0;
+        while (!conflict && i < block.excluded.length) {
+            pubk = block.excluded[i].split(':')[0];
+            conflict = !!(~pubkeys.indexOf(pubk));
+            pubkeys.push(pubk);
+            i++;
+        }
+        if (conflict) {
+            throw Error('Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded');
+        }
+        return true;
+    }),
+    checkCertificationOneByIssuer: (block, conf, index) => __awaiter(this, void 0, void 0, function* () {
+        if (block.number > 0) {
+            const cindex = indexer.cindex(index);
+            const certFromA = _.uniq(cindex.map((row) => row.issuer));
+            if (certFromA.length !== cindex.length) {
+                throw Error('Block cannot contain two certifications from same issuer');
+            }
+        }
+        return true;
+    }),
+    checkCertificationUnicity: (block, conf, index) => __awaiter(this, void 0, void 0, function* () {
+        const cindex = indexer.cindex(index);
+        const certAtoB = _.uniq(cindex.map((row) => row.issuer + row.receiver));
+        if (certAtoB.length !== cindex.length) {
+            throw Error('Block cannot contain identical certifications (A -> B)');
+        }
+        return true;
+    }),
+    checkCertificationIsntForLeaverOrExcluded: (block, conf, index) => __awaiter(this, void 0, void 0, function* () {
+        const cindex = indexer.cindex(index);
+        const iindex = indexer.iindex(index);
+        const mindex = indexer.mindex(index);
+        const certified = cindex.map((row) => row.receiver);
+        for (const pub of certified) {
+            const exclusions = _(iindex).where({ op: constants.IDX_UPDATE, member: false, pub: pub });
+            const leavers = _(mindex).where({ op: constants.IDX_UPDATE, leaving: true, pub: pub });
+            if (exclusions.length > 0 || leavers.length > 0) {
+                throw Error('Block cannot contain certifications concerning leavers or excluded members');
+            }
+        }
+        return true;
+    }),
+    checkTxVersion: (block) => __awaiter(this, void 0, void 0, function* () {
+        const txs = block.transactions;
+        // Check rule against each transaction
+        for (const tx of txs) {
+            if (tx.version != 10) {
+                throw Error('A transaction must have the version 10');
+            }
+        }
+        return true;
+    }),
+    checkTxLen: (block) => __awaiter(this, void 0, void 0, function* () {
+        const txs = block.transactions;
+        // Check rule against each transaction
+        for (const tx of txs) {
+            const txLen = Transaction.getLen(tx);
+            if (txLen > constants.MAXIMUM_LEN_OF_COMPACT_TX) {
+                throw constants.ERRORS.A_TRANSACTION_HAS_A_MAX_SIZE;
+            }
+        }
+        // Check rule against each output of each transaction
+        for (const tx of txs) {
+            for (const output of tx.outputs) {
+                const out = typeof output === 'string' ? output : Transaction.outputObj2Str(output);
+                if (out.length > constants.MAXIMUM_LEN_OF_OUTPUT) {
+                    throw constants.ERRORS.MAXIMUM_LEN_OF_OUTPUT;
+                }
+            }
+        }
+        // Check rule against each unlock of each transaction
+        for (const tx of txs) {
+            for (const unlock of tx.unlocks) {
+                if (unlock.length > constants.MAXIMUM_LEN_OF_UNLOCK) {
+                    throw constants.ERRORS.MAXIMUM_LEN_OF_UNLOCK;
+                }
+            }
+        }
+        return true;
+    }),
+    checkTxIssuers: (block) => __awaiter(this, void 0, void 0, function* () {
+        const txs = block.transactions;
+        // Check rule against each transaction
+        for (const tx of txs) {
+            if (tx.issuers.length == 0) {
+                throw Error('A transaction must have at least 1 issuer');
+            }
+        }
+        return true;
+    }),
+    checkTxSources: (block) => __awaiter(this, void 0, void 0, function* () {
+        const dto = BlockDTO_1.BlockDTO.fromJSONObject(block);
+        for (const tx of dto.transactions) {
+            if (!tx.inputs || tx.inputs.length == 0) {
+                throw Error('A transaction must have at least 1 source');
+            }
+        }
+        const sindex = indexer.localSIndex(dto);
+        const inputs = _.filter(sindex, (row) => row.op == constants.IDX_UPDATE).map((row) => [row.op, row.identifier, row.pos].join('-'));
+        if (inputs.length !== _.uniq(inputs).length) {
+            throw Error('It cannot exist 2 identical sources for transactions inside a given block');
+        }
+        const outputs = _.filter(sindex, (row) => row.op == constants.IDX_CREATE).map((row) => [row.op, row.identifier, row.pos].join('-'));
+        if (outputs.length !== _.uniq(outputs).length) {
+            throw Error('It cannot exist 2 identical sources for transactions inside a given block');
+        }
+        return true;
+    }),
+    checkTxAmounts: (block) => __awaiter(this, void 0, void 0, function* () {
+        for (const tx of block.transactions) {
+            exports.HELPERS.checkTxAmountsValidity(tx);
+        }
+    }),
+    checkTxRecipients: (block) => __awaiter(this, void 0, void 0, function* () {
+        const txs = block.transactions;
+        // Check rule against each transaction
+        for (const tx of txs) {
+            if (!tx.outputs || tx.outputs.length == 0) {
+                throw Error('A transaction must have at least 1 recipient');
+            }
+            else {
+                // Cannot have empty output condition
+                for (const output of tx.outputsAsObjects()) {
+                    if (!output.conditions.match(/(SIG|XHX)/)) {
+                        throw Error('Empty conditions are forbidden');
+                    }
+                }
+            }
+        }
+        return true;
+    }),
+    checkTxSignature: (block) => __awaiter(this, void 0, void 0, function* () {
+        const txs = block.transactions;
+        // Check rule against each transaction
+        for (const tx of txs) {
+            let sigResult = getSigResult(tx);
+            if (!sigResult.matching) {
+                throw Error('Signature from a transaction must match');
+            }
+        }
+        return true;
+    })
 };
-
 function checkSingleMembershipSignature(ms) {
-  return keyring.verify(ms.getRaw(), ms.signature, ms.issuer);
+    return keyring.verify(ms.getRaw(), ms.signature, ms.issuer);
 }
-
 function getSigResult(tx) {
-  let sigResult = { sigs: {}, matching: true };
-  let json = { "version": tx.version, "currency": tx.currency, "blockstamp": tx.blockstamp, "locktime": tx.locktime, "inputs": [], "outputs": [], "issuers": tx.issuers, "signatures": [], "comment": tx.comment };
-  tx.inputs.forEach(function (input) {
-    json.inputs.push(input.raw);
-  });
-  tx.outputs.forEach(function (output) {
-    json.outputs.push(output.raw);
-  });
-  json.unlocks = tx.unlocks;
-  let i = 0;
-  let signaturesMatching = true;
-  const raw = tx.getRawTxNoSig()
-  while (signaturesMatching && i < tx.signatures.length) {
-    const sig = tx.signatures[i];
-    const pub = tx.issuers[i];
-    signaturesMatching = keyring.verify(raw, sig, pub);
-    sigResult.sigs[pub] = {
-      matching: signaturesMatching,
-      index: i
-    };
-    i++;
-  }
-  sigResult.matching = signaturesMatching;
-  return sigResult;
+    let sigResult = { sigs: {}, matching: true };
+    let json = { "version": tx.version, "currency": tx.currency, "blockstamp": tx.blockstamp, "locktime": tx.locktime, "inputs": [], "outputs": [], "issuers": tx.issuers, "signatures": [], "comment": tx.comment, unlocks: [] };
+    tx.inputs.forEach(function (input) {
+        json.inputs.push(input.raw);
+    });
+    tx.outputs.forEach(function (output) {
+        json.outputs.push(output.raw);
+    });
+    json.unlocks = tx.unlocks;
+    let i = 0;
+    let signaturesMatching = true;
+    const raw = tx.getRawTxNoSig();
+    while (signaturesMatching && i < tx.signatures.length) {
+        const sig = tx.signatures[i];
+        const pub = tx.issuers[i];
+        signaturesMatching = keyring.verify(raw, sig, pub);
+        sigResult.sigs[pub] = {
+            matching: signaturesMatching,
+            index: i
+        };
+        i++;
+    }
+    sigResult.matching = signaturesMatching;
+    return sigResult;
 }
-
-function checkBunchOfTransactions(transactions, done){
-  const block = { transactions };
-  return co(function *() {
-    try {
-      let local_rule = rules.FUNCTIONS;
-      yield local_rule.checkTxLen(block);
-      yield local_rule.checkTxIssuers(block);
-      yield local_rule.checkTxSources(block);
-      yield local_rule.checkTxRecipients(block);
-      yield local_rule.checkTxAmounts(block);
-      yield local_rule.checkTxSignature(block);
-      done && done();
-    } catch (err) {
-      if (done) return done(err);
-      throw err;
-    }
-  });
+function checkBunchOfTransactions(transactions, done) {
+    const block = { transactions };
+    return (() => __awaiter(this, void 0, void 0, function* () {
+        try {
+            let local_rule = exports.FUNCTIONS;
+            yield local_rule.checkTxLen(block);
+            yield local_rule.checkTxIssuers(block);
+            yield local_rule.checkTxSources(block);
+            yield local_rule.checkTxRecipients(block);
+            yield local_rule.checkTxAmounts(block);
+            yield local_rule.checkTxSignature(block);
+            done && done();
+        }
+        catch (err) {
+            if (done)
+                return done(err);
+            throw err;
+        }
+    }))();
 }
-
-rules.HELPERS = {
-
-  maxAcceleration: (conf) => maxAcceleration(conf),
-
-  checkSingleMembershipSignature: checkSingleMembershipSignature,
-
-  getSigResult: getSigResult,
-
-  checkBunchOfTransactions: checkBunchOfTransactions,
-
-  checkSingleTransactionLocally: (tx, done) => checkBunchOfTransactions([tx], done),
-
-  checkTxAmountsValidity: (tx) => {
-    const inputs = tx.inputsAsObjects()
-    const outputs = tx.outputsAsObjects()
-    // Rule of money conservation
-    const commonBase = inputs.concat(outputs).reduce((min, input) => {
-      if (min === null) return input.base;
-      return Math.min(min, parseInt(input.base));
-    }, null);
-    const inputSumCommonBase = inputs.reduce((sum, input) => {
-      return sum + input.amount * Math.pow(10, input.base - commonBase);
-    }, 0);
-    const outputSumCommonBase = outputs.reduce((sum, output) => {
-      return sum + output.amount * Math.pow(10, output.base - commonBase);
-    }, 0);
-    if (inputSumCommonBase !== outputSumCommonBase) {
-      throw constants.ERRORS.TX_INPUTS_OUTPUTS_NOT_EQUAL;
-    }
-    // Rule of unit base transformation
-    const maxOutputBase = outputs.reduce((max, output) => {
-      return Math.max(max, parseInt(output.base));
-    }, 0);
-    // Compute deltas
-    const deltas = {};
-    for (let i = commonBase; i <= maxOutputBase; i++) {
-      const inputBaseSum = inputs.reduce((sum, input) => {
-        if (input.base == i) {
-          return sum + input.amount * Math.pow(10, input.base - commonBase);
-        } else {
-          return sum;
-        }
-      }, 0);
-      const outputBaseSum = outputs.reduce((sum, output) => {
-        if (output.base == i) {
-          return sum + output.amount * Math.pow(10, output.base - commonBase);
-        } else {
-          return sum;
-        }
-      }, 0);
-      const delta = outputBaseSum - inputBaseSum;
-      let sumUpToBase = 0;
-      for (let j = commonBase; j < i; j++) {
-        sumUpToBase -= deltas[j];
-      }
-      if (delta > 0 && delta > sumUpToBase) {
-        throw constants.ERRORS.TX_OUTPUT_SUM_NOT_EQUALS_PREV_DELTAS;
-      }
-      deltas[i] = delta;
-    }
-  },
-
-  getMaxPossibleVersionNumber: (current) => co(function*() {
-    // Looking at current blockchain, find what is the next maximum version we can produce
-
-    // 1. We follow previous block's version
-    let version = current ? current.version : constants.BLOCK_GENERATED_VERSION;
-
-    // 2. If we can, we go to the next version
-    return version;
-  })
+exports.HELPERS = {
+    maxAcceleration: (conf) => maxAcceleration(conf),
+    checkSingleMembershipSignature: checkSingleMembershipSignature,
+    getSigResult: getSigResult,
+    checkBunchOfTransactions: checkBunchOfTransactions,
+    checkSingleTransactionLocally: (tx, done) => checkBunchOfTransactions([tx], done),
+    checkTxAmountsValidity: (tx) => {
+        const inputs = tx.inputsAsObjects();
+        const outputs = tx.outputsAsObjects();
+        // Rule of money conservation
+        const commonBase = inputs.concat(outputs).reduce((min, input) => {
+            if (min === null)
+                return input.base;
+            return Math.min(min, input.base);
+        }, 0);
+        const inputSumCommonBase = inputs.reduce((sum, input) => {
+            return sum + input.amount * Math.pow(10, input.base - commonBase);
+        }, 0);
+        const outputSumCommonBase = outputs.reduce((sum, output) => {
+            return sum + output.amount * Math.pow(10, output.base - commonBase);
+        }, 0);
+        if (inputSumCommonBase !== outputSumCommonBase) {
+            throw constants.ERRORS.TX_INPUTS_OUTPUTS_NOT_EQUAL;
+        }
+        // Rule of unit base transformation
+        const maxOutputBase = outputs.reduce((max, output) => {
+            return Math.max(max, output.base);
+        }, 0);
+        // Compute deltas
+        const deltas = {};
+        for (let i = commonBase; i <= maxOutputBase; i++) {
+            const inputBaseSum = inputs.reduce((sum, input) => {
+                if (input.base == i) {
+                    return sum + input.amount * Math.pow(10, input.base - commonBase);
+                }
+                else {
+                    return sum;
+                }
+            }, 0);
+            const outputBaseSum = outputs.reduce((sum, output) => {
+                if (output.base == i) {
+                    return sum + output.amount * Math.pow(10, output.base - commonBase);
+                }
+                else {
+                    return sum;
+                }
+            }, 0);
+            const delta = outputBaseSum - inputBaseSum;
+            let sumUpToBase = 0;
+            for (let j = commonBase; j < i; j++) {
+                sumUpToBase -= deltas[j];
+            }
+            if (delta > 0 && delta > sumUpToBase) {
+                throw constants.ERRORS.TX_OUTPUT_SUM_NOT_EQUALS_PREV_DELTAS;
+            }
+            deltas[i] = delta;
+        }
+    },
+    getMaxPossibleVersionNumber: (current) => __awaiter(this, void 0, void 0, function* () {
+        // Looking at current blockchain, find what is the next maximum version we can produce
+        // 1. We follow previous block's version
+        let version = current ? current.version : constants.BLOCK_GENERATED_VERSION;
+        // 2. If we can, we go to the next version
+        return version;
+    })
 };
-
-module.exports = rules;
+//# sourceMappingURL=local_rules.js.map
\ No newline at end of file
diff --git a/app/lib/rules/local_rules.ts b/app/lib/rules/local_rules.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f6d53255624665cc4ba0196685c1f04b64b1e90f
--- /dev/null
+++ b/app/lib/rules/local_rules.ts
@@ -0,0 +1,506 @@
+"use strict";
+import {BlockDTO} from "../dto/BlockDTO"
+import {ConfDTO} from "../dto/ConfDTO"
+import {CindexEntry, MindexEntry, SindexEntry} from "../indexer"
+import {BaseDTO, TransactionDTO} from "../dto/TransactionDTO"
+import {DBBlock} from "../db/DBBlock"
+
+const _          = require('underscore');
+const common     = require('duniter-common');
+const indexer    = require('../indexer').Indexer;
+
+const constants       = common.constants
+const hashf           = common.hashf
+const keyring         = common.keyring
+const Block           = common.document.Block
+const Identity        = common.document.Identity
+const Membership      = common.document.Membership
+const Transaction     = common.document.Transaction
+const maxAcceleration = require('./helpers').maxAcceleration
+
+export const FUNCTIONS = {
+
+  checkParameters: async (block:BlockDTO) => {
+    if (block.number == 0 && !block.parameters) {
+      throw Error('Parameters must be provided for root block');
+    }
+    else if (block.number > 0 && block.parameters) {
+      throw Error('Parameters must not be provided for non-root block');
+    }
+    return true;
+  },
+
+  checkProofOfWork: async (block:BlockDTO) => {
+    let remainder = block.powMin % 16;
+    let nb_zeros = (block.powMin - remainder) / 16;
+    const powRegexp = new RegExp('^0{' + nb_zeros + '}');
+    if (!block.hash.match(powRegexp)) {
+      throw Error('Not a proof-of-work');
+    }
+    return true;
+  },
+
+  checkInnerHash: async (block:BlockDTO) => {
+    let inner_hash = hashf(block.getRawInnerPart()).toUpperCase();
+    if (block.inner_hash != inner_hash) {
+      throw Error('Wrong inner hash');
+    }
+    return true;
+  },
+
+  checkPreviousHash: async (block:BlockDTO) => {
+    if (block.number == 0 && block.previousHash) {
+      throw Error('PreviousHash must not be provided for root block');
+    }
+    else if (block.number > 0 && !block.previousHash) {
+      throw Error('PreviousHash must be provided for non-root block');
+    }
+    return true;
+  },
+
+  checkPreviousIssuer: async (block:BlockDTO) => {
+    if (block.number == 0 && block.previousIssuer)
+      throw Error('PreviousIssuer must not be provided for root block');
+    else if (block.number > 0 && !block.previousIssuer)
+      throw Error('PreviousIssuer must be provided for non-root block');
+    return true;
+  },
+
+  checkUnitBase: async (block:BlockDTO) => {
+    if (block.number == 0 && block.unitbase != 0) {
+      throw Error('UnitBase must equal 0 for root block');
+    }
+    return true;
+  },
+
+  checkBlockSignature: async (block:BlockDTO) => {
+    if (!keyring.verify(block.getSignedPart(), block.signature, block.issuer))
+      throw Error('Block\'s signature must match');
+    return true;
+  },
+
+  checkBlockTimes: async (block:BlockDTO, conf:ConfDTO) => {
+    const time = block.time
+    const medianTime = block.medianTime
+    if (block.number > 0 && (time < medianTime || time > medianTime + maxAcceleration(conf)))
+      throw Error('A block must have its Time between MedianTime and MedianTime + ' + maxAcceleration(conf));
+    else if (block.number == 0 && time != medianTime)
+      throw Error('Root block must have Time equal MedianTime');
+    return true;
+  },
+
+  checkIdentitiesSignature: async (block:BlockDTO) => {
+    let i = 0;
+    let wrongSig = false;
+    while (!wrongSig && i < block.identities.length) {
+      const idty = Identity.fromInline(block.identities[i]);
+      idty.currency = block.currency;
+      wrongSig = !keyring.verify(idty.rawWithoutSig(), idty.sig, idty.pubkey);
+      if (wrongSig) {
+        throw Error('Identity\'s signature must match');
+      }
+      i++;
+    }
+    return true;
+  },
+
+  checkIdentitiesUserIDConflict: async (block:BlockDTO, conf:ConfDTO, index:SindexEntry) => {
+    const creates = indexer.iindexCreate(index);
+    const uids = _.chain(creates).pluck('uid').uniq().value();
+    if (creates.length !== uids.length) {
+      throw Error('Block must not contain twice same identity uid');
+    }
+    return true;
+  },
+
+  checkIdentitiesPubkeyConflict: async (block:BlockDTO, conf:ConfDTO, index:SindexEntry) => {
+    const creates = indexer.iindexCreate(index);
+    const pubkeys = _.chain(creates).pluck('pub').uniq().value();
+    if (creates.length !== pubkeys.length) {
+      throw Error('Block must not contain twice same identity pubkey');
+    }
+    return true;
+  },
+
+  checkIdentitiesMatchJoin: async (block:BlockDTO, conf:ConfDTO, index:SindexEntry) => {
+    const icreates = indexer.iindexCreate(index);
+    const mcreates = indexer.mindexCreate(index);
+    for (const icreate of icreates) {
+      const matching = _(mcreates).filter({ pub: icreate.pub });
+      if (matching.length == 0) {
+        throw Error('Each identity must match a newcomer line with same userid and certts');
+      }
+    }
+    return true;
+  },
+
+  checkRevokedAreExcluded: async (block:BlockDTO, conf:ConfDTO, index:SindexEntry) => {
+    const iindex = indexer.iindex(index);
+    const mindex = indexer.mindex(index);
+    const revocations = _.chain(mindex)
+      .filter((row:MindexEntry) => row.op == constants.IDX_UPDATE && row.revoked_on !== null)
+      .pluck('pub')
+      .value();
+    for (const pub of revocations) {
+      const exclusions = _(iindex).where({ op: constants.IDX_UPDATE, member: false, pub });
+      if (exclusions.length == 0) {
+        throw Error('A revoked member must be excluded');
+      }
+    }
+    return true;
+  },
+
+  checkRevokedUnicity: async (block:BlockDTO, conf:ConfDTO, index:SindexEntry) => {
+    try {
+      await FUNCTIONS.checkMembershipUnicity(block, conf, index);
+    } catch (e) {
+      throw Error('A single revocation per member is allowed');
+    }
+    return true;
+  },
+
+  checkMembershipUnicity: async (block:BlockDTO, conf:ConfDTO, index:SindexEntry) => {
+    const mindex = indexer.mindex(index);
+    const pubkeys = _.chain(mindex).pluck('pub').uniq().value();
+    if (pubkeys.length !== mindex.length) {
+      throw Error('Unicity constraint PUBLIC_KEY on MINDEX is not respected');
+    }
+    return true;
+  },
+
+  checkMembershipsSignature: async (block:BlockDTO) => {
+    let i = 0;
+    let wrongSig = false, ms;
+    // Joiners
+    while (!wrongSig && i < block.joiners.length) {
+      ms = Membership.fromInline(block.joiners[i], 'IN', block.currency);
+      wrongSig = !checkSingleMembershipSignature(ms);
+      i++;
+    }
+    // Actives
+    i = 0;
+    while (!wrongSig && i < block.actives.length) {
+      ms = Membership.fromInline(block.actives[i], 'IN', block.currency);
+      wrongSig = !checkSingleMembershipSignature(ms);
+      i++;
+    }
+    // Leavers
+    i = 0;
+    while (!wrongSig && i < block.leavers.length) {
+      ms = Membership.fromInline(block.leavers[i], 'OUT', block.currency);
+      wrongSig = !checkSingleMembershipSignature(ms);
+      i++;
+    }
+    if (wrongSig) {
+      throw Error('Membership\'s signature must match');
+    }
+    return true;
+  },
+
+  checkPubkeyUnicity: async (block:BlockDTO) => {
+    const pubkeys = [];
+    let conflict = false;
+    let pubk;
+    // Joiners
+    let i = 0;
+    while (!conflict && i < block.joiners.length) {
+      pubk = block.joiners[i].split(':')[0];
+      conflict = !!(~pubkeys.indexOf(pubk))
+      pubkeys.push(pubk);
+      i++;
+    }
+    // Actives
+    i = 0;
+    while (!conflict && i < block.actives.length) {
+      pubk = block.actives[i].split(':')[0];
+      conflict = !!(~pubkeys.indexOf(pubk))
+      pubkeys.push(pubk);
+      i++;
+    }
+    // Leavers
+    i = 0;
+    while (!conflict && i < block.leavers.length) {
+      pubk = block.leavers[i].split(':')[0];
+      conflict = !!(~pubkeys.indexOf(pubk))
+      pubkeys.push(pubk);
+      i++;
+    }
+    // Excluded
+    i = 0;
+    while (!conflict && i < block.excluded.length) {
+      pubk = block.excluded[i].split(':')[0];
+      conflict = !!(~pubkeys.indexOf(pubk))
+      pubkeys.push(pubk);
+      i++;
+    }
+    if (conflict) {
+      throw Error('Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded');
+    }
+    return true;
+  },
+
+  checkCertificationOneByIssuer: async (block:BlockDTO, conf:ConfDTO, index:SindexEntry) => {
+    if (block.number > 0) {
+      const cindex = indexer.cindex(index);
+      const certFromA = _.uniq(cindex.map((row:CindexEntry) => row.issuer));
+      if (certFromA.length !== cindex.length) {
+        throw Error('Block cannot contain two certifications from same issuer');
+      }
+    }
+    return true;
+  },
+
+  checkCertificationUnicity: async (block:BlockDTO, conf:ConfDTO, index:SindexEntry) => {
+    const cindex = indexer.cindex(index);
+    const certAtoB = _.uniq(cindex.map((row:CindexEntry) => row.issuer + row.receiver));
+    if (certAtoB.length !== cindex.length) {
+      throw Error('Block cannot contain identical certifications (A -> B)');
+    }
+    return true;
+  },
+
+  checkCertificationIsntForLeaverOrExcluded: async (block:BlockDTO, conf:ConfDTO, index:SindexEntry) => {
+    const cindex = indexer.cindex(index);
+    const iindex = indexer.iindex(index);
+    const mindex = indexer.mindex(index);
+    const certified = cindex.map((row:CindexEntry) => row.receiver);
+    for (const pub of certified) {
+      const exclusions = _(iindex).where({ op: constants.IDX_UPDATE, member: false, pub: pub });
+      const leavers    = _(mindex).where({ op: constants.IDX_UPDATE, leaving: true, pub: pub });
+      if (exclusions.length > 0 || leavers.length > 0) {
+        throw Error('Block cannot contain certifications concerning leavers or excluded members');
+      }
+    }
+    return true;
+  },
+
+  checkTxVersion: async (block:BlockDTO) => {
+    const txs = block.transactions
+    // Check rule against each transaction
+    for (const tx of txs) {
+      if (tx.version != 10) {
+        throw Error('A transaction must have the version 10');
+      }
+    }
+    return true;
+  },
+
+  checkTxLen: async (block:BlockDTO) => {
+    const txs = block.transactions
+    // Check rule against each transaction
+    for (const tx of txs) {
+      const txLen = Transaction.getLen(tx);
+      if (txLen > constants.MAXIMUM_LEN_OF_COMPACT_TX) {
+        throw constants.ERRORS.A_TRANSACTION_HAS_A_MAX_SIZE;
+      }
+    }
+    // Check rule against each output of each transaction
+    for (const tx of txs) {
+      for (const output of tx.outputs) {
+        const out = typeof output === 'string' ? output : Transaction.outputObj2Str(output)
+        if (out.length > constants.MAXIMUM_LEN_OF_OUTPUT) {
+          throw constants.ERRORS.MAXIMUM_LEN_OF_OUTPUT
+        }
+      }
+    }
+    // Check rule against each unlock of each transaction
+    for (const tx of txs) {
+      for (const unlock of tx.unlocks) {
+        if (unlock.length > constants.MAXIMUM_LEN_OF_UNLOCK) {
+          throw constants.ERRORS.MAXIMUM_LEN_OF_UNLOCK
+        }
+      }
+    }
+    return true;
+  },
+
+  checkTxIssuers: async (block:BlockDTO) => {
+    const txs = block.transactions
+    // Check rule against each transaction
+    for (const tx of txs) {
+      if (tx.issuers.length == 0) {
+        throw Error('A transaction must have at least 1 issuer');
+      }
+    }
+    return true;
+  },
+
+  checkTxSources: async (block:BlockDTO) => {
+    const dto = BlockDTO.fromJSONObject(block)
+    for (const tx of dto.transactions) {
+      if (!tx.inputs || tx.inputs.length == 0) {
+        throw Error('A transaction must have at least 1 source');
+      }
+    }
+    const sindex = indexer.localSIndex(dto);
+    const inputs = _.filter(sindex, (row:SindexEntry) => row.op == constants.IDX_UPDATE).map((row:SindexEntry) => [row.op, row.identifier, row.pos].join('-'));
+    if (inputs.length !== _.uniq(inputs).length) {
+      throw Error('It cannot exist 2 identical sources for transactions inside a given block');
+    }
+    const outputs = _.filter(sindex, (row:SindexEntry) => row.op == constants.IDX_CREATE).map((row:SindexEntry) => [row.op, row.identifier, row.pos].join('-'));
+    if (outputs.length !== _.uniq(outputs).length) {
+      throw Error('It cannot exist 2 identical sources for transactions inside a given block');
+    }
+    return true;
+  },
+
+  checkTxAmounts: async (block:BlockDTO) => {
+    for (const tx of block.transactions) {
+      HELPERS.checkTxAmountsValidity(tx);
+    }
+  },
+
+  checkTxRecipients: async (block:BlockDTO) => {
+    const txs = block.transactions
+    // Check rule against each transaction
+    for (const tx of txs) {
+      if (!tx.outputs || tx.outputs.length == 0) {
+        throw Error('A transaction must have at least 1 recipient');
+      }
+      else {
+        // Cannot have empty output condition
+        for (const output of tx.outputsAsObjects()) {
+          if (!output.conditions.match(/(SIG|XHX)/)) {
+            throw Error('Empty conditions are forbidden');
+          }
+        }
+      }
+    }
+    return true;
+  },
+
+  checkTxSignature: async (block:BlockDTO) => {
+    const txs = block.transactions
+    // Check rule against each transaction
+    for (const tx of txs) {
+      let sigResult = getSigResult(tx);
+      if (!sigResult.matching) {
+        throw Error('Signature from a transaction must match');
+      }
+    }
+    return true;
+  }
+}
+
+function checkSingleMembershipSignature(ms:any) {
+  return keyring.verify(ms.getRaw(), ms.signature, ms.issuer);
+}
+
+function getSigResult(tx:any) {
+  let sigResult:any = { sigs: {}, matching: true };
+  let json:any = { "version": tx.version, "currency": tx.currency, "blockstamp": tx.blockstamp, "locktime": tx.locktime, "inputs": [], "outputs": [], "issuers": tx.issuers, "signatures": [], "comment": tx.comment, unlocks: [] };
+  tx.inputs.forEach(function (input:any) {
+    json.inputs.push(input.raw);
+  });
+  tx.outputs.forEach(function (output:any) {
+    json.outputs.push(output.raw);
+  });
+  json.unlocks = tx.unlocks;
+  let i = 0;
+  let signaturesMatching = true;
+  const raw = tx.getRawTxNoSig()
+  while (signaturesMatching && i < tx.signatures.length) {
+    const sig = tx.signatures[i];
+    const pub = tx.issuers[i];
+    signaturesMatching = keyring.verify(raw, sig, pub);
+    sigResult.sigs[pub] = {
+      matching: signaturesMatching,
+      index: i
+    };
+    i++;
+  }
+  sigResult.matching = signaturesMatching;
+  return sigResult;
+}
+
+function checkBunchOfTransactions(transactions:TransactionDTO[], done:any){
+  const block:any = { transactions };
+  return (async () => {
+    try {
+      let local_rule = FUNCTIONS;
+      await local_rule.checkTxLen(block);
+      await local_rule.checkTxIssuers(block);
+      await local_rule.checkTxSources(block);
+      await local_rule.checkTxRecipients(block);
+      await local_rule.checkTxAmounts(block);
+      await local_rule.checkTxSignature(block);
+      done && done();
+    } catch (err) {
+      if (done) return done(err);
+      throw err;
+    }
+  })()
+}
+
+export const HELPERS = {
+
+  maxAcceleration: (conf:ConfDTO) => maxAcceleration(conf),
+
+  checkSingleMembershipSignature: checkSingleMembershipSignature,
+
+  getSigResult: getSigResult,
+
+  checkBunchOfTransactions: checkBunchOfTransactions,
+
+  checkSingleTransactionLocally: (tx:any, done:any) => checkBunchOfTransactions([tx], done),
+
+  checkTxAmountsValidity: (tx:TransactionDTO) => {
+    const inputs = tx.inputsAsObjects()
+    const outputs = tx.outputsAsObjects()
+    // Rule of money conservation
+    const commonBase:number = (inputs as BaseDTO[]).concat(outputs).reduce((min:number, input) => {
+      if (min === null) return input.base;
+      return Math.min(min, input.base)
+    }, 0)
+    const inputSumCommonBase = inputs.reduce((sum, input) => {
+      return sum + input.amount * Math.pow(10, input.base - commonBase);
+    }, 0);
+    const outputSumCommonBase = outputs.reduce((sum, output) => {
+      return sum + output.amount * Math.pow(10, output.base - commonBase);
+    }, 0);
+    if (inputSumCommonBase !== outputSumCommonBase) {
+      throw constants.ERRORS.TX_INPUTS_OUTPUTS_NOT_EQUAL;
+    }
+    // Rule of unit base transformation
+    const maxOutputBase = outputs.reduce((max, output) => {
+      return Math.max(max, output.base)
+    }, 0)
+    // Compute deltas
+    const deltas:any = {};
+    for (let i = commonBase; i <= maxOutputBase; i++) {
+      const inputBaseSum = inputs.reduce((sum, input) => {
+        if (input.base == i) {
+          return sum + input.amount * Math.pow(10, input.base - commonBase);
+        } else {
+          return sum;
+        }
+      }, 0);
+      const outputBaseSum = outputs.reduce((sum, output) => {
+        if (output.base == i) {
+          return sum + output.amount * Math.pow(10, output.base - commonBase);
+        } else {
+          return sum;
+        }
+      }, 0);
+      const delta = outputBaseSum - inputBaseSum;
+      let sumUpToBase = 0;
+      for (let j = commonBase; j < i; j++) {
+        sumUpToBase -= deltas[j];
+      }
+      if (delta > 0 && delta > sumUpToBase) {
+        throw constants.ERRORS.TX_OUTPUT_SUM_NOT_EQUALS_PREV_DELTAS;
+      }
+      deltas[i] = delta;
+    }
+  },
+
+  getMaxPossibleVersionNumber: async (current:DBBlock) => {
+    // Looking at current blockchain, find what is the next maximum version we can produce
+
+    // 1. We follow previous block's version
+    let version = current ? current.version : constants.BLOCK_GENERATED_VERSION;
+
+    // 2. If we can, we go to the next version
+    return version;
+  }
+}
\ No newline at end of file