diff --git a/.gitignore b/.gitignore
index 5a0e84e16d4892f4743ee5a47dbd0436634290b5..d9a84d8a68bb72dcd95e39d300b55eddef2c7e98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,11 +5,9 @@ cache
 bin/env
 network/node
 network/bootnode
-network/contracts/*
 network/*.pid
 test2network*/node
 test2network*/bootnode
-test2network*/contracts/*
 test2network/*.pid
 node_modules
 src/*/*.bin
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..9c3ccff9fefb836d18ac2fc3cdf10d1985d46522
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LGPLv2-only
\ No newline at end of file
diff --git a/bin/bfageth b/bin/bfageth
new file mode 100755
index 0000000000000000000000000000000000000000..17ba3df8dabe334619e97cbdf38e2bfebe07e508
--- /dev/null
+++ b/bin/bfageth
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+if [ -z "${BFAHOME}" ]; then echo "\$BFAHOME not set. Did you source bfa/bin/env ?" >&2; exit 1; fi
+source ${BFAHOME}/bin/libbfa.sh || exit 1
+
+exec geth --datadir ${BFANODEDIR} --networkid ${BFANETWORKID} "$@"
diff --git a/bin/events.js b/bin/events.js
new file mode 100755
index 0000000000000000000000000000000000000000..ffaf83c37df2beca7f1a98394935a52f9b1909bd
--- /dev/null
+++ b/bin/events.js
@@ -0,0 +1,96 @@
+#!/usr/bin/node
+// 20200828 Robert Martin-Legene
+// License: GPLv2-only
+// (c) Secretaria Legal y Tecnica, Presidencia De La Nacion, Argentina
+
+// Looks for the contract you're specifying as argument.
+// Tries to show you all the events that contract has ever logged on the blockchain.
+// The contract's ABI must be in a text file in ${BFANETWORKDIR}/contracts/${contractaddr}/abi
+
+const Web3		= require( "web3" );
+const fs		= require( "fs" );
+const web3		= new Web3( "http://127.0.0.1:8545" );
+
+function writeEvent(ev)
+{
+	console.log("");
+	console.log( "Block number: " + ev.blockNumber );
+	console.log( "TX hash: "      + ev.transactionHash );
+	console.log( "Event name: "   + ev.event );
+	Object.keys( ev.returnValues ).forEach(
+		function writeEventValue( value )
+		{
+			// Will skip keys which are made entirely of digits.
+			if ( ! value.match(/^[0-9]+$/) )
+			{
+				console.log( "* " + value + ": " + ev.returnValues[value] )
+			}
+		}
+	);
+}
+
+
+function gotPastEvents( e, result )
+{
+	// We are called a single time.
+	// Either success or failure.
+	if ( e )
+	{
+		console.error( e );
+		process.exit( 1 );
+	}
+	result.forEach( writeEvent );
+	console.log( "\n" + result.length + " events." );
+}
+
+var	contractname	=	process.argv[2];
+if ( contractname === undefined )
+{
+        console.error( "Usage: " + process.argv[1] + " <contractname|contractaddr>" );
+        process.exit( 1 );
+}
+if ( process.env.BFANETWORKDIR === undefined ) 
+{
+	console.error( "$BFANETWORKDIR must be defined" );
+	process.exit( 1 );
+}
+var	pathprefix	=	process.env.BFANETWORKDIR + "/contracts/";
+var	filename;
+if ( fs.existsSync( 		contractname ))
+	filename	=	contractname;
+else
+if ( fs.existsSync( 		contractname.toLowerCase() ))
+	filename	=	contractname.toLowerCase();
+else
+if ( fs.existsSync( 		pathprefix + contractname ))
+	filename	=	pathprefix + contractname;
+else
+if ( fs.existsSync( 		pathprefix + contractname.toLowerCase() ))
+	filename	=	pathprefix + contractname.toLowerCase();
+if ( filename === undefined )
+{
+	console.error( "Contract not found." );
+	process.exit( 1 );
+}
+var	contractaddr	=	filename;
+var	dirname		=	"";
+var	idx		=	filename.lastIndexOf("/");
+if ( idx > -1 )
+{
+	dirname		=	filename.substr( 0, idx+1 );
+	filename	=	filename.substr( idx+1    )
+	contractaddr	=	filename;
+}
+var	stats		=	fs.lstatSync( dirname + filename );
+if ( stats.isSymbolicLink() )
+{
+	let linkname	=	fs.readlinkSync( dirname + filename, { encoding: 'utf8' } );
+	filename	=	dirname + linkname;
+	contractaddr	=	linkname;
+}
+filename		+=	"/abi";
+var	abi_str		=	fs.readFileSync( filename, { encoding: 'utf8' } );
+var	abi		=	JSON.parse( abi_str );
+var	contract	=	new web3.eth.Contract( abi, contractaddr );
+console.log( "Address: "      + contractaddr );
+contract.getPastEvents( "allEvents", { fromBlock: "earliest", toBlock: "latest" }, gotPastEvents);
diff --git a/bin/installbfa.sh b/bin/installbfa.sh
index f95aef8b1970b1da9a030cb36e2274cd57961783..39e5dfb1224e53d2c2feb828eb779be51779af0b 100755
--- a/bin/installbfa.sh
+++ b/bin/installbfa.sh
@@ -28,19 +28,16 @@ function    info
 # Runs as the owner of the given directory, and in the given directory
 function runasownerof
 {
-    path=$1
-    precmd=
+    local where=$1 precmd=
     shift 1
-    pushd $path > /dev/null
+    pushd $where > /dev/null
     if [ $( stat --format=%u . ) -ne $UID ]
     then
-        precmd="sudo --preserve-env --set-home -u $( stat --format=%U . ) PATH=${PATH}"
+        precmd="sudo --preserve-env --shell --set-home --user=$( stat --format=%U . ) PATH=${PATH}"
     fi
-    unset path
     ${precmd} "$@"
-    rv=$?
+    local rv=$?
     popd > /dev/null
-    unset precmd
     return $rv
 }
 
@@ -66,56 +63,7 @@ function nodejsinstall
     aptinstall nodejs
     info "Installing nodejs modules (will show many warnings)"
     runasownerof ${BFAHOME} npm install
-}
-
-function golanginstall
-{
-    if [ ! -d /usr/local/go ]
-    then
-        info "Downloading package of go binaries."
-        mkdir -p ${NEW}
-        cd       ${NEW}
-	local arch=$( uname -m )
-        case "${arch}" in
-            x86_64)
-                arch=amd64
-                ;;
-            *)
-		echo "We have no recipe for how to build on your \"${arch}\" platform." >&2
-                exit 1
-                ;;
-        esac
-
-        #Download go*.linux-${ARCH}.tar.gz from https://golang.org/dl/
-        golangurl=$(
-            curl -f https://golang.org/dl/ 2>/dev/null |
-            grep linux-${arch}.tar.gz |
-            grep downloadBox |
-            sed 's/.*href="//;s/".*//' |
-            head -1
-        )
-        name=$( basename $golangurl )
-        if [ -r "$name" ]
-        then
-            # If we have the download, check if it is corrupt
-            tar -ztf "$name" >/dev/null 2>&1    ||
-            rm -f "$name"
-        fi
-        if [ ! -r "$name" ]
-        then
-            curl -f -O $golangurl               ||
-                rm -f "$name"
-        fi
-        # Integrity checking the archive
-        tar -xzf "$name"
-        info "Unpacking $name into /usr/local"
-        tar -C /usr/local -xzf go*.tar.gz
-    fi
-    if [ $( expand < ~bfa/.bashrc | grep -E "^PATH=.*/usr/local/go/bin" | wc -l ) -eq 0 ]
-    then
-        echo "PATH=\${PATH}:/usr/local/go/bin" >> ~bfa/.bashrc
-    fi
-    export PATH=${PATH}:/usr/local/go/bin
+    runasownerof ${BFAHOME} npm audit fix
 }
 
 function gethinstall
@@ -142,13 +90,14 @@ function gethinstall
 function initgenesis
 {
     (
+    	HOME=$( echo ~bfa )
         source ${BFAHOME}/bin/env
         BFANETWORKDIR=${BFANETWORKDIR:-${BFAHOME}/network}
         BFANODEDIR=${BFANODEDIR:-${BFANETWORKDIR}/node}
         if [ ! -d "${BFANODEDIR}" -o ! -d "${BFANODEDIR}/geth/chaindata" ]
         then 
             info "Node is not initialised. Initialising with genesis."
-            geth --networkid ${BFANETWORKID} --cache 0 --datadir "${BFANODEDIR}" init "${BFANETWORKDIR}/genesis.json"
+            runasownerof "${BFAHOME}" geth --networkid ${BFANETWORKID} --cache 0 --datadir "${BFANODEDIR}" init "${BFANETWORKDIR}/genesis.json"
             chown -R bfa:bfa ~bfa
         fi
     )
@@ -259,11 +208,10 @@ grep -q Ubuntu /etc/issue && apt-add-repository multiverse
 #
 apt update
 # development tools
-aptinstall dirmngr apt-transport-https curl git curl build-essential sudo software-properties-common
+aptinstall dirmngr apt-transport-https curl git curl build-essential sudo software-properties-common golang
 aptinstall jq libjson-perl libwww-perl libclass-accessor-perl
 userconfig
 nodejsinstall
-golanginstall
 gethinstall
 initgenesis
 cronit
diff --git a/bin/libbfa.js b/bin/libbfa.js
index 5f562c411337eb04a304d7ae120970607998746f..c968b578fb8aa1dc6b821fb7e63cf2b498b435e6 100644
--- a/bin/libbfa.js
+++ b/bin/libbfa.js
@@ -167,13 +167,16 @@ module.exports = class Libbfa
             });
             socket.on("error", (e) => {
                 console.error(e);
-                err = e;
+                err             =   e;
             });
             socket.on("close", () => {
+                if ( result === undefined )
+                    err         =   'Error undefined (close)'
+                else
                 if ( result.error && result.error.code && result.error.message )
-                    err = 'Error ' + result.error.code + ": "+ result.error.message;
+                    err         =   'Error ' + result.error.code + ": "+ result.error.message;
                 else
-                    result = result.result;
+                    result      =   result.result;
                 callback( err, result );
             });
         }
diff --git a/bin/monitor.js b/bin/monitor.js
index 0596f89767877e73296453229da7e8913feb2765..303af882e0924853926cf35c7d3e29c9315aea5c 100755
--- a/bin/monitor.js
+++ b/bin/monitor.js
@@ -156,25 +156,24 @@ function gotAdminPeers( err, nodelist )
     // Try to connect to a random node if we have very few peers
     if ( nowpeers.length < 5 )
     {
-        var     candidatenew    =   [];
+        var     candidate       =   [];
         // find candidate nodes which we can connect to
         // (it comes from peers.cache)
         peerscache.forEach(
             function(acachedpeer) {
-                if (
-                    // Add "a cached peer" to "candidate new" peers
-                    // if the cached peer is not in the list of current node.
-                    currentnodes.find( function(element) { element.info != acachedpeer } )
-                )
+                // Add "a cached peer" to "candidate" peers
+                // if the cached peer is not in the list of currently
+                // connected nodes.
+                if ( ! currentnodes.includes( acachedpeer ) )
                 {
-                    candidatenew.push( acachedpeer );
+                    candidate.push( acachedpeer );
                 }
             }
         );
-        if ( candidatenew.length > 0 )
+        if ( candidate.length > 0 )
         {
-            var     i           =   Math.floor( Math.random() * candidatenew.length );
-            var     enode       =   candidatenew[i];
+            var     i           =   Math.floor( Math.random() * candidate.length );
+            var     enode       =   candidate[i];
             console.log(
                 "We have "
                 + nowpeers.length
@@ -311,6 +310,10 @@ function    unlock()
 
 function    timer()
 {
+    if ( bfa.sockettype == 'ipc' && ! bfa.fs.existsSync( bfa.socketurl ) )
+    {
+        return;
+    }
     if ( netid == 0 )
     {
         web3.eth.net.getId()
@@ -318,7 +321,7 @@ function    timer()
             netid = x;
         } )
 	.catch( err => {
-            console.error("monitor.js non-fatal: "+err)
+            console.log( "monitor.js waiting for geth to open socket." );
         });
         return;
     }
diff --git a/bin/registro-verificar b/bin/registro-verificar
new file mode 100755
index 0000000000000000000000000000000000000000..ed2e286a8e8c629efa26e7bfd5694097cd13af62
--- /dev/null
+++ b/bin/registro-verificar
@@ -0,0 +1,275 @@
+#!/usr/bin/node
+
+const   Web3                =   require( 'web3' );
+// make sure we die early if this is not installed
+const   dummy               =   require( 'web3-eth-accounts' );
+const   fs                  =   require( 'fs' );
+var     web3                =   new Web3( 'http://localhost:8545/' );
+var     myArgs              =   process.argv.slice(2);
+var     myaccount           =   myArgs[0];
+var     proof               =   myArgs[1];
+var     password            =   myArgs[2] || '';
+var     contractaddr;
+var     networkid;
+var     chainid;
+var     rawdata;
+var     estimate;
+
+if ( myArgs.length < 2 || myArgs.length > 3 )
+{
+    let     myname                  =   process.argv[1].replace(/^.*\//, '');
+    console.error( "Wrong number of arguments.");
+    console.error( "Usage: " + myname + " <account> <proof> [<privatekeypassword>]");
+    process.exit( 1 );
+}
+
+function    findMatchingFile( needle, accountdir )
+{
+    return new Promise(
+        function(resolve, reject)
+        {
+            // strip leading '0x' from the needle, if present
+            // also lowercase the name
+            needle                  =   needle.replace(/^0x/, '').toLowerCase();
+            let     haystack        =   fs.readdirSync( accountdir, 'utf8' );
+            haystack.forEach(
+                (filename) => {
+                    if ( filename.toLowerCase().endsWith('--'+needle) )
+                        resolve(accountdir + filename);
+                }
+            );
+            reject( 'Account not found. Try specifying the absolute path to your key file, or set one of $BFAHOME $BFANETWORKDIR $BFANODEDIR $KEYSTORE' );
+        }
+    );
+}
+
+function    findaccountfile( accountfile )
+{
+    return new Promise(
+        function( resolve, reject )
+        {
+            // find account file
+            // is it actually just a file?
+            if ( accountfile.includes('/') )
+            {
+                resolve( accountfile );
+                return;
+            }
+            let     accountdir;
+            // if not a file, then look (only) at the most specific directory we have been told about.
+            if ( process.env.KEYSTORE !== undefined )
+                accountdir                  =   process.env.KEYSTORE;
+            else
+            if ( process.env.BFANODEDIR !== undefined )
+                accountdir                  =   process.env.BFANODEDIR + '/keystore/';
+            else
+            if ( process.env.BFANETWORKDIR !== undefined )
+                accountdir                  =   process.env.BFANETWORKDIR + '/node/keystore/';
+            else
+            if ( process.env.BFAHOME !== undefined )
+                accountdir                  =   process.env.BFAHOME + '/network/node/keystore/';
+            else
+                accountdir                  =   process.env.HOME + '.geth/keystore';
+            findMatchingFile( myaccount, accountdir )
+            .then( resolve )
+            .catch( reject );
+        }
+    );
+}
+
+function    loadaccount( path )
+{
+    return new Promise(
+        function( resolve, reject )
+        {
+            let     contents        =   fs.readFileSync( path, 'utf8' );
+            fs.readFile( path, 'utf8', (err,data) => {
+                if ( err )
+                {
+                    reject( err );
+                    return;
+                }
+                let     keystore;
+                try {
+                    keystore        =   JSON.parse( data );
+                }
+                catch(e)
+                {
+                    reject( e );
+                    return;
+                }
+                let     w;
+                try {
+                    w               =   web3.eth.accounts.wallet.decrypt( [keystore], password );
+                }
+                catch(e)
+                {
+                    reject( e );
+                    return;
+                }
+                // Make sure we have the address properly written (instead of a
+                // filename) if that was given as parameter when starting the program.
+                if ( w === undefined )
+                {
+                    reject( "Unable to decrypt the key file." );
+                    return;
+                }
+                myaccount               =   w[0]["address"];
+                resolve();
+            });
+        }
+    );
+}
+
+function    gasestimate(method)
+{
+    return new Promise(
+        function( resolve, reject )
+        {
+            method.estimateGas()
+            .then( g  => { estimate = g } )
+            .then( resolve )
+        }
+    );
+}
+
+function getnetworkid()
+{
+    return new Promise(
+        function( resolve, reject )
+        {
+            web3.eth.net.getId()
+            .then( id => {
+                networkid                   =   id;
+                if ( networkid == 0xb10c4d39a )
+                {
+                    contractaddr            =   "0xc3cF96aF51d3c41DfBE428dE75a8E5597d4D7A7B";
+                    chainid                 =   0xbfa2018;
+                }
+                else
+                {
+                    reject( "There is insufficient information to function on this chain." );
+                    return;
+                }
+                resolve();
+            });
+        }
+    );
+}
+            
+function getnonce( account )
+{
+    return new Promise(
+        function( resolve, reject )
+        {
+            // Cuantas transacciones ya hemos hecho?
+            web3.eth.getTransactionCount( account )
+            .then( txcount => {
+                nonce                       =   txcount;
+                resolve();
+            });
+        }
+    );
+}
+
+function prepareCall()
+{
+    var     abi                     =   [
+        {
+            "name"                  : "hash",
+            "type"                  : "function",
+            "inputs"                : [ { "name": "key", "type": "string"  } ],
+            "outputs"               : [ { "name": "",    "type": "bytes32" } ],
+            "constant"              : true,
+            "payable"               : false,
+            "stateMutability"       : "pure"
+        },
+        {
+            "name"                  : "proveControl",
+            "type"                  : "function",
+            "inputs"                : [ { "name": "key", "type": "string" } ],
+            "outputs"               : [ { "name": "",    "type": "bool"   } ],
+            "constant"              : false,
+            "payable"               : false,
+            "stateMutability"       : "nonpayable"
+        }
+    ];
+    return new Promise(
+        function(resolve, reject)
+        {
+            let     p1              =   getnetworkid();
+            let     p2              =   getnonce( myaccount );
+            Promise.all([ p1, p2 ])
+            .then( () => {
+                if ( contractaddr === undefined )
+                {
+                    reject( "The contract address is not defined for this network." );
+                    return;
+                }
+                // Creá una instancia del contrato
+                var     contractInstance    =   new web3.eth.Contract(abi, contractaddr );
+                //Estimá el gas que vas a gastar
+                let     method      =   contractInstance.methods.proveControl( proof );
+//              contractInstance.methods.hash(proof).call().then( console.log );
+                rawdata             =   method.encodeABI();
+                gasestimate( method )
+                .then( resolve );
+            });
+        }
+    );
+}
+
+function    signrawtx()
+{
+    //Generá una transacción
+    let     promise             =   web3.eth.accounts.signTransaction(
+        {
+            "from"              :   myaccount,
+            "to"                :   contractaddr,
+            "nonce"             :   nonce,
+            "value"             :   0,
+            "data"              :   rawdata,
+            "gas"               :   estimate,
+            "gasPrice"          :   1000000000,
+            "common"            :
+            {
+                "customChain"   :
+                {
+                    "name"      :   "BFA 2018",
+                    "chainId"   :   chainid,
+                    "networkId" :   networkid
+                }
+            }
+        },
+        web3.eth.accounts.wallet[0].privateKey
+    );
+    return promise;
+}
+
+function    printReceipt( r )
+{
+    console.log( "blockNumber: " + r.blockNumber );
+    console.log( "transactionHash: " + r.transactionHash );
+    console.log( "Status: " + (r.status?'Transaction successful':'Transaction failed') );
+}
+
+findaccountfile( myaccount )
+.then( (path)=> { return loadaccount(path) } )
+// prepara la instancia del contrato
+.then (         prepareCall )
+// Firma "offline"
+.then( ()    => { return signrawtx() })
+// Manda la transaccion a la blockchain
+.then( tx    => {
+    console.log( "Sending this transaction: " + tx.rawTransaction );
+console.log( "TX hash: " + tx.transactionHash );
+//process.exit( 0 );
+    web3.eth.sendSignedTransaction( tx.rawTransaction )
+    //Obtené el recibo.
+    .once( 'receipt', printReceipt )
+    .catch( (e) => { console.error( "It would seem that the transaction has failed:\n" + e)} );
+})
+.catch( e    => {
+    console.error( "Oh no. Something most definitely seems to have gone wrong. What I was told is:" );
+    console.error( e );
+});
diff --git a/bin/singlestart.sh b/bin/singlestart.sh
index aa7da1c932b63df4d67c36c56e7d40034d5b0782..df3f158329d0dd11f0552c047d4cf7d1d011873e 100755
--- a/bin/singlestart.sh
+++ b/bin/singlestart.sh
@@ -10,46 +10,73 @@ bootnodekeyfile=${BFANETWORKDIR}/bootnode/key
 # Bail out if anything fails.
 trap "exit 1" ERR
 # Detect children dying
-trap "reaper" SIGINT SIGCHLD
-unset LOGDIR LOGPIPE PIDIDX
-declare -A PIDIDX
-trap "killallprocs" SIGTERM
+trap "reaper" SIGCHLD
+trap "killprocs" SIGTERM
+trap "killallprocs" EXIT
+unset LOGPIPE PIDIDX ALLPIDIDX TMPDIR SHUTTING_DOWN
+declare -A PIDIDX ALLPIDIDX
+SHUTTING_DOWN=0
 
 function reaper()
 {
-    local wecare=0
+    local reaped_a_child_status=0
+    # Find out which child died
     for pid in ${!PIDIDX[*]}
     do
         kill -0 $pid 2>/dev/null && continue
+        reaped_a_child_status=1
         local rc=0
         wait $pid || rc=$? || true
-        echo "*** ${PIDIDX[$pid]} (pid $pid) has exited with value $rc" 
+	local name=${PIDIDX[$pid]}
+        echo "*** $name (pid $pid) has exited with value $rc" 
+	if [ $name = 'geth' ]; then geth_rc_status=$rc; fi
         unset PIDIDX[$pid]
-        wecare=1
     done
-    test $wecare = 1 || return
-    # kill all - an extra kill doesn't hurt. I hope.
+    # Return if we didn't find out which child died.
+    if [ $reaped_a_child_status = 0 ]; then return; fi
+    # Kill all other registered processes
     for pid in ${!PIDIDX[*]}
     do
-        # don't kill log.sh, because it may still be logging for us
-        if [ "${PIDIDX[$pid]}" != "log.sh" ]
-        then
-            echo "*** Killing ${PIDIDX[$pid]} (pid $pid)."
-            kill $pid || true
-        fi
+        kill -0 $pid 2>/dev/null || continue
+        echo "*** Killing ${PIDIDX[$pid]} (pid $pid)."
+        kill $pid || true
     done
-    max=30
 }
 
-function killallprocs()
+function killprocs()
 {
+    SHUTTING_DOWN=1
     if [ ${#PIDIDX[*]} -gt 0 ]
     then
         echo "*** Killing all remaining processes: ${PIDIDX[*]} (${!PIDIDX[*]})."
-        kill -KILL ${!PIDIDX[*]} 2>/dev/null || true
+        kill ${!PIDIDX[*]} 2>/dev/null || true
     fi
 }
 
+function killallprocs()
+{
+    # merge the 2 pid list, to make killing and echoing easier
+    for pid in ${!ALLPIDIDX[*]}
+    do
+        PIDIDX[$pid]=${ALLPIDIDX[$pid]}
+        unset ALLPIDIDX[$pid]
+    done
+    killprocs
+    if [ -n "$TMPDIR" -a -e "$TMPDIR" ]
+    then
+	rm -rf "$TMPDIR"
+    fi
+}
+
+function startgeth()
+{
+    echo "***" Starting geth $*
+    # "NoPruning=true" means "--gcmode archive"
+    geth --config ${BFATOML} $* &
+    gethpid=$!
+    PIDIDX[${gethpid}]="geth"
+}
+
 # You can start as:
 # BFAHOME=/home/bfa/bfa singlestart.sh
 # singlestart.sh /home/bfa/bfa
@@ -75,8 +102,19 @@ else
 fi
 source ${BFAHOME}/bin/libbfa.sh
 
+TMPDIR=$( mktemp -d )
+mknod             ${TMPDIR}/sleeppipe p
+sleep 987654321 > ${TMPDIR}/sleeppipe &
+ALLPIDIDX[$!]='sleep'
 if [ "$VIRTUALIZATION" = "DOCKER" ]
 then
+    echo
+    echo
+    echo
+    echo
+    date
+    echo $0 startup
+    echo
     echo "See log info with \"docker logs\""
 else
     echo "Logging mostly everything to ${BFANODEDIR}/log"
@@ -86,21 +124,40 @@ else
     # Docker has it's own logging facility, so we will not use our own
     # logging functionality if we're in docker.
     echo "*** Setting up logging."
-    # Clean up logging
-    LOGDIR=$( mktemp -d )
-    trap "rm -rf ${LOGDIR}" EXIT
-    LOGPIPE=${LOGDIR}/logpipe
+    LOGPIPE=${TMPDIR}/logpipe
     mknod ${LOGPIPE} p
     ${BFAHOME}/bin/log.sh ${BFANODEDIR}/log < ${LOGPIPE} &
-    PIDIDX[$!]="log.sh"
+    # Separate pididx for processes we don't want to send signals to
+    # until in the very end.
+    ALLPIDIDX[$!]="log.sh"
     exec > ${LOGPIPE} 2>&1
 fi
 
-echo "*** Starting geth."
-# "NoPruning=true" means "--gcmode archive"
-geth --config ${BFATOML} &
-PIDIDX[$!]="geth"
+function sleep()
+{
+    read -t ${1:-1} < ${TMPDIR}/sleeppipe || true
+}
+
+# Start a sync
+startgeth --exitwhensynced
+echo "*** Starting monitor.js"
+monitor.js &
+PIDIDX[$!]="monitor.js"
+
+# geth will exit when it has synced, then we kill it's monitor.
+# Then wait here for their exit status to get reaped
+while [ "${#PIDIDX[*]}" -gt 0 ]; do sleep 1; done
+
+# if it went well, start a normal geth (to run "forever")
+test $geth_rc_status = 0 || exit $geth_rc_status
+test "$SHUTTING_DOWN" != 0 && exit 0
 
+# regular geth
+startgeth 
+# monitor
+echo "*** Starting monitor.js"
+    monitor.js &
+PIDIDX[$!]="monitor.js"
 # bootnode
 if [ -r "$bootnodekeyfile" ]
 then
@@ -109,16 +166,4 @@ then
     PIDIDX[$!]="bootnode"
 fi
 
-echo "*** Starting monitor.js"
-monitor.js &
-PIDIDX[$!]="monitor.js"
-
-max=-1
-# Exit if only 1 process remains - we hope it is log.sh, but either way,
-# it is time to end.
-while [ "${#PIDIDX[*]}" -gt 1 -a $max -ne 0 ]
-do
-    sleep 1
-    max=$(( $max - 1 ))
-done
-killallprocs
+while [ "${#PIDIDX[*]}" -gt 0 ]; do sleep 1; done
diff --git a/bin/start.sh b/bin/start.sh
index 4fc9770e1f830235080a87b1fb2eebfd0bdefb53..c7934af8855f07edc44f841bfcd15290f4844c38 100755
--- a/bin/start.sh
+++ b/bin/start.sh
@@ -11,7 +11,7 @@ bootARIUv6="enode://${enodeARIU}@[2800:110:44:6300::aad2:2db3]:30301"
 enodeUNC="82b66b13d7addcf9ffe1e4e972a105f6ccf50557161c4a0978a5d9ce595ababde609ea8a49897ae89b1d41e90551cb2e9241363238593e950ca68bd5af7c24d6"
 bootUNCv4="enode://${enodeUNC}@[200.16.28.28]:30301"
 
-enodeDGSI="59ae768ecdee632e0daceccb6f71b215392eba89230d626573f2fb4e9c0786c9a661027ab7343820ca63d96fe48ffd81ed1bf6e4d512f0ba50ec072c9efd9e4e"
+enodeDGSI="ddbf37799e8d33b0c42dddbda713037b17d14696b29ecf424ca3d57bb47db78a467505e22c5f2b709a98c3d55ac8ecbf4610765385317dd51049438f498881c6"
 bootDGSIv4="enode://${enodeDGSI}@[200.108.146.100]:30301"
 bootnodes="${bootARIUv6},${bootARIUv4},${bootUNCv4},${bootDGSIv4}"
 
diff --git a/bin/unlock.js b/bin/unlock.js
index 26809293734731598cb71fa537c13523f2184427..513c325ee9046bf1c66c7a5d63f2e73f8020c368 100755
--- a/bin/unlock.js
+++ b/bin/unlock.js
@@ -60,8 +60,12 @@ function unlockall()
 {
     var     wallets             =   new Array();
     web3.bfa.personal.listWallets(
-        function pushone(x)
+        function pushone(err,x)
         {
+            if ( err )
+                bye( 1, err );
+            if ( x == undefined )
+                bye( 1, "wallets not defined" );
             var     failures    =   0;
             var     promises    =   new Array();
             var     i           =   x.length;
diff --git a/network/config.toml b/network/config.toml
index 330e90db9debb7dc6f37f8378e351f3d604690e0..a462be1268f6cd56bea8a5eaa31ffae3b7cf7fb4 100644
--- a/network/config.toml
+++ b/network/config.toml
@@ -59,7 +59,7 @@ HTTPCors = ["*"]
 omitempty = ""
 IPCPath = "geth.ipc"
 HTTPPort = 8545
-HTTPVirtualHosts = ["localhost"]
+HTTPVirtualHosts = ["*"]
 HTTPModules = ["net", "web3", "eth", "clique"]
 WSHost = "0.0.0.0"
 WSPort = 8546
diff --git a/network/contracts/0xc3cf96af51d3c41dfbe428de75a8e5597d4d7a7b/abi b/network/contracts/0xc3cf96af51d3c41dfbe428de75a8e5597d4d7a7b/abi
new file mode 100644
index 0000000000000000000000000000000000000000..63249fb87ee8648ea0bdf30fa553d27b7b1be4f7
--- /dev/null
+++ b/network/contracts/0xc3cf96af51d3c41dfbe428de75a8e5597d4d7a7b/abi
@@ -0,0 +1,328 @@
+[
+  {
+    "name": "proofOfControlWeiAmountChanged",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": false, "name": "amount", "type": "uint256" }
+    ]
+  },
+  {
+    "name": "limitChanged",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": true, "name": "distr", "type": "address" },
+      { "indexed": true, "name": "ben", "type": "address" },
+      { "indexed": false, "name": "newLimit", "type": "uint256" }
+    ]
+  },
+  {
+    "name": "suitorApproved",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": true, "name": "suitor", "type": "address" }
+    ]
+  },
+  {
+    "name": "suitorNotApproved",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": true, "name": "suitor", "type": "address" }
+    ]
+  },
+  {
+    "name": "kickedDistributor",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": true, "name": "kicked", "type": "address" }
+    ]
+  },
+  {
+    "name": "suitorAdded",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": true, "name": "suitor", "type": "address" },
+      { "indexed": true, "name": "distr", "type": "address" }
+    ]
+  },
+  {
+    "name": "addedBeneficiary",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": true, "name": "distr", "type": "address" },
+      { "indexed": true, "name": "added", "type": "address" },
+      { "indexed": false, "name": "limit", "type": "uint256" }
+    ]
+  },
+  {
+    "name": "kickedBeneficiary",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": true, "name": "distr", "type": "address" },
+      { "indexed": true, "name": "kicked", "type": "address" }
+    ]
+  },
+  {
+    "name": "addedDistributor",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": true, "name": "added", "type": "address" },
+      { "indexed": false, "name": "top", "type": "uint256" }
+    ]
+  },
+  {
+    "name": "replenished",
+    "type": "event",
+    "anonymous": false,
+    "inputs": [
+      { "indexed": true, "name": "distr", "type": "address" },
+      { "indexed": false, "name": "amount", "type": "uint256" }
+    ]
+  },
+  {
+    "name": "addBeneficiary",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "acc", "type": "address" },
+      { "name": "limit", "type": "uint256" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable"
+  },
+  {
+    "name": "addSuitor",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "add", "type": "address" },
+      { "name": "password", "type": "bytes32" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": true,
+    "stateMutability": "payable"
+  },
+  {
+    "name": "changeLimit",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "acc", "type": "address" },
+      { "name": "limit", "type": "uint256" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable"
+  },
+  {
+    "name": "kickBeneficiary",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "acc", "type": "address" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable"
+  },
+  {
+    "name": "kickDistributor",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "acc", "type": "address" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable"
+  },
+  {
+    "name": "addDistributor",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "acc", "type": "address" },
+      { "name": "top", "type": "uint256" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable"
+  },
+  {
+    "name": "proveControl",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "key", "type": "string" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable"
+  },
+  {
+    "name": "replenish",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "acc", "type": "address" },
+      { "name": "amount", "type": "uint256" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": true,
+    "stateMutability": "payable"
+  },
+  {
+    "name": "replenishAll",
+    "type": "function",
+    "constant": false,
+    "inputs": [],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": true,
+    "stateMutability": "payable"
+  },
+  {
+    "name": "replenishList",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "accs", "type": "address[]" },
+      { "name": "amounts", "type": "uint256[]" }
+    ],
+    "outputs": [],
+    "payable": true,
+    "stateMutability": "payable"
+  },
+  {
+    "name": "setProofOfControlWeiAmount",
+    "type": "function",
+    "constant": false,
+    "inputs": [
+      { "name": "amount", "type": "uint256" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable"
+  },
+  {
+    "type": "constructor",
+    "inputs": [],
+    "payable": true,
+    "stateMutability": "payable"
+  },
+  {
+    "name": "getBeneficiariesCount",
+    "type": "function",
+    "constant": true,
+    "inputs": [],
+    "outputs": [
+      { "name": "", "type": "uint256" }
+    ],
+    "payable": false,
+    "stateMutability": "view"
+  },
+  {
+    "name": "getBeneficiaryLimit",
+    "type": "function",
+    "constant": true,
+    "inputs": [
+      { "name": "acc", "type": "address" }
+    ],
+    "outputs": [
+      { "name": "", "type": "uint256" }
+    ],
+    "payable": false,
+    "stateMutability": "view"
+  },
+  {
+    "name": "getDistributorLimit",
+    "type": "function",
+    "constant": true,
+    "inputs": [
+      { "name": "acc", "type": "address" }
+    ],
+    "outputs": [
+      { "name": "", "type": "uint256" }
+    ],
+    "payable": false,
+    "stateMutability": "view"
+  },
+  {
+    "name": "getProofOfControlWeiAmount",
+    "type": "function",
+    "constant": true,
+    "inputs": [],
+    "outputs": [
+      { "name": "", "type": "uint256" }
+    ],
+    "payable": false,
+    "stateMutability": "view"
+  },
+  {
+    "name": "hash",
+    "type": "function",
+    "constant": true,
+    "inputs": [
+      { "name": "key", "type": "string" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bytes32" }
+    ],
+    "payable": false,
+    "stateMutability": "pure"
+  },
+  {
+    "name": "isBeneficiary",
+    "type": "function",
+    "constant": true,
+    "inputs": [
+      { "name": "add", "type": "address" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": false,
+    "stateMutability": "view"
+  },
+  {
+    "name": "isDistributor",
+    "type": "function",
+    "constant": true,
+    "inputs": [
+      { "name": "add", "type": "address" }
+    ],
+    "outputs": [
+      { "name": "", "type": "bool" }
+    ],
+    "payable": false,
+    "stateMutability": "view"
+  }
+]
diff --git a/network/contracts/DGSIdistillery2 b/network/contracts/DGSIdistillery2
new file mode 120000
index 0000000000000000000000000000000000000000..8f90ade527de771b4f550cf819efada0c2706d1a
--- /dev/null
+++ b/network/contracts/DGSIdistillery2
@@ -0,0 +1 @@
+0xc3cf96af51d3c41dfbe428de75a8e5597d4d7a7b
\ No newline at end of file
diff --git a/network/docker-config.toml b/network/docker-config.toml
new file mode 100644
index 0000000000000000000000000000000000000000..f414c8755727a134f8e906bb7a2ff821c52b1364
--- /dev/null
+++ b/network/docker-config.toml
@@ -0,0 +1,21 @@
+[Eth]
+NetworkId = 47525974938
+SyncMode = "fast"
+NoPruning = false
+DatabaseCache = 2048
+
+[Node]
+DataDir = "/home/bfa/bfa/network/node"
+HTTPHost = "0.0.0.0"
+HTTPCors = ["*"]
+IPCPath = "geth.ipc"
+HTTPPort = 8545
+HTTPVirtualHosts = ["*"]
+HTTPModules = ["net", "web3", "eth", "clique"]
+WSHost = "0.0.0.0"
+WSPort = 8546
+WSModules = ["net", "web3", "eth", "clique"]
+
+[Node.P2P]
+BootstrapNodes = ["enode://7ec4dd9d5e1a2b29d6b60aa9f95677c0c3a5f9306e73d65dd3bcbfda3737a8c509b02d1eab763ce39f18cfe96423df7ee544d6c36191ec17f59ade75bc99d358@[2800:110:44:6300::aad2:2db3]:30301", "enode://7ec4dd9d5e1a2b29d6b60aa9f95677c0c3a5f9306e73d65dd3bcbfda3737a8c509b02d1eab763ce39f18cfe96423df7ee544d6c36191ec17f59ade75bc99d358@170.210.45.179:30301", "enode://82b66b13d7addcf9ffe1e4e972a105f6ccf50557161c4a0978a5d9ce595ababde609ea8a49897ae89b1d41e90551cb2e9241363238593e950ca68bd5af7c24d6@200.16.28.28:30301", "enode://59ae768ecdee632e0daceccb6f71b215392eba89230d626573f2fb4e9c0786c9a661027ab7343820ca63d96fe48ffd81ed1bf6e4d512f0ba50ec072c9efd9e4e@200.108.146.100:30301"]
+BootstrapNodesV5 = ["enode://7ec4dd9d5e1a2b29d6b60aa9f95677c0c3a5f9306e73d65dd3bcbfda3737a8c509b02d1eab763ce39f18cfe96423df7ee544d6c36191ec17f59ade75bc99d358@[2800:110:44:6300::aad2:2db3]:30301", "enode://7ec4dd9d5e1a2b29d6b60aa9f95677c0c3a5f9306e73d65dd3bcbfda3737a8c509b02d1eab763ce39f18cfe96423df7ee544d6c36191ec17f59ade75bc99d358@170.210.45.179:30301", "enode://82b66b13d7addcf9ffe1e4e972a105f6ccf50557161c4a0978a5d9ce595ababde609ea8a49897ae89b1d41e90551cb2e9241363238593e950ca68bd5af7c24d6@200.16.28.28:30301", "enode://59ae768ecdee632e0daceccb6f71b215392eba89230d626573f2fb4e9c0786c9a661027ab7343820ca63d96fe48ffd81ed1bf6e4d512f0ba50ec072c9efd9e4e@200.108.146.100:30301"]
diff --git a/package.json b/package.json
index 6851a64b7ce534f7cea965c1bca8efd3b6d1b1ed..20d4037b9d4e1a80251b578ef6dd20331e98d262 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,8 @@
     "bignumber.js": "^9.0.0",
     "request": "^2.88.0",
     "require": "^2.4.20",
-    "web3": "^1.0.0-beta.55",
+    "web3": "^1.2.11",
+    "web3-eth-accounts": "^1.2.11",
     "xmlhttprequest-ssl": "^1.5.5"
   },
   "repository": {
diff --git a/test2network/config.toml b/test2network/config.toml
index 1d776baf40a50b3019f58a2efdafe2c0551676a9..d548f4d13fce561074c6d1f39fa96b7661165821 100644
--- a/test2network/config.toml
+++ b/test2network/config.toml
@@ -59,7 +59,7 @@ HTTPCors = ["*"]
 omitempty = ""
 IPCPath = "geth.ipc"
 HTTPPort = 8545
-HTTPVirtualHosts = ["localhost"]
+HTTPVirtualHosts = ["*"]
 HTTPModules = ["net", "web3", "eth", "clique"]
 WSHost = "0.0.0.0"
 WSPort = 8546
diff --git a/test2network/docker-config.toml b/test2network/docker-config.toml
new file mode 100644
index 0000000000000000000000000000000000000000..af884e27a0f4cb111ad3e3084d603e0952f385d1
--- /dev/null
+++ b/test2network/docker-config.toml
@@ -0,0 +1,21 @@
+[Eth]
+NetworkId = 55555000000
+SyncMode = "fast"
+NoPruning = false
+DatabaseCache = 2048
+
+[Node]
+DataDir = "/home/bfa/bfa/test2network/node"
+HTTPHost = "0.0.0.0"
+HTTPCors = ["*"]
+IPCPath = "geth.ipc"
+HTTPPort = 8545
+HTTPVirtualHosts = ["*"]
+HTTPModules = ["net", "web3", "eth", "clique"]
+WSHost = "0.0.0.0"
+WSPort = 8546
+WSModules = ["net", "web3", "eth", "clique"]
+
+[Node.P2P]
+BootstrapNodes = ["enode://7ec4dd9d5e1a2b29d6b60aa9f95677c0c3a5f9306e73d65dd3bcbfda3737a8c509b02d1eab763ce39f18cfe96423df7ee544d6c36191ec17f59ade75bc99d358@[2800:110:44:6300::aad2:2db3]:30301", "enode://7ec4dd9d5e1a2b29d6b60aa9f95677c0c3a5f9306e73d65dd3bcbfda3737a8c509b02d1eab763ce39f18cfe96423df7ee544d6c36191ec17f59ade75bc99d358@170.210.45.179:30301", "enode://82b66b13d7addcf9ffe1e4e972a105f6ccf50557161c4a0978a5d9ce595ababde609ea8a49897ae89b1d41e90551cb2e9241363238593e950ca68bd5af7c24d6@200.16.28.28:30301", "enode://59ae768ecdee632e0daceccb6f71b215392eba89230d626573f2fb4e9c0786c9a661027ab7343820ca63d96fe48ffd81ed1bf6e4d512f0ba50ec072c9efd9e4e@200.108.146.100:30301"]
+BootstrapNodesV5 = ["enode://7ec4dd9d5e1a2b29d6b60aa9f95677c0c3a5f9306e73d65dd3bcbfda3737a8c509b02d1eab763ce39f18cfe96423df7ee544d6c36191ec17f59ade75bc99d358@[2800:110:44:6300::aad2:2db3]:30301", "enode://7ec4dd9d5e1a2b29d6b60aa9f95677c0c3a5f9306e73d65dd3bcbfda3737a8c509b02d1eab763ce39f18cfe96423df7ee544d6c36191ec17f59ade75bc99d358@170.210.45.179:30301", "enode://82b66b13d7addcf9ffe1e4e972a105f6ccf50557161c4a0978a5d9ce595ababde609ea8a49897ae89b1d41e90551cb2e9241363238593e950ca68bd5af7c24d6@200.16.28.28:30301", "enode://59ae768ecdee632e0daceccb6f71b215392eba89230d626573f2fb4e9c0786c9a661027ab7343820ca63d96fe48ffd81ed1bf6e4d512f0ba50ec072c9efd9e4e@200.108.146.100:30301"]