diff --git a/client/client.go b/client/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..d131f510b8af05ee34f0566500bf377e97b1cceb
--- /dev/null
+++ b/client/client.go
@@ -0,0 +1,82 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package client
+
+import (
+	"fmt"
+
+	"github.com/tendermint/go-rpc/client"
+
+	"github.com/eris-ltd/eris-db/account"
+	tendermint_client "github.com/eris-ltd/eris-db/rpc/tendermint/client"
+	"github.com/eris-ltd/eris-db/txs"
+)
+
+type NodeClient interface {
+	Broadcast(transaction txs.Tx) (*txs.Receipt, error)
+
+	GetAccount(address []byte) (*account.Account, error)
+}
+
+// NOTE [ben] Compiler check to ensure ErisClient successfully implements
+// eris-db/client.NodeClient
+var _ NodeClient = (*ErisNodeClient)(nil)
+
+// Eris-Client is a simple struct exposing the client rpc methods
+
+type ErisNodeClient struct {
+	broadcastRPC string
+}
+
+// ErisKeyClient.New returns a new eris-keys client for provided rpc location
+// Eris-keys connects over http request-responses
+func NewErisNodeClient(rpcString string) *ErisNodeClient {
+	return &ErisNodeClient{
+		broadcastRPC: rpcString,
+	}
+}
+
+//------------------------------------------------------------------------------------
+// broadcast to blockchain node
+// NOTE: [ben] Eris Client first continues from tendermint rpc, but will have handshake to negotiate
+// protocol version for moving towards rpc/v1
+
+func (erisClient *ErisNodeClient) Broadcast(tx txs.Tx) (*txs.Receipt, error) {
+	client := rpcclient.NewClientURI(erisClient.broadcastRPC)
+	receipt, err := tendermint_client.BroadcastTx(client, tx)
+	if err != nil {
+		return nil, err
+	}
+	return &receipt, nil
+}
+
+func (erisClient *ErisNodeClient) GetAccount(address []byte) (*account.Account, error) {
+	// fetch nonce from node
+	client := rpcclient.NewClientURI(erisClient.broadcastRPC)
+	account, err := tendermint_client.GetAccount(client, address)
+	if err != nil {
+		err = fmt.Errorf("Error connecting to node (%s) to fetch account (%X): %s",
+			erisClient.broadcastRPC, address, err.Error())
+		return nil, err
+	}
+	if account == nil {
+		err = fmt.Errorf("Unknown account %X at node (%s)", address, erisClient.broadcastRPC)
+		return nil, err
+	}
+
+	return account.Copy(), nil
+}
diff --git a/client/cmd/eris-client.go b/client/cmd/eris-client.go
new file mode 100644
index 0000000000000000000000000000000000000000..9176ca0a272daf6ec7105eb22cdf2a654b50d69b
--- /dev/null
+++ b/client/cmd/eris-client.go
@@ -0,0 +1,118 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package commands
+
+import (
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/spf13/cobra"
+
+	log "github.com/eris-ltd/eris-logger"
+
+	"github.com/eris-ltd/eris-db/definitions"
+	"github.com/eris-ltd/eris-db/version"
+)
+
+// Global flags for persistent flags
+var clientDo *definitions.ClientDo
+
+var ErisClientCmd = &cobra.Command{
+	Use:   "eris-client",
+	Short: "Eris-client interacts with a running Eris chain.",
+	Long: `Eris-client interacts with a running Eris chain.
+
+Made with <3 by Eris Industries.
+
+Complete documentation is available at https://docs.erisindustries.com
+` + "\nVERSION:\n " + version.VERSION,
+	PersistentPreRun: func(cmd *cobra.Command, args []string) {
+		log.SetLevel(log.WarnLevel)
+		if clientDo.Verbose {
+			log.SetLevel(log.InfoLevel)
+		} else if clientDo.Debug {
+			log.SetLevel(log.DebugLevel)
+		}
+	},
+	Run: func(cmd *cobra.Command, args []string) { cmd.Help() },
+}
+
+func Execute() {
+	InitErisClientInit()
+	AddGlobalFlags()
+	AddClientCommands()
+	ErisClientCmd.Execute()
+}
+
+func InitErisClientInit() {
+	// initialise an empty ClientDo struct for command execution
+	clientDo = definitions.NewClientDo()
+}
+
+func AddGlobalFlags() {
+	ErisClientCmd.PersistentFlags().BoolVarP(&clientDo.Verbose, "verbose", "v", defaultVerbose(), "verbose output; more output than no output flags; less output than debug level; default respects $ERIS_CLIENT_VERBOSE")
+	ErisClientCmd.PersistentFlags().BoolVarP(&clientDo.Debug, "debug", "d", defaultDebug(), "debug level output; the most output available for eris-client; if it is too chatty use verbose flag; default respects $ERIS_CLIENT_DEBUG")
+}
+
+func AddClientCommands() {
+	buildTransactionCommand()
+	ErisClientCmd.AddCommand(TransactionCmd)
+}
+
+//------------------------------------------------------------------------------
+// Defaults
+
+// defaultVerbose is set to false unless the ERIS_CLIENT_VERBOSE environment
+// variable is set to a parsable boolean.
+func defaultVerbose() bool {
+	return setDefaultBool("ERIS_CLIENT_VERBOSE", false)
+}
+
+// defaultDebug is set to false unless the ERIS_CLIENT_DEBUG environment
+// variable is set to a parsable boolean.
+func defaultDebug() bool {
+	return setDefaultBool("ERIS_CLIENT_DEBUG", false)
+}
+
+// setDefaultBool returns the provided default value if the environment variable
+// is not set or not parsable as a bool.
+func setDefaultBool(environmentVariable string, defaultValue bool) bool {
+	value := os.Getenv(environmentVariable)
+	if value != "" {
+		if parsedValue, err := strconv.ParseBool(value); err == nil {
+			return parsedValue
+		}
+	}
+	return defaultValue
+}
+
+func setDefaultString(envVar, def string) string {
+	env := os.Getenv(envVar)
+	if env != "" {
+		return env
+	}
+	return def
+}
+
+func setDefaultStringSlice(envVar string, def []string) []string {
+	env := os.Getenv(envVar)
+	if env != "" {
+		return strings.Split(env, ",")
+	}
+	return def
+}
diff --git a/client/cmd/eris-client/main.go b/client/cmd/eris-client/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..8219af99a32a54afdbeff73b10d13f88cf38f3a3
--- /dev/null
+++ b/client/cmd/eris-client/main.go
@@ -0,0 +1,25 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+	commands "github.com/eris-ltd/eris-db/client/cmd"
+)
+
+func main() {
+	commands.Execute()
+}
diff --git a/client/cmd/transaction.go b/client/cmd/transaction.go
new file mode 100644
index 0000000000000000000000000000000000000000..13076b0ec366a21a1e06082de52ff2dbf99f8e17
--- /dev/null
+++ b/client/cmd/transaction.go
@@ -0,0 +1,205 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package commands
+
+import (
+	"os"
+	"strings"
+
+	"github.com/spf13/cobra"
+
+	log "github.com/eris-ltd/eris-logger"
+
+	"github.com/eris-ltd/eris-db/client/transaction"
+)
+
+var TransactionCmd = &cobra.Command{
+	Use:   "tx",
+	Short: "eris-client tx formulates and signs a transaction to a chain",
+	Long: `eris-client tx formulates and signs a transaction to a chain.
+`,
+	Run: func(cmd *cobra.Command, args []string) { cmd.Help() },
+}
+
+func buildTransactionCommand() {
+	// Transaction command has subcommands send, name, call, bond,
+	// unbond, rebond, permissions. Dupeout transaction is not accessible through the command line.
+
+	addTransactionPersistentFlags()
+
+	// SendTx
+	var sendCmd = &cobra.Command{
+		Use:   "send",
+		Short: "eris-client tx send --amt <amt> --to <addr>",
+		Long:  "eris-client tx send --amt <amt> --to <addr>",
+		Run: func(cmd *cobra.Command, args []string) {
+			transaction.Send(clientDo)
+		},
+		PreRun: assertParameters,
+	}
+	sendCmd.Flags().StringVarP(&clientDo.AmtFlag, "amt", "a", "", "specify an amount")
+	sendCmd.Flags().StringVarP(&clientDo.ToFlag, "to", "t", "", "specify an address to send to")
+
+	// NameTx
+	var nameCmd = &cobra.Command{
+		Use:   "name",
+		Short: "eris-client tx name --amt <amt> --name <name> --data <data>",
+		Long:  "eris-client tx name --amt <amt> --name <name> --data <data>",
+		Run: func(cmd *cobra.Command, args []string) {
+			// transaction.Name(clientDo)
+		},
+		PreRun: assertParameters,
+	}
+	nameCmd.Flags().StringVarP(&clientDo.AmtFlag, "amt", "a", "", "specify an amount")
+	nameCmd.Flags().StringVarP(&clientDo.NameFlag, "name", "n", "", "specify a name")
+	nameCmd.Flags().StringVarP(&clientDo.DataFlag, "data", "", "", "specify some data")
+	nameCmd.Flags().StringVarP(&clientDo.DataFileFlag, "data-file", "", "", "specify a file with some data")
+	nameCmd.Flags().StringVarP(&clientDo.FeeFlag, "fee", "f", "", "specify the fee to send")
+
+	// CallTx
+	var callCmd = &cobra.Command{
+		Use:   "call",
+		Short: "eris-client tx call --amt <amt> --fee <fee> --gas <gas> --to <contract addr> --data <data>",
+		Long:  "eris-client tx call --amt <amt> --fee <fee> --gas <gas> --to <contract addr> --data <data>",
+		Run: func(cmd *cobra.Command, args []string) {
+			transaction.Call(clientDo)
+		},
+		PreRun: assertParameters,
+	}
+	callCmd.Flags().StringVarP(&clientDo.AmtFlag, "amt", "a", "", "specify an amount")
+	callCmd.Flags().StringVarP(&clientDo.ToFlag, "to", "t", "", "specify an address to send to")
+	callCmd.Flags().StringVarP(&clientDo.DataFlag, "data", "", "", "specify some data")
+	callCmd.Flags().StringVarP(&clientDo.FeeFlag, "fee", "f", "", "specify the fee to send")
+	callCmd.Flags().StringVarP(&clientDo.GasFlag, "gas", "g", "", "specify the gas limit for a CallTx")
+
+	// BondTx
+	var bondCmd = &cobra.Command{
+		Use:   "bond",
+		Short: "eris-client tx bond --pubkey <pubkey> --amt <amt> --unbond-to <address>",
+		Long:  "eris-client tx bond --pubkey <pubkey> --amt <amt> --unbond-to <address>",
+		Run: func(cmd *cobra.Command, args []string) {
+			// transaction.Bond(clientDo)
+		},
+		PreRun: assertParameters,
+	}
+	bondCmd.Flags().StringVarP(&clientDo.AmtFlag, "amt", "a", "", "specify an amount")
+	bondCmd.Flags().StringVarP(&clientDo.UnbondtoFlag, "to", "t", "", "specify an address to unbond to")
+
+	// UnbondTx
+	var unbondCmd = &cobra.Command{
+		Use:   "unbond",
+		Short: "eris-client tx unbond --addr <address> --height <block_height>",
+		Long:  "eris-client tx unbond --addr <address> --height <block_height>",
+		Run: func(cmd *cobra.Command, args []string) {
+			// transaction.Unbond(clientDo)
+		},
+		PreRun: assertParameters,
+	}
+	unbondCmd.Flags().StringVarP(&clientDo.AddrFlag, "addr", "a", "", "specify an address")
+	unbondCmd.Flags().StringVarP(&clientDo.HeightFlag, "height", "n", "", "specify a height to unbond at")
+
+	// RebondTx
+	var rebondCmd = &cobra.Command{
+		Use:   "rebond",
+		Short: "eris-client tx rebond --addr <address> --height <block_height>",
+		Long:  "eris-client tx rebond --addr <address> --height <block_height>",
+		Run: func(cmd *cobra.Command, args []string) {
+			// transaction.Rebond(clientDo)
+		},
+		PreRun: assertParameters,
+	}
+	rebondCmd.Flags().StringVarP(&clientDo.AddrFlag, "addr", "a", "", "specify an address")
+	rebondCmd.Flags().StringVarP(&clientDo.HeightFlag, "height", "n", "", "specify a height to unbond at")
+
+	// PermissionsTx
+	var permissionsCmd = &cobra.Command{
+		Use:   "permission",
+		Short: "eris-client tx perm <function name> <args ...>",
+		Long:  "eris-client tx perm <function name> <args ...>",
+		Run: func(cmd *cobra.Command, args []string) {
+			// transaction.Permsissions(clientDo)
+		},
+		PreRun: assertParameters,
+	}
+
+	TransactionCmd.AddCommand(sendCmd, nameCmd, callCmd, bondCmd, unbondCmd, rebondCmd, permissionsCmd)
+}
+
+func addTransactionPersistentFlags() {
+	TransactionCmd.PersistentFlags().StringVarP(&clientDo.SignAddrFlag, "sign-addr", "", defaultKeyDaemonAddress(), "set eris-keys daemon address (default respects $ERIS_CLIENT_SIGN_ADDRESS)")
+	TransactionCmd.PersistentFlags().StringVarP(&clientDo.NodeAddrFlag, "node-addr", "", defaultNodeRpcAddress(), "set the eris-db node rpc server address (default respects $ERIS_CLIENT_NODE_ADDRESS)")
+	TransactionCmd.PersistentFlags().StringVarP(&clientDo.PubkeyFlag, "pubkey", "", defaultPublicKey(), "specify the public key to sign with (defaults to $ERIS_CLIENT_PUBLIC_KEY)")
+	TransactionCmd.PersistentFlags().StringVarP(&clientDo.AddrFlag, "addr", "", defaultAddress(), "specify the account address (for which the public key can be found at eris-keys) (default respects $ERIS_CLIENT_ADDRESS)")
+	TransactionCmd.PersistentFlags().StringVarP(&clientDo.ChainidFlag, "chain-id", "", defaultChainId(), "specify the chainID (default respects $CHAIN_ID)")
+	TransactionCmd.PersistentFlags().StringVarP(&clientDo.NonceFlag, "nonce", "", "", "specify the nonce to use for the transaction (should equal the sender account's nonce + 1)")
+
+	// TransactionCmd.PersistentFlags().BoolVarP(&clientDo.SignFlag, "sign", "s", false, "sign the transaction using the eris-keys daemon")
+	TransactionCmd.PersistentFlags().BoolVarP(&clientDo.BroadcastFlag, "broadcast", "b", true, "broadcast the transaction to the blockchain")
+	TransactionCmd.PersistentFlags().BoolVarP(&clientDo.WaitFlag, "wait", "w", false, "wait for the transaction to be committed in a block")
+}
+
+//------------------------------------------------------------------------------
+// Defaults
+
+func defaultChainId() string {
+	return setDefaultString("CHAIN_ID", "")
+}
+
+func defaultKeyDaemonAddress() string {
+	return setDefaultString("ERIS_CLIENT_SIGN_ADDRESS", "http://127.0.0.1:4767")
+}
+
+func defaultNodeRpcAddress() string {
+	return setDefaultString("ERIS_CLIENT_NODE_ADDRESS", "tcp://127.0.0.1:46657")
+}
+
+func defaultPublicKey() string {
+	return setDefaultString("ERIS_CLIENT_PUBLIC_KEY", "")
+}
+
+func defaultAddress() string {
+	return setDefaultString("ERIS_CLIENT_ADDRESS", "")
+}
+
+//------------------------------------------------------------------------------
+// Helper functions
+
+func assertParameters(cmd *cobra.Command, args []string) {
+	if clientDo.ChainidFlag == "" {
+		log.Fatal(`Please provide a chain id either through the flag --chain-id or environment variable $CHAIN_ID.`)
+		os.Exit(1)
+	}
+
+	if !strings.HasPrefix(clientDo.NodeAddrFlag, "tcp://") &&
+		!strings.HasPrefix(clientDo.NodeAddrFlag, "unix://") {
+		// TODO: [ben] go-rpc will deprecate reformatting; also it is bad practice to auto-correct for this;
+		log.Warn(`Please use fully formed listening address for the node, including the tcp:// or unix:// prefix.`)
+	}
+
+	if !strings.HasPrefix(clientDo.SignAddrFlag, "http://") {
+		// NOTE: [ben] we preserve the auto-correction here as it is a simple http request-response to the key server.
+		clientDo.SignAddrFlag = "http://" + clientDo.SignAddrFlag
+		log.WithFields(log.Fields{
+			"signing address": clientDo.SignAddrFlag,
+		}).Warn(`Please use fully formed listening address for the key server; adding http:// prefix`)
+	}
+	log.WithFields(log.Fields{
+		"signing address": clientDo.SignAddrFlag,
+		"node address":    clientDo.NodeAddrFlag,
+		"chain id":        clientDo.ChainidFlag,
+	}).Debug("Asserted parameters")
+}
diff --git a/client/core/transaction_factory.go b/client/core/transaction_factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..73c6f5c2814b182a9588f960b2504acab1a9d910
--- /dev/null
+++ b/client/core/transaction_factory.go
@@ -0,0 +1,450 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+	"encoding/hex"
+	"fmt"
+	"strconv"
+	// "strings"
+	// "time"
+
+	ptypes "github.com/eris-ltd/eris-db/permission/types"
+
+	// log "github.com/eris-ltd/eris-logger"
+
+	"github.com/eris-ltd/eris-db/client"
+	"github.com/eris-ltd/eris-db/keys"
+	"github.com/eris-ltd/eris-db/txs"
+)
+
+var (
+	MaxCommitWaitTimeSeconds = 20
+)
+
+//------------------------------------------------------------------------------------
+// core functions with string args.
+// validates strings and forms transaction
+
+func Send(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, toAddr, amtS, nonceS string) (*txs.SendTx, error) {
+	pub, amt, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, nonceS)
+	if err != nil {
+		return nil, err
+	}
+
+	if toAddr == "" {
+		return nil, fmt.Errorf("destination address must be given with --to flag")
+	}
+
+	toAddrBytes, err := hex.DecodeString(toAddr)
+	if err != nil {
+		return nil, fmt.Errorf("toAddr is bad hex: %v", err)
+	}
+
+	tx := txs.NewSendTx()
+	tx.AddInputWithNonce(pub, amt, int(nonce))
+	tx.AddOutput(toAddrBytes, amt)
+
+	return tx, nil
+}
+
+func Call(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, toAddr, amtS, nonceS, gasS, feeS, data string) (*txs.CallTx, error) {
+	pub, amt, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, nonceS)
+	if err != nil {
+		return nil, err
+	}
+
+	toAddrBytes, err := hex.DecodeString(toAddr)
+	if err != nil {
+		return nil, fmt.Errorf("toAddr is bad hex: %v", err)
+	}
+
+	fee, err := strconv.ParseInt(feeS, 10, 64)
+	if err != nil {
+		return nil, fmt.Errorf("fee is misformatted: %v", err)
+	}
+
+	gas, err := strconv.ParseInt(gasS, 10, 64)
+	if err != nil {
+		return nil, fmt.Errorf("gas is misformatted: %v", err)
+	}
+
+	dataBytes, err := hex.DecodeString(data)
+	if err != nil {
+		return nil, fmt.Errorf("data is bad hex: %v", err)
+	}
+
+	tx := txs.NewCallTxWithNonce(pub, toAddrBytes, dataBytes, amt, gas, fee, int(nonce))
+	return tx, nil
+}
+
+func Name(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, amtS, nonceS, feeS, name, data string) (*txs.NameTx, error) {
+	pub, amt, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, nonceS)
+	if err != nil {
+		return nil, err
+	}
+
+	fee, err := strconv.ParseInt(feeS, 10, 64)
+	if err != nil {
+		return nil, fmt.Errorf("fee is misformatted: %v", err)
+	}
+
+	tx := txs.NewNameTxWithNonce(pub, name, data, amt, fee, int(nonce))
+	return tx, nil
+}
+
+type PermFunc struct {
+	Name string
+	Args string
+}
+
+var PermsFuncs = []PermFunc{
+	{"set_base", "address, permission flag, value"},
+	{"unset_base", "address, permission flag"},
+	{"set_global", "permission flag, value"},
+	{"add_role", "address, role"},
+	{"rm_role", "address, role"},
+}
+
+func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addrS, nonceS, permFunc string, argsS []string) (*txs.PermissionsTx, error) {
+	pub, _, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addrS, "0", nonceS)
+	if err != nil {
+		return nil, err
+	}
+	var args ptypes.PermArgs
+	switch permFunc {
+	case "set_base":
+		addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1])
+		if err != nil {
+			return nil, err
+		}
+		if len(argsS) != 3 {
+			return nil, fmt.Errorf("set_base also takes a value (true or false)")
+		}
+		var value bool
+		if argsS[2] == "true" {
+			value = true
+		} else if argsS[2] == "false" {
+			value = false
+		} else {
+			return nil, fmt.Errorf("Unknown value %s", argsS[2])
+		}
+		args = &ptypes.SetBaseArgs{addr, pF, value}
+	case "unset_base":
+		addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1])
+		if err != nil {
+			return nil, err
+		}
+		args = &ptypes.UnsetBaseArgs{addr, pF}
+	case "set_global":
+		pF, err := ptypes.PermStringToFlag(argsS[0])
+		if err != nil {
+			return nil, err
+		}
+		var value bool
+		if argsS[1] == "true" {
+			value = true
+		} else if argsS[1] == "false" {
+			value = false
+		} else {
+			return nil, fmt.Errorf("Unknown value %s", argsS[1])
+		}
+		args = &ptypes.SetGlobalArgs{pF, value}
+	case "add_role":
+		addr, err := hex.DecodeString(argsS[0])
+		if err != nil {
+			return nil, err
+		}
+		args = &ptypes.AddRoleArgs{addr, argsS[1]}
+	case "rm_role":
+		addr, err := hex.DecodeString(argsS[0])
+		if err != nil {
+			return nil, err
+		}
+		args = &ptypes.RmRoleArgs{addr, argsS[1]}
+	default:
+		return nil, fmt.Errorf("Invalid permission function for use in PermissionsTx: %s", permFunc)
+	}
+	// args := snativeArgs(
+	tx := txs.NewPermissionsTxWithNonce(pub, args, int(nonce))
+	return tx, nil
+}
+
+// type NameGetter struct {
+// 	client cclient.Client
+// }
+
+// func (n NameGetter) GetNameRegEntry(name string) *txs.NameRegEntry {
+// 	entry, err := n.client.GetName(name)
+// 	if err != nil {
+// 		panic(err)
+// 	}
+// 	return entry.Entry
+// }
+
+/*
+func coreNewAccount(nodeAddr, pubkey, chainID string) (*types.NewAccountTx, error) {
+	pub, _, _, err := checkCommon(nodeAddr, pubkey, "", "0", "0")
+	if err != nil {
+		return nil, err
+	}
+
+	client := cclient.NewClient(nodeAddr, "HTTP")
+	return types.NewNewAccountTx(NameGetter{client}, pub, chainID)
+}
+*/
+
+// func Bond(nodeAddr, signAddr, pubkey, unbondAddr, amtS, nonceS string) (*txs.BondTx, error) {
+// 	pub, amt, nonce, err := checkCommon(nodeAddr, signAddr, pubkey, "", amtS, nonceS)
+// 	if err != nil {
+// 		return nil, err
+// 	}
+// 	var pubKey crypto.PubKeyEd25519
+// 	var unbondAddrBytes []byte
+
+// 	if unbondAddr == "" {
+// 		pkb, _ := hex.DecodeString(pubkey)
+// 		copy(pubKey[:], pkb)
+// 		unbondAddrBytes = pubKey.Address()
+// 	} else {
+// 		unbondAddrBytes, err = hex.DecodeString(unbondAddr)
+// 		if err != nil {
+// 			return nil, fmt.Errorf("unbondAddr is bad hex: %v", err)
+// 		}
+
+// 	}
+
+// 	tx, err := types.NewBondTx(pub)
+// 	if err != nil {
+// 		return nil, err
+// 	}
+// 	tx.AddInputWithNonce(pub, amt, int(nonce))
+// 	tx.AddOutput(unbondAddrBytes, amt)
+
+// 	return tx, nil
+// }
+
+// func Unbond(addrS, heightS string) (*txs.UnbondTx, error) {
+// 	if addrS == "" {
+// 		return nil, fmt.Errorf("Validator address must be given with --addr flag")
+// 	}
+
+// 	addrBytes, err := hex.DecodeString(addrS)
+// 	if err != nil {
+// 		return nil, fmt.Errorf("addr is bad hex: %v", err)
+// 	}
+
+// 	height, err := strconv.ParseInt(heightS, 10, 32)
+// 	if err != nil {
+// 		return nil, fmt.Errorf("height is misformatted: %v", err)
+// 	}
+
+// 	return &types.UnbondTx{
+// 		Address: addrBytes,
+// 		Height:  int(height),
+// 	}, nil
+// }
+
+// func Rebond(addrS, heightS string) (*txs.RebondTx, error) {
+// 	if addrS == "" {
+// 		return nil, fmt.Errorf("Validator address must be given with --addr flag")
+// 	}
+
+// 	addrBytes, err := hex.DecodeString(addrS)
+// 	if err != nil {
+// 		return nil, fmt.Errorf("addr is bad hex: %v", err)
+// 	}
+
+// 	height, err := strconv.ParseInt(heightS, 10, 32)
+// 	if err != nil {
+// 		return nil, fmt.Errorf("height is misformatted: %v", err)
+// 	}
+
+// 	return &types.RebondTx{
+// 		Address: addrBytes,
+// 		Height:  int(height),
+// 	}, nil
+// }
+
+type TxResult struct {
+	BlockHash []byte // all txs get in a block
+	Hash      []byte // all txs get a hash
+
+	// only CallTx
+	Address   []byte // only for new contracts
+	Return    []byte
+	Exception string
+
+	//TODO: make Broadcast() errors more responsive so we
+	// can differentiate mempool errors from other
+}
+
+// Preserve
+func SignAndBroadcast(chainID string, nodeClient client.NodeClient, keyClient keys.KeyClient, tx txs.Tx, sign, broadcast, wait bool) (txResult *TxResult, err error) {
+	// var inputAddr []byte
+	if sign {
+		_, tx, err = signTx(keyClient, chainID, tx)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if broadcast {
+		// if wait {
+		// 	var ch chan Msg
+		// 	ch, err = subscribeAndWait(tx, chainID, nodeAddr, inputAddr)
+		// 	if err != nil {
+		// 		return nil, err
+		// 	} else {
+		// 		defer func() {
+		// 			if err != nil {
+		// 				// if broadcast threw an error, just return
+		// 				return
+		// 			}
+		// 			log.WithFields(log.Fields{
+		// 				"",
+		// 				}).Debug("Waiting for tx to be committed")
+		// 			msg := <-ch
+		// 			if msg.Error != nil {
+		// 				logger.Infof("Encountered error waiting for event: %v\n", msg.Error)
+		// 				err = msg.Error
+		// 			} else {
+		// 				txResult.BlockHash = msg.BlockHash
+		// 				txResult.Return = msg.Value
+		// 				txResult.Exception = msg.Exception
+		// 			}
+		// 		}()
+		// 	}
+		// }
+		var receipt *txs.Receipt
+		receipt, err = nodeClient.Broadcast(tx)
+		if err != nil {
+			return nil, err
+		}
+		txResult = &TxResult{
+			Hash: receipt.TxHash,
+		}
+		// NOTE: [ben] is this consistent with the Ethereum protocol?  It should seem
+		// reasonable to get this returned from the chain directly.  Alternatively,
+		// the benefit is that the we don't need to trust the chain node
+		if tx_, ok := tx.(*txs.CallTx); ok {
+			if len(tx_.Address) == 0 {
+				txResult.Address = txs.NewContractAddress(tx_.Input.Address, tx_.Input.Sequence)
+			}
+		}
+	}
+	return
+}
+
+//------------------------------------------------------------------------------------
+// wait for events
+
+type Msg struct {
+	BlockHash []byte
+	Value     []byte
+	Exception string
+	Error     error
+}
+
+// func subscribeAndWait(tx txs.Tx, chainID, nodeAddr string, inputAddr []byte) (chan Msg, error) {
+// 	// subscribe to event and wait for tx to be committed
+// 	var wsAddr string
+// 	if strings.HasPrefix(nodeAddr, "http://") {
+// 		wsAddr = strings.TrimPrefix(nodeAddr, "http://")
+// 	}
+// 	if strings.HasPrefix(nodeAddr, "tcp://") {
+// 		wsAddr = strings.TrimPrefix(nodeAddr, "tcp://")
+// 	}
+// 	if strings.HasPrefix(nodeAddr, "unix://") {
+// 		log.WithFields(log.Fields{
+// 			"node address": nodeAddr,
+// 			}).Warn("Unable to subscribe to websocket from unix socket.")
+// 		return nil, fmt.Errorf("Unable to subscribe to websocket from unix socket: %s", nodeAddr)
+// 	}
+// 	wsAddr = "ws://" + wsAddr
+// 	log.WithFields(log.Fields{
+// 		"websocket address": wsAddr,
+// 		"endpoint": "/websocket",
+// 		}).Debug("Subscribing to websocket address")
+// 	wsClient := rpcclient.NewWSClient(wsAddr, "/websocket")
+// 	wsClient.Start()
+// 	eid := txs.EventStringAccInput(inputAddr)
+// 	if err := wsClient.Subscribe(eid); err != nil {
+// 		return nil, fmt.Errorf("Error subscribing to AccInput event: %v", err)
+// 	}
+// 	if err := wsClient.Subscribe(txs.EventStringNewBlock()); err != nil {
+// 		return nil, fmt.Errorf("Error subscribing to NewBlock event: %v", err)
+// 	}
+
+// 	resultChan := make(chan Msg, 1)
+
+// 	var latestBlockHash []byte
+
+// 	// Read message
+// 	go func() {
+// 		for {
+// 			result := <-wsClient.EventsCh
+// 			// if its a block, remember the block hash
+// 			blockData, ok := result.Data.(txs.EventDataNewBlock)
+// 			if ok {
+// 				log.Infoln(blockData.Block)
+// 				latestBlockHash = blockData.Block.Hash()
+// 				continue
+// 			}
+
+// 			// we don't accept events unless they came after a new block (ie. in)
+// 			if latestBlockHash == nil {
+// 				continue
+// 			}
+
+// 			if result.Event != eid {
+// 				logger.Debugf("received unsolicited event! Got %s, expected %s\n", result.Event, eid)
+// 				continue
+// 			}
+
+// 			data, ok := result.Data.(types.EventDataTx)
+// 			if !ok {
+// 				resultChan <- Msg{Error: fmt.Errorf("response error: expected result.Data to be *types.EventDataTx")}
+// 				return
+// 			}
+
+// 			if !bytes.Equal(types.TxID(chainID, data.Tx), types.TxID(chainID, tx)) {
+// 				logger.Debugf("Received event for same input from another transaction: %X\n", types.TxID(chainID, data.Tx))
+// 				continue
+// 			}
+
+// 			if data.Exception != "" {
+// 				resultChan <- Msg{BlockHash: latestBlockHash, Value: data.Return, Exception: data.Exception}
+// 				return
+// 			}
+
+// 			// GOOD!
+// 			resultChan <- Msg{BlockHash: latestBlockHash, Value: data.Return}
+// 			return
+// 		}
+// 	}()
+
+// 	// txs should take no more than 10 seconds
+// 	timeoutTicker := time.Tick(time.Duration(MaxCommitWaitTimeSeconds) * time.Second)
+
+// 	go func() {
+// 		<-timeoutTicker
+// 		resultChan <- Msg{Error: fmt.Errorf("timed out waiting for event")}
+// 		return
+// 	}()
+// 	return resultChan, nil
+// }
diff --git a/client/core/transaction_factory_test.go b/client/core/transaction_factory_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..aa492b986d1bb70a525422b1d83f944118183b0c
--- /dev/null
+++ b/client/core/transaction_factory_test.go
@@ -0,0 +1,153 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+	"fmt"
+	"testing"
+
+	mockclient "github.com/eris-ltd/eris-db/client/mock"
+	mockkeys "github.com/eris-ltd/eris-db/keys/mock"
+)
+
+func TestTransactionFactory(t *testing.T) {
+	mockKeyClient := mockkeys.NewMockKeyClient()
+	mockNodeClient := mockclient.NewMockNodeClient()
+	testTransactionFactorySend(t, mockNodeClient, mockKeyClient)
+	testTransactionFactoryCall(t, mockNodeClient, mockKeyClient)
+	testTransactionFactoryName(t, mockNodeClient, mockKeyClient)
+	testTransactionFactoryPermissions(t, mockNodeClient, mockKeyClient)
+	// t.Run("BondTransaction", )
+	// t.Run("UnbondTransaction", )
+	// t.Run("RebondTransaction", )
+}
+
+func testTransactionFactorySend(t *testing.T,
+	nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) {
+
+	// generate an ED25519 key and ripemd160 address
+	addressString := fmt.Sprintf("%X", keyClient.NewKey())
+	// Public key can be queried from mockKeyClient.PublicKey(address)
+	// but here we let the transaction factory retrieve the public key
+	// which will then also overwrite the address we provide the function.
+	// As a result we will assert whether address generated above, is identical
+	// to address in generated transation.
+	publicKeyString := ""
+	// generate an additional address to send amount to
+	toAddressString := fmt.Sprintf("%X", keyClient.NewKey())
+	// set an amount to transfer
+	amountString := "1000"
+	// unset nonce so that we retrieve nonce from account
+	nonceString := ""
+
+	_, err := Send(nodeClient, keyClient, publicKeyString, addressString,
+		toAddressString, amountString, nonceString)
+	if err != nil {
+		t.Logf("Error in SendTx: %s", err)
+		t.Fail()
+	}
+	// TODO: test content of Transaction
+}
+
+func testTransactionFactoryCall(t *testing.T,
+	nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) {
+
+	// generate an ED25519 key and ripemd160 address
+	addressString := fmt.Sprintf("%X", keyClient.NewKey())
+	// Public key can be queried from mockKeyClient.PublicKey(address)
+	// but here we let the transaction factory retrieve the public key
+	// which will then also overwrite the address we provide the function.
+	// As a result we will assert whether address generated above, is identical
+	// to address in generated transation.
+	publicKeyString := ""
+	// generate an additional address to send amount to
+	toAddressString := fmt.Sprintf("%X", keyClient.NewKey())
+	// set an amount to transfer
+	amountString := "1000"
+	// unset nonce so that we retrieve nonce from account
+	nonceString := ""
+	// set gas
+	gasString := "1000"
+	// set fee
+	feeString := "100"
+	// set data
+	dataString := fmt.Sprintf("%X", "We are DOUG.")
+
+	_, err := Call(nodeClient, keyClient, publicKeyString, addressString,
+		toAddressString, amountString, nonceString, gasString, feeString, dataString)
+	if err != nil {
+		t.Logf("Error in CallTx: %s", err)
+		t.Fail()
+	}
+	// TODO: test content of Transaction
+}
+
+func testTransactionFactoryName(t *testing.T,
+	nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) {
+
+	// generate an ED25519 key and ripemd160 address
+	addressString := fmt.Sprintf("%X", keyClient.NewKey())
+	// Public key can be queried from mockKeyClient.PublicKey(address)
+	// but here we let the transaction factory retrieve the public key
+	// which will then also overwrite the address we provide the function.
+	// As a result we will assert whether address generated above, is identical
+	// to address in generated transation.
+	publicKeyString := ""
+	// set an amount to transfer
+	amountString := "1000"
+	// unset nonce so that we retrieve nonce from account
+	nonceString := ""
+	// set fee
+	feeString := "100"
+	// set data
+	dataString := fmt.Sprintf("%X", "We are DOUG.")
+	// set name
+	nameString := fmt.Sprintf("%s", "DOUG")
+
+	_, err := Name(nodeClient, keyClient, publicKeyString, addressString,
+		amountString, nonceString, feeString, nameString, dataString)
+	if err != nil {
+		t.Logf("Error in NameTx: %s", err)
+		t.Fail()
+	}
+	// TODO: test content of Transaction
+}
+
+func testTransactionFactoryPermissions(t *testing.T,
+	nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) {
+
+	// generate an ED25519 key and ripemd160 address
+	addressString := fmt.Sprintf("%X", keyClient.NewKey())
+	// Public key can be queried from mockKeyClient.PublicKey(address)
+	// but here we let the transaction factory retrieve the public key
+	// which will then also overwrite the address we provide the function.
+	// As a result we will assert whether address generated above, is identical
+	// to address in generated transation.
+	publicKeyString := ""
+	// generate an additional address to set permissions for
+	permAddressString := fmt.Sprintf("%X", keyClient.NewKey())
+	// unset nonce so that we retrieve nonce from account
+	nonceString := ""
+
+	_, err := Permissions(nodeClient, keyClient, publicKeyString, addressString,
+		nonceString, "set_base", []string{permAddressString, "root", "true"})
+	if err != nil {
+		t.Logf("Error in PermissionsTx: %s", err)
+		t.Fail()
+	}
+	// TODO: test content of Transaction
+}
\ No newline at end of file
diff --git a/client/core/transaction_factory_util.go b/client/core/transaction_factory_util.go
new file mode 100644
index 0000000000000000000000000000000000000000..b5ed8250b4fc3affe355c85ac4d799c5f0086af6
--- /dev/null
+++ b/client/core/transaction_factory_util.go
@@ -0,0 +1,173 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+	"encoding/hex"
+	"fmt"
+	"strconv"
+
+	log "github.com/eris-ltd/eris-logger"
+
+	"github.com/tendermint/go-crypto"
+
+	acc "github.com/eris-ltd/eris-db/account"
+	"github.com/eris-ltd/eris-db/client"
+	"github.com/eris-ltd/eris-db/keys"
+	ptypes "github.com/eris-ltd/eris-db/permission/types"
+	"github.com/eris-ltd/eris-db/txs"
+)
+
+//------------------------------------------------------------------------------------
+// sign and broadcast convenience
+
+// tx has either one input or we default to the first one (ie for send/bond)
+// TODO: better support for multisig and bonding
+func signTx(keyClient keys.KeyClient, chainID string, tx_ txs.Tx) ([]byte, txs.Tx, error) {
+	signBytes := []byte(fmt.Sprintf("%X", acc.SignBytes(chainID, tx_)))
+	var inputAddr []byte
+	var sigED crypto.SignatureEd25519
+	switch tx := tx_.(type) {
+	case *txs.SendTx:
+		inputAddr = tx.Inputs[0].Address
+		defer func(s *crypto.SignatureEd25519) { tx.Inputs[0].Signature = *s }(&sigED)
+	case *txs.NameTx:
+		inputAddr = tx.Input.Address
+		defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED)
+	case *txs.CallTx:
+		inputAddr = tx.Input.Address
+		defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED)
+	case *txs.PermissionsTx:
+		inputAddr = tx.Input.Address
+		defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED)
+	case *txs.BondTx:
+		inputAddr = tx.Inputs[0].Address
+		defer func(s *crypto.SignatureEd25519) {
+			tx.Signature = *s
+			tx.Inputs[0].Signature = *s
+		}(&sigED)
+	case *txs.UnbondTx:
+		inputAddr = tx.Address
+		defer func(s *crypto.SignatureEd25519) { tx.Signature = *s }(&sigED)
+	case *txs.RebondTx:
+		inputAddr = tx.Address
+		defer func(s *crypto.SignatureEd25519) { tx.Signature = *s }(&sigED)
+	}
+	sig, err := keyClient.Sign(signBytes, inputAddr)
+	if err != nil {
+		return nil, nil, err
+	}
+	// TODO: [ben] temporarily address the type conflict here, to be cleaned up
+	// with full type restructuring
+	var sig64 [64]byte
+	copy(sig64[:], sig)
+	sigED = crypto.SignatureEd25519(sig64)
+	log.WithFields(log.Fields{
+		"transaction sign bytes": fmt.Sprintf("%X", signBytes),
+		"account address":        fmt.Sprintf("%X", inputAddr),
+		"signature":              fmt.Sprintf("%X", sig64),
+	}).Debug("Signed transaction")
+	return inputAddr, tx_, nil
+}
+
+func decodeAddressPermFlag(addrS, permFlagS string) (addr []byte, pFlag ptypes.PermFlag, err error) {
+	if addr, err = hex.DecodeString(addrS); err != nil {
+		return
+	}
+	if pFlag, err = ptypes.PermStringToFlag(permFlagS); err != nil {
+		return
+	}
+	return
+}
+
+func checkCommon(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, amtS, nonceS string) (pub crypto.PubKey, amt int64, nonce int64, err error) {
+	if amtS == "" {
+		err = fmt.Errorf("input must specify an amount with the --amt flag")
+		return
+	}
+
+	var pubKeyBytes []byte
+	if pubkey == "" && addr == "" {
+		err = fmt.Errorf("at least one of --pubkey or --addr must be given")
+		return
+	} else if pubkey != "" {
+		if addr != "" {
+			log.WithFields(log.Fields{
+				"public key": pubkey,
+				"address":    addr,
+			}).Info("you have specified both a pubkey and an address. the pubkey takes precedent")
+		}
+		pubKeyBytes, err = hex.DecodeString(pubkey)
+		if err != nil {
+			err = fmt.Errorf("pubkey is bad hex: %v", err)
+			return
+		}
+	} else {
+		// grab the pubkey from eris-keys
+		addressBytes, err2 := hex.DecodeString(addr)
+		if err2 != nil {
+			err = fmt.Errorf("Bad hex string for address (%s): %v", addr, err)
+			return
+		}
+		pubKeyBytes, err2 = keyClient.PublicKey(addressBytes)
+		if err2 != nil {
+			err = fmt.Errorf("Failed to fetch pubkey for address (%s): %v", addr, err)
+			return
+		}
+
+	}
+
+	if len(pubKeyBytes) == 0 {
+		err = fmt.Errorf("Error resolving public key")
+		return
+	}
+
+	amt, err = strconv.ParseInt(amtS, 10, 64)
+	if err != nil {
+		err = fmt.Errorf("amt is misformatted: %v", err)
+	}
+
+	var pubArray [32]byte
+	copy(pubArray[:], pubKeyBytes)
+	pub = crypto.PubKeyEd25519(pubArray)
+	addrBytes := pub.Address()
+
+	if nonceS == "" {
+		if nodeClient == nil {
+			err = fmt.Errorf("input must specify a nonce with the --nonce flag or use --node-addr (or ERIS_CLIENT_NODE_ADDR) to fetch the nonce from a node")
+			return
+		}
+		// fetch nonce from node
+		account, err2 := nodeClient.GetAccount(addrBytes)
+		if err2 != nil {
+			return pub, amt, nonce, err2
+		}
+		nonce = int64(account.Sequence) + 1
+		log.WithFields(log.Fields{
+			"nonce":           nonce,
+			"account address": fmt.Sprintf("%X", addrBytes),
+		}).Debug("Fetch nonce from node")
+	} else {
+		nonce, err = strconv.ParseInt(nonceS, 10, 64)
+		if err != nil {
+			err = fmt.Errorf("nonce is misformatted: %v", err)
+			return
+		}
+	}
+
+	return
+}
\ No newline at end of file
diff --git a/client/mock/client_mock.go b/client/mock/client_mock.go
new file mode 100644
index 0000000000000000000000000000000000000000..0fc7e576753a3d123650e99ea27f0b839a47bdec
--- /dev/null
+++ b/client/mock/client_mock.go
@@ -0,0 +1,70 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package mock
+
+import (
+	"github.com/tendermint/go-crypto"
+
+	acc "github.com/eris-ltd/eris-db/account"
+	. "github.com/eris-ltd/eris-db/client"
+	"github.com/eris-ltd/eris-db/txs"
+)
+
+// NOTE [ben] Compiler check to ensure ErisMockClient successfully implements
+// eris-db/client.NodeClient
+var _ NodeClient = (*MockNodeClient)(nil)
+
+type MockNodeClient struct {
+	accounts map[string]*acc.Account
+}
+
+func NewMockNodeClient() *MockNodeClient {
+	return &MockNodeClient{
+		accounts: make(map[string]*acc.Account),
+	}
+}
+
+func (mock *MockNodeClient) Broadcast(transaction txs.Tx) (*txs.Receipt, error) {
+	// make zero transaction receipt
+	txReceipt := &txs.Receipt{
+		TxHash:          make([]byte, 20, 20),
+		CreatesContract: 0,
+		ContractAddr:    make([]byte, 20, 20),
+	}
+	return txReceipt, nil
+}
+
+func (mock *MockNodeClient) GetAccount(address []byte) (*acc.Account, error) {
+	// make zero account
+	var zero [32]byte
+	copyAddressBytes := make([]byte, len(address), len(address))
+	copy(copyAddressBytes, address)
+	account := &acc.Account{
+		Address:     copyAddressBytes,
+		PubKey:      crypto.PubKey(crypto.PubKeyEd25519(zero)),
+		Sequence:    0,
+		Balance:     0,
+		Code:        make([]byte, 0),
+		StorageRoot: make([]byte, 0),
+	}
+	return account, nil
+}
+
+func (mock *MockNodeClient) MockAddAccount(account *acc.Account) {
+	addressString := string(account.Address[:])
+	mock.accounts[addressString] = account.Copy()
+}
diff --git a/client/transaction/transaction.go b/client/transaction/transaction.go
new file mode 100644
index 0000000000000000000000000000000000000000..e9840d244cacff3bb8b88d90d6dd23bf4eb00974
--- /dev/null
+++ b/client/transaction/transaction.go
@@ -0,0 +1,96 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package transaction
+
+import (
+	"fmt"
+	"os"
+
+	log "github.com/eris-ltd/eris-logger"
+
+	"github.com/eris-ltd/eris-db/client"
+	"github.com/eris-ltd/eris-db/client/core"
+	"github.com/eris-ltd/eris-db/definitions"
+	"github.com/eris-ltd/eris-db/keys"
+)
+
+func Send(do *definitions.ClientDo) {
+	// construct two clients to call out to keys server and
+	// blockchain node.
+	erisKeyClient := keys.NewErisKeyClient(do.SignAddrFlag)
+	erisNodeClient := client.NewErisNodeClient(do.NodeAddrFlag)
+	// form the send transaction
+	sendTransaction, err := core.Send(erisNodeClient, erisKeyClient,
+		do.PubkeyFlag, do.AddrFlag, do.ToFlag, do.AmtFlag, do.NonceFlag)
+	if err != nil {
+		log.Fatalf("Failed on forming Send Transaction: %s", err)
+		return
+	}
+	// TODO: [ben] we carry over the sign bool, but always set it to true,
+	// as we move away from and deprecate the api that allows sending unsigned
+	// transactions and relying on (our) receiving node to sign it.
+	unpackSignAndBroadcast(
+		core.SignAndBroadcast(do.ChainidFlag, erisNodeClient,
+			erisKeyClient, sendTransaction, true, do.BroadcastFlag, do.WaitFlag))
+}
+
+func Call(do *definitions.ClientDo) {
+	// construct two clients to call out to keys server and
+	// blockchain node.
+	erisKeyClient := keys.NewErisKeyClient(do.SignAddrFlag)
+	erisNodeClient := client.NewErisNodeClient(do.NodeAddrFlag)
+	// form the call transaction
+	callTransaction, err := core.Call(erisNodeClient, erisKeyClient,
+		do.PubkeyFlag, do.AddrFlag, do.ToFlag, do.AmtFlag, do.NonceFlag,
+		do.GasFlag, do.FeeFlag, do.DataFlag)
+	if err != nil {
+		log.Fatalf("Failed on forming Call Transaction: %s", err)
+		return
+	}
+	// TODO: [ben] we carry over the sign bool, but always set it to true,
+	// as we move away from and deprecate the api that allows sending unsigned
+	// transactions and relying on (our) receiving node to sign it.
+	unpackSignAndBroadcast(
+		core.SignAndBroadcast(do.ChainidFlag, erisNodeClient,
+			erisKeyClient, callTransaction, true, do.BroadcastFlag, do.WaitFlag))
+}
+
+//----------------------------------------------------------------------
+// Helper functions
+
+func unpackSignAndBroadcast(result *core.TxResult, err error) {
+	if err != nil {
+		log.Fatalf("Failed on signing (and broadcasting) transaction: %s", err)
+		os.Exit(1)
+	}
+	if result == nil {
+		// if we don't provide --sign or --broadcast
+		return
+	}
+	printResult := log.Fields{
+		"transaction hash": fmt.Sprintf("%X", result.Hash),
+	}
+	if result.Address != nil {
+		printResult["Contract Address"] = fmt.Sprintf("%X", result.Address)
+	}
+	if result.Return != nil {
+		printResult["Block Hash"] = fmt.Sprintf("%X", result.BlockHash)
+		printResult["Return Value"] = fmt.Sprintf("%X", result.Return)
+		printResult["Exception"] = fmt.Sprintf("%s", result.Exception)
+	}
+	log.WithFields(printResult).Warn("Result")
+}
diff --git a/cmd/eris-db.go b/cmd/eris-db.go
index 369623252fae2011232472db1add45e6dde5ae6a..4894bd295d3b01ede143a6b361ac7d670556a7bc 100644
--- a/cmd/eris-db.go
+++ b/cmd/eris-db.go
@@ -34,8 +34,8 @@ var do *definitions.Do
 
 var ErisDbCmd = &cobra.Command{
 	Use:   "eris-db",
-	Short: "Eris-DB is the heart of the eris chain.",
-	Long: `Eris-DB is the heart of the eris chain.  Eris-DB combines
+	Short: "Eris-DB is the server side of the eris chain.",
+	Long: `Eris-DB is the server side of the eris chain.  Eris-DB combines
 a modular consensus engine and application manager to run a chain to suit
 your needs.
 
@@ -56,15 +56,15 @@ Complete documentation is available at https://docs.erisindustries.com
 }
 
 func Execute() {
-	InitErisDb()
+	InitErisDbCli()
 	AddGlobalFlags()
 	AddCommands()
 	ErisDbCmd.Execute()
 }
 
-func InitErisDb() {
-	// initialise an empty do struct for command execution
-	do = definitions.NowDo()
+func InitErisDbCli() {
+	// initialise an empty Do struct for command execution
+	do = definitions.NewDo()
 }
 
 func AddGlobalFlags() {
diff --git a/definitions/client_do.go b/definitions/client_do.go
new file mode 100644
index 0000000000000000000000000000000000000000..1c103d9ae70260961397fe9fb4421977388f2afd
--- /dev/null
+++ b/definitions/client_do.go
@@ -0,0 +1,78 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package definitions
+
+type ClientDo struct {
+	// Persistent flags not reflected in the configuration files
+	// only set through command line flags or environment variables
+	Debug   bool // ERIS_DB_DEBUG
+	Verbose bool // ERIS_DB_VERBOSE
+
+	// Following parameters are global flags for eris-client tx
+	SignAddrFlag string
+	NodeAddrFlag string
+	PubkeyFlag   string
+	AddrFlag     string
+	ChainidFlag  string
+
+	// signFlag      bool // TODO: remove; unsafe signing without eris-keys
+	BroadcastFlag bool
+	WaitFlag      bool
+
+	// Following parameters are vary for different Transaction subcommands
+	// some of these are strings rather than flags because the `core`
+	// functions have a pure string interface so they work nicely from http
+	AmtFlag      string
+	NonceFlag    string
+	NameFlag     string
+	DataFlag     string
+	DataFileFlag string
+	ToFlag       string
+	FeeFlag      string
+	GasFlag      string
+	UnbondtoFlag string
+	HeightFlag   string
+}
+
+func NewClientDo() *ClientDo {
+	clientDo := new(ClientDo)
+	clientDo.Debug = false
+	clientDo.Verbose = false
+	
+	clientDo.SignAddrFlag = ""
+	clientDo.NodeAddrFlag = ""
+	clientDo.PubkeyFlag = ""
+	clientDo.AddrFlag = ""
+	clientDo.ChainidFlag = ""
+
+	// clientDo.signFlag = false
+	clientDo.BroadcastFlag = false
+	clientDo.WaitFlag = false
+
+	clientDo.AmtFlag = ""
+	clientDo.NonceFlag = ""
+	clientDo.NameFlag = ""
+	clientDo.DataFlag = ""
+	clientDo.DataFileFlag = ""
+	clientDo.ToFlag = ""
+	clientDo.FeeFlag = ""
+	clientDo.GasFlag = ""
+	clientDo.UnbondtoFlag = ""
+	clientDo.HeightFlag = ""
+
+	return clientDo
+}
\ No newline at end of file
diff --git a/definitions/do.go b/definitions/do.go
index 273dc34800250c750b842fd4e23576c3ef124912..f022aabbc2495d91933b0da38cbd39c4f0be4399 100644
--- a/definitions/do.go
+++ b/definitions/do.go
@@ -53,7 +53,7 @@ type Do struct {
 	// Result       string
 }
 
-func NowDo() *Do {
+func NewDo() *Do {
 	do := new(Do)
 	do.Debug = false
 	do.Verbose = false
diff --git a/glide.lock b/glide.lock
index 57f5ee49216192fb15c66205c11df139555405af..8014bc99f9a0ae8a20de92413fb8fbcbe023c1ae 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,5 +1,5 @@
-hash: 11dc81a6cb059737f9fb51d33ea7d80dd6edfa1f4293f6b4b74507011a536f32
-updated: 2016-06-21T11:22:28.775429287+01:00
+hash: f1f85c5d4b9520217cc6fa9fd7b7e97790e737def9bc08ab45d53d5db729c779
+updated: 2016-09-14T20:54:48.289839938+02:00
 imports:
 - name: github.com/Azure/go-ansiterm
   version: 388960b655244e76e24c75f48631564eaefade62
@@ -21,14 +21,22 @@ imports:
   version: d6191e27ad06236eaad65d79e49a08b03b9f8029
 - name: github.com/BurntSushi/toml
   version: f0aeabca5a127c4078abb8c8d64298b147264b55
+- name: github.com/davecgh/go-spew
+  version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
+  subpackages:
+  - spew
 - name: github.com/docker/docker
   version: 097d1a2b6186dcf1df8ae6135470871aca309a31
   subpackages:
+  - pkg/ioutils
   - pkg/term
+  - pkg/longpath
   - pkg/system
   - pkg/term/windows
 - name: github.com/docker/go-units
   version: f2d77a61e3c169b43402a0a1e84f06daf29b8190
+- name: github.com/eris-ltd/eris-keys
+  version: 114ebc77443db9a153692233294e48bc7e184215
 - name: github.com/eris-ltd/eris-logger
   version: ea48a395d6ecc0eccc67a26da9fc7a6106fabb84
 - name: github.com/fsnotify/fsnotify
@@ -73,6 +81,10 @@ imports:
   version: d2dd0262208475919e1a362f675cfc0e7c10e905
 - name: github.com/naoina/toml
   version: 751171607256bb66e64c9f0220c00662420c38e9
+- name: github.com/pmezard/go-difflib
+  version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
+  subpackages:
+  - difflib
 - name: github.com/Sirupsen/logrus
   version: f3cfb454f4c209e6668c95216c4744b8fddb2356
 - name: github.com/spf13/cast
@@ -87,6 +99,8 @@ imports:
   version: c1ccc378a054ea8d4e38d8c67f6938d4760b53dd
 - name: github.com/stretchr/testify
   version: d77da356e56a7428ad25149ca77381849a6a5232
+  subpackages:
+  - assert
 - name: github.com/syndtr/goleveldb
   version: fa5b5c78794bc5c18f330361059f871ae8c2b9d6
   subpackages:
@@ -130,10 +144,10 @@ imports:
   subpackages:
   - upnp
 - name: github.com/tendermint/go-rpc
-  version: 1d9e89812adc202811b7fb8e9e0837e73adadb43
+  version: 479510be0e80dd9e5d6b1f941adad168df0af85f
   subpackages:
-  - server
   - client
+  - server
   - types
 - name: github.com/tendermint/go-wire
   version: 3b0adbc86ed8425eaed98516165b6788d9f4de7a
@@ -147,13 +161,13 @@ imports:
   - node
   - proxy
   - types
+  - version
   - consensus
   - rpc/core/types
   - blockchain
   - mempool
   - rpc/core
   - state
-  - version
 - name: github.com/tendermint/tmsp
   version: 73e5c3cb7bbee2f9c49792e5a0fcbcab442bf7dc
   subpackages:
@@ -175,13 +189,14 @@ imports:
   - openpgp/errors
   - curve25519
 - name: golang.org/x/net
-  version: 7c71ca708c71bcbd0e6c856b01468ee07fe24557
+  version: de35ec43e7a9aabd6a9c54d2898220ea7e44de7d
   subpackages:
   - http2
   - context
   - netutil
   - trace
   - http2/hpack
+  - idna
   - lex/httplex
   - internal/timeseries
 - name: golang.org/x/sys
diff --git a/glide.yaml b/glide.yaml
index 179439bd07c88b83fbd7838fc69e337598d89cc6..3d3d1d0d7166243a3d7d130aca7e32b3c4a38d04 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -1,6 +1,7 @@
 package: github.com/eris-ltd/eris-db
 import:
 - package: github.com/eris-ltd/eris-logger
+- package: github.com/eris-ltd/eris-keys
 - package: github.com/spf13/cobra
 - package: github.com/spf13/viper
 - package: github.com/tendermint/tendermint
diff --git a/keys/key_client.go b/keys/key_client.go
new file mode 100644
index 0000000000000000000000000000000000000000..0372805a25c1ad3f7d5e8f00e3f88d7d779380de
--- /dev/null
+++ b/keys/key_client.go
@@ -0,0 +1,80 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package keys
+
+import (
+	"encoding/hex"
+)
+
+type KeyClient interface {
+	// Sign needs to return the signature bytes for given message to sign
+	// and the address to sign it with.
+	Sign(signBytes []byte, signAddress []byte) (signature []byte, err error)
+	// PublicKey needs to return the public key associated with a given address
+	PublicKey(address []byte) (publicKey []byte, err error)
+}
+
+// NOTE [ben] Compiler check to ensure ErisKeyClient successfully implements
+// eris-db/keys.KeyClient
+var _ KeyClient = (*ErisKeyClient)(nil)
+
+type ErisKeyClient struct {
+	rpcString string
+}
+
+// ErisKeyClient.New returns a new eris-keys client for provided rpc location
+// Eris-keys connects over http request-responses
+func NewErisKeyClient(rpcString string) *ErisKeyClient {
+	return &ErisKeyClient{
+		rpcString: rpcString,
+	}
+}
+
+// Eris-keys client Sign requests the signature from ErisKeysClient over rpc for the given
+// bytes to be signed and the address to sign them with.
+func (erisKeys *ErisKeyClient) Sign(signBytes []byte, signAddress []byte) (signature []byte, err error) {
+	args := map[string]string{
+		"msg":  string(signBytes),
+		"hash": string(signBytes), // TODO:[ben] backwards compatibility
+		"addr": string(signAddress),
+	}
+	sigS, err := RequestResponse(erisKeys.rpcString, "sign", args)
+	if err != nil {
+		return
+	}
+	sigBytes, err := hex.DecodeString(sigS)
+	if err != nil {
+		return
+	}
+	copy(signature[:], sigBytes)
+	return
+}
+
+// Eris-keys client PublicKey requests the public key associated with an address from
+// the eris-keys server.
+func (erisKeys *ErisKeyClient) PublicKey(address []byte) (publicKey []byte, err error) {
+	args := map[string]string{
+		"addr": string(address),
+	}
+	pubS, err := RequestResponse(erisKeys.rpcString, "pub", args)
+	if err != nil {
+		return
+	}
+	// TODO: [ben] assert that received public key results in
+	// address
+	return hex.DecodeString(pubS)
+}
diff --git a/keys/key_client_util.go b/keys/key_client_util.go
new file mode 100644
index 0000000000000000000000000000000000000000..7e3dcbba43d67f732d3ff98213075ccf2973534c
--- /dev/null
+++ b/keys/key_client_util.go
@@ -0,0 +1,91 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+// version provides the current Eris-DB version and a VersionIdentifier
+// for the modules to identify their version with.
+
+package keys
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	log "github.com/eris-ltd/eris-logger"
+)
+
+// Eris-Keys server connects over http request-response structures
+
+type HTTPResponse struct {
+	Response string
+	Error    string
+}
+
+func RequestResponse(addr, method string, args map[string]string) (string, error) {
+	b, err := json.Marshal(args)
+	if err != nil {
+		return "", err
+	}
+	endpoint := fmt.Sprintf("%s/%s", addr, method)
+	log.WithFields(log.Fields{
+		"key server endpoint": endpoint,
+		"request body":        string(b),
+	}).Debugf("Sending request body to key server")
+	req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(b))
+	if err != nil {
+		return "", err
+	}
+	req.Header.Add("Content-Type", "application/json")
+	res, errS, err := requestResponse(req)
+	if err != nil {
+		return "", fmt.Errorf("Error calling eris-keys at %s: %s", endpoint, err.Error())
+	}
+	if errS != "" {
+		return "", fmt.Errorf("Error (string) calling eris-keys at %s: %s", endpoint, errS)
+	}
+	log.WithFields(log.Fields{
+		"endpoint":     endpoint,
+		"request body": string(b),
+		"response":     res,
+	}).Debugf("Received response from key server")
+	return res, nil
+}
+
+func requestResponse(req *http.Request) (string, string, error) {
+	client := new(http.Client)
+	resp, err := client.Do(req)
+	if err != nil {
+		return "", "", err
+	}
+	if resp.StatusCode >= 400 {
+		return "", "", fmt.Errorf(resp.Status)
+	}
+	return unpackResponse(resp)
+}
+
+func unpackResponse(resp *http.Response) (string, string, error) {
+	b, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return "", "", err
+	}
+	r := new(HTTPResponse)
+	if err := json.Unmarshal(b, r); err != nil {
+		return "", "", err
+	}
+	return r.Response, r.Error, nil
+}
diff --git a/keys/mock/key_client_mock.go b/keys/mock/key_client_mock.go
new file mode 100644
index 0000000000000000000000000000000000000000..67b2ad9c11e6104873f62614378b8afa50210259
--- /dev/null
+++ b/keys/mock/key_client_mock.go
@@ -0,0 +1,73 @@
+// Copyright 2015, 2016 Eris Industries (UK) Ltd.
+// This file is part of Eris-RT
+
+// Eris-RT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Eris-RT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
+
+package mock
+
+import (
+	"fmt"
+
+	// for the mock of key server we explicitly import
+	// the keys server to ensure the core components are
+	// compatible with eris-db.
+	"github.com/eris-ltd/eris-keys/crypto"
+
+	. "github.com/eris-ltd/eris-db/keys"
+)
+
+//---------------------------------------------------------------------
+// Mock client for replacing signing done by eris-keys
+
+// NOTE [ben] Compiler check to ensure MockKeyClient successfully implements
+// eris-db/keys.KeyClient
+var _ KeyClient = (*MockKeyClient)(nil)
+
+type MockKeyClient struct {
+	knownKeys map[string]*crypto.Key
+}
+
+func NewMockKeyClient() *MockKeyClient {
+	return &MockKeyClient{
+		knownKeys: make(map[string]*crypto.Key),
+	}
+}
+
+func (mock *MockKeyClient) NewKey() (address []byte) {
+	// Only tests ED25519 curve and ripemd160.
+	keyType := crypto.KeyType{crypto.CurveTypeEd25519,
+		crypto.AddrTypeRipemd160}
+	key, err := crypto.NewKey(keyType)
+	if err != nil {
+		panic(fmt.Sprintf("Mocked key client failed on key generation (%s): %s", keyType.String(), err))
+	}
+	mock.knownKeys[fmt.Sprintf("%X", key.Address)] = key
+	return key.Address
+}
+
+func (mock *MockKeyClient) Sign(signBytes []byte, signAddress []byte) (signature []byte, err error) {
+	key := mock.knownKeys[fmt.Sprintf("%X", signAddress)]
+	if key == nil {
+		return nil, fmt.Errorf("Unknown address (%X)", signAddress)
+	}
+	return key.Sign(signBytes)
+}
+
+func (mock *MockKeyClient) PublicKey(address []byte) (publicKey []byte, err error) {
+	key := mock.knownKeys[fmt.Sprintf("%X", address)]
+	if key == nil {
+		return nil, fmt.Errorf("Unknown address (%X)", address)
+	}
+	return key.Pubkey()
+}
diff --git a/version/version.go b/version/version.go
index 521120043705cac24a3090a269625717a411cb9f..d285dbecac9f2028de9896410cebf1caf07d721d 100644
--- a/version/version.go
+++ b/version/version.go
@@ -129,4 +129,4 @@ func (version *VersionIdentifier) MatchesMinorVersion(
 
 // IMPORTANT: Eris-DB version must be on the last line of this file for
 // the deployment script tests/build_tool.sh to pick up the right label.
-const VERSION = "0.12.0-rc3"
\ No newline at end of file
+const VERSION = "0.12.0-rc3"