From 359d1e80e8379719a06099fc0e308576c2ba06bd Mon Sep 17 00:00:00 2001 From: mrz1836 Date: Fri, 2 Oct 2020 10:24:46 -0400 Subject: [PATCH 1/5] Creating a basic example of making a Tx --- transaction.go | 80 +++++++++++++++++++++++++++++++++++++++++++++ transaction_test.go | 35 ++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 transaction.go create mode 100644 transaction_test.go diff --git a/transaction.go b/transaction.go new file mode 100644 index 0000000..d1b6868 --- /dev/null +++ b/transaction.go @@ -0,0 +1,80 @@ +package bitcoin + +import ( + "errors" + + "github.com/bitcoinsv/bsvutil" + "github.com/libsv/libsv/transaction" + "github.com/libsv/libsv/transaction/output" + "github.com/libsv/libsv/transaction/signature" +) + +// Utxo is an unspent transaction output +type Utxo struct { + Satoshis uint64 `json:"satoshis"` + ScriptSig string `json:"string"` + TxID string `json:"tx_id"` + Vout uint32 `json:"vout"` +} + +// PayToAddress is the pay-to-address +type PayToAddress struct { + Address string `json:"address"` + Satoshis uint64 `json:"satoshis"` +} + +// OpReturnData is the op return data to include in the tx +type OpReturnData struct { + Data string `json:"data"` +} + +// CreateTx will create a basic transaction +func CreateTx(utxos []*Utxo, addresses []*PayToAddress, opReturns []*OpReturnData, wif string) (string, error) { + + // Missing utxos + if len(utxos) == 0 { + return "", errors.New("utxos are required to create a tx") + } + + // Start creating a new transaction + tx := transaction.New() + + // Loop all utxos and add to the transaction + var err error + for _, utxo := range utxos { + if err = tx.From(utxo.TxID, utxo.Vout, utxo.ScriptSig, utxo.Satoshis); err != nil { + return "", err + } + } + + // Loop any pay addresses + for _, address := range addresses { + if err = tx.PayTo(address.Address, address.Satoshis); err != nil { + return "", err + } + } + + // Loop any op returns + var outPut *output.Output + for _, op := range opReturns { + if outPut, err = output.NewOpReturn([]byte(op.Data)); err != nil { + return "", err + } + tx.AddOutput(outPut) + } + + // Decode the WIF + var decodedWif *bsvutil.WIF + if decodedWif, err = bsvutil.DecodeWIF(wif); err != nil { + return "", err + } + + // Sign the transaction + signer := signature.InternalSigner{PrivateKey: decodedWif.PrivKey, SigHashFlag: 0} + if err = tx.SignAuto(&signer); err != nil { + return "", err + } + + // Return the transaction as a raw string + return tx.ToString(), nil +} diff --git a/transaction_test.go b/transaction_test.go new file mode 100644 index 0000000..6c1be3d --- /dev/null +++ b/transaction_test.go @@ -0,0 +1,35 @@ +package bitcoin + +import "testing" + +// TestCreateTx will test the method CreateTx() +func TestCreateTx(t *testing.T) { + + // Example from: https://github.com/libsv/libsv + + // Use a new UTXO + utxo := &Utxo{ + TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", + Vout: 0, + ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac", + Satoshis: 1000, + } + + // Add a pay-to address + payTo := &PayToAddress{ + Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", + Satoshis: 500, + } + + // Add some op return data + opReturns := &OpReturnData{Data: "This is the example data!"} + + // Generate the TX + rawTx, err := CreateTx([]*Utxo{utxo}, []*PayToAddress{payTo}, []*OpReturnData{opReturns}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu") + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } + + // Show the results + t.Logf("created tx: %s", rawTx) +} From 833c327feb02bf3540b1bf19be667cbe2f0857db Mon Sep 17 00:00:00 2001 From: mrz1836 Date: Fri, 2 Oct 2020 10:26:31 -0400 Subject: [PATCH 2/5] Single vs plural --- transaction_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transaction_test.go b/transaction_test.go index 6c1be3d..710ceb0 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -22,10 +22,10 @@ func TestCreateTx(t *testing.T) { } // Add some op return data - opReturns := &OpReturnData{Data: "This is the example data!"} + opReturn := &OpReturnData{Data: "This is the example data!"} // Generate the TX - rawTx, err := CreateTx([]*Utxo{utxo}, []*PayToAddress{payTo}, []*OpReturnData{opReturns}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu") + rawTx, err := CreateTx([]*Utxo{utxo}, []*PayToAddress{payTo}, []*OpReturnData{opReturn}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu") if err != nil { t.Fatalf("error occurred: %s", err.Error()) } From 43c250809f43fd26131ed67daed16319ced81a77 Mon Sep 17 00:00:00 2001 From: Luke Rohenaz Date: Fri, 2 Oct 2020 10:57:25 -0400 Subject: [PATCH 3/5] multiple op_returns, support raw bytes --- transaction.go | 8 +++----- transaction_test.go | 5 +++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/transaction.go b/transaction.go index d1b6868..d33162d 100644 --- a/transaction.go +++ b/transaction.go @@ -24,12 +24,10 @@ type PayToAddress struct { } // OpReturnData is the op return data to include in the tx -type OpReturnData struct { - Data string `json:"data"` -} +type OpReturnData [][]byte // CreateTx will create a basic transaction -func CreateTx(utxos []*Utxo, addresses []*PayToAddress, opReturns []*OpReturnData, wif string) (string, error) { +func CreateTx(utxos []*Utxo, addresses []*PayToAddress, opReturns []OpReturnData, wif string) (string, error) { // Missing utxos if len(utxos) == 0 { @@ -57,7 +55,7 @@ func CreateTx(utxos []*Utxo, addresses []*PayToAddress, opReturns []*OpReturnDat // Loop any op returns var outPut *output.Output for _, op := range opReturns { - if outPut, err = output.NewOpReturn([]byte(op.Data)); err != nil { + if outPut, err = output.NewOpReturnParts(op); err != nil { return "", err } tx.AddOutput(outPut) diff --git a/transaction_test.go b/transaction_test.go index 710ceb0..3c9b6b3 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -22,10 +22,11 @@ func TestCreateTx(t *testing.T) { } // Add some op return data - opReturn := &OpReturnData{Data: "This is the example data!"} + opReturn1 := OpReturnData{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}} + opReturn2 := OpReturnData{[]byte("prefix2"), []byte("more example data")} // Generate the TX - rawTx, err := CreateTx([]*Utxo{utxo}, []*PayToAddress{payTo}, []*OpReturnData{opReturn}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu") + rawTx, err := CreateTx([]*Utxo{utxo}, []*PayToAddress{payTo}, []OpReturnData{opReturn1, opReturn2}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu") if err != nil { t.Fatalf("error occurred: %s", err.Error()) } From 10f4e7e4d1a9984993edcab19416622f4dac2e2f Mon Sep 17 00:00:00 2001 From: mrz1836 Date: Fri, 2 Oct 2020 11:13:35 -0400 Subject: [PATCH 4/5] Added package docs --- bitcoin.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 bitcoin.go diff --git a/bitcoin.go b/bitcoin.go new file mode 100644 index 0000000..8f7b4ea --- /dev/null +++ b/bitcoin.go @@ -0,0 +1,7 @@ +// Package bitcoin is a small collection of utility functions for working with Bitcoin (BSV) +// +// If you have any suggestions or comments, please feel free to open an issue on +// this GitHub repository! +// +// By BitcoinSchema Organization (https://bitcoinschema.org) +package bitcoin From 8346b1903258efb8dc7263578893198d0dbda394 Mon Sep 17 00:00:00 2001 From: mrz1836 Date: Fri, 2 Oct 2020 15:32:33 -0400 Subject: [PATCH 5/5] Added TxFromHex method --- transaction.go | 5 ++++ transaction_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index d33162d..95ec35b 100644 --- a/transaction.go +++ b/transaction.go @@ -26,6 +26,11 @@ type PayToAddress struct { // OpReturnData is the op return data to include in the tx type OpReturnData [][]byte +// TxFromHex will return a libsv.tx from a raw hex string +func TxFromHex(rawHex string) (*transaction.Transaction, error) { + return transaction.NewFromString(rawHex) +} + // CreateTx will create a basic transaction func CreateTx(utxos []*Utxo, addresses []*PayToAddress, opReturns []OpReturnData, wif string) (string, error) { diff --git a/transaction_test.go b/transaction_test.go index 3c9b6b3..38c44b5 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -1,6 +1,61 @@ package bitcoin -import "testing" +import ( + "fmt" + "testing" +) + +// TestTxFromHex will test the method TxFromHex() +func TestTxFromHex(t *testing.T) { + t.Parallel() + + // Create the list of tests + var tests = []struct { + inputHex string + expectedTxID string + expectedNil bool + expectedError bool + }{ + {"", "", true, true}, + {"0", "", true, true}, + {"000", "", true, true}, + {"bad-hex", "", true, true}, + {"01000000012adda020db81f2155ebba69e7c841275517ebf91674268c32ff2f5c7e2853b2c010000006b483045022100872051ef0b6c47714130c12a067db4f38b988bfc22fe270731c2146f5229386b02207abf68bbf092ec03e2c616defcc4c868ad1fc3cdbffb34bcedfab391a1274f3e412102affe8c91d0a61235a3d07b1903476a2e2f7a90451b2ed592fea9937696a07077ffffffff02ed1a0000000000001976a91491b3753cf827f139d2dc654ce36f05331138ddb588acc9670300000000001976a914da036233873cc6489ff65a0185e207d243b5154888ac00000000", "64cd12102af20195d54a107e0ee5989ac5db3491893a0b9d42e24354732a22a5", false, false}, + } + + // Run tests + for _, test := range tests { + if rawTx, err := TxFromHex(test.inputHex); err != nil && !test.expectedError { + t.Errorf("%s Failed: [%s] inputted and error not expected but got: %s", t.Name(), test.inputHex, err.Error()) + } else if err == nil && test.expectedError { + t.Errorf("%s Failed: [%s] inputted and error was expected", t.Name(), test.inputHex) + } else if rawTx == nil && !test.expectedNil { + t.Errorf("%s Failed: [%s] inputted and was nil but not expected", t.Name(), test.inputHex) + } else if rawTx != nil && test.expectedNil { + t.Errorf("%s Failed: [%s] inputted and was NOT nil but expected to be nil", t.Name(), test.inputHex) + } else if rawTx != nil && rawTx.GetTxID() != test.expectedTxID { + t.Errorf("%s Failed: [%s] inputted [%s] expected but failed comparison of txIDs, got: %s", t.Name(), test.inputHex, test.expectedTxID, rawTx.GetTxID()) + } + } +} + +// ExampleTxFromHex example using TxFromHex() +func ExampleTxFromHex() { + tx, err := TxFromHex("01000000012adda020db81f2155ebba69e7c841275517ebf91674268c32ff2f5c7e2853b2c010000006b483045022100872051ef0b6c47714130c12a067db4f38b988bfc22fe270731c2146f5229386b02207abf68bbf092ec03e2c616defcc4c868ad1fc3cdbffb34bcedfab391a1274f3e412102affe8c91d0a61235a3d07b1903476a2e2f7a90451b2ed592fea9937696a07077ffffffff02ed1a0000000000001976a91491b3753cf827f139d2dc654ce36f05331138ddb588acc9670300000000001976a914da036233873cc6489ff65a0185e207d243b5154888ac00000000") + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } + fmt.Printf("txID: %s", tx.GetTxID()) + // Output:txID: 64cd12102af20195d54a107e0ee5989ac5db3491893a0b9d42e24354732a22a5 +} + +// BenchmarkTxFromHex benchmarks the method TxFromHex() +func BenchmarkTxFromHex(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = TxFromHex("01000000012adda020db81f2155ebba69e7c841275517ebf91674268c32ff2f5c7e2853b2c010000006b483045022100872051ef0b6c47714130c12a067db4f38b988bfc22fe270731c2146f5229386b02207abf68bbf092ec03e2c616defcc4c868ad1fc3cdbffb34bcedfab391a1274f3e412102affe8c91d0a61235a3d07b1903476a2e2f7a90451b2ed592fea9937696a07077ffffffff02ed1a0000000000001976a91491b3753cf827f139d2dc654ce36f05331138ddb588acc9670300000000001976a914da036233873cc6489ff65a0185e207d243b5154888ac00000000") + } +} // TestCreateTx will test the method CreateTx() func TestCreateTx(t *testing.T) {