From 4dd913a509259b67dd9d0e37957325dc5605b90d Mon Sep 17 00:00:00 2001
From: Silas Davis <silas@monax.io>
Date: Tue, 3 Apr 2018 22:22:20 +0100
Subject: [PATCH] Refactor tx types into individual files

Signed-off-by: Silas Davis <silas@monax.io>
---
 execution/transactor.go |  24 +--
 txs/README.md           |  61 -------
 txs/bond_tx.go          | 105 +++++++++++
 txs/call_tx.go          |  81 +++++++++
 txs/name_tx.go          | 114 ++++++++++++
 txs/names.go            |  18 --
 txs/permission_tx.go    |  72 ++++++++
 txs/rebond_tx.go        |  46 +++++
 txs/send_tx.go          |  96 ++++++++++
 txs/tx.go               | 388 +++++-----------------------------------
 txs/tx_input.go         |  35 ++++
 txs/tx_output.go        |  32 ++++
 txs/tx_utils.go         | 272 ----------------------------
 txs/unbond_tx.go        |  46 +++++
 14 files changed, 679 insertions(+), 711 deletions(-)
 delete mode 100644 txs/README.md
 create mode 100644 txs/bond_tx.go
 create mode 100644 txs/call_tx.go
 create mode 100644 txs/name_tx.go
 create mode 100644 txs/permission_tx.go
 create mode 100644 txs/rebond_tx.go
 create mode 100644 txs/send_tx.go
 create mode 100644 txs/tx_input.go
 create mode 100644 txs/tx_output.go
 delete mode 100644 txs/tx_utils.go
 create mode 100644 txs/unbond_tx.go

diff --git a/execution/transactor.go b/execution/transactor.go
index 7d0a81c7..952aaab0 100644
--- a/execution/transactor.go
+++ b/execution/transactor.go
@@ -375,40 +375,40 @@ func (trans *Transactor) TransactNameReg(privKey []byte, name, data string, amou
 }
 
 // Sign a transaction
-func (trans *Transactor) SignTx(tx txs.Tx, privAccounts []acm.SigningAccount) (txs.Tx, error) {
+func (trans *Transactor) SignTx(tx txs.Tx, signingAccounts []acm.SigningAccount) (txs.Tx, error) {
 	// more checks?
 
 	chainID := trans.tip.ChainID()
 	switch tx.(type) {
 	case *txs.NameTx:
 		nameTx := tx.(*txs.NameTx)
-		nameTx.Input.PublicKey = privAccounts[0].PublicKey()
-		nameTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, nameTx)
+		nameTx.Input.PublicKey = signingAccounts[0].PublicKey()
+		nameTx.Input.Signature = acm.ChainSign(signingAccounts[0], chainID, nameTx)
 	case *txs.SendTx:
 		sendTx := tx.(*txs.SendTx)
 		for i, input := range sendTx.Inputs {
-			input.PublicKey = privAccounts[i].PublicKey()
-			input.Signature = acm.ChainSign(privAccounts[i], chainID, sendTx)
+			input.PublicKey = signingAccounts[i].PublicKey()
+			input.Signature = acm.ChainSign(signingAccounts[i], chainID, sendTx)
 		}
 	case *txs.CallTx:
 		callTx := tx.(*txs.CallTx)
-		callTx.Input.PublicKey = privAccounts[0].PublicKey()
-		callTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, callTx)
+		callTx.Input.PublicKey = signingAccounts[0].PublicKey()
+		callTx.Input.Signature = acm.ChainSign(signingAccounts[0], chainID, callTx)
 	case *txs.BondTx:
 		bondTx := tx.(*txs.BondTx)
 		// the first privaccount corresponds to the BondTx pub key.
 		// the rest to the inputs
-		bondTx.Signature = acm.ChainSign(privAccounts[0], chainID, bondTx)
+		bondTx.Signature = acm.ChainSign(signingAccounts[0], chainID, bondTx)
 		for i, input := range bondTx.Inputs {
-			input.PublicKey = privAccounts[i+1].PublicKey()
-			input.Signature = acm.ChainSign(privAccounts[i+1], chainID, bondTx)
+			input.PublicKey = signingAccounts[i+1].PublicKey()
+			input.Signature = acm.ChainSign(signingAccounts[i+1], chainID, bondTx)
 		}
 	case *txs.UnbondTx:
 		unbondTx := tx.(*txs.UnbondTx)
-		unbondTx.Signature = acm.ChainSign(privAccounts[0], chainID, unbondTx)
+		unbondTx.Signature = acm.ChainSign(signingAccounts[0], chainID, unbondTx)
 	case *txs.RebondTx:
 		rebondTx := tx.(*txs.RebondTx)
-		rebondTx.Signature = acm.ChainSign(privAccounts[0], chainID, rebondTx)
+		rebondTx.Signature = acm.ChainSign(signingAccounts[0], chainID, rebondTx)
 	default:
 		return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx)
 	}
diff --git a/txs/README.md b/txs/README.md
deleted file mode 100644
index f99294b0..00000000
--- a/txs/README.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# `tendermint/block`
-
-## Block
-
-TODO: document
-
-### Header
-
-### Validation
-
-### Data
-
-## PartSet
-
-PartSet is used to split a byteslice of data into parts (pieces) for transmission.
-By splitting data into smaller parts and computing a Merkle root hash on the list,
-you can verify that a part is legitimately part of the complete data, and the
-part can be forwarded to other peers before all the parts are known.  In short,
-it's a fast way to propagate a large file over a gossip network.
-
-PartSet was inspired by the LibSwift project.
-
-Usage:
-
-```Go
-data := RandBytes(2 << 20) // Something large
-
-partSet := NewPartSetFromData(data)
-partSet.Total()     // Total number of 4KB parts
-partSet.Count()     // Equal to the Total, since we already have all the parts
-partSet.Hash()      // The Merkle root hash
-partSet.BitArray()  // A BitArray of partSet.Total() 1's
-
-header := partSet.Header() // Send this to the peer
-header.Total        // Total number of parts
-header.Hash         // The merkle root hash
-
-// Now we'll reconstruct the data from the parts
-partSet2 := NewPartSetFromHeader(header)
-partSet2.Total()    // Same total as partSet.Total()
-partSet2.Count()    // Zero, since this PartSet doesn't have any parts yet.
-partSet2.Hash()     // Same hash as in partSet.Hash()
-partSet2.BitArray() // A BitArray of partSet.Total() 0's
-
-// In a gossip network the parts would arrive in arbitrary order, perhaps
-// in response to explicit requests for parts, or optimistically in response
-// to the receiving peer's partSet.BitArray().
-for !partSet2.IsComplete() {
-    part := receivePartFromGossipNetwork()
-    added, err := partSet2.AddPart(part)
-    if err != nil {
-		// A wrong part,
-        // the merkle trail does not hash to partSet2.Hash()
-    } else if !added {
-        // A duplicate part already received
-    }
-}
-
-data2, _ := ioutil.ReadAll(partSet2.GetReader())
-bytes.Equal(data, data2) // true
-```
diff --git a/txs/bond_tx.go b/txs/bond_tx.go
new file mode 100644
index 00000000..cbb4bf8a
--- /dev/null
+++ b/txs/bond_tx.go
@@ -0,0 +1,105 @@
+package txs
+
+import (
+	"fmt"
+	"io"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/hyperledger/burrow/account/state"
+	"github.com/tendermint/go-wire"
+)
+
+type BondTx struct {
+	PubKey    acm.PublicKey
+	Signature acm.Signature
+	Inputs    []*TxInput
+	UnbondTo  []*TxOutput
+	txHashMemoizer
+}
+
+var _ Tx = &BondTx{}
+
+func NewBondTx(pubkey acm.PublicKey) (*BondTx, error) {
+	return &BondTx{
+		PubKey:   pubkey,
+		Inputs:   []*TxInput{},
+		UnbondTo: []*TxOutput{},
+	}, nil
+}
+
+func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
+	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
+	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err)
+	for i, in := range tx.Inputs {
+		in.WriteSignBytes(w, n, err)
+		if i != len(tx.Inputs)-1 {
+			wire.WriteTo([]byte(","), w, n, err)
+		}
+	}
+	wire.WriteTo([]byte(fmt.Sprintf(`],"pub_key":`)), w, n, err)
+	wire.WriteTo(wire.JSONBytes(tx.PubKey), w, n, err)
+	wire.WriteTo([]byte(`,"unbond_to":[`), w, n, err)
+	for i, out := range tx.UnbondTo {
+		out.WriteSignBytes(w, n, err)
+		if i != len(tx.UnbondTo)-1 {
+			wire.WriteTo([]byte(","), w, n, err)
+		}
+	}
+	wire.WriteTo([]byte(`]}]}`), w, n, err)
+}
+
+func (tx *BondTx) GetInputs() []TxInput {
+	return copyInputs(tx.Inputs)
+}
+
+func (tx *BondTx) String() string {
+	return fmt.Sprintf("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo)
+}
+
+func (tx *BondTx) Hash(chainID string) []byte {
+	return tx.txHashMemoizer.hash(chainID, tx)
+}
+
+func (tx *BondTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error {
+	addr := pubkey.Address()
+	acc, err := st.GetAccount(addr)
+	if err != nil {
+		return err
+	}
+	if acc == nil {
+		return fmt.Errorf("Invalid address %s from pubkey %s", addr, pubkey)
+	}
+	return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+uint64(1))
+}
+
+func (tx *BondTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error {
+	tx.Inputs = append(tx.Inputs, &TxInput{
+		Address:   pubkey.Address(),
+		Amount:    amt,
+		Sequence:  sequence,
+		PublicKey: pubkey,
+	})
+	return nil
+}
+
+func (tx *BondTx) AddOutput(addr acm.Address, amt uint64) error {
+	tx.UnbondTo = append(tx.UnbondTo, &TxOutput{
+		Address: addr,
+		Amount:  amt,
+	})
+	return nil
+}
+
+func (tx *BondTx) SignBond(chainID string, privAccount acm.SigningAccount) error {
+	tx.Signature = acm.ChainSign(privAccount, chainID, tx)
+	return nil
+}
+
+func (tx *BondTx) SignInput(chainID string, i int, privAccount acm.SigningAccount) error {
+	if i >= len(tx.Inputs) {
+		return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs))
+	}
+	tx.Inputs[i].PublicKey = privAccount.PublicKey()
+	tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx)
+	return nil
+}
diff --git a/txs/call_tx.go b/txs/call_tx.go
new file mode 100644
index 00000000..ff568aef
--- /dev/null
+++ b/txs/call_tx.go
@@ -0,0 +1,81 @@
+package txs
+
+import (
+	"fmt"
+	"io"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/hyperledger/burrow/account/state"
+	"github.com/tendermint/go-wire"
+)
+
+type CallTx struct {
+	Input *TxInput
+	// Pointer since CallTx defines unset 'to' address as inducing account creation
+	Address  *acm.Address
+	GasLimit uint64
+	Fee      uint64
+	Data     []byte
+	txHashMemoizer
+}
+
+var _ Tx = &CallTx{}
+
+func NewCallTx(st state.AccountGetter, from acm.PublicKey, to *acm.Address, data []byte,
+	amt, gasLimit, fee uint64) (*CallTx, error) {
+
+	addr := from.Address()
+	acc, err := st.GetAccount(addr)
+	if err != nil {
+		return nil, err
+	}
+	if acc == nil {
+		return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from)
+	}
+
+	sequence := acc.Sequence() + 1
+	return NewCallTxWithSequence(from, to, data, amt, gasLimit, fee, sequence), nil
+}
+
+func NewCallTxWithSequence(from acm.PublicKey, to *acm.Address, data []byte,
+	amt, gasLimit, fee, sequence uint64) *CallTx {
+	input := &TxInput{
+		Address:   from.Address(),
+		Amount:    amt,
+		Sequence:  sequence,
+		PublicKey: from,
+	}
+
+	return &CallTx{
+		Input:    input,
+		Address:  to,
+		GasLimit: gasLimit,
+		Fee:      fee,
+		Data:     data,
+	}
+}
+
+func (tx *CallTx) Sign(chainID string, privAccount acm.SigningAccount) {
+	tx.Input.PublicKey = privAccount.PublicKey()
+	tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx)
+}
+
+func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
+	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
+	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err)
+	wire.WriteTo([]byte(fmt.Sprintf(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err)
+	tx.Input.WriteSignBytes(w, n, err)
+	wire.WriteTo([]byte(`}]}`), w, n, err)
+}
+
+func (tx *CallTx) GetInputs() []TxInput {
+	return []TxInput{*tx.Input}
+}
+
+func (tx *CallTx) String() string {
+	return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data)
+}
+
+func (tx *CallTx) Hash(chainID string) []byte {
+	return tx.txHashMemoizer.hash(chainID, tx)
+}
diff --git a/txs/name_tx.go b/txs/name_tx.go
new file mode 100644
index 00000000..c00b6839
--- /dev/null
+++ b/txs/name_tx.go
@@ -0,0 +1,114 @@
+package txs
+
+import (
+	"fmt"
+	"io"
+
+	"regexp"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/hyperledger/burrow/account/state"
+	"github.com/tendermint/go-wire"
+)
+
+// Name should be file system lik
+// Data should be anything permitted in JSON
+var regexpAlphaNum = regexp.MustCompile("^[a-zA-Z0-9._/-@]*$")
+var regexpJSON = regexp.MustCompile(`^[a-zA-Z0-9_/ \-+"':,\n\t.{}()\[\]]*$`)
+
+type NameTx struct {
+	Input *TxInput
+	Name  string
+	Data  string
+	Fee   uint64
+	txHashMemoizer
+}
+
+var _ Tx = &NameTx{}
+
+func NewNameTx(st state.AccountGetter, from acm.PublicKey, name, data string, amt, fee uint64) (*NameTx, error) {
+	addr := from.Address()
+	acc, err := st.GetAccount(addr)
+	if err != nil {
+		return nil, err
+	}
+	if acc == nil {
+		return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from)
+	}
+
+	sequence := acc.Sequence() + 1
+	return NewNameTxWithSequence(from, name, data, amt, fee, sequence), nil
+}
+
+func NewNameTxWithSequence(from acm.PublicKey, name, data string, amt, fee, sequence uint64) *NameTx {
+	input := &TxInput{
+		Address:   from.Address(),
+		Amount:    amt,
+		Sequence:  sequence,
+		PublicKey: from,
+	}
+
+	return &NameTx{
+		Input: input,
+		Name:  name,
+		Data:  data,
+		Fee:   fee,
+	}
+}
+
+func (tx *NameTx) Sign(chainID string, signingAccount acm.SigningAccount) {
+	tx.Input.PublicKey = signingAccount.PublicKey()
+	tx.Input.Signature = acm.ChainSign(signingAccount, chainID, tx)
+}
+
+func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
+	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
+	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"data":%s,"fee":%v`, TxTypeName, jsonEscape(tx.Data), tx.Fee)), w, n, err)
+	wire.WriteTo([]byte(`,"input":`), w, n, err)
+	tx.Input.WriteSignBytes(w, n, err)
+	wire.WriteTo([]byte(fmt.Sprintf(`,"name":%s`, jsonEscape(tx.Name))), w, n, err)
+	wire.WriteTo([]byte(`}]}`), w, n, err)
+}
+
+func (tx *NameTx) GetInputs() []TxInput {
+	return []TxInput{*tx.Input}
+}
+
+func (tx *NameTx) ValidateStrings() error {
+	if len(tx.Name) == 0 {
+		return ErrTxInvalidString{"Name must not be empty"}
+	}
+	if len(tx.Name) > MaxNameLength {
+		return ErrTxInvalidString{fmt.Sprintf("Name is too long. Max %d bytes", MaxNameLength)}
+	}
+	if len(tx.Data) > MaxDataLength {
+		return ErrTxInvalidString{fmt.Sprintf("Data is too long. Max %d bytes", MaxDataLength)}
+	}
+
+	if !validateNameRegEntryName(tx.Name) {
+		return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Name (%s). Only alphanumeric, underscores, dashes, forward slashes, and @ are allowed", tx.Name)}
+	}
+
+	if !validateNameRegEntryData(tx.Data) {
+		return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Data (%s). Only the kind of things found in a JSON file are allowed", tx.Data)}
+	}
+
+	return nil
+}
+
+func (tx *NameTx) String() string {
+	return fmt.Sprintf("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data)
+}
+
+func (tx *NameTx) Hash(chainID string) []byte {
+	return tx.txHashMemoizer.hash(chainID, tx)
+}
+
+// filter strings
+func validateNameRegEntryName(name string) bool {
+	return regexpAlphaNum.Match([]byte(name))
+}
+
+func validateNameRegEntryData(data string) bool {
+	return regexpJSON.Match([]byte(data))
+}
diff --git a/txs/names.go b/txs/names.go
index 23c693f0..3b0bceff 100644
--- a/txs/names.go
+++ b/txs/names.go
@@ -14,10 +14,6 @@
 
 package txs
 
-import (
-	"regexp"
-)
-
 var (
 	MinNameRegistrationPeriod uint64 = 5
 
@@ -31,22 +27,8 @@ var (
 
 	MaxNameLength = 64
 	MaxDataLength = 1 << 16
-
-	// Name should be file system lik
-	// Data should be anything permitted in JSON
-	regexpAlphaNum = regexp.MustCompile("^[a-zA-Z0-9._/-@]*$")
-	regexpJSON     = regexp.MustCompile(`^[a-zA-Z0-9_/ \-+"':,\n\t.{}()\[\]]*$`)
 )
 
-// filter strings
-func validateNameRegEntryName(name string) bool {
-	return regexpAlphaNum.Match([]byte(name))
-}
-
-func validateNameRegEntryData(data string) bool {
-	return regexpJSON.Match([]byte(data))
-}
-
 // base cost is "effective" number of bytes
 func NameBaseCost(name, data string) uint64 {
 	return uint64(len(data) + 32)
diff --git a/txs/permission_tx.go b/txs/permission_tx.go
new file mode 100644
index 00000000..755dbfe3
--- /dev/null
+++ b/txs/permission_tx.go
@@ -0,0 +1,72 @@
+package txs
+
+import (
+	"fmt"
+	"io"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/hyperledger/burrow/account/state"
+	"github.com/hyperledger/burrow/permission/snatives"
+	"github.com/tendermint/go-wire"
+)
+
+type PermissionsTx struct {
+	Input    *TxInput
+	PermArgs snatives.PermArgs
+	txHashMemoizer
+}
+
+var _ Tx = &PermissionsTx{}
+
+func NewPermissionsTx(st state.AccountGetter, from acm.PublicKey, args snatives.PermArgs) (*PermissionsTx, error) {
+	addr := from.Address()
+	acc, err := st.GetAccount(addr)
+	if err != nil {
+		return nil, err
+	}
+	if acc == nil {
+		return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from)
+	}
+
+	sequence := acc.Sequence() + 1
+	return NewPermissionsTxWithSequence(from, args, sequence), nil
+}
+
+func NewPermissionsTxWithSequence(from acm.PublicKey, args snatives.PermArgs, sequence uint64) *PermissionsTx {
+	input := &TxInput{
+		Address:   from.Address(),
+		Amount:    1, // NOTE: amounts can't be 0 ...
+		Sequence:  sequence,
+		PublicKey: from,
+	}
+
+	return &PermissionsTx{
+		Input:    input,
+		PermArgs: args,
+	}
+}
+
+func (tx *PermissionsTx) Sign(chainID string, privAccount acm.SigningAccount) {
+	tx.Input.PublicKey = privAccount.PublicKey()
+	tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx)
+}
+func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
+	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
+	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err)
+	wire.WriteJSON(&tx.PermArgs, w, n, err)
+	wire.WriteTo([]byte(`","input":`), w, n, err)
+	tx.Input.WriteSignBytes(w, n, err)
+	wire.WriteTo([]byte(`}]}`), w, n, err)
+}
+
+func (tx *PermissionsTx) GetInputs() []TxInput {
+	return []TxInput{*tx.Input}
+}
+
+func (tx *PermissionsTx) String() string {
+	return fmt.Sprintf("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs)
+}
+
+func (tx *PermissionsTx) Hash(chainID string) []byte {
+	return tx.txHashMemoizer.hash(chainID, tx)
+}
diff --git a/txs/rebond_tx.go b/txs/rebond_tx.go
new file mode 100644
index 00000000..ec13498b
--- /dev/null
+++ b/txs/rebond_tx.go
@@ -0,0 +1,46 @@
+package txs
+
+import (
+	"fmt"
+	"io"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/tendermint/go-wire"
+)
+
+type RebondTx struct {
+	Address   acm.Address
+	Height    int
+	Signature acm.Signature
+	txHashMemoizer
+}
+
+var _ Tx = &RebondTx{}
+
+func NewRebondTx(addr acm.Address, height int) *RebondTx {
+	return &RebondTx{
+		Address: addr,
+		Height:  height,
+	}
+}
+
+func (tx *RebondTx) Sign(chainID string, privAccount acm.SigningAccount) {
+	tx.Signature = acm.ChainSign(privAccount, chainID, tx)
+}
+
+func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
+	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
+	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err)
+}
+
+func (tx *RebondTx) GetInputs() []TxInput {
+	return nil
+}
+
+func (tx *RebondTx) String() string {
+	return fmt.Sprintf("RebondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature)
+}
+
+func (tx *RebondTx) Hash(chainID string) []byte {
+	return tx.txHashMemoizer.hash(chainID, tx)
+}
diff --git a/txs/send_tx.go b/txs/send_tx.go
new file mode 100644
index 00000000..06c651a3
--- /dev/null
+++ b/txs/send_tx.go
@@ -0,0 +1,96 @@
+package txs
+
+import (
+	"fmt"
+	"io"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/hyperledger/burrow/account/state"
+	"github.com/tendermint/go-wire"
+)
+
+type SendTx struct {
+	Inputs  []*TxInput
+	Outputs []*TxOutput
+	txHashMemoizer
+}
+
+var _ Tx = &SendTx{}
+
+func NewSendTx() *SendTx {
+	return &SendTx{
+		Inputs:  []*TxInput{},
+		Outputs: []*TxOutput{},
+	}
+}
+
+func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
+	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
+	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err)
+	for i, in := range tx.Inputs {
+		in.WriteSignBytes(w, n, err)
+		if i != len(tx.Inputs)-1 {
+			wire.WriteTo([]byte(","), w, n, err)
+		}
+	}
+	wire.WriteTo([]byte(`],"outputs":[`), w, n, err)
+	for i, out := range tx.Outputs {
+		out.WriteSignBytes(w, n, err)
+		if i != len(tx.Outputs)-1 {
+			wire.WriteTo([]byte(","), w, n, err)
+		}
+	}
+	wire.WriteTo([]byte(`]}]}`), w, n, err)
+}
+
+func (tx *SendTx) GetInputs() []TxInput {
+	return copyInputs(tx.Inputs)
+}
+
+func (tx *SendTx) String() string {
+	return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs)
+}
+
+func (tx *SendTx) Hash(chainID string) []byte {
+	return tx.txHashMemoizer.hash(chainID, tx)
+}
+
+func (tx *SendTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error {
+	addr := pubkey.Address()
+	acc, err := st.GetAccount(addr)
+	if err != nil {
+		return err
+	}
+	if acc == nil {
+		return fmt.Errorf("invalid address %s from pubkey %s", addr, pubkey)
+	}
+	return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+1)
+}
+
+func (tx *SendTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error {
+	addr := pubkey.Address()
+	tx.Inputs = append(tx.Inputs, &TxInput{
+		Address:   addr,
+		Amount:    amt,
+		Sequence:  sequence,
+		PublicKey: pubkey,
+	})
+	return nil
+}
+
+func (tx *SendTx) AddOutput(addr acm.Address, amt uint64) error {
+	tx.Outputs = append(tx.Outputs, &TxOutput{
+		Address: addr,
+		Amount:  amt,
+	})
+	return nil
+}
+
+func (tx *SendTx) SignInput(chainID string, i int, privAccount acm.SigningAccount) error {
+	if i >= len(tx.Inputs) {
+		return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs))
+	}
+	tx.Inputs[i].PublicKey = privAccount.PublicKey()
+	tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx)
+	return nil
+}
diff --git a/txs/tx.go b/txs/tx.go
index e79c1e45..bfcf3ac1 100644
--- a/txs/tx.go
+++ b/txs/tx.go
@@ -21,8 +21,6 @@ import (
 	"io"
 
 	acm "github.com/hyperledger/burrow/account"
-	"github.com/hyperledger/burrow/permission/snatives"
-	"github.com/tendermint/go-wire"
 	"github.com/tendermint/go-wire/data"
 	"golang.org/x/crypto/ripemd160"
 )
@@ -37,23 +35,6 @@ var (
 	ErrTxInvalidSignature  = errors.New("error invalid signature")
 )
 
-type ErrTxInvalidString struct {
-	Msg string
-}
-
-func (e ErrTxInvalidString) Error() string {
-	return e.Msg
-}
-
-type ErrTxInvalidSequence struct {
-	Got      uint64
-	Expected uint64
-}
-
-func (e ErrTxInvalidSequence) Error() string {
-	return fmt.Sprintf("Error invalid sequence. Got %d, expected %d", e.Got, e.Expected)
-}
-
 /*
 Tx (Transaction) is an atomic operation on the ledger state.
 
@@ -95,104 +76,34 @@ var mapper = data.NewMapper(Wrapper{}).
 	RegisterImplementation(&RebondTx{}, "rebond_tx", TxTypeRebond).
 	RegisterImplementation(&PermissionsTx{}, "permissions_tx", TxTypePermissions)
 
-//-----------------------------------------------------------------------------
-
-type (
-	// TODO: replace with sum-type struct like ResultEvent
-	Tx interface {
-		WriteSignBytes(chainID string, w io.Writer, n *int, err *error)
-		String() string
-		GetInputs() []TxInput
-		Hash(chainID string) []byte
-	}
-
-	Wrapper struct {
-		Tx `json:"unwrap"`
-	}
+	//-----------------------------------------------------------------------------
 
-	Encoder interface {
-		EncodeTx(tx Tx) ([]byte, error)
-	}
-
-	Decoder interface {
-		DecodeTx(txBytes []byte) (Tx, error)
-	}
-
-	TxInput struct {
-		Address   acm.Address
-		Amount    uint64
-		Sequence  uint64
-		Signature acm.Signature
-		PublicKey acm.PublicKey
-	}
-
-	TxOutput struct {
-		Address acm.Address
-		Amount  uint64
-	}
-
-	// BroadcastTx or Transact
-	Receipt struct {
-		TxHash          []byte
-		CreatesContract bool
-		ContractAddress acm.Address
-	}
-
-	//-------------------
-	// Transaction Types
-	SendTx struct {
-		Inputs  []*TxInput
-		Outputs []*TxOutput
-		txHashMemoizer
-	}
-
-	NameTx struct {
-		Input *TxInput
-		Name  string
-		Data  string
-		Fee   uint64
-		txHashMemoizer
-	}
-
-	CallTx struct {
-		Input *TxInput
-		// Pointer since CallTx defines unset 'to' address as inducing account creation
-		Address  *acm.Address
-		GasLimit uint64
-		Fee      uint64
-		Data     []byte
-		txHashMemoizer
-	}
+// TODO: replace with sum-type struct like ResultEvent
+type Tx interface {
+	WriteSignBytes(chainID string, w io.Writer, n *int, err *error)
+	String() string
+	GetInputs() []TxInput
+	Hash(chainID string) []byte
+}
 
-	PermissionsTx struct {
-		Input    *TxInput
-		PermArgs snatives.PermArgs
-		txHashMemoizer
-	}
+type Encoder interface {
+	EncodeTx(tx Tx) ([]byte, error)
+}
 
-	// Out of service
-	BondTx struct {
-		PubKey    acm.PublicKey
-		Signature acm.Signature
-		Inputs    []*TxInput
-		UnbondTo  []*TxOutput
-		txHashMemoizer
-	}
+type Decoder interface {
+	DecodeTx(txBytes []byte) (Tx, error)
+}
 
-	UnbondTx struct {
-		Address   acm.Address
-		Height    int
-		Signature acm.Signature
-		txHashMemoizer
-	}
+// BroadcastTx or Transact
+type Receipt struct {
+	TxHash          []byte
+	CreatesContract bool
+	ContractAddress acm.Address
+}
 
-	RebondTx struct {
-		Address   acm.Address
-		Height    int
-		Signature acm.Signature
-		txHashMemoizer
-	}
-)
+type Wrapper struct {
+	Tx `json:"unwrap"`
+}
 
 // Wrap the Tx in a struct that allows for go-wire JSON serialisation
 func Wrap(tx Tx) Wrapper {
@@ -226,242 +137,6 @@ func (txw *Wrapper) Unwrap() Tx {
 	return txw.Tx
 }
 
-func (txIn *TxInput) ValidateBasic() error {
-	if len(txIn.Address) != 20 {
-		return ErrTxInvalidAddress
-	}
-	if txIn.Amount == 0 {
-		return ErrTxInvalidAmount
-	}
-	return nil
-}
-
-func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)), w, n, err)
-}
-
-func (txIn *TxInput) String() string {
-	return fmt.Sprintf("TxInput{%s,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PublicKey)
-}
-
-//-----------------------------------------------------------------------------
-
-func (txOut *TxOutput) ValidateBasic() error {
-	if len(txOut.Address) != 20 {
-		return ErrTxInvalidAddress
-	}
-	if txOut.Amount == 0 {
-		return ErrTxInvalidAmount
-	}
-	return nil
-}
-
-func (txOut *TxOutput) WriteSignBytes(w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v}`, txOut.Address, txOut.Amount)), w, n, err)
-}
-
-func (txOut *TxOutput) String() string {
-	return fmt.Sprintf("TxOutput{%s,%v}", txOut.Address, txOut.Amount)
-}
-
-//-----------------------------------------------------------------------------
-
-func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err)
-	for i, in := range tx.Inputs {
-		in.WriteSignBytes(w, n, err)
-		if i != len(tx.Inputs)-1 {
-			wire.WriteTo([]byte(","), w, n, err)
-		}
-	}
-	wire.WriteTo([]byte(`],"outputs":[`), w, n, err)
-	for i, out := range tx.Outputs {
-		out.WriteSignBytes(w, n, err)
-		if i != len(tx.Outputs)-1 {
-			wire.WriteTo([]byte(","), w, n, err)
-		}
-	}
-	wire.WriteTo([]byte(`]}]}`), w, n, err)
-}
-
-func (tx *SendTx) GetInputs() []TxInput {
-	return copyInputs(tx.Inputs)
-}
-
-func (tx *SendTx) String() string {
-	return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs)
-}
-
-func (tx *SendTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-
-//-----------------------------------------------------------------------------
-
-func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err)
-	tx.Input.WriteSignBytes(w, n, err)
-	wire.WriteTo([]byte(`}]}`), w, n, err)
-}
-
-func (tx *CallTx) GetInputs() []TxInput {
-	return []TxInput{*tx.Input}
-}
-
-func (tx *CallTx) String() string {
-	return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data)
-}
-
-func (tx *CallTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-
-//-----------------------------------------------------------------------------
-
-func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"data":%s,"fee":%v`, TxTypeName, jsonEscape(tx.Data), tx.Fee)), w, n, err)
-	wire.WriteTo([]byte(`,"input":`), w, n, err)
-	tx.Input.WriteSignBytes(w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"name":%s`, jsonEscape(tx.Name))), w, n, err)
-	wire.WriteTo([]byte(`}]}`), w, n, err)
-}
-
-func (tx *NameTx) GetInputs() []TxInput {
-	return []TxInput{*tx.Input}
-}
-
-func (tx *NameTx) ValidateStrings() error {
-	if len(tx.Name) == 0 {
-		return ErrTxInvalidString{"Name must not be empty"}
-	}
-	if len(tx.Name) > MaxNameLength {
-		return ErrTxInvalidString{fmt.Sprintf("Name is too long. Max %d bytes", MaxNameLength)}
-	}
-	if len(tx.Data) > MaxDataLength {
-		return ErrTxInvalidString{fmt.Sprintf("Data is too long. Max %d bytes", MaxDataLength)}
-	}
-
-	if !validateNameRegEntryName(tx.Name) {
-		return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Name (%s). Only alphanumeric, underscores, dashes, forward slashes, and @ are allowed", tx.Name)}
-	}
-
-	if !validateNameRegEntryData(tx.Data) {
-		return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Data (%s). Only the kind of things found in a JSON file are allowed", tx.Data)}
-	}
-
-	return nil
-}
-
-func (tx *NameTx) String() string {
-	return fmt.Sprintf("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data)
-}
-
-func (tx *NameTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-
-//-----------------------------------------------------------------------------
-
-func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err)
-	for i, in := range tx.Inputs {
-		in.WriteSignBytes(w, n, err)
-		if i != len(tx.Inputs)-1 {
-			wire.WriteTo([]byte(","), w, n, err)
-		}
-	}
-	wire.WriteTo([]byte(fmt.Sprintf(`],"pub_key":`)), w, n, err)
-	wire.WriteTo(wire.JSONBytes(tx.PubKey), w, n, err)
-	wire.WriteTo([]byte(`,"unbond_to":[`), w, n, err)
-	for i, out := range tx.UnbondTo {
-		out.WriteSignBytes(w, n, err)
-		if i != len(tx.UnbondTo)-1 {
-			wire.WriteTo([]byte(","), w, n, err)
-		}
-	}
-	wire.WriteTo([]byte(`]}]}`), w, n, err)
-}
-
-func (tx *BondTx) GetInputs() []TxInput {
-	return copyInputs(tx.Inputs)
-}
-
-func (tx *BondTx) String() string {
-	return fmt.Sprintf("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo)
-}
-
-func (tx *BondTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-
-//-----------------------------------------------------------------------------
-
-func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err)
-}
-
-func (tx *UnbondTx) GetInputs() []TxInput {
-	return nil
-}
-
-func (tx *UnbondTx) String() string {
-	return fmt.Sprintf("UnbondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature)
-}
-
-func (tx *UnbondTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-
-//-----------------------------------------------------------------------------
-
-func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err)
-}
-
-func (tx *RebondTx) GetInputs() []TxInput {
-	return nil
-}
-
-func (tx *RebondTx) String() string {
-	return fmt.Sprintf("RebondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature)
-}
-
-func (tx *RebondTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-
-//-----------------------------------------------------------------------------
-
-func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err)
-	wire.WriteJSON(&tx.PermArgs, w, n, err)
-	wire.WriteTo([]byte(`","input":`), w, n, err)
-	tx.Input.WriteSignBytes(w, n, err)
-	wire.WriteTo([]byte(`}]}`), w, n, err)
-}
-
-func (tx *PermissionsTx) GetInputs() []TxInput {
-	return []TxInput{*tx.Input}
-}
-
-func (tx *PermissionsTx) String() string {
-	return fmt.Sprintf("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs)
-}
-
-func (tx *PermissionsTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-
-//-----------------------------------------------------------------------------
-
 // Avoid re-hashing the same in-memory Tx
 type txHashMemoizer struct {
 	txHashBytes []byte
@@ -499,6 +174,23 @@ func GenerateReceipt(chainId string, tx Tx) Receipt {
 	return receipt
 }
 
+type ErrTxInvalidString struct {
+	Msg string
+}
+
+func (e ErrTxInvalidString) Error() string {
+	return e.Msg
+}
+
+type ErrTxInvalidSequence struct {
+	Got      uint64
+	Expected uint64
+}
+
+func (e ErrTxInvalidSequence) Error() string {
+	return fmt.Sprintf("Error invalid sequence. Got %d, expected %d", e.Got, e.Expected)
+}
+
 //--------------------------------------------------------------------------------
 
 func copyInputs(inputs []*TxInput) []TxInput {
diff --git a/txs/tx_input.go b/txs/tx_input.go
new file mode 100644
index 00000000..8ed9059d
--- /dev/null
+++ b/txs/tx_input.go
@@ -0,0 +1,35 @@
+package txs
+
+import (
+	"fmt"
+	"io"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/tendermint/go-wire"
+)
+
+type TxInput struct {
+	Address   acm.Address
+	Amount    uint64
+	Sequence  uint64
+	Signature acm.Signature
+	PublicKey acm.PublicKey
+}
+
+func (txIn *TxInput) ValidateBasic() error {
+	if len(txIn.Address) != 20 {
+		return ErrTxInvalidAddress
+	}
+	if txIn.Amount == 0 {
+		return ErrTxInvalidAmount
+	}
+	return nil
+}
+
+func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int, err *error) {
+	wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)), w, n, err)
+}
+
+func (txIn *TxInput) String() string {
+	return fmt.Sprintf("TxInput{%s,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PublicKey)
+}
diff --git a/txs/tx_output.go b/txs/tx_output.go
new file mode 100644
index 00000000..79c17b49
--- /dev/null
+++ b/txs/tx_output.go
@@ -0,0 +1,32 @@
+package txs
+
+import (
+	"fmt"
+	"io"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/tendermint/go-wire"
+)
+
+type TxOutput struct {
+	Address acm.Address
+	Amount  uint64
+}
+
+func (txOut *TxOutput) ValidateBasic() error {
+	if len(txOut.Address) != 20 {
+		return ErrTxInvalidAddress
+	}
+	if txOut.Amount == 0 {
+		return ErrTxInvalidAmount
+	}
+	return nil
+}
+
+func (txOut *TxOutput) WriteSignBytes(w io.Writer, n *int, err *error) {
+	wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v}`, txOut.Address, txOut.Amount)), w, n, err)
+}
+
+func (txOut *TxOutput) String() string {
+	return fmt.Sprintf("TxOutput{%s,%v}", txOut.Address, txOut.Amount)
+}
diff --git a/txs/tx_utils.go b/txs/tx_utils.go
deleted file mode 100644
index e1b8a8a4..00000000
--- a/txs/tx_utils.go
+++ /dev/null
@@ -1,272 +0,0 @@
-// 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 txs
-
-import (
-	"fmt"
-
-	acm "github.com/hyperledger/burrow/account"
-	"github.com/hyperledger/burrow/account/state"
-	"github.com/hyperledger/burrow/permission/snatives"
-)
-
-//----------------------------------------------------------------------------
-// SendTx interface for adding inputs/outputs and adding signatures
-
-func NewSendTx() *SendTx {
-	return &SendTx{
-		Inputs:  []*TxInput{},
-		Outputs: []*TxOutput{},
-	}
-}
-
-func (tx *SendTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error {
-	addr := pubkey.Address()
-	acc, err := st.GetAccount(addr)
-	if err != nil {
-		return err
-	}
-	if acc == nil {
-		return fmt.Errorf("invalid address %s from pubkey %s", addr, pubkey)
-	}
-	return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+1)
-}
-
-func (tx *SendTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error {
-	addr := pubkey.Address()
-	tx.Inputs = append(tx.Inputs, &TxInput{
-		Address:   addr,
-		Amount:    amt,
-		Sequence:  sequence,
-		PublicKey: pubkey,
-	})
-	return nil
-}
-
-func (tx *SendTx) AddOutput(addr acm.Address, amt uint64) error {
-	tx.Outputs = append(tx.Outputs, &TxOutput{
-		Address: addr,
-		Amount:  amt,
-	})
-	return nil
-}
-
-func (tx *SendTx) SignInput(chainID string, i int, privAccount acm.SigningAccount) error {
-	if i >= len(tx.Inputs) {
-		return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs))
-	}
-	tx.Inputs[i].PublicKey = privAccount.PublicKey()
-	tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx)
-	return nil
-}
-
-//----------------------------------------------------------------------------
-// CallTx interface for creating tx
-
-func NewCallTx(st state.AccountGetter, from acm.PublicKey, to *acm.Address, data []byte,
-	amt, gasLimit, fee uint64) (*CallTx, error) {
-
-	addr := from.Address()
-	acc, err := st.GetAccount(addr)
-	if err != nil {
-		return nil, err
-	}
-	if acc == nil {
-		return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from)
-	}
-
-	sequence := acc.Sequence() + 1
-	return NewCallTxWithSequence(from, to, data, amt, gasLimit, fee, sequence), nil
-}
-
-func NewCallTxWithSequence(from acm.PublicKey, to *acm.Address, data []byte,
-	amt, gasLimit, fee, sequence uint64) *CallTx {
-	input := &TxInput{
-		Address:   from.Address(),
-		Amount:    amt,
-		Sequence:  sequence,
-		PublicKey: from,
-	}
-
-	return &CallTx{
-		Input:    input,
-		Address:  to,
-		GasLimit: gasLimit,
-		Fee:      fee,
-		Data:     data,
-	}
-}
-
-func (tx *CallTx) Sign(chainID string, privAccount acm.SigningAccount) {
-	tx.Input.PublicKey = privAccount.PublicKey()
-	tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx)
-}
-
-//----------------------------------------------------------------------------
-// NameTx interface for creating tx
-
-func NewNameTx(st state.AccountGetter, from acm.PublicKey, name, data string, amt, fee uint64) (*NameTx, error) {
-	addr := from.Address()
-	acc, err := st.GetAccount(addr)
-	if err != nil {
-		return nil, err
-	}
-	if acc == nil {
-		return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from)
-	}
-
-	sequence := acc.Sequence() + 1
-	return NewNameTxWithSequence(from, name, data, amt, fee, sequence), nil
-}
-
-func NewNameTxWithSequence(from acm.PublicKey, name, data string, amt, fee, sequence uint64) *NameTx {
-	input := &TxInput{
-		Address:   from.Address(),
-		Amount:    amt,
-		Sequence:  sequence,
-		PublicKey: from,
-	}
-
-	return &NameTx{
-		Input: input,
-		Name:  name,
-		Data:  data,
-		Fee:   fee,
-	}
-}
-
-func (tx *NameTx) Sign(chainID string, privAccount acm.SigningAccount) {
-	tx.Input.PublicKey = privAccount.PublicKey()
-	tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx)
-}
-
-//----------------------------------------------------------------------------
-// BondTx interface for adding inputs/outputs and adding signatures
-
-func NewBondTx(pubkey acm.PublicKey) (*BondTx, error) {
-	return &BondTx{
-		PubKey:   pubkey,
-		Inputs:   []*TxInput{},
-		UnbondTo: []*TxOutput{},
-	}, nil
-}
-
-func (tx *BondTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error {
-	addr := pubkey.Address()
-	acc, err := st.GetAccount(addr)
-	if err != nil {
-		return err
-	}
-	if acc == nil {
-		return fmt.Errorf("Invalid address %s from pubkey %s", addr, pubkey)
-	}
-	return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+uint64(1))
-}
-
-func (tx *BondTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error {
-	tx.Inputs = append(tx.Inputs, &TxInput{
-		Address:   pubkey.Address(),
-		Amount:    amt,
-		Sequence:  sequence,
-		PublicKey: pubkey,
-	})
-	return nil
-}
-
-func (tx *BondTx) AddOutput(addr acm.Address, amt uint64) error {
-	tx.UnbondTo = append(tx.UnbondTo, &TxOutput{
-		Address: addr,
-		Amount:  amt,
-	})
-	return nil
-}
-
-func (tx *BondTx) SignBond(chainID string, privAccount acm.SigningAccount) error {
-	tx.Signature = acm.ChainSign(privAccount, chainID, tx)
-	return nil
-}
-
-func (tx *BondTx) SignInput(chainID string, i int, privAccount acm.SigningAccount) error {
-	if i >= len(tx.Inputs) {
-		return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs))
-	}
-	tx.Inputs[i].PublicKey = privAccount.PublicKey()
-	tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx)
-	return nil
-}
-
-//----------------------------------------------------------------------
-// UnbondTx interface for creating tx
-
-func NewUnbondTx(addr acm.Address, height int) *UnbondTx {
-	return &UnbondTx{
-		Address: addr,
-		Height:  height,
-	}
-}
-
-func (tx *UnbondTx) Sign(chainID string, privAccount acm.SigningAccount) {
-	tx.Signature = acm.ChainSign(privAccount, chainID, tx)
-}
-
-//----------------------------------------------------------------------
-// RebondTx interface for creating tx
-
-func NewRebondTx(addr acm.Address, height int) *RebondTx {
-	return &RebondTx{
-		Address: addr,
-		Height:  height,
-	}
-}
-
-func (tx *RebondTx) Sign(chainID string, privAccount acm.SigningAccount) {
-	tx.Signature = acm.ChainSign(privAccount, chainID, tx)
-}
-
-//----------------------------------------------------------------------------
-// PermissionsTx interface for creating tx
-
-func NewPermissionsTx(st state.AccountGetter, from acm.PublicKey, args snatives.PermArgs) (*PermissionsTx, error) {
-	addr := from.Address()
-	acc, err := st.GetAccount(addr)
-	if err != nil {
-		return nil, err
-	}
-	if acc == nil {
-		return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from)
-	}
-
-	sequence := acc.Sequence() + 1
-	return NewPermissionsTxWithSequence(from, args, sequence), nil
-}
-
-func NewPermissionsTxWithSequence(from acm.PublicKey, args snatives.PermArgs, sequence uint64) *PermissionsTx {
-	input := &TxInput{
-		Address:   from.Address(),
-		Amount:    1, // NOTE: amounts can't be 0 ...
-		Sequence:  sequence,
-		PublicKey: from,
-	}
-
-	return &PermissionsTx{
-		Input:    input,
-		PermArgs: args,
-	}
-}
-
-func (tx *PermissionsTx) Sign(chainID string, privAccount acm.SigningAccount) {
-	tx.Input.PublicKey = privAccount.PublicKey()
-	tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx)
-}
diff --git a/txs/unbond_tx.go b/txs/unbond_tx.go
new file mode 100644
index 00000000..2411eaf9
--- /dev/null
+++ b/txs/unbond_tx.go
@@ -0,0 +1,46 @@
+package txs
+
+import (
+	"fmt"
+	"io"
+
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/tendermint/go-wire"
+)
+
+type UnbondTx struct {
+	Address   acm.Address
+	Height    int
+	Signature acm.Signature
+	txHashMemoizer
+}
+
+var _ Tx = &UnbondTx{}
+
+func NewUnbondTx(addr acm.Address, height int) *UnbondTx {
+	return &UnbondTx{
+		Address: addr,
+		Height:  height,
+	}
+}
+
+func (tx *UnbondTx) Sign(chainID string, privAccount acm.SigningAccount) {
+	tx.Signature = acm.ChainSign(privAccount, chainID, tx)
+}
+
+func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
+	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
+	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err)
+}
+
+func (tx *UnbondTx) GetInputs() []TxInput {
+	return nil
+}
+
+func (tx *UnbondTx) String() string {
+	return fmt.Sprintf("UnbondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature)
+}
+
+func (tx *UnbondTx) Hash(chainID string) []byte {
+	return tx.txHashMemoizer.hash(chainID, tx)
+}
-- 
GitLab