diff --git a/.gitignore b/.gitignore
index 3eb225fe6f9ee49c2fa8077e8f43c749716e3737..5a0e84e16d4892f4743ee5a47dbd0436634290b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,5 @@ test2network*/bootnode
 test2network*/contracts/*
 test2network/*.pid
 node_modules
+src/*/*.bin
+src/*/*.abi
diff --git a/bin/installbfa.sh b/bin/installbfa.sh
index 1f193a9f5dba5408c8b73396ac4f56d7a3242dc4..5af9f0bd9cf1c87f03b99ce48df33ce7758655e8 100755
--- a/bin/installbfa.sh
+++ b/bin/installbfa.sh
@@ -259,7 +259,7 @@ grep -q Ubuntu /etc/issue && apt-add-repository multiverse
 #
 apt update
 # development tools
-aptinstall dirmngr apt-transport-https curl git curl build-essential sudo
+aptinstall dirmngr apt-transport-https curl git curl build-essential sudo software-properties-common
 aptinstall jq libjson-perl libwww-perl libclass-accessor-perl
 userconfig
 nodejsinstall
diff --git a/src/Majority.sol b/src/Majority.sol
deleted file mode 100644
index 07fafc28a8f116b0d93a080297a49b104ef54fb9..0000000000000000000000000000000000000000
--- a/src/Majority.sol
+++ /dev/null
@@ -1,250 +0,0 @@
-// Robert Martin-Legene <robert@nic.ar>
-// vim:syntax:filetype=javascript:ai:sm
-
-pragma solidity ^0.5;
-
-// This contract is supposed to maintain a list of accounts authorized
-// to control members of "the club" using a majority (n/1+1).
-// For instance, could be useful for
-// - a list of sealers
-// - a board of directors
-// - a local chess club
-// - even outsourcing this kind of management from another smart contract.
-
-contract Majority {
-    // This struct contains a list of votes and who has voted for each victim/beneficiary.
-    // Votes taking longer than 30K blocks, will have to restart.
-    struct Proposal {
-        address         victim;     // Whom are we voting about.
-        uint            votestart;  // In which block did this vote begin?
-        bool		promotion;  // true=promotion, false=demotion
-        address[]	voters;     // List of voters.
-    }
-    address[]           public  voters;
-    Proposal[]          public  voterproposals;
-
-    event               voteCast( address voter, address victim, bool promotionOrDemotion );
-    event               adminChange( address admin, bool promotionOrDemotion );
-
-    constructor() public
-    {
-        // Don't want anyone to find a way to trigger the constructor again.
-        if ( voters.length == 0 )
-            voters.push( msg.sender );
-    }
-
-    // This function is used to know how many voters exist.
-    function    votersLength() public view returns (uint)
-    {
-        return voters.length;
-    }
-
-    // This function is to be used to get the address from a specific position in the list.
-    // Remember first position is 0.
-    // Last position is voters.length-1.
-    function    voterPosition( uint idx ) public view returns (address)
-    {
-        require( idx < voters.length, "There are not that many entries in the list." );
-        return voters[idx];
-    }
-
-    function    isVoter( address subject ) public view returns (bool)
-    {
-        return ( _findAddressInList( voters, subject ) > 0 );
-    }
-
-    // Returns an index to the position of the needle inside haystack.
-    // Beware that:
-    // 0 = not found.
-    // 1 = first position in a list (so this is actually the real list position + 1).
-    function    _findAddressInList( address[] memory haystack, address needle ) private pure returns (uint)
-    {
-        uint    i   =   haystack.length;
-        while ( i-- > 0 )
-            if ( needle == haystack[i] )
-                return i+1;
-        return 0;
-    }
-
-    function    _remove_proposal( uint idx ) private
-    {
-        // Move all items in the list (after idx) one step closer to the
-        // front of the list.
-        uint    max     =   voterproposals.length;
-        while ( ++idx < max )
-            voterproposals[idx-1] = voterproposals[idx];
-        // "pop" the end of the list, making the list shorter.
-        voterproposals.length--;
-    }
-
-    function    _del_votes( address victim ) private
-    {
-        uint    i_max   =   voterproposals.length;
-        uint    i       =   i_max;
-        // check all proposals
-        while ( i-- > 0 )
-        {
-            // check all voters for every proposal
-            uint    j_max   =   voterproposals[i].voters.length;
-            uint    j       =   j_max;
-            while ( j-- > 0 )
-                if ( voterproposals[i].voters[j] == victim )
-                {
-                    // Found the victim as voter, but since he is about
-                    // to be deleted, we will remove him from the list.
-                    uint    k   =   j;
-                    while ( ++k < j_max )
-                        voterproposals[i].voters[k-1] = voterproposals[i].voters[k];
-                    voterproposals[i].voters.length--;
-                    j_max--;
-                    if ( voterproposals[i].voters.length == 0 )
-                    {
-                        _remove_proposal( i );
-                        i_max--;
-                    }
-                }
-        }
-    }
-
-    function    _remove_voter( address victim ) private
-    {
-        // Remove votes that the victim has already cast.
-        _del_votes( victim );
-        // Move all items in the list (after match) one step closer to the
-        // front of the list.
-        uint    max         =   voters.length;
-        uint    i           =   max;
-        while ( i-- > 0 )
-            if ( voters[i] == victim )
-            {
-                // We could have recycled 'i' here, but for clarity, we don't.
-                uint    j   =   i;
-                while ( ++j < max )
-                    voters[j-1] = voters[j];
-                // "pop" the end of the list, making the list shorter.
-                voters.length--;
-                return;
-            }
-    }
-
-    // This function sees if any proposals have overstayed their welcome and thus
-    // needs to be removed. This is a bit like spring cleaning. We remove proposals
-    // that are over 30k blocks old.
-    function _trimProposals() private
-    {
-        uint    i           =   voterproposals.length;
-        while ( i-- > 0 )
-        {
-            // If a proposal is more than 30K blocks old, then remove it from the list.
-            if ( voterproposals[i].votestart + 30000 <= block.number )
-                _remove_proposal( i );
-        }
-    }
-
-    // We run through the entire list of proposals, checking if they fulfill the
-    // requirements. Why the whole list? Because if a voter has been removed, whom has
-    // not yet voted for a proposal, that proposal may now have achieved majority.
-    function _promotedemote() private
-    {
-        uint    prevlength  =   0;
-        // Keep looping over the list until the number of proposals stops changing.
-        while ( prevlength != voterproposals.length )
-        {
-            uint    i           =   voterproposals.length;
-            prevlength          =   i;
-            uint    majority    =   voters.length / 2 + 1;
-            // Loop over all proposals
-            while ( i-- > 0 )
-            {
-                // If we have enough votes to perform the actual promotion/demotion
-                if ( voterproposals[i].voters.length >= majority )
-                {
-                    // Is it a promotion or a demotion?
-                    if (                voterproposals[i].promotion )
-                        // Add victim to member list
-                        voters.push(   voterproposals[i].victim );
-                    else
-                        // Remove victim from member list
-                        _remove_voter( voterproposals[i].victim );
-    
-                    // Send notification
-                    emit adminChange(   voterproposals[i].victim,
-                                        voterproposals[i].promotion );
-                    // Remove the proposal because the voting is complete.
-                    _remove_proposal( i );
-                }
-            }
-        }
-    }
-    // Returns an index to the position of the proposal inside matching the [victim,promotion] tuple
-    // Beware that:
-    // 0 = not found.
-    // 1 = first position.
-    function    promotionIdx( address victim, bool promotion ) public view returns (uint)
-    {
-	uint   	i       =   voterproposals.length;
-	while ( i-- > 0)
-            if ( voterproposals[i].victim == victim && voterproposals[i].promotion == promotion )
-                return i+1;
-        return 0;
-    }
-
-    // You can call this for free and know if your promote call
-    // will get accepted. Save the network, call this first.
-    function mayVote( address voter, address victim, bool promotion ) public view returns (bool)
-    {
-        // Is caller a voter?
-        if ( !isVoter(voter) )
-            return false;
-
-        // Can't promote someone who is already a voter.
-        bool victimVoter    =   isVoter( victim );
-        if ( victimVoter && promotion )
-            return false;
-
-        // Can't demote someone who is not a member.
-        if ( !victimVoter && !promotion )
-            return false;
-
-        // See if the voter is already in the list of voters for this [victim,promotion] tuple
-        uint    proppos     =   promotionIdx( victim, promotion );
-        if ( proppos > 0 && _findAddressInList( voterproposals[proppos-1].voters, voter ) > 0 )
-            return false;
-        return true;
-    }
-
-    // Calling this function will vote for adding an additional or
-    // removing an existing member of "the inner circle".
-    // As per usual, this requires n/2+1 votes.
-    // The boolean must be true if you want to add a member
-    // and false if you want to remove one.
-    function vote( address victim, bool promotion ) public
-    {
-        if ( ! mayVote(msg.sender, victim, promotion))
-            revert("That seems redundant or is otherwise not allowed.");
-        _trimProposals();
-
-        // Send notification of the vote
-        emit voteCast( msg.sender, victim, promotion );
-
-        uint    proppos     =   promotionIdx( victim, promotion );
-        if ( proppos == 0 )
-        {
-            // This is a new proposal, so we will add it, so we can vote on it.
-            address[] memory emptyAddressList;
-	    proppos = voterproposals.push( Proposal(victim, block.number, promotion, emptyAddressList ) );
-        }
-        proppos--;
-
-        // Add our vote
-        voterproposals[proppos].voters.push( msg.sender );
-
-        // See if we're ready to promote/demote anyone, based on the proposals.
-        _promotedemote();
-
-        // If we have no more voters, we have no reason to live.
-        if ( voters.length == 0 )
-            selfdestruct( msg.sender );
-    }
-}
-
diff --git a/src/Majority/Majority-test.js b/src/Majority/Majority-test.js
new file mode 100755
index 0000000000000000000000000000000000000000..dbf655d2122f0bc84b12a618c75128262ed17dca
--- /dev/null
+++ b/src/Majority/Majority-test.js
@@ -0,0 +1,402 @@
+#!/usr/bin/node
+
+"use strict"
+
+const   Libbfa      =   require( process.env.BFAHOME + '/bin/libbfa.js');
+const   BigInteger  =   require( 'big-integer' );
+const   fs          =   require( 'fs' );
+var     bfa         =   new Libbfa();
+var     web3        =   bfa.newweb3();
+// default
+var     gasprice    =   BigInteger(10).pow(9);
+// globals
+var     accounts;
+var     thecontract;
+
+function cat(filename)
+{
+    try {
+        return fs.readFileSync( filename ).toString();
+    } catch (err) {
+        throw( 'Errors: ' + err.syscall + ' ' + err.path + ': ' + err.code);
+    }
+}
+
+function plural(num, one, many)
+{
+    if (num == 1)
+        return one;
+    return many;
+}
+
+function isittrue( thebool, errortext )
+{
+    return new Promise((resolve,reject) => {
+        if ( undefined == thebool )
+            reject( "thebool is undefined: " + errortext );
+        else
+        if ( thebool == true )
+        {
+            console.log( "Test OK: " + errortext );
+            resolve();
+        }
+        else
+            reject( errortext );
+    })
+}
+
+function echo( txt )
+{
+    return new Promise((resolve,reject) => {
+        console.log( txt );
+        resolve();
+    })
+}
+
+function getgasprice()
+{
+    return new Promise( (resolve, reject) => {
+        web3.eth.getGasPrice()
+        .then(
+            (sat) => {
+                gasprice        =   BigInteger(sat);
+                resolve();
+            },
+            reject
+        )
+    })
+}
+
+function unlockall()
+{
+    return new Promise((resolve,reject) => {
+        var     proms           =   new Array();
+        accounts.forEach(
+            (addr) => {
+                proms.push(
+                    web3.eth.personal.unlockAccount( addr, '', 600 )
+                );
+            }
+        )
+        Promise.all( proms )
+        .then( resolve )
+        .catch( reject );
+    })
+}
+
+function gettestaccountnames()
+{
+    return new Promise( (resolve, reject) => {
+        accounts        =   new Array();
+        web3.bfa.personal.listWallets(
+            ( walletlist ) => {
+                walletlist.forEach(
+                    ( wallet ) => {
+                        wallet.accounts.forEach(
+                            ( account ) => {
+                                var addr    =   account.address;
+                                if ( addr.startsWith('0x7e57') )
+                                    accounts.push( addr );
+                            }
+                        )
+                    }
+                )
+                accounts.sort();
+                resolve();
+            }
+        );
+    })
+}
+
+function getaccountbalances()
+{
+    return new Promise((resolve,reject) => {
+        if ( accounts.length < 4 )
+            throw( "Too few test accounts (found " + accounts.length + ", but need 4)." );
+        var     proms       =   new Array();
+        var     minimum     =   BigInteger(10).pow(9);
+        var     failtxt;
+        accounts.forEach(
+            ( acct ) => {
+                proms.push(
+                    web3.eth.getBalance(acct)
+                    .then(
+                        ( bal ) => {
+                            var     val     =   BigInteger( bal );
+                            if ( val.lesser( minimum ) )
+                            {
+                                if ( undefined == failtxt )
+                                    failtxt +=
+                                        "The minimum balance on each test account must be at least "
+                                        + minimum
+                                        + " satoshi.\n"
+                                failtxt +=
+                                    "The account balance of "
+                                    + accounts[i]
+                                    + " is "
+                                    + val
+                                    + " satoshi.\n"
+                            }
+                        }
+                    )
+                )
+            }
+        );
+        Promise.all( proms )
+        .then(
+            () => {
+                if ( undefined == failtxt )
+                    resolve();
+                else
+                    reject(
+                        failtxt +
+                        "Tests can not be performed without balance on the accounts."
+                    );
+            }
+        );
+    });
+}
+
+function councillistdetails()
+{
+    return new Promise((resolve,reject) => {
+        thecontract.methods.councilLength.call()
+        .then(
+            (howmany) => {
+                console.log(
+                    'The council has '
+                    + howmany
+                    + ' member'
+                    + plural(howmany,'','s')
+                    + '.');
+                return( howmany );
+            }
+        )
+        .then(
+            (howmany) => {
+                var     proms   =   new Array();
+                for ( var i=0 ; i<howmany ; i++ )
+                    proms.push( thecontract.methods.council(i).call() );
+                Promise.all( proms )
+                .then( (addrlist) => {
+                    addrlist.forEach( (addr)=>{console.log("Council member: "+addr)} );
+                    resolve( howmany );
+                });
+            }
+        );
+    })
+}
+
+function voteslistdetails()
+{
+    return new Promise((resolve,reject) => {
+        thecontract.methods.votesLength.call()
+        .then(
+            (howmany) => {
+                console.log(
+                    'There '
+                    + plural(howmany,'is ','are ')
+                    + howmany
+                    + ' vote'
+                    + plural(howmany,'','s')
+                    + ' registered.');
+                return( howmany );
+            }
+        )
+        .then(
+            (howmany) => {
+                var     proms   =   new Array();
+                for ( var i=0 ; i<howmany ; i++ )
+                    proms.push( thecontract.methods.votes(i).call() );
+                Promise.all( proms )
+                .then( (list) => {
+                    list.forEach( (obj)=>{
+                        console.log(
+                            'Lodged vote: '
+                            + obj.voter
+                            + ' has voted to '
+                            + (obj.promotion?'promote':'demote')
+                            + ' '
+                            + obj.victim
+                        );
+                    } );
+                    resolve( howmany );
+                });
+            }
+        );
+    })
+}
+
+function deploynew()
+{
+    return new Promise((resolve, reject) => {
+        var     abi         =   JSON.parse( cat('Majority.abi').trim() );
+        var     bin         =   cat('Majority.bin').trim();
+        var     cAddress;
+        var     fetus       =   new web3.eth.Contract( abi );
+        console.log( "Deploying contract." );
+        var     timeout     =   BigInteger( 86400 );
+        fetus.deploy(
+            {
+                data:       '0x'+bin,
+                arguments:  [ '0x'+timeout.toString(16) ]
+            }
+        )
+        .send(
+            {
+                from:       accounts[0],
+                gas:        3000000,
+                gasPrice:   '0x'+gasprice.toString(16),
+            }
+        )
+        .on( 'transactionHash', (hash) => {
+            console.log( "Deployed in txhash " + hash + "." );
+        })
+        .on( 'confirmation', (num,obj) => {
+            if ( undefined == cAddress )
+            {
+                cAddress    =   obj.contractAddress;
+                thecontract =   new web3.eth.Contract( abi, cAddress );
+                console.log( "Contract is at " + cAddress );
+                resolve();
+            }
+        })
+        .on( 'error', reject );
+    });
+}
+
+function isCouncil( acctpos )
+{
+    return new Promise((resolve,reject) => {
+        var     acct            =   accounts[acctpos];
+        thecontract.methods.isCouncil(acct).call()
+        .then(
+            (yesno) => {
+                var     not     =   'not ';
+                if (yesno)
+                    not         =   '';
+                console.log( acct + ' is ' + not + 'a council member.' );
+                resolve(yesno);
+            }
+        )
+        .catch( reject )
+    })
+}
+
+function vote( voterpos, victimpos, promotion )
+{
+    return new Promise((resolve,reject) => {
+        var     voter           =   accounts[voterpos];
+        var     victim          =   accounts[victimpos];
+        var     confirmed       =   false;
+        var     demote          =   'demote';
+        if (promotion)
+            demote      =   'promote';
+        console.log( voter + " voting to " + demote + " " + victim );
+        thecontract.methods.vote( victim, promotion ).send({
+            from:       voter,
+            gasPrice:   "0x"+gasprice.toString(16),
+            gas:        '0x'+BigInteger(10).pow(6).toString(16)
+        })
+        .on( 'transactionHash', (txhash) => {
+            console.log( ' - txhash ' + txhash );
+        })
+        .on( 'receipt', (rcpt) => {
+            console.log( ' - got a receipt' );
+        })
+        .on( 'confirmation', (num,obj) => {
+            if ( ! confirmed )
+            {
+                confirmed       =   true;
+                resolve();
+            }
+        })
+        .on( 'error', reject )
+    })
+}
+
+function mayVote(voterpos, victimpos, promotion)
+{
+    return new Promise((resolve,reject) => {
+        var     voter           =   accounts[voterpos];
+        var     victim          =   accounts[victimpos];
+        var     demote          =   'demote';
+        if (promotion)
+            demote      =   'promote';
+        thecontract.methods.mayVote( voter, victim, promotion ).call()
+        .then( (may) => {
+                var not     =   'not ';
+                if ( may )
+                    not     =   '';
+                console.log( voter + " may " + not + "vote to " + demote + " " + victim );
+                resolve( may );
+            },
+            reject
+        );
+    })
+}
+
+getgasprice()
+.then( gettestaccountnames )
+.then( getaccountbalances )
+.then( unlockall )
+.then( deploynew )
+
+// initial conditions after deploying the contract
+.then( councillistdetails )
+.then( (n)  => {return isittrue( n==1, "There should be 1 account in the council list." )})
+.then( voteslistdetails )
+.then( (n)  => {return isittrue( n==0, "There should be no entries in the list of registered votes." )})
+.then( ()   => {return isCouncil(0)} )
+.then( (b)  => {return isittrue( b, "Account should be a voter." )})
+.then( ()   => {return isCouncil(1)} )
+.then( (b)  => {return isittrue( !b, "Account should not be a voter." )})
+
+// Adding second account to the council - takes effect immediately, so votes.length == 0
+.then( ()   => {return mayVote(0,1,true)} )
+.then( (b)  => {return isittrue( b, "Account should be allowed to vote for the approval of the new account." )})
+.then( ()   => {return vote(0,1,true)} )
+.then( voteslistdetails )
+.then( (n)  => {return isittrue( n==0, "There should be no entries in the list of registered votes." )})
+.then( councillistdetails )
+.then( (n)  => {return isittrue( n==2, "There should be 2 accounts in the council list." )})
+// Start voting to include third account
+.then( ()   => {return mayVote(1,2,true)} )
+.then( (b)  => {return isittrue( b, "Account should be allowed to vote for the approval of the new account." )})
+.then( ()   => {return vote(1,2,true)} )
+.then( voteslistdetails )
+.then( (n)  => {return isittrue( n==1, "There should be 1 entry in the list of registered votes." )})
+.then( councillistdetails )
+.then( (n)  => {return isittrue( n==2, "There should be 2 accounts in the council list." )})
+// Start voting to remove second account (using second account)
+.then( ()   => {return mayVote(1,1,false)} )
+.then( (b)  => {return isittrue( b, "Account should be allowed to vote for the removal of the account." )})
+.then( ()   => {return vote(1,1,false)} )
+.then( voteslistdetails )
+.then( (n)  => {return isittrue( n==2, "There should be 2 entries in the list of registered votes." )})
+.then( councillistdetails )
+.then( (n)  => {return isittrue( n==2, "There should be 2 accounts in the council list." )})
+// Finalizing vote to remove second account
+.then( ()   => {return mayVote(0,1,false)} )
+.then( (b)  => {return isittrue( b, "Account should be allowed to vote for the removal of the account." )})
+.then( ()   => {return vote(0,1,false)} )
+.then( voteslistdetails )
+.then( (n)  => {return isittrue( n==0, "There should be no entries in the list of registered votes." )})
+.then( councillistdetails )
+.then( (n)  => {return isittrue( n==1, "There should be 1 account in the council list." )})
+// Vote to remove 3rd account.
+.then( ()   => {return mayVote(0,2,true)} )
+.then( (b)  => {return isittrue( b, "Account should be allowed to vote for the approval of the new account." )})
+.then( ()   => {return vote(0,2,true)} )
+.then( voteslistdetails )
+.then( (n)  => {return isittrue( n==0, "There should be no entries in the list of registered votes." )})
+.then( councillistdetails )
+.then( (n)  => {return isittrue( n==2, "There should be 1 account in the council list." )})
+
+// this should self-destruct the contract
+.then( ()   => {return vote(0,0,false)} )
+.catch( (x) => {bfa.fatal("Test FAIL: " + x)})
+.finally( ()   => {
+    console.log('** All tests completed successfully **');
+    process.exit();
+});
diff --git a/src/Majority/Majority.sol b/src/Majority/Majority.sol
new file mode 100644
index 0000000000000000000000000000000000000000..31c5bff342c2f688cfa755b436ac1949eaa56492
--- /dev/null
+++ b/src/Majority/Majority.sol
@@ -0,0 +1,201 @@
+// Robert Martin-Legene <robert@nic.ar>
+// vim:syntax:filetype=javascript:ai:sm
+
+pragma solidity ^0.5;
+
+// This contract is supposed to maintain a list of accounts authorized
+// to control members of "the club" using a majority (n/1+1). We call
+// that group the "council".
+// For instance, could be useful for
+// - a list of sealers
+// - a board of directors
+// - a local chess club
+// - even outsourcing this kind of management from another smart contract.
+
+contract Majority {
+    // This struct contains a list of votes and who has voted for each victim/beneficiary.
+    struct Vote {
+        address             voter;      // Voter.
+        address             victim;     // Whom are we voting about.
+        uint                voteStart;  // When was the vote cast
+        bool		    promotion;  // true=promotion, false=demotion
+    }
+    address[]       public  council;
+    Vote[]          public  votes;
+    uint            public  votetimeout = 604800; // default a week
+
+    event               voteCast( address voter, address victim, bool promotion );
+    event               adminChange( address admin, bool promotion );
+
+    constructor( uint timeout ) public
+    {
+        if ( timeout >= 3600 )
+            votetimeout         =   timeout;
+        council.push( address(msg.sender) );
+    }
+
+    function    setTimeout( uint timeout ) public
+    {
+        if ( ! isCouncil(msg.sender) )
+            revert("Only council members may use this function.");
+        if ( timeout >= 3600 )
+            votetimeout         =   timeout;
+    }
+
+    function    councilLength() public view returns (uint)
+    {
+        return council.length;
+    }
+
+    function    votesLength() public view returns (uint)
+    {
+        return votes.length;
+    }
+
+    // True or false if the subject is a member of the council.
+    function    isCouncil( address subject ) public view returns (bool)
+    {
+        uint    i           =   council.length;
+        while ( i-- > 0 )
+            if ( subject == council[i] )
+                return true;
+        return false;
+    }
+
+    // Move all items in the list (after idx) one step closer to the
+    // front of the list.
+    function    _remove_vote( uint idx ) private
+    {
+        uint    max         =   votes.length;
+        while ( ++idx < max )
+            votes[idx-1] = votes[idx];
+        // "pop" the end of the list, making the list shorter.
+        votes.length--;
+    }
+
+    function    _remove_council_member( address exmember ) private
+    {
+        // Remove votes that the exmember has already cast.
+        uint        i       =   votes.length;
+        while ( i-- > 0 )
+            if ( votes[i].voter == exmember )
+                _remove_vote( i );
+        // Move all items in the council list (after match) one step closer to the
+        // front of the list.
+        i                   =   council.length;
+        while ( i-- > 0 )
+        {
+            if ( council[i] == exmember )
+            {
+                uint    idx =   i;
+                uint    max =   council.length;
+                while ( ++idx < max )
+                    council[idx-1] = council[idx];
+                // "pop" the end of the list, making the list shorter.
+                council.length--;
+                return;
+            }
+        }
+    }
+
+    // We run through the entire list of votes, checking if they fulfill the
+    // requirements.
+    function _promotedemote( address victim, bool promotion ) private
+    {
+        uint    numvotes        =   0;
+        uint    majority        =   council.length / 2 + 1;
+        uint    i               =   votes.length;
+        while ( i-- > 0 )
+            if ((    votes[i].victim    == victim )
+                && ( votes[i].promotion == promotion )
+            )
+                numvotes++;
+        // If we don't have enough votes to perform the actual promotion/demotion
+        if ( numvotes < majority )
+            return;
+        // Is it a promotion or a demotion?
+        if ( promotion )
+            // Add victim to member list
+            council.push( victim );
+        else
+            // Remove victim from member list
+            _remove_council_member( victim );
+        // Send notification
+        emit adminChange( victim, promotion );
+        // Remove the vote because the voting is complete.
+        i                   =   votes.length;
+        while ( i-- > 0 )
+            if ((    votes[i].victim    == victim )
+                && ( votes[i].promotion == promotion )
+            )
+            _remove_vote( i );
+    }
+
+    // You can call this for free and know if your promote call
+    // will get accepted. Save the network, call this first.
+    function mayVote( address voter, address victim, bool promotion ) public view returns (bool)
+    {
+        bool    voterIsOnCouncil    =   isCouncil( voter );
+        bool    victimIsOnCouncil   =   isCouncil( victim );
+
+        if ( ! voterIsOnCouncil )
+            return false;
+
+        // Can't promote someone who is already on the council
+        if ( victimIsOnCouncil && promotion )
+            return false;
+
+        // Can't demote someone who is not a council member.
+        if ( !victimIsOnCouncil && !promotion )
+            return false;
+
+        // See if he is trying to cast a vote already registered
+        uint    ancient             =   block.timestamp - votetimeout;
+        uint    i                   =   votes.length;
+        while ( i-- > 0 )
+        {
+            if (   (votes[i].voter     == voter)
+                && (votes[i].victim    == victim)
+                && (votes[i].promotion == promotion)
+                && (votes[i].voteStart > ancient)
+            )
+                return false;
+        }
+        return true;
+    }
+
+    // Calling this function will vote for adding an additional or
+    // removing an existing member of council.
+    // This requires n/2+1 votes (more than 50%).
+    // The boolean must be true if you want to add a member
+    // and false if you want to remove one.
+    function vote( address victim, bool promotion ) public
+    {
+        if ( ! mayVote(msg.sender, victim, promotion))
+            revert("That seems redundant or is otherwise not allowed.");
+        // A little house keeping - if a vote is too old, then remove it
+        uint    ancient                 =   block.timestamp - votetimeout;
+        uint    i                       =   votes.length;
+        while ( i-- > 0 )
+            if ( votes[i].voteStart < ancient )
+                _remove_vote( i );
+        // End of house keeping
+        // Store the vote
+	votes.push(
+            Vote(
+                msg.sender,
+                victim,
+                block.timestamp,
+                promotion
+            )
+        );
+        // Send notification of the vote
+        emit voteCast( address(msg.sender), victim, promotion );
+        // See if we're ready to promote/demote anyone, based on the votes.
+        _promotedemote( victim, promotion );
+
+        // If we have no more council members, then we have no reason to continue living.
+        if ( council.length == 0 )
+            selfdestruct( msg.sender );
+    }
+}
diff --git a/src/Majority/README.md b/src/Majority/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..6e918289c3d916ef60fb5b7a776f57f4d3d36a76
--- /dev/null
+++ b/src/Majority/README.md
@@ -0,0 +1,99 @@
+# Majority
+
+This contract maintains a list of accounts authorized
+to control members of "the club" using a majority (n/1+1).
+We call that group the "council".
+
+For instance, could be useful for
+  * a list of sealers
+  * a board of directors
+  * a local chess club
+  * even outsourcing this kind of management from another smart contract.
+
+There are a few functions that can be used to see the contents of the
+data structures used. Usually, you probably only need to **vote()** and
+to ask if some account **isCouncil()**
+
+See the test suite for examples of how to extract information from the
+different datastructures, if you feel you really need that.
+
+## Events
+
+### voteCast
+
+**voteCast( address voter, address victim, bool promotion )**
+
+Gives the address of who voted, who they voted for and if it should
+be a promotion (true) or demotion (false).
+
+A promotion means to become a member of the council.
+
+A demotion means to be removed from the list of council members.
+
+### adminChange
+
+**adminChange( address admin, bool promotion )**
+
+This event is emitted when an address has received enough votes to be
+promoted or demoted and that action is taken.
+
+## Functions
+
+### constructor
+
+**constructor( uint timeout )**
+
+This function is called when the contract is getting deployed.
+
+The deploying address automatically becomes the only council member and
+needs to vote to include other's. The contract creator has no special
+powers other than other council members, and as such can be voted out
+and lose all control over the contract.
+
+If you specify an integer when deploying the contract, you can change
+the time it takes for a vote to time out. The timeout can not be set
+lower than one hour. The default is 7 days.
+
+### setTimeout
+
+**setTimeout( uint timeout )**
+
+Change the timeout (in seconds) for validity of votes cast.
+Any council member can change the timeout.
+The timeout can not be set lower than one hour.
+
+### councilLength
+
+**councilLength()**
+
+Returns a uint telling how many entries are in the council list.
+
+### votesLength
+    
+**votesLength()**
+
+Returns a uint telling how many structs are in the votes array.
+
+### isCouncil
+
+**isCouncil( address subject )**
+
+Returns true or false whether the given address is a member of the
+council.
+
+### mayVote
+
+**mayVote( address voter, address victim, bool promotion )**
+
+Returns true or false if a certain address may vote a certain vote for
+a certain address.
+
+### vote
+
+**vote( address victim, bool promotion )**
+
+Performs actual voting on including (true) or removing (false) an address
+from the council.
+
+If the final member of the council votes to remove itself, the contract
+will self-destruct.
diff --git a/src/Majority/test.sh b/src/Majority/test.sh
new file mode 120000
index 0000000000000000000000000000000000000000..f32190cf204c3de403bb87332ac09b9eea67256b
--- /dev/null
+++ b/src/Majority/test.sh
@@ -0,0 +1 @@
+../test/test-harness-v1.sh
\ No newline at end of file
diff --git a/src/test/README.md b/src/test/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e8aa778c240521e80ef15f43e05c30bf3c766513
--- /dev/null
+++ b/src/test/README.md
@@ -0,0 +1,2 @@
+Esta carpeta contiene llaves privadas.
+Cuidado usar las llavas para algo importante (fuera de pruebas).
diff --git a/src/test/test-harness-v1.sh b/src/test/test-harness-v1.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4b2fa254fd00b704c3c51fd0bea6b11bb098443a
--- /dev/null
+++ b/src/test/test-harness-v1.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+true ${BFAHOME:=$( echo ~bfa/bfa )}
+source ${BFAHOME}/bin/libbfa.sh || exit 1
+
+function testfatal
+{
+    test -n "$1" && echo $*
+    exit 1
+}
+
+function testcleanup
+{
+    for filename in ${keyfiles}
+    do
+        test -f "${filename}"
+        rm "${filename}"
+    done
+}
+
+function writetestkeysto
+{
+    local destinationdirectory=$1
+    for i in {1..4}
+    do
+        case $i in
+            1)
+                set 7e5705d6e4fd660ad6d2d6533f3b4b6647dbb0a2 ac6c3c3e7351f858a3ffd9db00dc70b72a16e5ab0723be0a3da6da228778ce29 717f2930a84107df56fcb3308c116cfe 64fc98daae4a96777b8c08396a92bc8dde1117d439c7eb0b9a016a11e512dc0a 89e869cae11f4a9b3d1f8d684119a64d6c1b25451152b5fb413c531813f83b0d 0643d804-b9df-4df9-8819-d38463480f15
+                ;;
+            2)
+                set 7e57105001438399e318753387b061993753a545 3d3f422c4c227a37db5728e72c91e43cbd391f7e338fe6aaeb68b65416b33b48 676cf0e07562c52f6c933de714c0506f 8e9869dc6aab090ff389ecc84815d0abc108f437d11c1d56a91c83277c77acd5 87237d3a481ab6a1c50b9ef07b24c0118644e7bf3b27170766ec6b860300890d ee0c6421-b0f1-41db-934e-3cab0c419875
+                ;;
+            3)
+                set 7e57215a8af47c70baca431c45ecf14199f4bd9a 84c5a655cdd6a402ef88ea1f64a4c427ce3cdaec88f352c0194254095b8b71c4 80e54ea9307624e2d4ec7179f2fcfa3c 9aaeba47fbb5e7ba8d327a1a544712184424021db8e4d238f60bdba81f02a4ca 7ef19ab55611ea649b65de754fb68d5f810550fe9ccfcfd2f067d3e56b4aee04 4b1c00d1-292f-4610-bbc0-2bb4d6489ea4
+                ;;
+            4)
+                set 7e57348aec1b1fd574ef2c0702acaa197c46d613 2d6e3b21ab6c69ad789703acc967f93955690626333c667bc34e48650bf95d59 fc98dd18e1292cb5c6578ecbbc29cbb3 75cc35ea980da5b70aa146ac02fa9c3fd9d0015cdda8b6737ba17ba95f24cd8a fcb3ed29dfe7ec444c929e880ecc822af894ee497de7e29484ecccfcd7187b8b 05d45b22-185e-49d4-bc91-9ab684e8e75a
+                ;;
+            *)
+                false
+                ;;
+        esac
+        local address=$1
+        local ciphertext=$2
+        local iv=$3
+        local salt=$4
+        local mac=$5
+        local id=$6
+        local cipherparams="{\"iv\":\"${iv}\"}"
+        local kdfparams="{\"dklen\":32,\"n\":262144,\"p\":1,\"r\":8,\"salt\":\"${salt}\"}"
+        local crypto="{\"cipher\":\"aes-128-ctr\",\"ciphertext\":\"${ciphertext}\",\"cipherparams\":${cipherparams},\"kdf\":\"scrypt\",\"kdfparams\":${kdfparams},\"mac\":\"${mac}\"}"
+        local thisfilename="${destinationdirectory}/UTC--2018-12-24T12-00-00.000000000Z--${address}"
+        echo "{\"address\":\"${address}\",\"crypto\":${crypto},\"id\":\"${id}\",\"version\":3}" > ${thisfilename}
+        keyfiles="${keyfiles} ${thisfilename}"
+    done
+}
+
+keyfiles=
+trap testfatal ERR
+trap testcleanup EXIT
+contractname=$( ls -1 *.sol | head -1 | sed 's/\.sol$//' )
+test -f ${contractname}.sol
+test -r ${contractname}.sol
+solc --combined-json abi,bin ${contractname}.sol > ${contractname}.json
+jq -r ".contracts.\"${contractname}.sol:${contractname}\".abi" < ${contractname}.json > ${contractname}.abi
+jq -r ".contracts.\"${contractname}.sol:${contractname}\".bin" < ${contractname}.json > ${contractname}.bin
+rm ${contractname}.json
+writetestkeysto ${BFANODEDIR}/keystore
+for tester in ${contractname}-test*
+do
+    if [ -n "${tester}" -a -x "${tester}" -a -r "${tester}" ]
+    then
+        ./${tester}
+    fi
+done