Repository: kaspanet/kaspad
Pull request #1951 (2022-02-20 10:56:15)

Add support for auto-compound in `kaspawallet send`
This PR fixes a big problem in kaspawallet - when the number of inputs is greater then one can fit into a single transaction (such as often happens when a big miner wants to send a lot of kaspa) - the transaction generated is rejected by the node.

This PR splits such transaction into multiple smaller transactions, sending the funds into a change address, then adds another, merge transaction, which would send the funds to their final destination.

Note: there are a quite a few redundant serialize/deserialize of PartiallySignedTransaction(s)
This is there because libkaspawallet exposes methods that work with byte-slices rather then PartiallySignedTransaction(s)
I plan to refactor libkaspawallet to expose only methods for deserialized data in an upcoming PR.

codecov[bot]: # [Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/1951?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) Report
> Merging [#1951](https://codecov.io/gh/kaspanet/kaspad/pull/1951?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) (e4c453c) into [dev](https://codecov.io/gh/kaspanet/kaspad/commit/6449b030348ad0c6492c439ef2308fdf97fc5245?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) (6449b03) will **decrease** coverage by `0.26%`.
> The diff coverage is `34.48%`.

[![Impacted file tree graph](https://codecov.io/gh/kaspanet/kaspad/pull/1951/graphs/tree.svg?width=650&height=150&src=pr&token=2FBIEEJTNU&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet)](https://codecov.io/gh/kaspanet/kaspad/pull/1951?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet)

```diff
@@            Coverage Diff             @@
##              dev    #1951      +/-   ##
==========================================
- Coverage   61.02%   60.75%   -0.27%     
==========================================
  Files         644      646       +2     
  Lines       30245    30440     +195     
==========================================
+ Hits        18457    18495      +38     
- Misses       9177     9330     +153     
- Partials     2611     2615       +4     
```


| [Impacted Files](https://codecov.io/gh/kaspanet/kaspad/pull/1951?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) | Coverage Δ | |
|---|---|---|
| [cmd/kaspawallet/create\_unsigned\_tx.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-Y21kL2thc3Bhd2FsbGV0L2NyZWF0ZV91bnNpZ25lZF90eC5nbw==) | `0.00% <0.00%> (ø)` | |
| [cmd/kaspawallet/daemon/server/address.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-Y21kL2thc3Bhd2FsbGV0L2RhZW1vbi9zZXJ2ZXIvYWRkcmVzcy5nbw==) | `0.00% <0.00%> (ø)` | |
| [...allet/daemon/server/create\_unsigned\_transaction.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-Y21kL2thc3Bhd2FsbGV0L2RhZW1vbi9zZXJ2ZXIvY3JlYXRlX3Vuc2lnbmVkX3RyYW5zYWN0aW9uLmdv) | `0.00% <0.00%> (ø)` | |
| [cmd/kaspawallet/daemon/server/server.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-Y21kL2thc3Bhd2FsbGV0L2RhZW1vbi9zZXJ2ZXIvc2VydmVyLmdv) | `0.00% <0.00%> (ø)` | |
| [cmd/kaspawallet/libkaspawallet/sign.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-Y21kL2thc3Bhd2FsbGV0L2xpYmthc3Bhd2FsbGV0L3NpZ24uZ28=) | `65.38% <ø> (ø)` | |
| [cmd/kaspawallet/send.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-Y21kL2thc3Bhd2FsbGV0L3NlbmQuZ28=) | `0.00% <0.00%> (ø)` | |
| [domain/consensus/consensus.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-ZG9tYWluL2NvbnNlbnN1cy9jb25zZW5zdXMuZ28=) | `69.26% <ø> (ø)` | |
| [domain/dagconfig/params.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-ZG9tYWluL2RhZ2NvbmZpZy9wYXJhbXMuZ28=) | `89.47% <ø> (ø)` | |
| [...ger/mempool/fill\_inputs\_and\_get\_missing\_parents.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-ZG9tYWluL21pbmluZ21hbmFnZXIvbWVtcG9vbC9maWxsX2lucHV0c19hbmRfZ2V0X21pc3NpbmdfcGFyZW50cy5nbw==) | `73.91% <0.00%> (ø)` | |
| [infrastructure/config/config.go](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-aW5mcmFzdHJ1Y3R1cmUvY29uZmlnL2NvbmZpZy5nbw==) | `12.45% <ø> (ø)` | |
| ... and [12 more](https://codecov.io/gh/kaspanet/kaspad/pull/1951/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/1951?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/1951?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet). Last update [6449b03...e4c453c](https://codecov.io/gh/kaspanet/kaspad/pull/1951?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet).


Patch:
diff --git a/cmd/kaspawallet/broadcast.go b/cmd/kaspawallet/broadcast.go
index 9b4c2e2d..a6f84e7f 100644
--- a/cmd/kaspawallet/broadcast.go
+++ b/cmd/kaspawallet/broadcast.go
@@ -2,13 +2,13 @@ package main
 
 import (
 	"context"
-	"encoding/hex"
 	"fmt"
+	"io/ioutil"
+	"strings"
+
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
 	"github.com/pkg/errors"
-	"io/ioutil"
-	"strings"
 )
 
 func broadcast(conf *broadcastConfig) error {
@@ -21,34 +21,40 @@ func broadcast(conf *broadcastConfig) error {
 	ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
 	defer cancel()
 
-	if conf.Transaction == "" && conf.TransactionFile == "" {
+	if conf.Transactions == "" && conf.TransactionsFile == "" {
 		return errors.Errorf("Either --transaction or --transaction-file is required")
 	}
-	if conf.Transaction != "" && conf.TransactionFile != "" {
+	if conf.Transactions != "" && conf.TransactionsFile != "" {
 		return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time")
 	}
 
-	transactionHex := conf.Transaction
-	if conf.TransactionFile != "" {
-		transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile)
+	transactionsHex := conf.Transactions
+	if conf.TransactionsFile != "" {
+		transactionHexBytes, err := ioutil.ReadFile(conf.TransactionsFile)
 		if err != nil {
-			return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionFile)
+			return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionsFile)
 		}
-		transactionHex = strings.TrimSpace(string(transactionHexBytes))
+		transactionsHex = strings.TrimSpace(string(transactionHexBytes))
 	}
 
-	transaction, err := hex.DecodeString(transactionHex)
+	transactions, err := decodeTransactionsFromHex(transactionsHex)
 	if err != nil {
 		return err
 	}
 
-	response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transaction: transaction})
-	if err != nil {
-		return err
+	transactionsCount := len(transactions)
+	for i, transaction := range transactions {
+		response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transaction: transaction})
+		if err != nil {
+			return err
+		}
+		if transactionsCount == 1 {
+			fmt.Println("Transaction was sent successfully")
+		} else {
+			fmt.Printf("Transaction %d (out of %d) was sent successfully\n", i+1, transactionsCount)
+		}
+		fmt.Printf("Transaction ID: \t%s\n", response.TxID)
 	}
 
-	fmt.Println("Transaction was sent successfully")
-	fmt.Printf("Transaction ID: \t%s\n", response.TxID)
-
 	return nil
 }
diff --git a/cmd/kaspawallet/config.go b/cmd/kaspawallet/config.go
index b02a9446..2ef71966 100644
--- a/cmd/kaspawallet/config.go
+++ b/cmd/kaspawallet/config.go
@@ -1,9 +1,10 @@
 package main
 
 import (
+	"os"
+
 	"github.com/kaspanet/kaspad/infrastructure/config"
 	"github.com/pkg/errors"
-	"os"
 
 	"github.com/jessevdk/go-flags"
 )
@@ -68,15 +69,15 @@ type createUnsignedTransactionConfig struct {
 type signConfig struct {
 	KeysFile        string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
 	Password        string `long:"password" short:"p" description:"Wallet password"`
-	Transaction     string `long:"transaction" short:"t" description:"The unsigned transaction to sign on (encoded in hex)"`
-	TransactionFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction to sign on (encoded in hex)"`
+	Transaction     string `long:"transaction" short:"t" description:"The unsigned transaction(s) to sign on (encoded in hex)"`
+	TransactionFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction(s) to sign on (encoded in hex)"`
 	config.NetworkFlags
 }
 
 type broadcastConfig struct {
-	DaemonAddress   string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
-	Transaction     string `long:"transaction" short:"t" description:"The signed transaction to broadcast (encoded in hex)"`
-	TransactionFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction to sign on (encoded in hex)"`
+	DaemonAddress    string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
+	Transactions     string `long:"transaction" short:"t" description:"The signed transaction to broadcast (encoded in hex)"`
+	TransactionsFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction to sign on (encoded in hex)"`
 	config.NetworkFlags
 }
 
diff --git a/cmd/kaspawallet/create_unsigned_tx.go b/cmd/kaspawallet/create_unsigned_tx.go
index 22eac3ec..5d674932 100644
--- a/cmd/kaspawallet/create_unsigned_tx.go
+++ b/cmd/kaspawallet/create_unsigned_tx.go
@@ -2,8 +2,8 @@ package main
 
 import (
 	"context"
-	"encoding/hex"
 	"fmt"
+
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
 	"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
@@ -20,7 +20,7 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
 	defer cancel()
 
 	sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
-	response, err := daemonClient.CreateUnsignedTransaction(ctx, &pb.CreateUnsignedTransactionRequest{
+	response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
 		Address: conf.ToAddress,
 		Amount:  sendAmountSompi,
 	})
@@ -29,6 +29,6 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
 	}
 
 	fmt.Println("Created unsigned transaction")
-	fmt.Println(hex.EncodeToString(response.UnsignedTransaction))
+	fmt.Println(encodeTransactionsToHex(response.UnsignedTransactions))
 	return nil
 }
diff --git a/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go b/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go
index a72f259e..c43b947a 100644
--- a/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go
+++ b/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go
@@ -1,6 +1,6 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.27.1
+// 	protoc-gen-go v1.26.0
 // 	protoc        v3.12.3
 // source: kaspawalletd.proto
 
@@ -184,7 +184,7 @@ func (x *AddressBalances) GetPending() uint64 {
 	return 0
 }
 
-type CreateUnsignedTransactionRequest struct {
+type CreateUnsignedTransactionsRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
@@ -193,8 +193,8 @@ type CreateUnsignedTransactionRequest struct {
 	Amount  uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
 }
 
-func (x *CreateUnsignedTransactionRequest) Reset() {
-	*x = CreateUnsignedTransactionRequest{}
+func (x *CreateUnsignedTransactionsRequest) Reset() {
+	*x = CreateUnsignedTransactionsRequest{}
 	if protoimpl.UnsafeEnabled {
 		mi := &file_kaspawalletd_proto_msgTypes[3]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -202,13 +202,13 @@ func (x *CreateUnsignedTransactionRequest) Reset() {
 	}
 }
 
-func (x *CreateUnsignedTransactionRequest) String() string {
+func (x *CreateUnsignedTransactionsRequest) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*CreateUnsignedTransactionRequest) ProtoMessage() {}
+func (*CreateUnsignedTransactionsRequest) ProtoMessage() {}
 
-func (x *CreateUnsignedTransactionRequest) ProtoReflect() protoreflect.Message {
+func (x *CreateUnsignedTransactionsRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_kaspawalletd_proto_msgTypes[3]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -220,35 +220,35 @@ func (x *CreateUnsignedTransactionRequest) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use CreateUnsignedTransactionRequest.ProtoReflect.Descriptor instead.
-func (*CreateUnsignedTransactionRequest) Descriptor() ([]byte, []int) {
+// Deprecated: Use CreateUnsignedTransactionsRequest.ProtoReflect.Descriptor instead.
+func (*CreateUnsignedTransactionsRequest) Descriptor() ([]byte, []int) {
 	return file_kaspawalletd_proto_rawDescGZIP(), []int{3}
 }
 
-func (x *CreateUnsignedTransactionRequest) GetAddress() string {
+func (x *CreateUnsignedTransactionsRequest) GetAddress() string {
 	if x != nil {
 		return x.Address
 	}
 	return ""
 }
 
-func (x *CreateUnsignedTransactionRequest) GetAmount() uint64 {
+func (x *CreateUnsignedTransactionsRequest) GetAmount() uint64 {
 	if x != nil {
 		return x.Amount
 	}
 	return 0
 }
 
-type CreateUnsignedTransactionResponse struct {
+type CreateUnsignedTransactionsResponse struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	UnsignedTransaction []byte `protobuf:"bytes,1,opt,name=unsignedTransaction,proto3" json:"unsignedTransaction,omitempty"`
+	UnsignedTransactions [][]byte `protobuf:"bytes,1,rep,name=unsignedTransactions,proto3" json:"unsignedTransactions,omitempty"`
 }
 
-func (x *CreateUnsignedTransactionResponse) Reset() {
-	*x = CreateUnsignedTransactionResponse{}
+func (x *CreateUnsignedTransactionsResponse) Reset() {
+	*x = CreateUnsignedTransactionsResponse{}
 	if protoimpl.UnsafeEnabled {
 		mi := &file_kaspawalletd_proto_msgTypes[4]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -256,13 +256,13 @@ func (x *CreateUnsignedTransactionResponse) Reset() {
 	}
 }
 
-func (x *CreateUnsignedTransactionResponse) String() string {
+func (x *CreateUnsignedTransactionsResponse) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*CreateUnsignedTransactionResponse) ProtoMessage() {}
+func (*CreateUnsignedTransactionsResponse) ProtoMessage() {}
 
-func (x *CreateUnsignedTransactionResponse) ProtoReflect() protoreflect.Message {
+func (x *CreateUnsignedTransactionsResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_kaspawalletd_proto_msgTypes[4]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -274,14 +274,14 @@ func (x *CreateUnsignedTransactionResponse) ProtoReflect() protoreflect.Message
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use CreateUnsignedTransactionResponse.ProtoReflect.Descriptor instead.
-func (*CreateUnsignedTransactionResponse) Descriptor() ([]byte, []int) {
+// Deprecated: Use CreateUnsignedTransactionsResponse.ProtoReflect.Descriptor instead.
+func (*CreateUnsignedTransactionsResponse) Descriptor() ([]byte, []int) {
 	return file_kaspawalletd_proto_rawDescGZIP(), []int{4}
 }
 
-func (x *CreateUnsignedTransactionResponse) GetUnsignedTransaction() []byte {
+func (x *CreateUnsignedTransactionsResponse) GetUnsignedTransactions() [][]byte {
 	if x != nil {
-		return x.UnsignedTransaction
+		return x.UnsignedTransactions
 	}
 	return nil
 }
@@ -646,64 +646,65 @@ var file_kaspawalletd_proto_rawDesc = []byte{
 	0x73, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02,
 	0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12,
 	0x18, 0x0a, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04,
-	0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x54, 0x0a, 0x20, 0x43, 0x72, 0x65,
+	0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x55, 0x0a, 0x21, 0x43, 0x72, 0x65,
 	0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73,
-	0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a,
-	0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
-	0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e,
-	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22,
-	0x55, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65,
-	0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64,
-	0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x0c, 0x52, 0x13, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73,
-	0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64,
-	0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x31,
-	0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
-	0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
-	0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64,
-	0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07,
-	0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
-	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x34, 0x0a, 0x10, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63,
-	0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x72,
-	0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
-	0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x27, 0x0a, 0x11,
-	0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x04, 0x74, 0x78, 0x49, 0x44, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77,
-	0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74,
-	0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x91, 0x03, 0x0a,
-	0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x37, 0x0a,
-	0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x47, 0x65,
-	0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x13, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x64, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
-	0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
-	0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69,
-	0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55,
-	0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
-	0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d,
-	0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x15, 0x2e,
-	0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65,
-	0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37,
-	0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x2e, 0x4e,
-	0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x1a, 0x13, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64,
-	0x6f, 0x77, 0x6e, 0x12, 0x10, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x09, 0x42, 0x72,
-	0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63,
-	0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x42, 0x72, 0x6f,
-	0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
-	0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b,
-	0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63,
-	0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x64,
-	0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18,
+	0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75,
+	0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
+	0x22, 0x58, 0x0a, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e,
+	0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e,
+	0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01,
+	0x20, 0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72,
+	0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68,
+	0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x22, 0x31, 0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
+	0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61,
+	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64,
+	0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72,
+	0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65,
+	0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x34, 0x0a, 0x10, 0x42, 0x72,
+	0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20,
+	0x0a, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0c, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+	0x22, 0x27, 0x0a, 0x11, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x49, 0x44, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x49, 0x44, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75,
+	0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10,
+	0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x32, 0x94, 0x03, 0x0a, 0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
+	0x64, 0x12, 0x37, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12,
+	0x12, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x67, 0x0a, 0x1a, 0x43, 0x72,
+	0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e,
+	0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
+	0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x43,
+	0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61,
+	0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65,
+	0x73, 0x73, 0x65, 0x73, 0x12, 0x15, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65,
+	0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x53, 0x68,
+	0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72,
+	0x65, 0x73, 0x73, 0x12, 0x12, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64,
+	0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31,
+	0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x10, 0x2e, 0x53, 0x68, 0x75,
+	0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x53,
+	0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
+	0x00, 0x12, 0x34, 0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x11,
+	0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x12, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b,
+	0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77,
+	0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62,
+	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -720,30 +721,30 @@ func file_kaspawalletd_proto_rawDescGZIP() []byte {
 
 var file_kaspawalletd_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
 var file_kaspawalletd_proto_goTypes = []interface{}{
-	(*GetBalanceRequest)(nil),                 // 0: GetBalanceRequest
-	(*GetBalanceResponse)(nil),                // 1: GetBalanceResponse
-	(*AddressBalances)(nil),                   // 2: AddressBalances
-	(*CreateUnsignedTransactionRequest)(nil),  // 3: CreateUnsignedTransactionRequest
-	(*CreateUnsignedTransactionResponse)(nil), // 4: CreateUnsignedTransactionResponse
-	(*ShowAddressesRequest)(nil),              // 5: ShowAddressesRequest
-	(*ShowAddressesResponse)(nil),             // 6: ShowAddressesResponse
-	(*NewAddressRequest)(nil),                 // 7: NewAddressRequest
-	(*NewAddressResponse)(nil),                // 8: NewAddressResponse
-	(*BroadcastRequest)(nil),                  // 9: BroadcastRequest
-	(*BroadcastResponse)(nil),                 // 10: BroadcastResponse
-	(*ShutdownRequest)(nil),                   // 11: ShutdownRequest
-	(*ShutdownResponse)(nil),                  // 12: ShutdownResponse
+	(*GetBalanceRequest)(nil),                  // 0: GetBalanceRequest
+	(*GetBalanceResponse)(nil),                 // 1: GetBalanceResponse
+	(*AddressBalances)(nil),                    // 2: AddressBalances
+	(*CreateUnsignedTransactionsRequest)(nil),  // 3: CreateUnsignedTransactionsRequest
+	(*CreateUnsignedTransactionsResponse)(nil), // 4: CreateUnsignedTransactionsResponse
+	(*ShowAddressesRequest)(nil),               // 5: ShowAddressesRequest
+	(*ShowAddressesResponse)(nil),              // 6: ShowAddressesResponse
+	(*NewAddressRequest)(nil),                  // 7: NewAddressRequest
+	(*NewAddressResponse)(nil),                 // 8: NewAddressResponse
+	(*BroadcastRequest)(nil),                   // 9: BroadcastRequest
+	(*BroadcastResponse)(nil),                  // 10: BroadcastResponse
+	(*ShutdownRequest)(nil),                    // 11: ShutdownRequest
+	(*ShutdownResponse)(nil),                   // 12: ShutdownResponse
 }
 var file_kaspawalletd_proto_depIdxs = []int32{
 	2,  // 0: GetBalanceResponse.addressBalances:type_name -> AddressBalances
 	0,  // 1: kaspawalletd.GetBalance:input_type -> GetBalanceRequest
-	3,  // 2: kaspawalletd.CreateUnsignedTransaction:input_type -> CreateUnsignedTransactionRequest
+	3,  // 2: kaspawalletd.CreateUnsignedTransactions:input_type -> CreateUnsignedTransactionsRequest
 	5,  // 3: kaspawalletd.ShowAddresses:input_type -> ShowAddressesRequest
 	7,  // 4: kaspawalletd.NewAddress:input_type -> NewAddressRequest
 	11, // 5: kaspawalletd.Shutdown:input_type -> ShutdownRequest
 	9,  // 6: kaspawalletd.Broadcast:input_type -> BroadcastRequest
 	1,  // 7: kaspawalletd.GetBalance:output_type -> GetBalanceResponse
-	4,  // 8: kaspawalletd.CreateUnsignedTransaction:output_type -> CreateUnsignedTransactionResponse
+	4,  // 8: kaspawalletd.CreateUnsignedTransactions:output_type -> CreateUnsignedTransactionsResponse
 	6,  // 9: kaspawalletd.ShowAddresses:output_type -> ShowAddressesResponse
 	8,  // 10: kaspawalletd.NewAddress:output_type -> NewAddressResponse
 	12, // 11: kaspawalletd.Shutdown:output_type -> ShutdownResponse
@@ -798,7 +799,7 @@ func file_kaspawalletd_proto_init() {
 			}
 		}
 		file_kaspawalletd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CreateUnsignedTransactionRequest); i {
+			switch v := v.(*CreateUnsignedTransactionsRequest); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -810,7 +811,7 @@ func file_kaspawalletd_proto_init() {
 			}
 		}
 		file_kaspawalletd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CreateUnsignedTransactionResponse); i {
+			switch v := v.(*CreateUnsignedTransactionsResponse); i {
 			case 0:
 				return &v.state
 			case 1:
diff --git a/cmd/kaspawallet/daemon/pb/kaspawalletd.proto b/cmd/kaspawallet/daemon/pb/kaspawalletd.proto
index 3c34b42e..1992d5cd 100644
--- a/cmd/kaspawallet/daemon/pb/kaspawalletd.proto
+++ b/cmd/kaspawallet/daemon/pb/kaspawalletd.proto
@@ -4,7 +4,7 @@ option go_package = "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb";
 
 service kaspawalletd {
   rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse) {}
-  rpc CreateUnsignedTransaction (CreateUnsignedTransactionRequest) returns (CreateUnsignedTransactionResponse) {}
+  rpc CreateUnsignedTransactions (CreateUnsignedTransactionsRequest) returns (CreateUnsignedTransactionsResponse) {}
   rpc ShowAddresses (ShowAddressesRequest) returns (ShowAddressesResponse) {}
   rpc NewAddress (NewAddressRequest) returns (NewAddressResponse) {}
   rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {}
@@ -26,13 +26,13 @@ message AddressBalances {
     uint64 pending = 3;
 }
 
-message CreateUnsignedTransactionRequest {
+message CreateUnsignedTransactionsRequest {
   string address = 1;
   uint64 amount = 2;
 }
 
-message CreateUnsignedTransactionResponse {
-  bytes unsignedTransaction = 1;
+message CreateUnsignedTransactionsResponse {
+  repeated bytes unsignedTransactions = 1;
 }
 
 message ShowAddressesRequest {
diff --git a/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go b/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go
index 04112b6f..f23b245d 100644
--- a/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go
+++ b/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go
@@ -19,7 +19,7 @@ const _ = grpc.SupportPackageIsVersion7
 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
 type KaspawalletdClient interface {
 	GetBalance(ctx context.Context, in *GetBalanceRequest, opts ...grpc.CallOption) (*GetBalanceResponse, error)
-	CreateUnsignedTransaction(ctx context.Context, in *CreateUnsignedTransactionRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionResponse, error)
+	CreateUnsignedTransactions(ctx context.Context, in *CreateUnsignedTransactionsRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionsResponse, error)
 	ShowAddresses(ctx context.Context, in *ShowAddressesRequest, opts ...grpc.CallOption) (*ShowAddressesResponse, error)
 	NewAddress(ctx context.Context, in *NewAddressRequest, opts ...grpc.CallOption) (*NewAddressResponse, error)
 	Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error)
@@ -43,9 +43,9 @@ func (c *kaspawalletdClient) GetBalance(ctx context.Context, in *GetBalanceReque
 	return out, nil
 }
 
-func (c *kaspawalletdClient) CreateUnsignedTransaction(ctx context.Context, in *CreateUnsignedTransactionRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionResponse, error) {
-	out := new(CreateUnsignedTransactionResponse)
-	err := c.cc.Invoke(ctx, "/kaspawalletd/CreateUnsignedTransaction", in, out, opts...)
+func (c *kaspawalletdClient) CreateUnsignedTransactions(ctx context.Context, in *CreateUnsignedTransactionsRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionsResponse, error) {
+	out := new(CreateUnsignedTransactionsResponse)
+	err := c.cc.Invoke(ctx, "/kaspawalletd/CreateUnsignedTransactions", in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -93,7 +93,7 @@ func (c *kaspawalletdClient) Broadcast(ctx context.Context, in *BroadcastRequest
 // for forward compatibility
 type KaspawalletdServer interface {
 	GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error)
-	CreateUnsignedTransaction(context.Context, *CreateUnsignedTransactionRequest) (*CreateUnsignedTransactionResponse, error)
+	CreateUnsignedTransactions(context.Context, *CreateUnsignedTransactionsRequest) (*CreateUnsignedTransactionsResponse, error)
 	ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error)
 	NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error)
 	Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error)
@@ -108,8 +108,8 @@ type UnimplementedKaspawalletdServer struct {
 func (UnimplementedKaspawalletdServer) GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method GetBalance not implemented")
 }
-func (UnimplementedKaspawalletdServer) CreateUnsignedTransaction(context.Context, *CreateUnsignedTransactionRequest) (*CreateUnsignedTransactionResponse, error) {
-	return nil, status.Errorf(codes.Unimplemented, "method CreateUnsignedTransaction not implemented")
+func (UnimplementedKaspawalletdServer) CreateUnsignedTransactions(context.Context, *CreateUnsignedTransactionsRequest) (*CreateUnsignedTransactionsResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method CreateUnsignedTransactions not implemented")
 }
 func (UnimplementedKaspawalletdServer) ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method ShowAddresses not implemented")
@@ -154,20 +154,20 @@ func _Kaspawalletd_GetBalance_Handler(srv interface{}, ctx context.Context, dec
 	return interceptor(ctx, in, info, handler)
 }
 
-func _Kaspawalletd_CreateUnsignedTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(CreateUnsignedTransactionRequest)
+func _Kaspawalletd_CreateUnsignedTransactions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CreateUnsignedTransactionsRequest)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
 	if interceptor == nil {
-		return srv.(KaspawalletdServer).CreateUnsignedTransaction(ctx, in)
+		return srv.(KaspawalletdServer).CreateUnsignedTransactions(ctx, in)
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: "/kaspawalletd/CreateUnsignedTransaction",
+		FullMethod: "/kaspawalletd/CreateUnsignedTransactions",
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(KaspawalletdServer).CreateUnsignedTransaction(ctx, req.(*CreateUnsignedTransactionRequest))
+		return srv.(KaspawalletdServer).CreateUnsignedTransactions(ctx, req.(*CreateUnsignedTransactionsRequest))
 	}
 	return interceptor(ctx, in, info, handler)
 }
@@ -256,8 +256,8 @@ var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{
 			Handler:    _Kaspawalletd_GetBalance_Handler,
 		},
 		{
-			MethodName: "CreateUnsignedTransaction",
-			Handler:    _Kaspawalletd_CreateUnsignedTransaction_Handler,
+			MethodName: "CreateUnsignedTransactions",
+			Handler:    _Kaspawalletd_CreateUnsignedTransactions_Handler,
 		},
 		{
 			MethodName: "ShowAddresses",
diff --git a/cmd/kaspawallet/daemon/server/address.go b/cmd/kaspawallet/daemon/server/address.go
index 621c4423..188ba896 100644
--- a/cmd/kaspawallet/daemon/server/address.go
+++ b/cmd/kaspawallet/daemon/server/address.go
@@ -10,15 +10,15 @@ import (
 	"github.com/pkg/errors"
 )
 
-func (s *server) changeAddress() (util.Address, error) {
+func (s *server) changeAddress() (util.Address, *walletAddress, error) {
 	err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	err = s.keysFile.Save()
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	walletAddr := &walletAddress{
@@ -27,7 +27,11 @@ func (s *server) changeAddress() (util.Address, error) {
 		keyChain:      libkaspawallet.InternalKeychain,
 	}
 	path := s.walletAddressPath(walletAddr)
-	return libkaspawallet.Address(s.params, s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, path, s.keysFile.ECDSA)
+	address, err := libkaspawallet.Address(s.params, s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, path, s.keysFile.ECDSA)
+	if err != nil {
+		return nil, nil, err
+	}
+	return address, walletAddr, nil
 }
 
 func (s *server) ShowAddresses(_ context.Context, request *pb.ShowAddressesRequest) (*pb.ShowAddressesResponse, error) {
diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go
index 68e6364e..ac12711b 100644
--- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go
+++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go
@@ -10,7 +10,11 @@ import (
 	"github.com/pkg/errors"
 )
 
-func (s *server) CreateUnsignedTransaction(_ context.Context, request *pb.CreateUnsignedTransactionRequest) (*pb.CreateUnsignedTransactionResponse, error) {
+// TODO: Implement a better fee estimation mechanism
+const feePerInput = 10000
+
+func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
+	*pb.CreateUnsignedTransactionsResponse, error) {
 	s.lock.Lock()
 	defer s.lock.Unlock()
 
@@ -28,14 +32,12 @@ func (s *server) CreateUnsignedTransaction(_ context.Context, request *pb.Create
 		return nil, err
 	}
 
-	// TODO: Implement a better fee estimation mechanism
-	const feePerInput = 10000
 	selectedUTXOs, changeSompi, err := s.selectUTXOs(request.Amount, feePerInput)
 	if err != nil {
 		return nil, err
 	}
 
-	changeAddress, err := s.changeAddress()
+	changeAddress, changeWalletAddress, err := s.changeAddress()
 	if err != nil {
 		return nil, err
 	}
@@ -53,7 +55,12 @@ func (s *server) CreateUnsignedTransaction(_ context.Context, request *pb.Create
 		return nil, err
 	}
 
-	return &pb.CreateUnsignedTransactionResponse{UnsignedTransaction: unsignedTransaction}, nil
+	unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress)
+	if err != nil {
+		return nil, err
+	}
+
+	return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
 }
 
 func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64) (
diff --git a/cmd/kaspawallet/daemon/server/server.go b/cmd/kaspawallet/daemon/server/server.go
index d9fe2546..1335aa3a 100644
--- a/cmd/kaspawallet/daemon/server/server.go
+++ b/cmd/kaspawallet/daemon/server/server.go
@@ -7,6 +7,8 @@ import (
 	"sync"
 	"time"
 
+	"github.com/kaspanet/kaspad/util/txmass"
+
 	"github.com/kaspanet/kaspad/util/profiling"
 
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
@@ -32,6 +34,7 @@ type server struct {
 	keysFile            *keys.File
 	shutdown            chan struct{}
 	addressSet          walletAddressSet
+	txMassCalculator    *txmass.Calculator
 }
 
 // Start starts the kaspawalletd server
@@ -69,6 +72,7 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
 		keysFile:            keysFile,
 		shutdown:            make(chan struct{}),
 		addressSet:          make(walletAddressSet),
+		txMassCalculator:    txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
 	}
 
 	spawn("serverInstance.sync", func() {
diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go
new file mode 100644
index 00000000..fd672b01
--- /dev/null
+++ b/cmd/kaspawallet/daemon/server/split_transaction.go
@@ -0,0 +1,278 @@
+package server
+
+import (
+	"github.com/kaspanet/go-secp256k1"
+	"github.com/pkg/errors"
+
+	"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
+	"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
+	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
+	"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
+	"github.com/kaspanet/kaspad/util"
+)
+
+// maybeAutoCompoundTransaction checks if a transaction's mass is higher that what is allowed for a standard
+// transaction.
+// If it is - the transaction is split into multiple transactions, each with a portion of the inputs and a single output
+// into a change address.
+// An additional `mergeTransaction` is generated - which merges the outputs of the above splits into a single output
+// paying to the original transaction's payee.
+func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte, toAddress util.Address,
+	changeAddress util.Address, changeWalletAddress *walletAddress) ([][]byte, error) {
+	transaction, err := serialization.DeserializePartiallySignedTransaction(transactionBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	splitTransactions, err := s.maybeSplitTransaction(transaction, changeAddress)
+	if err != nil {
+		return nil, err
+	}
+	if len(splitTransactions) > 1 {
+		mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress)
+		if err != nil {
+			return nil, err
+		}
+		splitTransactions = append(splitTransactions, mergeTransaction)
+	}
+
+	splitTransactionsBytes := make([][]byte, len(splitTransactions))
+	for i, splitTransaction := range splitTransactions {
+		splitTransactionsBytes[i], err = serialization.SerializePartiallySignedTransaction(splitTransaction)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return splitTransactionsBytes, nil
+}
+
+func (s *server) mergeTransaction(
+	splitTransactions []*serialization.PartiallySignedTransaction,
+	originalTransaction *serialization.PartiallySignedTransaction,
+	toAddress util.Address,
+	changeAddress util.Address,
+	changeWalletAddress *walletAddress,
+) (*serialization.PartiallySignedTransaction, error) {
+	numOutputs := len(originalTransaction.Tx.Outputs)
+	if numOutputs > 2 || numOutputs == 0 {
+		// This is a sanity check to make sure originalTransaction has either 1 or 2 outputs:
+		// 1. For the payment itself
+		// 2. (optional) for change
+		return nil, errors.Errorf("original transaction has %d outputs, while 1 or 2 are expected",
+			len(originalTransaction.Tx.Outputs))
+	}
+
+	totalValue := uint64(0)
+	sentValue := originalTransaction.Tx.Outputs[0].Value
+	utxos := make([]*libkaspawallet.UTXO, len(splitTransactions))
+	for i, splitTransaction := range splitTransactions {
+		output := splitTransaction.Tx.Outputs[0]
+		utxos[i] = &libkaspawallet.UTXO{
+			Outpoint: &externalapi.DomainOutpoint{
+				TransactionID: *consensushashing.TransactionID(splitTransaction.Tx),
+				Index:         0,
+			},
+			UTXOEntry:      utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, constants.UnacceptedDAAScore),
+			DerivationPath: s.walletAddressPath(changeWalletAddress),
+		}
+		totalValue += output.Value
+		totalValue -= feePerInput
+	}
+
+	if totalValue < sentValue {
+		// sometimes the fees from compound transactions make the total output higher than what's available from selected
+		// utxos, in such cases - find one more UTXO and use it.
+		additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue)
+		if err != nil {
+			return nil, err
+		}
+		utxos = append(utxos, additionalUTXOs...)
+		totalValue += totalValueAdded
+	}
+
+	payments := []*libkaspawallet.Payment{{
+		Address: toAddress,
+		Amount:  sentValue,
+	}}
+	if totalValue > sentValue {
+		payments = append(payments, &libkaspawallet.Payment{
+			Address: changeAddress,
+			Amount:  totalValue - sentValue,
+		})
+	}
+
+	mergeTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
+		s.keysFile.MinimumSignatures, payments, utxos)
+	if err != nil {
+		return nil, err
+	}
+
+	return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes)
+}
+
+func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySignedTransaction,
+	changeAddress util.Address) ([]*serialization.PartiallySignedTransaction, error) {
+
+	transactionMass, err := s.estimateMassAfterSignatures(transaction)
+	if err != nil {
+		return nil, err
+	}
+
+	if transactionMass < mempool.MaximumStandardTransactionMass {
+		return []*serialization.PartiallySignedTransaction{transaction}, nil
+	}
+
+	splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress)
+	if err != nil {
+		return nil, err
+	}
+
+	splitTransactions := make([]*serialization.PartiallySignedTransaction, splitCount)
+	for i := 0; i < splitCount; i++ {
+		startIndex := i * inputCountPerSplit
+		endIndex := startIndex + inputCountPerSplit
+		var err error
+		splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return splitTransactions, nil
+}
+
+// splitAndInputPerSplitCounts calculates the number of splits to create, and the number of inputs to assign per split.
+func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64,
+	changeAddress util.Address) (splitCount, inputsPerSplitCount int, err error) {
+
+	// Create a dummy transaction which is a clone of the original transaction, but without inputs,
+	// to calculate how much mass do all the inputs have
+	transactionWithoutInputs := transaction.Tx.Clone()
+	transactionWithoutInputs.Inputs = []*externalapi.DomainTransactionInput{}
+	massWithoutInputs := s.txMassCalculator.CalculateTransactionMass(transactionWithoutInputs)
+
+	massOfAllInputs := transactionMass - massWithoutInputs
+
+	// Since the transaction was generated by kaspawallet, we assume all inputs have the same number of signatures, and
+	// thus - the same mass.
+	inputCount := len(transaction.Tx.Inputs)
+	massPerInput := massOfAllInputs / uint64(inputCount)
+	if massOfAllInputs%uint64(inputCount) > 0 {
+		massPerInput++
+	}
+
+	// Create another dummy transaction, this time one similar to the split transactions we wish to generate,
+	// but with 0 inputs, to calculate how much mass for inputs do we have available in the split transactions
+	splitTransactionWithoutInputs, err := s.createSplitTransaction(transaction, changeAddress, 0, 0)
+	if err != nil {
+		return 0, 0, err
+	}
+	massForEverythingExceptInputsInSplitTransaction :=
+		s.txMassCalculator.CalculateTransactionMass(splitTransactionWithoutInputs.Tx)
+	massForInputsInSplitTransaction := mempool.MaximumStandardTransactionMass - massForEverythingExceptInputsInSplitTransaction
+
+	inputsPerSplitCount = int(massForInputsInSplitTransaction / massPerInput)
+	splitCount = inputCount / inputsPerSplitCount
+	if inputCount%inputsPerSplitCount > 0 {
+		splitCount++
+	}
+
+	return splitCount, inputsPerSplitCount, nil
+}
+
+func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,
+	changeAddress util.Address, startIndex int, endIndex int) (*serialization.PartiallySignedTransaction, error) {
+
+	selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex)
+	totalSompi := uint64(0)
+
+	for i := startIndex; i < endIndex && i < len(transaction.PartiallySignedInputs); i++ {
+		partiallySignedInput := transaction.PartiallySignedInputs[i]
+		selectedUTXOs = append(selectedUTXOs, &libkaspawallet.UTXO{
+			Outpoint: &transaction.Tx.Inputs[i].PreviousOutpoint,
+			UTXOEntry: utxo.NewUTXOEntry(
+				partiallySignedInput.PrevOutput.Value, partiallySignedInput.PrevOutput.ScriptPublicKey,
+				false, constants.UnacceptedDAAScore),
+			DerivationPath: partiallySignedInput.DerivationPath,
+		})
+
+		totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount()
+		totalSompi -= feePerInput
+	}
+	unsignedTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
+		s.keysFile.MinimumSignatures,
+		[]*libkaspawallet.Payment{{
+			Address: changeAddress,
+			Amount:  totalSompi,
+		}}, selectedUTXOs)
+	if err != nil {
+		return nil, err
+	}
+
+	return serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes)
+}
+
+func (s *server) estimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) {
+	transaction = transaction.Clone()
+	var signatureSize uint64
+	if s.keysFile.ECDSA {
+		signatureSize = secp256k1.SerializedECDSASignatureSize
+	} else {
+		signatureSize = secp256k1.SerializedSchnorrSignatureSize
+	}
+
+	for i, input := range transaction.PartiallySignedInputs {
+		for j, pubKeyPair := range input.PubKeySignaturePairs {
+			if uint32(j) >= s.keysFile.MinimumSignatures {
+				break
+			}
+			pubKeyPair.Signature = make([]byte, signatureSize+1) // +1 for SigHashType
+		}
+		transaction.Tx.Inputs[i].SigOpCount = byte(len(input.PubKeySignaturePairs))
+	}
+
+	transactionWithSignatures, err := libkaspawallet.ExtractTransactionDeserialized(transaction, s.keysFile.ECDSA)
+	if err != nil {
+		return 0, err
+	}
+
+	return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil
+}
+
+func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64) (
+	additionalUTXOs []*libkaspawallet.UTXO, totalValueAdded uint64, err error) {
+
+	dagInfo, err := s.rpcClient.GetBlockDAGInfo()
+	if err != nil {
+		return nil, 0, err
+	}
+	alreadySelectedUTXOsMap := make(map[externalapi.DomainOutpoint]struct{}, len(alreadySelectedUTXOs))
+	for _, alreadySelectedUTXO := range alreadySelectedUTXOs {
+		alreadySelectedUTXOsMap[*alreadySelectedUTXO.Outpoint] = struct{}{}
+	}
+
+	for _, utxo := range s.utxosSortedByAmount {
+		if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok {
+			continue
+		}
+		if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
+			continue
+		}
+		additionalUTXOs = append(additionalUTXOs, &libkaspawallet.UTXO{
+			Outpoint:       utxo.Outpoint,
+			UTXOEntry:      utxo.UTXOEntry,
+			DerivationPath: s.walletAddressPath(utxo.address)})
+		totalValueAdded += utxo.UTXOEntry.Amount() - feePerInput
+		if totalValueAdded >= requiredAmount {
+			break
+		}
+	}
+	if totalValueAdded < requiredAmount {
+		return nil, 0, errors.Errorf("Insufficient funds for merge transaction")
+	}
+
+	return additionalUTXOs, totalValueAdded, nil
+}
diff --git a/cmd/kaspawallet/libkaspawallet/serialization/serialization.go b/cmd/kaspawallet/libkaspawallet/serialization/serialization.go
index 3abdaef4..4abc6b5d 100644
--- a/cmd/kaspawallet/libkaspawallet/serialization/serialization.go
+++ b/cmd/kaspawallet/libkaspawallet/serialization/serialization.go
@@ -34,6 +34,44 @@ type PubKeySignaturePair struct {
 	Signature         []byte
 }
 
+// Clone creates a deep-clone of this PartiallySignedTransaction
+func (pst *PartiallySignedTransaction) Clone() *PartiallySignedTransaction {
+	clone := &PartiallySignedTransaction{
+		Tx:                    pst.Tx.Clone(),
+		PartiallySignedInputs: make([]*PartiallySignedInput, len(pst.PartiallySignedInputs)),
+	}
+	for i, partiallySignedInput := range pst.PartiallySignedInputs {
+		clone.PartiallySignedInputs[i] = partiallySignedInput.Clone()
+	}
+	return clone
+}
+
+// Clone creates a deep-clone of this PartiallySignedInput
+func (psi PartiallySignedInput) Clone() *PartiallySignedInput {
+	clone := &PartiallySignedInput{
+		PrevOutput:           psi.PrevOutput.Clone(),
+		MinimumSignatures:    psi.MinimumSignatures,
+		PubKeySignaturePairs: make([]*PubKeySignaturePair, len(psi.PubKeySignaturePairs)),
+		DerivationPath:       psi.DerivationPath,
+	}
+	for i, pubKeySignaturePair := range psi.PubKeySignaturePairs {
+		clone.PubKeySignaturePairs[i] = pubKeySignaturePair.Clone()
+	}
+	return clone
+}
+
+// Clone creates a deep-clone of this PubKeySignaturePair
+func (psp PubKeySignaturePair) Clone() *PubKeySignaturePair {
+	clone := &PubKeySignaturePair{
+		ExtendedPublicKey: psp.ExtendedPublicKey,
+	}
+	if psp.Signature != nil {
+		clone.Signature = make([]byte, len(psp.Signature))
+		copy(clone.Signature, psp.Signature)
+	}
+	return clone
+}
+
 // DeserializePartiallySignedTransaction deserializes a byte slice into PartiallySignedTransaction.
 func DeserializePartiallySignedTransaction(serializedPartiallySignedTransaction []byte) (*PartiallySignedTransaction, error) {
 	protoPartiallySignedTransaction := &protoserialization.PartiallySignedTransaction{}
diff --git a/cmd/kaspawallet/libkaspawallet/sign.go b/cmd/kaspawallet/libkaspawallet/sign.go
index 5bef3bf0..98057ddb 100644
--- a/cmd/kaspawallet/libkaspawallet/sign.go
+++ b/cmd/kaspawallet/libkaspawallet/sign.go
@@ -40,7 +40,6 @@ func Sign(params *dagconfig.Params, mnemonics []string, serializedPSTx []byte, e
 			return nil, err
 		}
 	}
-
 	return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction)
 }
 
diff --git a/cmd/kaspawallet/libkaspawallet/transaction.go b/cmd/kaspawallet/libkaspawallet/transaction.go
index a1b2e549..62664b66 100644
--- a/cmd/kaspawallet/libkaspawallet/transaction.go
+++ b/cmd/kaspawallet/libkaspawallet/transaction.go
@@ -159,6 +159,7 @@ func createUnsignedTransaction(
 		Tx:                    domainTransaction,
 		PartiallySignedInputs: partiallySignedInputs,
 	}, nil
+
 }
 
 // IsTransactionFullySigned returns whether the transaction is fully signed and ready to broadcast.
@@ -194,10 +195,14 @@ func ExtractTransaction(partiallySignedTransactionBytes []byte, ecdsa bool) (*ex
 		return nil, err
 	}
 
-	return extractTransaction(partiallySignedTransaction, ecdsa)
+	return ExtractTransactionDeserialized(partiallySignedTransaction, ecdsa)
 }
 
-func extractTransaction(partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) (*externalapi.DomainTransaction, error) {
+// ExtractTransactionDeserialized does the same thing ExtractTransaction does, only receives the PartiallySignedTransaction
+// in an already deserialized format
+func ExtractTransactionDeserialized(partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) (
+	*externalapi.DomainTransaction, error) {
+
 	for i, input := range partiallySignedTransaction.PartiallySignedInputs {
 		isMultisig := len(input.PubKeySignaturePairs) > 1
 		scriptBuilder := txscript.NewScriptBuilder()
diff --git a/cmd/kaspawallet/send.go b/cmd/kaspawallet/send.go
index e101c544..c3fa1e80 100644
--- a/cmd/kaspawallet/send.go
+++ b/cmd/kaspawallet/send.go
@@ -3,6 +3,7 @@ package main
 import (
 	"context"
 	"fmt"
+
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
@@ -31,10 +32,11 @@ func send(conf *sendConfig) error {
 	defer cancel()
 
 	sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
-	createUnsignedTransactionResponse, err := daemonClient.CreateUnsignedTransaction(ctx, &pb.CreateUnsignedTransactionRequest{
-		Address: conf.ToAddress,
-		Amount:  sendAmountSompi,
-	})
+	createUnsignedTransactionsResponse, err :=
+		daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
+			Address: conf.ToAddress,
+			Amount:  sendAmountSompi,
+		})
 	if err != nil {
 		return err
 	}
@@ -44,22 +46,37 @@ func send(conf *sendConfig) error {
 		return err
 	}
 
-	signedTransaction, err := libkaspawallet.Sign(conf.NetParams(), mnemonics, createUnsignedTransactionResponse.UnsignedTransaction, keysFile.ECDSA)
-	if err != nil {
-		return err
+	signedTransactions := make([][]byte, len(createUnsignedTransactionsResponse.UnsignedTransactions))
+	for i, unsignedTransaction := range createUnsignedTransactionsResponse.UnsignedTransactions {
+		signedTransaction, err := libkaspawallet.Sign(conf.NetParams(), mnemonics, unsignedTransaction, keysFile.ECDSA)
+		if err != nil {
+			return err
+		}
+		signedTransactions[i] = signedTransaction
 	}
 
-	ctx2, cancel2 := context.WithTimeout(context.Background(), daemonTimeout)
-	defer cancel2()
-	broadcastResponse, err := daemonClient.Broadcast(ctx2, &pb.BroadcastRequest{
-		Transaction: signedTransaction,
-	})
-	if err != nil {
-		return err
+	if len(signedTransactions) > 1 {
+		fmt.Printf("Broadcasting %d transactions\n", len(signedTransactions))
 	}
+	for _, signedTransaction := range signedTransactions {
+		err := func() error { // surround with func so that defer runs separately per transaction
+			ctx2, cancel2 := context.WithTimeout(context.Background(), daemonTimeout)
+			defer cancel2()
+			broadcastResponse, err := daemonClient.Broadcast(ctx2, &pb.BroadcastRequest{
+				Transaction: signedTransaction,
+			})
+			if err != nil {
+				return err
+			}
 
-	fmt.Println("Transaction was sent successfully")
-	fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID)
+			fmt.Println("Transaction was sent successfully")
+			fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID)
+			return nil
+		}()
+		if err != nil {
+			return err
+		}
+	}
 
 	return nil
 }
diff --git a/cmd/kaspawallet/sign.go b/cmd/kaspawallet/sign.go
index 6ee60c8b..4ea60f78 100644
--- a/cmd/kaspawallet/sign.go
+++ b/cmd/kaspawallet/sign.go
@@ -1,21 +1,16 @@
 package main
 
 import (
-	"encoding/hex"
 	"fmt"
+	"io/ioutil"
+	"strings"
+
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
 	"github.com/pkg/errors"
-	"io/ioutil"
-	"strings"
 )
 
 func sign(conf *signConfig) error {
-	keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
-	if err != nil {
-		return err
-	}
-
 	if conf.Transaction == "" && conf.TransactionFile == "" {
 		return errors.Errorf("Either --transaction or --transaction-file is required")
 	}
@@ -23,41 +18,56 @@ func sign(conf *signConfig) error {
 		return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time")
 	}
 
-	transactionHex := conf.Transaction
-	if conf.TransactionFile != "" {
-		transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile)
-		if err != nil {
-			return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionFile)
-		}
-		transactionHex = strings.TrimSpace(string(transactionHexBytes))
-	}
-
-	partiallySignedTransaction, err := hex.DecodeString(transactionHex)
+	keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
 	if err != nil {
 		return err
 	}
-
 	privateKeys, err := keysFile.DecryptMnemonics(conf.Password)
 	if err != nil {
 		return err
 	}
 
-	updatedPartiallySignedTransaction, err := libkaspawallet.Sign(conf.NetParams(), privateKeys, partiallySignedTransaction, keysFile.ECDSA)
+	transactionsHex := conf.Transaction
+	if conf.TransactionFile != "" {
+		transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile)
+		if err != nil {
+			return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionFile)
+		}
+		transactionsHex = strings.TrimSpace(string(transactionHexBytes))
+	}
+	partiallySignedTransactions, err := decodeTransactionsFromHex(transactionsHex)
 	if err != nil {
 		return err
 	}
 
-	isFullySigned, err := libkaspawallet.IsTransactionFullySigned(updatedPartiallySignedTransaction)
-	if err != nil {
-		return err
+	updatedPartiallySignedTransactions := make([][]byte, len(partiallySignedTransactions))
+	for i, partiallySignedTransaction := range partiallySignedTransactions {
+		updatedPartiallySignedTransactions[i], err =
+			libkaspawallet.Sign(conf.NetParams(), privateKeys, partiallySignedTransaction, keysFile.ECDSA)
+		if err != nil {
+			return err
+		}
+	}
+
+	areAllTransactionsFullySigned := true
+	for _, updatedPartiallySignedTransaction := range updatedPartiallySignedTransactions {
+		// This is somewhat redundant to check all transactions, but we do that just-in-case
+		isFullySigned, err := libkaspawallet.IsTransactionFullySigned(updatedPartiallySignedTransaction)
+		if err != nil {
+			return err
+		}
+		if !isFullySigned {
+			areAllTransactionsFullySigned = false
+		}
 	}
 
-	if isFullySigned {
+	if areAllTransactionsFullySigned {
 		fmt.Println("The transaction is signed and ready to broadcast")
 	} else {
 		fmt.Println("Successfully signed transaction")
 	}
 
-	fmt.Printf("Transaction: %x\n", updatedPartiallySignedTransaction)
+	fmt.Println("Transaction: ")
+	fmt.Println(encodeTransactionsToHex(updatedPartiallySignedTransactions))
 	return nil
 }
diff --git a/cmd/kaspawallet/transactions_hex_encoding.go b/cmd/kaspawallet/transactions_hex_encoding.go
new file mode 100644
index 00000000..e7e857bf
--- /dev/null
+++ b/cmd/kaspawallet/transactions_hex_encoding.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+	"encoding/hex"
+	"strings"
+)
+
+// hexTransactionsSeparator is used to mark the end of one transaction and the beginning of the next one.
+// We use a separator that is not in the hex alphabet, but which will not split selection with a double click
+const hexTransactionsSeparator = "_"
+
+func encodeTransactionsToHex(transactions [][]byte) string {
+	transactionsInHex := make([]string, len(transactions))
+	for i, transaction := range transactions {
+		transactionsInHex[i] = hex.EncodeToString(transaction)
+	}
+	return strings.Join(transactionsInHex, hexTransactionsSeparator)
+}
+
+func decodeTransactionsFromHex(transactionsHex string) ([][]byte, error) {
+	splitTransactionsHexes := strings.Split(transactionsHex, hexTransactionsSeparator)
+	transactions := make([][]byte, len(splitTransactionsHexes))
+
+	var err error
+	for i, transactionHex := range splitTransactionsHexes {
+		transactions[i], err = hex.DecodeString(transactionHex)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return transactions, nil
+}
diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go
index 19efb52c..df1777fc 100644
--- a/domain/consensus/consensus.go
+++ b/domain/consensus/consensus.go
@@ -1,8 +1,6 @@
 package consensus
 
 import (
-	"github.com/kaspanet/kaspad/infrastructure/logger"
-	"github.com/kaspanet/kaspad/util/staging"
 	"math/big"
 	"sync"
 
@@ -10,6 +8,8 @@ import (
 	"github.com/kaspanet/kaspad/domain/consensus/model"
 	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
 	"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
+	"github.com/kaspanet/kaspad/infrastructure/logger"
+	"github.com/kaspanet/kaspad/util/staging"
 	"github.com/pkg/errors"
 )
 
diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go
index ee6dc063..d427f50a 100644
--- a/domain/consensus/factory.go
+++ b/domain/consensus/factory.go
@@ -1,17 +1,18 @@
 package consensus
 
 import (
+	"io/ioutil"
+	"os"
+	"sync"
+
 	"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockwindowheapslicestore"
 	"github.com/kaspanet/kaspad/domain/consensus/datastructures/daawindowstore"
 	"github.com/kaspanet/kaspad/domain/consensus/model"
 	"github.com/kaspanet/kaspad/domain/consensus/processes/blockparentbuilder"
 	parentssanager "github.com/kaspanet/kaspad/domain/consensus/processes/parentsmanager"
 	"github.com/kaspanet/kaspad/domain/consensus/processes/pruningproofmanager"
-	"io/ioutil"
-	"os"
-	"sync"
-
 	"github.com/kaspanet/kaspad/domain/prefixmanager/prefix"
+	"github.com/kaspanet/kaspad/util/txmass"
 
 	consensusdatabase "github.com/kaspanet/kaspad/domain/consensus/database"
 	"github.com/kaspanet/kaspad/domain/consensus/datastructures/acceptancedatastore"
@@ -171,6 +172,9 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
 		config.GenesisHash,
 		config.MaxBlockLevel,
 	)
+
+	txMassCalculator := txmass.NewCalculator(config.MassPerTxByte, config.MassPerScriptPubKeyByte, config.MassPerSigOp)
+
 	pastMedianTimeManager := f.pastMedianTimeConsructor(
 		config.TimestampDeviationTolerance,
 		dbManager,
@@ -180,14 +184,12 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
 		config.GenesisHash)
 	transactionValidator := transactionvalidator.New(config.BlockCoinbaseMaturity,
 		config.EnableNonNativeSubnetworks,
-		config.MassPerTxByte,
-		config.MassPerScriptPubKeyByte,
-		config.MassPerSigOp,
 		config.MaxCoinbasePayloadLength,
 		dbManager,
 		pastMedianTimeManager,
 		ghostdagDataStore,
-		daaBlocksStore)
+		daaBlocksStore,
+		txMassCalculator)
 	difficultyManager := f.difficultyConstructor(
 		dbManager,
 		ghostdagManager,
@@ -331,6 +333,8 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
 		reachabilityDataStore,
 		consensusStateStore,
 		daaBlocksStore,
+
+		txMassCalculator,
 	)
 
 	syncManager := syncmanager.New(
diff --git a/domain/consensus/processes/blockvalidator/blockvalidator.go b/domain/consensus/processes/blockvalidator/blockvalidator.go
index 7a9be22b..870ce8c4 100644
--- a/domain/consensus/processes/blockvalidator/blockvalidator.go
+++ b/domain/consensus/processes/blockvalidator/blockvalidator.go
@@ -4,6 +4,8 @@ import (
 	"math/big"
 	"time"
 
+	"github.com/kaspanet/kaspad/util/txmass"
+
 	"github.com/kaspanet/kaspad/domain/consensus/model"
 	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
 	"github.com/kaspanet/kaspad/util/difficulty"
@@ -48,6 +50,8 @@ type blockValidator struct {
 	reachabilityStore   model.ReachabilityDataStore
 	consensusStateStore model.ConsensusStateStore
 	daaBlocksStore      model.DAABlocksStore
+
+	txMassCalculator *txmass.Calculator
 }
 
 // New instantiates a new BlockValidator
@@ -87,6 +91,8 @@ func New(powMax *big.Int,
 	reachabilityStore model.ReachabilityDataStore,
 	consensusStateStore model.ConsensusStateStore,
 	daaBlocksStore model.DAABlocksStore,
+
+	txMassCalculator *txmass.Calculator,
 ) model.BlockValidator {
 
 	return &blockValidator{
@@ -126,5 +132,7 @@ func New(powMax *big.Int,
 		reachabilityStore:   reachabilityStore,
 		consensusStateStore: consensusStateStore,
 		daaBlocksStore:      daaBlocksStore,
+
+		txMassCalculator: txMassCalculator,
 	}
 }
diff --git a/domain/consensus/processes/transactionvalidator/mass.go b/domain/consensus/processes/transactionvalidator/mass.go
index c4e4a7aa..d9941047 100644
--- a/domain/consensus/processes/transactionvalidator/mass.go
+++ b/domain/consensus/processes/transactionvalidator/mass.go
@@ -2,99 +2,12 @@ package transactionvalidator
 
 import (
 	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
-	"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
 )
 
+// PopulateMass calculates and populates the mass of the given transaction
 func (v *transactionValidator) PopulateMass(transaction *externalapi.DomainTransaction) {
 	if transaction.Mass != 0 {
 		return
 	}
-	transaction.Mass = v.transactionMass(transaction)
-}
-
-func (v *transactionValidator) transactionMass(transaction *externalapi.DomainTransaction) uint64 {
-	if transactionhelper.IsCoinBase(transaction) {
-		return 0
-	}
-
-	// calculate mass for size
-	size := transactionEstimatedSerializedSize(transaction)
-	massForSize := size * v.massPerTxByte
-
-	// calculate mass for scriptPubKey
-	totalScriptPubKeySize := uint64(0)
-	for _, output := range transaction.Outputs {
-		totalScriptPubKeySize += 2 //output.ScriptPublicKey.Version (uint16)
-		totalScriptPubKeySize += uint64(len(output.ScriptPublicKey.Script))
-	}
-	massForScriptPubKey := totalScriptPubKeySize * v.massPerScriptPubKeyByte
-
-	// calculate mass for SigOps
-	totalSigOpCount := uint64(0)
-	for _, input := range transaction.Inputs {
-		totalSigOpCount += uint64(input.SigOpCount)
-	}
-	massForSigOps := totalSigOpCount * v.massPerSigOp
-
-	// Sum all components of mass
-	return massForSize + massForScriptPubKey + massForSigOps
-}
-
-// transactionEstimatedSerializedSize is the estimated size of a transaction in some
-// serialization. This has to be deterministic, but not necessarily accurate, since
-// it's only used as the size component in the transaction and block mass limit
-// calculation.
-func transactionEstimatedSerializedSize(tx *externalapi.DomainTransaction) uint64 {
-	if transactionhelper.IsCoinBase(tx) {
-		return 0
-	}
-	size := uint64(0)
-	size += 2 // Txn Version
-	size += 8 // number of inputs (uint64)
-	for _, input := range tx.Inputs {
-		size += transactionInputEstimatedSerializedSize(input)
-	}
-
-	size += 8 // number of outputs (uint64)
-	for _, output := range tx.Outputs {
-		size += TransactionOutputEstimatedSerializedSize(output)
-	}
-
-	size += 8 // lock time (uint64)
-	size += externalapi.DomainSubnetworkIDSize
-	size += 8                          // gas (uint64)
-	size += externalapi.DomainHashSize // payload hash
-
-	size += 8 // length of the payload (uint64)
-	size += uint64(len(tx.Payload))
-
-	return size
-}
-
-func transactionInputEstimatedSerializedSize(input *externalapi.DomainTransactionInput) uint64 {
-	size := uint64(0)
-	size += outpointEstimatedSerializedSize()
-
-	size += 8 // length of signature script (uint64)
-	size += uint64(len(input.SignatureScript))
-
-	size += 8 // sequence (uint64)
-	return size
-}
-
-func outpointEstimatedSerializedSize() uint64 {
-	size := uint64(0)
-	size += externalapi.DomainHashSize // ID
-	size += 4                          // index (uint32)
-	return size
-}
-
-// TransactionOutputEstimatedSerializedSize is the same as transactionEstimatedSerializedSize but for outputs only
-func TransactionOutputEstimatedSerializedSize(output *externalapi.DomainTransactionOutput) uint64 {
-	size := uint64(0)
-	size += 8 // value (uint64)
-	size += 2 // output.ScriptPublicKey.Version (uint 16)
-	size += 8 // length of script public key (uint64)
-	size += uint64(len(output.ScriptPublicKey.Script))
-	return size
+	transaction.Mass = v.txMassCalculator.CalculateTransactionMass(transaction)
 }
diff --git a/domain/consensus/processes/transactionvalidator/transactionvalidator.go b/domain/consensus/processes/transactionvalidator/transactionvalidator.go
index 19f73188..73621e75 100644
--- a/domain/consensus/processes/transactionvalidator/transactionvalidator.go
+++ b/domain/consensus/processes/transactionvalidator/transactionvalidator.go
@@ -3,6 +3,7 @@ package transactionvalidator
 import (
 	"github.com/kaspanet/kaspad/domain/consensus/model"
 	"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
+	"github.com/kaspanet/kaspad/util/txmass"
 )
 
 const sigCacheSize = 10_000
@@ -16,32 +17,25 @@ type transactionValidator struct {
 	ghostdagDataStore          model.GHOSTDAGDataStore
 	daaBlocksStore             model.DAABlocksStore
 	enableNonNativeSubnetworks bool
-	massPerTxByte              uint64
-	massPerScriptPubKeyByte    uint64
-	massPerSigOp               uint64
 	maxCoinbasePayloadLength   uint64
 	sigCache                   *txscript.SigCache
 	sigCacheECDSA              *txscript.SigCacheECDSA
+	txMassCalculator           *txmass.Calculator
 }
 
 // New instantiates a new TransactionValidator
 func New(blockCoinbaseMaturity uint64,
 	enableNonNativeSubnetworks bool,
-	massPerTxByte uint64,
-	massPerScriptPubKeyByte uint64,
-	massPerSigOp uint64,
 	maxCoinbasePayloadLength uint64,
 	databaseContext model.DBReader,
 	pastMedianTimeManager model.PastMedianTimeManager,
 	ghostdagDataStore model.GHOSTDAGDataStore,
-	daaBlocksStore model.DAABlocksStore) model.TransactionValidator {
+	daaBlocksStore model.DAABlocksStore,
+	txMassCalculator *txmass.Calculator) model.TransactionValidator {
 
 	return &transactionValidator{
 		blockCoinbaseMaturity:      blockCoinbaseMaturity,
 		enableNonNativeSubnetworks: enableNonNativeSubnetworks,
-		massPerTxByte:              massPerTxByte,
-		massPerScriptPubKeyByte:    massPerScriptPubKeyByte,
-		massPerSigOp:               massPerSigOp,
 		maxCoinbasePayloadLength:   maxCoinbasePayloadLength,
 		databaseContext:            databaseContext,
 		pastMedianTimeManager:      pastMedianTimeManager,
@@ -49,5 +43,6 @@ func New(blockCoinbaseMaturity uint64,
 		daaBlocksStore:             daaBlocksStore,
 		sigCache:                   txscript.NewSigCache(sigCacheSize),
 		sigCacheECDSA:              txscript.NewSigCacheECDSA(sigCacheSize),
+		txMassCalculator:           txMassCalculator,
 	}
 }
diff --git a/domain/consensus/utils/constants/constants.go b/domain/consensus/utils/constants/constants.go
index aa1edf11..31a19719 100644
--- a/domain/consensus/utils/constants/constants.go
+++ b/domain/consensus/utils/constants/constants.go
@@ -35,4 +35,8 @@ const (
 	// LockTimeThreshold is the number below which a lock time is
 	// interpreted to be a DAA score.
 	LockTimeThreshold = 5e11 // Tue Nov 5 00:53:20 1985 UTC
+
+	// UnacceptedDAAScore is used to for UTXOEntries that were created by transactions in the mempool, or otherwise
+	// not-yet-accepted transactions.
+	UnacceptedDAAScore = math.MaxUint64
 )
diff --git a/domain/dagconfig/params.go b/domain/dagconfig/params.go
index 6d919849..2deda4e9 100644
--- a/domain/dagconfig/params.go
+++ b/domain/dagconfig/params.go
@@ -318,7 +318,7 @@ var TestnetParams = Params{
 	MinerConfirmationWindow:       2016,
 
 	// Mempool parameters
-	RelayNonStdTxs: true,
+	RelayNonStdTxs: false,
 
 	// AcceptUnroutable specifies whether this network accepts unroutable
 	// IP addresses, such as 10.0.0.0/8
@@ -386,7 +386,7 @@ var SimnetParams = Params{
 	MinerConfirmationWindow:       100,
 
 	// Mempool parameters
-	RelayNonStdTxs: true,
+	RelayNonStdTxs: false,
 
 	// AcceptUnroutable specifies whether this network accepts unroutable
 	// IP addresses, such as 10.0.0.0/8
@@ -445,7 +445,7 @@ var DevnetParams = Params{
 	MinerConfirmationWindow:       2016,
 
 	// Mempool parameters
-	RelayNonStdTxs: true,
+	RelayNonStdTxs: false,
 
 	// AcceptUnroutable specifies whether this network accepts unroutable
 	// IP addresses, such as 10.0.0.0/8
diff --git a/domain/miningmanager/mempool/check_transaction_standard.go b/domain/miningmanager/mempool/check_transaction_standard.go
index 39df3429..9a66e2b6 100644
--- a/domain/miningmanager/mempool/check_transaction_standard.go
+++ b/domain/miningmanager/mempool/check_transaction_standard.go
@@ -3,7 +3,7 @@ package mempool
 import (
 	"fmt"
 
-	"github.com/kaspanet/kaspad/domain/consensus/processes/transactionvalidator"
+	"github.com/kaspanet/kaspad/util/txmass"
 
 	"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
 
@@ -36,9 +36,9 @@ const (
 	// (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650
 	maximumStandardSignatureScriptSize = 1650
 
-	// maximumStandardTransactionMass is the maximum mass allowed for transactions that
+	// MaximumStandardTransactionMass is the maximum mass allowed for transactions that
 	// are considered standard and will therefore be relayed and considered for mining.
-	maximumStandardTransactionMass = 100000
+	MaximumStandardTransactionMass = 100_000
 )
 
 func (mp *mempool) checkTransactionStandardInIsolation(transaction *externalapi.DomainTransaction) error {
@@ -58,9 +58,9 @@ func (mp *mempool) checkTransactionStandardInIsolation(transaction *externalapi.
 	// almost as much to process as the sender fees, limit the maximum
 	// size of a transaction. This also helps mitigate CPU exhaustion
 	// attacks.
-	if transaction.Mass > maximumStandardTransactionMass {
+	if transaction.Mass > MaximumStandardTransactionMass {
 		str := fmt.Sprintf("transaction mass of %d is larger than max allowed size of %d",
-			transaction.Mass, maximumStandardTransactionMass)
+			transaction.Mass, MaximumStandardTransactionMass)
 		return transactionRuleError(RejectNonstandard, str)
 	}
 
@@ -127,7 +127,7 @@ func (mp *mempool) IsTransactionOutputDust(output *externalapi.DomainTransaction
 	// The most common scripts are pay-to-pubkey, and as per the above
 	// breakdown, the minimum size of a p2pk input script is 148 bytes. So
 	// that figure is used.
-	totalSerializedSize := transactionvalidator.TransactionOutputEstimatedSerializedSize(output) + 148
+	totalSerializedSize := txmass.TransactionOutputEstimatedSerializedSize(output) + 148
 
 	// The output is considered dust if the cost to the network to spend the
 	// coins is more than 1/3 of the minimum free transaction relay fee.
diff --git a/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go b/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go
index e269a079..ad9cfc60 100644
--- a/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go
+++ b/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go
@@ -3,6 +3,7 @@ package mempool
 import (
 	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
 	"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
 	"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
 	"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
 	"github.com/pkg/errors"
@@ -42,6 +43,6 @@ func fillInputs(transaction *externalapi.DomainTransaction, parentsInPool model.
 		}
 		relevantOutput := parent.Transaction().Outputs[input.PreviousOutpoint.Index]
 		input.UTXOEntry = utxo.NewUTXOEntry(relevantOutput.Value, relevantOutput.ScriptPublicKey,
-			false, model.UnacceptedDAAScore)
+			false, constants.UnacceptedDAAScore)
 	}
 }
diff --git a/domain/miningmanager/mempool/mempool_utxo_set.go b/domain/miningmanager/mempool/mempool_utxo_set.go
index efb1dd9b..aa20d05d 100644
--- a/domain/miningmanager/mempool/mempool_utxo_set.go
+++ b/domain/miningmanager/mempool/mempool_utxo_set.go
@@ -3,6 +3,8 @@ package mempool
 import (
 	"fmt"
 
+	"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
+
 	"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
 
 	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
@@ -42,7 +44,7 @@ func (mpus *mempoolUTXOSet) addTransaction(transaction *model.MempoolTransaction
 		outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID(), Index: uint32(i)}
 
 		mpus.poolUnspentOutputs[outpoint] =
-			utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, model.UnacceptedDAAScore)
+			utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, constants.UnacceptedDAAScore)
 	}
 }
 
diff --git a/domain/miningmanager/mempool/model/constants.go b/domain/miningmanager/mempool/model/constants.go
deleted file mode 100644
index d2d743bd..00000000
--- a/domain/miningmanager/mempool/model/constants.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package model
-
-import "math"
-
-// UnacceptedDAAScore is used to for UTXOEntries that were created by transactions in the mempool.
-const UnacceptedDAAScore = math.MaxUint64
diff --git a/domain/miningmanager/mempool/orphan_pool.go b/domain/miningmanager/mempool/orphan_pool.go
index df54903f..ce7d7777 100644
--- a/domain/miningmanager/mempool/orphan_pool.go
+++ b/domain/miningmanager/mempool/orphan_pool.go
@@ -3,6 +3,8 @@ package mempool
 import (
 	"fmt"
 
+	"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
+
 	"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
 
 	"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
@@ -153,7 +155,7 @@ func (op *orphansPool) processOrphansAfterAcceptedTransaction(acceptedTransactio
 			for _, input := range orphan.Transaction().Inputs {
 				if input.PreviousOutpoint.Equal(&outpoint) && input.UTXOEntry == nil {
 					input.UTXOEntry = utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false,
-						model.UnacceptedDAAScore)
+						constants.UnacceptedDAAScore)
 					break
 				}
 			}
diff --git a/infrastructure/config/config.go b/infrastructure/config/config.go
index d219076e..c8c8fd02 100644
--- a/infrastructure/config/config.go
+++ b/infrastructure/config/config.go
@@ -42,16 +42,16 @@ const (
 	defaultMaxRPCClients         = 10
 	defaultMaxRPCWebsockets      = 25
 	defaultMaxRPCConcurrentReqs  = 20
-	defaultBlockMaxMass          = 10000000
+	defaultBlockMaxMass          = 10_000_000
 	blockMaxMassMin              = 1000
-	blockMaxMassMax              = 10000000
+	blockMaxMassMax              = 10_000_000
 	defaultMinRelayTxFee         = 1e-5 // 1 sompi per byte
 	defaultMaxOrphanTransactions = 100
 	//DefaultMaxOrphanTxSize is the default maximum size for an orphan transaction
-	DefaultMaxOrphanTxSize  = 100000
-	defaultSigCacheMaxSize  = 100000
+	DefaultMaxOrphanTxSize  = 100_000
+	defaultSigCacheMaxSize  = 100_000
 	sampleConfigFilename    = "sample-kaspad.conf"
-	defaultMaxUTXOCacheSize = 5000000000
+	defaultMaxUTXOCacheSize = 5_000_000_000
 	defaultProtocolVersion  = 5
 )
 
diff --git a/util/txmass/calculator.go b/util/txmass/calculator.go
new file mode 100644
index 00000000..5206f7fe
--- /dev/null
+++ b/util/txmass/calculator.go
@@ -0,0 +1,119 @@
+package txmass
+
+import (
+	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
+)
+
+// Calculator exposes methods to calculate the mass of a transaction
+type Calculator struct {
+	massPerTxByte           uint64
+	massPerScriptPubKeyByte uint64
+	massPerSigOp            uint64
+}
+
+// NewCalculator creates a new instance of Calculator
+func NewCalculator(massPerTxByte, massPerScriptPubKeyByte, massPerSigOp uint64) *Calculator {
+	return &Calculator{
+		massPerTxByte:           massPerTxByte,
+		massPerScriptPubKeyByte: massPerScriptPubKeyByte,
+		massPerSigOp:            massPerSigOp,
+	}
+}
+
+// MassPerTxByte returns the mass per transaction byte configured for this Calculator
+func (c *Calculator) MassPerTxByte() uint64 { return c.massPerTxByte }
+
+// MassPerScriptPubKeyByte returns the mass per ScriptPublicKey byte configured for this Calculator
+func (c *Calculator) MassPerScriptPubKeyByte() uint64 { return c.massPerScriptPubKeyByte }
+
+// MassPerSigOp returns the mass per SigOp byte configured for this Calculator
+func (c *Calculator) MassPerSigOp() uint64 { return c.massPerSigOp }
+
+// CalculateTransactionMass calculates the mass of the given transaction
+func (c *Calculator) CalculateTransactionMass(transaction *externalapi.DomainTransaction) uint64 {
+	if transactionhelper.IsCoinBase(transaction) {
+		return 0
+	}
+
+	// calculate mass for size
+	size := transactionEstimatedSerializedSize(transaction)
+	massForSize := size * c.massPerTxByte
+
+	// calculate mass for scriptPubKey
+	totalScriptPubKeySize := uint64(0)
+	for _, output := range transaction.Outputs {
+		totalScriptPubKeySize += 2 //output.ScriptPublicKey.Version (uint16)
+		totalScriptPubKeySize += uint64(len(output.ScriptPublicKey.Script))
+	}
+	massForScriptPubKey := totalScriptPubKeySize * c.massPerScriptPubKeyByte
+
+	// calculate mass for SigOps
+	totalSigOpCount := uint64(0)
+	for _, input := range transaction.Inputs {
+		totalSigOpCount += uint64(input.SigOpCount)
+	}
+	massForSigOps := totalSigOpCount * c.massPerSigOp
+
+	// Sum all components of mass
+	return massForSize + massForScriptPubKey + massForSigOps
+}
+
+// transactionEstimatedSerializedSize is the estimated size of a transaction in some
+// serialization. This has to be deterministic, but not necessarily accurate, since
+// it's only used as the size component in the transaction and block mass limit
+// calculation.
+func transactionEstimatedSerializedSize(tx *externalapi.DomainTransaction) uint64 {
+	if transactionhelper.IsCoinBase(tx) {
+		return 0
+	}
+	size := uint64(0)
+	size += 2 // Txn Version
+	size += 8 // number of inputs (uint64)
+	for _, input := range tx.Inputs {
+		size += transactionInputEstimatedSerializedSize(input)
+	}
+
+	size += 8 // number of outputs (uint64)
+	for _, output := range tx.Outputs {
+		size += TransactionOutputEstimatedSerializedSize(output)
+	}
+
+	size += 8 // lock time (uint64)
+	size += externalapi.DomainSubnetworkIDSize
+	size += 8                          // gas (uint64)
+	size += externalapi.DomainHashSize // payload hash
+
+	size += 8 // length of the payload (uint64)
+	size += uint64(len(tx.Payload))
+
+	return size
+}
+
+func transactionInputEstimatedSerializedSize(input *externalapi.DomainTransactionInput) uint64 {
+	size := uint64(0)
+	size += outpointEstimatedSerializedSize()
+
+	size += 8 // length of signature script (uint64)
+	size += uint64(len(input.SignatureScript))
+
+	size += 8 // sequence (uint64)
+	return size
+}
+
+func outpointEstimatedSerializedSize() uint64 {
+	size := uint64(0)
+	size += externalapi.DomainHashSize // ID
+	size += 4                          // index (uint32)
+	return size
+}
+
+// TransactionOutputEstimatedSerializedSize is the same as transactionEstimatedSerializedSize but for outputs only
+func TransactionOutputEstimatedSerializedSize(output *externalapi.DomainTransactionOutput) uint64 {
+	size := uint64(0)
+	size += 8 // value (uint64)
+	size += 2 // output.ScriptPublicKey.Version (uint 16)
+	size += 8 // length of script public key (uint64)
+	size += uint64(len(output.ScriptPublicKey.Script))
+	return size
+}


Test patch:
diff --git a/cmd/kaspawallet/daemon/server/split_transaction_test.go b/cmd/kaspawallet/daemon/server/split_transaction_test.go
new file mode 100644
index 00000000..0f521bb6
--- /dev/null
+++ b/cmd/kaspawallet/daemon/server/split_transaction_test.go
@@ -0,0 +1,152 @@
+package server
+
+import (
+	"testing"
+
+	"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
+
+	"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
+	"github.com/kaspanet/kaspad/util/txmass"
+
+	"github.com/kaspanet/kaspad/domain/dagconfig"
+
+	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
+
+	"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
+	"github.com/kaspanet/kaspad/domain/consensus"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
+)
+
+func TestEstimateMassAfterSignatures(t *testing.T) {
+	testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
+		unsignedTransactionBytes, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig)
+		defer teardown(false)
+
+		serverInstance := &server{
+			params:           params,
+			keysFile:         &keys.File{MinimumSignatures: 2},
+			shutdown:         make(chan struct{}),
+			addressSet:       make(walletAddressSet),
+			txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
+		}
+
+		unsignedTransaction, err := serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes)
+		if err != nil {
+			t.Fatalf("Error deserializing unsignedTransaction: %s", err)
+		}
+
+		estimatedMassAfterSignatures, err := serverInstance.estimateMassAfterSignatures(unsignedTransaction)
+		if err != nil {
+			t.Fatalf("Error from estimateMassAfterSignatures: %s", err)
+		}
+
+		signedTxStep1Bytes, err := libkaspawallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false)
+		if err != nil {
+			t.Fatalf("Sign: %+v", err)
+		}
+
+		signedTxStep2Bytes, err := libkaspawallet.Sign(params, mnemonics[1:2], signedTxStep1Bytes, false)
+		if err != nil {
+			t.Fatalf("Sign: %+v", err)
+		}
+
+		extractedSignedTx, err := libkaspawallet.ExtractTransaction(signedTxStep2Bytes, false)
+		if err != nil {
+			t.Fatalf("ExtractTransaction: %+v", err)
+		}
+
+		actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionMass(extractedSignedTx)
+
+		if estimatedMassAfterSignatures != actualMassAfterSignatures {
+			t.Errorf("Estimated mass after signatures: %d but actually got %d",
+				estimatedMassAfterSignatures, actualMassAfterSignatures)
+		}
+	})
+}
+
+func testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *consensus.Config) (
+	[]byte, []string, *dagconfig.Params, func(keepDataDir bool)) {
+
+	consensusConfig.BlockCoinbaseMaturity = 0
+	params := &consensusConfig.Params
+
+	tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig")
+	if err != nil {
+		t.Fatalf("Error setting up tc: %+v", err)
+	}
+
+	const numKeys = 3
+	mnemonics := make([]string, numKeys)
+	publicKeys := make([]string, numKeys)
+	for i := 0; i < numKeys; i++ {
+		var err error
+		mnemonics[i], err = libkaspawallet.CreateMnemonic()
+		if err != nil {
+			t.Fatalf("CreateMnemonic: %+v", err)
+		}
+
+		publicKeys[i], err = libkaspawallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], true)
+		if err != nil {
+			t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err)
+		}
+	}
+
+	const minimumSignatures = 2
+	path := "m/1/2/3"
+	address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures, path, false)
+	if err != nil {
+		t.Fatalf("Address: %+v", err)
+	}
+
+	scriptPublicKey, err := txscript.PayToAddrScript(address)
+	if err != nil {
+		t.Fatalf("PayToAddrScript: %+v", err)
+	}
+
+	coinbaseData := &externalapi.DomainCoinbaseData{
+		ScriptPublicKey: scriptPublicKey,
+		ExtraData:       nil,
+	}
+
+	fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil)
+	if err != nil {
+		t.Fatalf("AddBlock: %+v", err)
+	}
+
+	block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil)
+	if err != nil {
+		t.Fatalf("AddBlock: %+v", err)
+	}
+
+	block1, err := tc.GetBlock(block1Hash)
+	if err != nil {
+		t.Fatalf("GetBlock: %+v", err)
+	}
+
+	block1Tx := block1.Transactions[0]
+	block1TxOut := block1Tx.Outputs[0]
+	selectedUTXOs := []*libkaspawallet.UTXO{
+		{
+			Outpoint: &externalapi.DomainOutpoint{
+				TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
+				Index:         0,
+			},
+			UTXOEntry:      utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
+			DerivationPath: path,
+		},
+	}
+
+	unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
+		[]*libkaspawallet.Payment{{
+			Address: address,
+			Amount:  10,
+		}}, selectedUTXOs)
+	if err != nil {
+		t.Fatalf("CreateUnsignedTransactions: %+v", err)
+	}
+
+	return unsignedTransaction, mnemonics, params, teardown
+}
diff --git a/cmd/kaspawallet/libkaspawallet/transaction_test.go b/cmd/kaspawallet/libkaspawallet/transaction_test.go
index d7c7a742..1b705e73 100644
--- a/cmd/kaspawallet/libkaspawallet/transaction_test.go
+++ b/cmd/kaspawallet/libkaspawallet/transaction_test.go
@@ -2,6 +2,9 @@ package libkaspawallet_test
 
 import (
 	"fmt"
+	"strings"
+	"testing"
+
 	"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
 	"github.com/kaspanet/kaspad/domain/consensus"
 	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
@@ -10,8 +13,6 @@ import (
 	"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
 	"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
 	"github.com/kaspanet/kaspad/util"
-	"strings"
-	"testing"
 )
 
 func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) {
@@ -106,7 +107,7 @@ func TestMultisig(t *testing.T) {
 					Amount:  10,
 				}}, selectedUTXOs)
 			if err != nil {
-				t.Fatalf("CreateUnsignedTransaction: %+v", err)
+				t.Fatalf("CreateUnsignedTransactions: %+v", err)
 			}
 
 			isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
@@ -267,7 +268,7 @@ func TestP2PK(t *testing.T) {
 					Amount:  10,
 				}}, selectedUTXOs)
 			if err != nil {
-				t.Fatalf("CreateUnsignedTransaction: %+v", err)
+				t.Fatalf("CreateUnsignedTransactions: %+v", err)
 			}
 
 			isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
diff --git a/domain/miningmanager/mempool/check_transaction_standard_test.go b/domain/miningmanager/mempool/check_transaction_standard_test.go
index 04e66867..9366e5a5 100644
--- a/domain/miningmanager/mempool/check_transaction_standard_test.go
+++ b/domain/miningmanager/mempool/check_transaction_standard_test.go
@@ -6,10 +6,11 @@ package mempool
 
 import (
 	"bytes"
-	"github.com/kaspanet/kaspad/domain/consensusreference"
 	"math"
 	"testing"
 
+	"github.com/kaspanet/kaspad/domain/consensusreference"
+
 	"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
 
 	"github.com/kaspanet/kaspad/domain/consensus"
@@ -45,13 +46,13 @@ func TestCalcMinRequiredTxRelayFee(t *testing.T) {
 		},
 		{
 			"max standard tx size with default minimum relay fee",
-			maximumStandardTransactionMass,
+			MaximumStandardTransactionMass,
 			defaultMinimumRelayTransactionFee,
 			100000,
 		},
 		{
 			"max standard tx size with max sompi relay fee",
-			maximumStandardTransactionMass,
+			MaximumStandardTransactionMass,
 			constants.MaxSompi,
 			constants.MaxSompi,
 		},
@@ -249,7 +250,7 @@ func TestCheckTransactionStandardInIsolation(t *testing.T) {
 			name: "Transaction size is too large",
 			tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{
 				Value:           0,
-				ScriptPublicKey: &externalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, maximumStandardTransactionMass+1), 0},
+				ScriptPublicKey: &externalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, MaximumStandardTransactionMass+1), 0},
 			}}},
 			height:     300000,
 			isStandard: false,