diff --git a/Gopkg.lock b/Gopkg.lock
index 99eff5eeb676cebb4462eedf0560de196c54c395..ce5d6fd3a2f7af0664a2a254d121a359548e2668 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -187,6 +187,12 @@
   revision = "792786c7400a136282c1664665ae0a8db921c6c2"
   version = "v1.0.0"
 
+[[projects]]
+  name = "github.com/powerman/rpc-codec"
+  packages = ["jsonrpc2"]
+  revision = "3e1ab3b635b7b0d5f771028cd45aa9a827fd9f31"
+  version = "v1.0.0"
+
 [[projects]]
   branch = "master"
   name = "github.com/rcrowley/go-metrics"
@@ -354,6 +360,12 @@
   revision = "9831f2c3ac1068a78f50999a30db84270f647af6"
   version = "v1.1"
 
+[[projects]]
+  name = "github.com/ybbus/jsonrpc"
+  packages = ["."]
+  revision = "dd866631e904a5df2067d934985c5def68f391ac"
+  version = "v2.1.2"
+
 [[projects]]
   branch = "master"
   name = "golang.org/x/crypto"
@@ -474,6 +486,6 @@
 [solve-meta]
   analyzer-name = "dep"
   analyzer-version = 1
-  inputs-digest = "5db3ac43679cafd5f68104e93a248b62266d313aef2d8ba3bd7f3986a5a99834"
+  inputs-digest = "658b10b4a49260c9ee26dcf77b0dcbe9e39d5c1604e234c69e9db99c01ba2d0d"
   solver-name = "gps-cdcl"
   solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index b1f9779a1f7fc121303e95d73ea11a438bde1664..e07ee9d5afb6e9cf54c9073f48f2fd167f527dd9 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -98,3 +98,11 @@
 [[constraint]]
   name = "gopkg.in/tylerb/graceful.v1"
   version = "1.2.15"
+
+[[constraint]]
+  name = "github.com/powerman/rpc-codec"
+  version = "1.0.0"
+
+[[constraint]]
+  name = "github.com/ybbus/jsonrpc"
+  version = "2.1.2"
diff --git a/config/config.go b/config/config.go
index 160f240632db14485053c0f20a91a77f3678e97e..fa39dba4e8dffc5c6b6211fd7eb1c4c9d27377ae 100644
--- a/config/config.go
+++ b/config/config.go
@@ -61,15 +61,14 @@ func (conf *BurrowConfig) Kernel(ctx context.Context) (*core.Kernel, error) {
 
 	var exeOptions []execution.ExecutionOption
 	if conf.Execution != nil {
-		var err error
 		exeOptions, err = conf.Execution.ExecutionOptions()
 		if err != nil {
 			return nil, err
 		}
 	}
 
-	return core.NewKernel(ctx, privValidator, conf.GenesisDoc, conf.Tendermint.TendermintConfig(), conf.RPC, exeOptions,
-		logger)
+	return core.NewKernel(ctx, keyClient, privValidator, conf.GenesisDoc, conf.Tendermint.TendermintConfig(), conf.RPC,
+		exeOptions, logger)
 }
 
 func (conf *BurrowConfig) JSONString() string {
diff --git a/core/integration/test_wrapper.go b/core/integration/test_wrapper.go
index b532e4e1021b1171e5ae9f156468bb569a3bac79..c5ee2892a651dcfac896cd7cc62a5731955848f3 100644
--- a/core/integration/test_wrapper.go
+++ b/core/integration/test_wrapper.go
@@ -46,7 +46,7 @@ const (
 var debugLogging = false
 
 // We use this to wrap tests
-func TestWrapper(privateAccounts []acm.PrivateAccount, genesisDoc *genesis.GenesisDoc, runner func() int) int {
+func TestWrapper(privateAccounts []acm.PrivateAccount, genesisDoc *genesis.GenesisDoc, runner func(*core.Kernel) int) int {
 	fmt.Println("Running with integration TestWrapper (core/integration/test_wrapper.go)...")
 
 	os.RemoveAll(testDir)
@@ -97,7 +97,7 @@ func TestWrapper(privateAccounts []acm.PrivateAccount, genesisDoc *genesis.Genes
 		panic(err)
 	}
 
-	return runner()
+	return runner(kernel)
 }
 
 func TestGenesisDoc(addressables []acm.PrivateAccount) *genesis.GenesisDoc {
diff --git a/core/kernel_test.go b/core/kernel_test.go
index aa1af7efc1aefc498cef965014a6d163d6e7f5ab..e1e4d65d17c0416ae3ea0a613e35632c059915e6 100644
--- a/core/kernel_test.go
+++ b/core/kernel_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/hyperledger/burrow/consensus/tendermint"
 	"github.com/hyperledger/burrow/consensus/tendermint/validator"
 	"github.com/hyperledger/burrow/genesis"
+	"github.com/hyperledger/burrow/keys/mock"
 	"github.com/hyperledger/burrow/logging"
 	"github.com/hyperledger/burrow/rpc"
 	"github.com/stretchr/testify/assert"
@@ -64,7 +65,8 @@ func bootWaitBlocksShutdown(privValidator tm_types.PrivValidator, genesisDoc *ge
 	tmConf *tm_config.Config, logger *logging.Logger,
 	blockChecker func(block *tm_types.EventDataNewBlock) (cont bool)) error {
 
-	kern, err := NewKernel(context.Background(), privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), nil, logger)
+	kern, err := NewKernel(context.Background(), mock.NewMockKeyClient(), privValidator, genesisDoc, tmConf,
+		rpc.DefaultRPCConfig(), nil, logger)
 	if err != nil {
 		return err
 	}
diff --git a/execution/transactor.go2 b/execution/transactor.go2
new file mode 100644
index 0000000000000000000000000000000000000000..9cdd63a23eec1368d457e0a90012e3e306064b03
--- /dev/null
+++ b/execution/transactor.go2
@@ -0,0 +1,377 @@
+// Copyright 2017 Monax Industries Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package execution
+
+import (
+	"context"
+	"fmt"
+	"runtime/debug"
+	"sync"
+	"time"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/hyperledger/burrow/account/state"
+	"github.com/hyperledger/burrow/binary"
+	"github.com/hyperledger/burrow/blockchain"
+	"github.com/hyperledger/burrow/consensus/tendermint/codes"
+	"github.com/hyperledger/burrow/event"
+	exe_events "github.com/hyperledger/burrow/execution/events"
+	"github.com/hyperledger/burrow/execution/evm"
+	evm_events "github.com/hyperledger/burrow/execution/evm/events"
+	"github.com/hyperledger/burrow/logging"
+	"github.com/hyperledger/burrow/logging/structure"
+	"github.com/hyperledger/burrow/txs"
+	abci_types "github.com/tendermint/abci/types"
+	"github.com/tendermint/go-wire"
+)
+
+const BlockingTimeoutSeconds = 30
+
+type Call struct {
+	Return  []byte
+	GasUsed uint64
+}
+
+type SequencedAddressableSigner interface {
+	acm.AddressableSigner
+	Sequence() uint64
+}
+
+// Transactor is the controller/middleware for the v0 RPC
+type Transactor struct {
+	sync.Mutex
+	tip              blockchain.Tip
+	eventEmitter     event.Emitter
+	broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error
+	logger           *logging.Logger
+}
+
+func NewTransactor(tip blockchain.Tip, eventEmitter event.Emitter,
+	broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error,
+	logger *logging.Logger) *Transactor {
+
+	return &Transactor{
+		tip:              tip,
+		eventEmitter:     eventEmitter,
+		broadcastTxAsync: broadcastTxAsync,
+		logger:           logger.With(structure.ComponentKey, "Transactor"),
+	}
+}
+
+// Run a contract's code on an isolated and unpersisted state
+// Cannot be used to create new contracts
+func (trans *Transactor) Call(iterable state.Iterable, fromAddress, toAddress acm.Address,
+	data []byte) (call *Call, err error) {
+
+	if evm.RegisteredNativeContract(toAddress.Word256()) {
+		return nil, fmt.Errorf("attempt to call native contract at address "+
+			"%X, but native contracts can not be called directly. Use a deployed "+
+			"contract that calls the native function instead", toAddress)
+	}
+	// This was being run against CheckTx cache, need to understand the reasoning
+	callee, err := state.GetMutableAccount(iterable, toAddress)
+	if err != nil {
+		return nil, err
+	}
+	if callee == nil {
+		return nil, fmt.Errorf("account %s does not exist", toAddress)
+	}
+	caller := acm.ConcreteAccount{Address: fromAddress}.MutableAccount()
+	txCache := state.NewCache(iterable)
+	params := vmParams(trans.tip)
+
+	vmach := evm.NewVM(txCache, params, caller.Address(), nil, trans.logger.WithScope("Call"))
+	vmach.SetPublisher(trans.eventEmitter)
+
+	gas := params.GasLimit
+	defer func() {
+		if r := recover(); r != nil {
+			err = fmt.Errorf("panic from VM in simulated call: %v\n%s", r, debug.Stack())
+		}
+	}()
+	ret, err := vmach.Call(caller, callee, callee.Code(), data, 0, &gas)
+	if err != nil {
+		return nil, err
+	}
+	gasUsed := params.GasLimit - gas
+	return &Call{Return: ret, GasUsed: gasUsed}, nil
+}
+
+// Run the given code on an isolated and unpersisted state
+// Cannot be used to create new contracts.
+func (trans *Transactor) CallCode(iterable state.Iterable, fromAddress acm.Address, code, data []byte) (*Call, error) {
+	// This was being run against CheckTx cache, need to understand the reasoning
+	callee := acm.ConcreteAccount{Address: fromAddress}.MutableAccount()
+	caller := acm.ConcreteAccount{Address: fromAddress}.MutableAccount()
+	txCache := state.NewCache(iterable)
+	params := vmParams(trans.tip)
+
+	vmach := evm.NewVM(txCache, params, caller.Address(), nil, trans.logger.WithScope("CallCode"))
+	gas := params.GasLimit
+	ret, err := vmach.Call(caller, callee, code, data, 0, &gas)
+	if err != nil {
+		return nil, err
+	}
+	gasUsed := params.GasLimit - gas
+	return &Call{Return: ret, GasUsed: gasUsed}, nil
+}
+
+func (trans *Transactor) BroadcastTxAsync(tx txs.Tx, callback func(res *abci_types.Response)) error {
+	return trans.broadcastTxAsync(tx, callback)
+}
+
+// Broadcast a transaction.
+func (trans *Transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) {
+	trans.logger.Trace.Log("method", "BroadcastTx",
+		"tx_hash", tx.Hash(trans.tip.ChainID()),
+		"tx", tx.String())
+	responseCh := make(chan *abci_types.Response, 1)
+	err := trans.BroadcastTxAsync(tx, func(res *abci_types.Response) {
+		responseCh <- res
+	})
+
+	if err != nil {
+		return nil, err
+	}
+	response := <-responseCh
+	checkTxResponse := response.GetCheckTx()
+	if checkTxResponse == nil {
+		return nil, fmt.Errorf("application did not return CheckTx response")
+	}
+
+	switch checkTxResponse.Code {
+	case codes.TxExecutionSuccessCode:
+		receipt := new(txs.Receipt)
+		err := wire.ReadBinaryBytes(checkTxResponse.Data, receipt)
+		if err != nil {
+			return nil, fmt.Errorf("could not deserialise transaction receipt: %s", err)
+		}
+		return receipt, nil
+	default:
+		return nil, fmt.Errorf("error returned by Tendermint in BroadcastTxSync "+
+			"ABCI code: %v, ABCI log: %v", checkTxResponse.Code, checkTxResponse.Log)
+	}
+}
+
+// Orders calls to BroadcastTx using lock (waits for response from core before releasing)
+func (trans *Transactor) Transact(inputAccount SequencedAddressableSigner, address *acm.Address, data []byte, gasLimit,
+	fee uint64) (*txs.Receipt, error) {
+	trans.Lock()
+	defer trans.Unlock()
+	// TODO: [Silas] we should consider revising this method and removing fee, or
+	// possibly adding an amount parameter. It is non-sensical to just be able to
+	// set the fee. Our support of fees in general is questionable since at the
+	// moment all we do is deduct the fee effectively leaking token. It is possible
+	// someone may be using the sending of native token to payable functions but
+	// they can be served by broadcasting a token.
+
+	// We hard-code the amount to be equal to the fee which means the CallTx we
+	// generate transfers 0 value, which is the most sensible default since in
+	// recent solidity compilers the EVM generated will throw an error if value
+	// is transferred to a non-payable function.
+	txInput := &txs.TxInput{
+		Address:   inputAccount.Address(),
+		Amount:    fee,
+		Sequence:  inputAccount.Sequence() + 1,
+		PublicKey: inputAccount.PublicKey(),
+	}
+	tx := &txs.CallTx{
+		Input:    txInput,
+		Address:  address,
+		GasLimit: gasLimit,
+		Fee:      fee,
+		Data:     data,
+	}
+
+	// Got ourselves a tx.
+	err := tx.Sign(trans.tip.ChainID(), inputAccount)
+	if err != nil {
+		return nil, err
+	}
+	return trans.BroadcastTx(tx)
+}
+
+func (trans *Transactor) TransactAndHold(inputAccount SequencedAddressableSigner, privKey []byte,
+	address *acm.Address, data []byte, gasLimit, fee uint64) (*evm_events.EventDataCall, error) {
+
+	receipt, err := trans.Transact(inputAccount, address, data, gasLimit, fee)
+	if err != nil {
+		return nil, err
+	}
+
+	// We want non-blocking on the first event received (but buffer the value),
+	// after which we want to block (and then discard the value - see below)
+	wc := make(chan *evm_events.EventDataCall, 1)
+
+	subID, err := event.GenerateSubscriptionID()
+	if err != nil {
+		return nil, err
+	}
+
+	err = evm_events.SubscribeAccountCall(context.Background(), trans.eventEmitter, subID, receipt.ContractAddress,
+		receipt.TxHash, wc)
+	if err != nil {
+		return nil, err
+	}
+	// Will clean up callback goroutine and subscription in pubsub
+	defer trans.eventEmitter.UnsubscribeAll(context.Background(), subID)
+
+	timer := time.NewTimer(BlockingTimeoutSeconds * time.Second)
+	defer timer.Stop()
+
+	select {
+	case <-timer.C:
+		return nil, fmt.Errorf("transaction timed out TxHash: %X", receipt.TxHash)
+	case eventDataCall := <-wc:
+		if eventDataCall.Exception != "" {
+			return nil, fmt.Errorf("error when transacting: " + eventDataCall.Exception)
+		} else {
+			return eventDataCall, nil
+		}
+	}
+}
+
+func (trans *Transactor) Send(inputAccount SequencedAddressableSigner, toAddress acm.Address, amount uint64) (*txs.Receipt, error) {
+	if len(privKey) != 64 {
+		return nil, fmt.Errorf("Private key is not of the right length: %d\n",
+			len(privKey))
+	}
+
+	pk := &[64]byte{}
+	copy(pk[:], privKey)
+	trans.Lock()
+	defer trans.Unlock()
+	pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey)
+	if err != nil {
+		return nil, err
+	}
+	acc, err := trans.state.GetAccount(pa.Address())
+	if err != nil {
+		return nil, err
+	}
+	sequence := uint64(1)
+	if acc != nil {
+		sequence = acc.Sequence() + uint64(1)
+	}
+
+	tx := txs.NewSendTx()
+
+	txInput := &txs.TxInput{
+		Address:   pa.Address(),
+		Amount:    amount,
+		Sequence:  sequence,
+		PublicKey: pa.PublicKey(),
+	}
+
+	tx.Inputs = append(tx.Inputs, txInput)
+
+	txOutput := &txs.TxOutput{Address: toAddress, Amount: amount}
+
+	tx.Outputs = append(tx.Outputs, txOutput)
+
+	// Got ourselves a tx.
+	txS, errS := trans.SignTx(tx, []acm.AddressableSigner{pa})
+	if errS != nil {
+		return nil, errS
+	}
+	return trans.BroadcastTx(txS)
+}
+
+func (trans *Transactor) SendAndHold(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) {
+	receipt, err := trans.Send(privKey, toAddress, amount)
+	if err != nil {
+		return nil, err
+	}
+
+	wc := make(chan *txs.SendTx)
+
+	subID, err := event.GenerateSubscriptionID()
+	if err != nil {
+		return nil, err
+	}
+
+	err = exe_events.SubscribeAccountOutputSendTx(context.Background(), trans.eventEmitter, subID, toAddress,
+		receipt.TxHash, wc)
+	if err != nil {
+		return nil, err
+	}
+	defer trans.eventEmitter.UnsubscribeAll(context.Background(), subID)
+
+	timer := time.NewTimer(BlockingTimeoutSeconds * time.Second)
+	defer timer.Stop()
+
+	pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey)
+	if err != nil {
+		return nil, err
+	}
+
+	select {
+	case <-timer.C:
+		return nil, fmt.Errorf("transaction timed out TxHash: %X", receipt.TxHash)
+	case sendTx := <-wc:
+		// This is a double check - we subscribed to this tx's hash so something has gone wrong if the amounts don't match
+		if sendTx.Inputs[0].Address == pa.Address() && sendTx.Inputs[0].Amount == amount {
+			return receipt, nil
+		}
+		return nil, fmt.Errorf("received SendTx but hash doesn't seem to match what we subscribed to, "+
+			"received SendTx: %v which does not match receipt on sending: %v", sendTx, receipt)
+	}
+}
+
+func (trans *Transactor) TransactNameReg(privKey []byte, name, data string, amount, fee uint64) (*txs.Receipt, error) {
+	if len(privKey) != 64 {
+		return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey))
+	}
+	trans.Lock()
+	defer trans.Unlock()
+	pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey)
+	if err != nil {
+		return nil, err
+	}
+	acc, err := trans.state.GetAccount(pa.Address())
+	if err != nil {
+		return nil, err
+	}
+	sequence := uint64(1)
+	if acc == nil {
+		sequence = acc.Sequence() + uint64(1)
+	}
+	tx := txs.NewNameTxWithSequence(pa.PublicKey(), name, data, amount, fee, sequence)
+	// Got ourselves a tx.
+	txS, errS := trans.SignTx(tx, []acm.AddressableSigner{pa})
+	if errS != nil {
+		return nil, errS
+	}
+	return trans.BroadcastTx(txS)
+}
+
+// Sign a transaction
+func (trans *Transactor) SignTx(tx txs.Tx, signingAccounts []acm.AddressableSigner) (txs.Tx, error) {
+	// more checks?
+	err := tx.Sign(trans.tip.ChainID(), signingAccounts...)
+	if err != nil {
+		return nil, err
+	}
+	return tx, nil
+}
+
+func vmParams(tip blockchain.Tip) evm.Params {
+	return evm.Params{
+		BlockHeight: tip.LastBlockHeight(),
+		BlockHash:   binary.LeftPadWord256(tip.LastBlockHash()),
+		BlockTime:   tip.LastBlockTime().Unix(),
+		GasLimit:    GasLimit,
+	}
+}
diff --git a/execution/transactor_test.go b/execution/transactor_test.go
index 83c281a425e82d011ae512ded8ecdc51dcdeb50f..7b0cdbfde99d760453eb6e7755a148bebd48b45f 100644
--- a/execution/transactor_test.go
+++ b/execution/transactor_test.go
@@ -40,3 +40,7 @@ func newTestTransactor(txProcessor func(tx txs.Tx) (*types.Response, error)) tes
 		Transactor:     trans,
 	}
 }
+
+func TestTransactor_Transact(t *testing.T) {
+	//trans := newTestTransactor()
+}
diff --git a/logging/logger.go b/logging/logger.go
index 51346c4f18d34ebd7c60603ee562afbf724c2509..03244b4da435bac02d64bd0687801df789f1a7e7 100644
--- a/logging/logger.go
+++ b/logging/logger.go
@@ -144,6 +144,6 @@ func Msg(logger kitlog.Logger, message string, keyvals ...interface{}) error {
 // Wrap the output loggers with a a set of standard transforms, a non-blocking
 // ChannelLogger and an outer context
 func wrapOutputLogger(outputLogger kitlog.Logger) (kitlog.Logger, channels.Channel) {
-	//return loggers.NonBlockingLogger(loggers.BurrowFormatLogger(outputLogger))
-	return loggers.BurrowFormatLogger(outputLogger), channels.NewDeadChannel()
+	return loggers.NonBlockingLogger(loggers.BurrowFormatLogger(outputLogger))
+	//return loggers.BurrowFormatLogger(outputLogger), channels.NewDeadChannel()
 }
diff --git a/rpc/jsonrpc.go b/rpc/jsonrpc.go
index 17b683beb81c67d4d9778ab15153fd9e36f6dfb8..b9b52e910f3070e00472b3b3b35876a638cdcb4d 100644
--- a/rpc/jsonrpc.go
+++ b/rpc/jsonrpc.go
@@ -16,6 +16,7 @@ package rpc
 
 import (
 	"encoding/json"
+	"fmt"
 )
 
 // JSON-RPC 2.0 error codes.
@@ -66,6 +67,10 @@ type (
 	}
 )
 
+func (err RPCError) Error() string {
+	return fmt.Sprintf("Error %v: %s", err.Code, err.Message)
+}
+
 // Create a new RPC request. This is the generic struct that is passed to RPC
 // methods
 func NewRPCRequest(id string, method string, params json.RawMessage) *RPCRequest {
diff --git a/rpc/tm/integration/shared_test.go b/rpc/tm/integration/main_test.go
similarity index 93%
rename from rpc/tm/integration/shared_test.go
rename to rpc/tm/integration/main_test.go
index 55a2a5b0b4e99955bae50b118c1529d8ba438981..9833d45c9668313b8104da27c639286373b376d0 100644
--- a/rpc/tm/integration/shared_test.go
+++ b/rpc/tm/integration/main_test.go
@@ -22,12 +22,13 @@ import (
 	"testing"
 	"time"
 
+	"github.com/hyperledger/burrow/core"
 	"github.com/hyperledger/burrow/core/integration"
 )
 
 // Needs to be in a _test.go file to be picked up
 func TestMain(m *testing.M) {
-	returnValue := integration.TestWrapper(privateAccounts, genesisDoc, func() int {
+	returnValue := integration.TestWrapper(privateAccounts, genesisDoc, func(*core.Kernel) int {
 		return m.Run()
 	})
 
diff --git a/rpc/tm/integration/shared.go b/rpc/tm/integration/shared.go
index 0f4ba18a5091a7d251fe6ba49472a3d8e1fe6c90..e9c1981f3554daaf5af1d065ac41d3db7b53ae62 100644
--- a/rpc/tm/integration/shared.go
+++ b/rpc/tm/integration/shared.go
@@ -48,7 +48,6 @@ var (
 		"JSONRPC": jsonRpcClient,
 		"HTTP":    httpClient,
 	}
-	// Initialised in initGlobalVariables
 	genesisDoc = integration.TestGenesisDoc(privateAccounts)
 )
 
diff --git a/rpc/v0/client.go b/rpc/v0/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..791f89310977068cd3b957a9fd86ad707f915175
--- /dev/null
+++ b/rpc/v0/client.go
@@ -0,0 +1,70 @@
+package v0
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"time"
+
+	"github.com/hyperledger/burrow/rpc"
+)
+
+type V0Client struct {
+	url    string
+	codec  rpc.Codec
+	client *http.Client
+}
+
+type RPCResponse struct {
+	Result  json.RawMessage `json:"result"`
+	Error   *rpc.RPCError   `json:"error"`
+	Id      string          `json:"id"`
+	JSONRPC string          `json:"jsonrpc"`
+}
+
+func NewV0Client(url string) *V0Client {
+	return &V0Client{
+		url:   url,
+		codec: NewTCodec(),
+		client: &http.Client{
+			Timeout: 10 * time.Second,
+		},
+	}
+}
+
+func (vc *V0Client) Call(method string, param interface{}, result interface{}) error {
+	// Marhsal into JSONRPC request object
+	bs, err := vc.codec.EncodeBytes(param)
+	if err != nil {
+		return err
+	}
+	request := rpc.NewRPCRequest("test", method, bs)
+	bs, err = json.Marshal(request)
+	if err != nil {
+		return err
+	}
+	// Post to JSONService
+	resp, err := vc.client.Post(vc.url, "application/json", bytes.NewBuffer(bs))
+	if err != nil {
+		return err
+	}
+	// Marshal into JSONRPC response object
+	bs, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return err
+	}
+	rpcResponse := new(RPCResponse)
+	err = json.Unmarshal(bs, rpcResponse)
+	if err != nil {
+		return err
+	}
+	if rpcResponse.Error != nil {
+		return rpcResponse.Error
+	}
+	err = json.Unmarshal(rpcResponse.Result, result)
+	if err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/rpc/v0/integration/main_test.go b/rpc/v0/integration/main_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5a0375653533e0e49b14512aca4d1517115783fc
--- /dev/null
+++ b/rpc/v0/integration/main_test.go
@@ -0,0 +1,42 @@
+// +build integration
+
+// Space above here matters
+// Copyright 2017 Monax Industries Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package integration
+
+import (
+	"os"
+	"testing"
+	"time"
+
+	"github.com/hyperledger/burrow/core"
+	"github.com/hyperledger/burrow/core/integration"
+)
+
+var privateAccounts = integration.MakePrivateAccounts(5) // make keys
+var genesisDoc = integration.TestGenesisDoc(privateAccounts)
+var kernel *core.Kernel
+
+// Needs to be in a _test.go file to be picked up
+func TestMain(m *testing.M) {
+	returnValue := integration.TestWrapper(privateAccounts, genesisDoc, func(kern *core.Kernel) int {
+		kernel = kern
+		return m.Run()
+	})
+
+	time.Sleep(3 * time.Second)
+	os.Exit(returnValue)
+}
diff --git a/rpc/v0/integration/v0_test.go b/rpc/v0/integration/v0_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..433b2fae0178a1883b8d752b8a2f33d3dd738483
--- /dev/null
+++ b/rpc/v0/integration/v0_test.go
@@ -0,0 +1,45 @@
+// +build integration
+
+// Space above here matters
+// Copyright 2017 Monax Industries Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package integration
+
+import (
+	"testing"
+
+	"github.com/hyperledger/burrow/rpc/v0"
+	"github.com/hyperledger/burrow/txs"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestTransact(t *testing.T) {
+	cli := v0.NewV0Client("http://localhost:1337/rpc")
+	receipt := new(txs.Receipt)
+
+	address := privateAccounts[1].Address()
+	param := v0.TransactParam{
+		PrivKey:  privateAccounts[0].PrivateKey().RawBytes(),
+		Address:  address.Bytes(),
+		Data:     []byte{},
+		Fee:      2,
+		GasLimit: 10000,
+	}
+	err := cli.Call(v0.TRANSACT, param, receipt)
+	require.NoError(t, err)
+	assert.False(t, receipt.CreatesContract)
+	assert.Equal(t, address, receipt.ContractAddress)
+}
diff --git a/rpc/v0/methods.go b/rpc/v0/methods.go
index 3d5c98b72bbe8e2ecf46a693c95ebefe61acd921..cfa075871e2ef4d228399b235888b467e36e83b1 100644
--- a/rpc/v0/methods.go
+++ b/rpc/v0/methods.go
@@ -229,6 +229,11 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m
 			if err != nil {
 				return nil, rpc.INVALID_PARAMS, err
 			}
+			//inputAccount, err := service.Mempool().SigningAccountFromPrivateKey(param.PrivKey)
+			//if err != nil {
+			//	return nil, rpc.INVALID_PARAMS, err
+			//}
+			//receipt, err := service.Transactor().Transact2(inputAccount, address, param.Data, param.GasLimit, param.Fee)
 			receipt, err := service.Transactor().Transact(param.PrivKey, address, param.Data, param.GasLimit, param.Fee)
 			if err != nil {
 				return nil, rpc.INTERNAL_ERROR, err