Skip to content

Commit

Permalink
Finishing mvp for create tx and tx from hex
Browse files Browse the repository at this point in the history
  • Loading branch information
mrz1836 committed Oct 3, 2020
1 parent 2e6e5fd commit 177e839
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 10 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ View the generated [documentation](https://pkg.go.dev/github.com/bitcoinschema/g
- [Generate HD Keys](hd_key.go)
- [Get PrivateKey by Path](hd_key.go)
- [Get HD Key by Path](hd_key.go)
- [Create Tx](transaction.go)
- [Tx from Hex](transaction.go)

### ToDo
- Support `testnet` addresses & keys
Expand All @@ -63,6 +65,7 @@ View the generated [documentation](https://pkg.go.dev/github.com/bitcoinschema/g
- [bitcoinsv/bsvd](https://github.com/bitcoinsv/bsvd)
- [bitcoinsv/bsvutil](https://github.com/bitcoinsv/bsvutil)
- [itchyny/base58-go](https://github.com/itchyny/base58-go)
- [libsv/libsv](https://github.com/libsv/libsv)
- [piotrnar/gocoin](https://github.com/piotrnar/gocoin)
</details>

Expand Down
38 changes: 38 additions & 0 deletions examples/create_tx/create_tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"log"

"github.com/bitcoinschema/go-bitcoin"
)

func main() {

// Use a new UTXO
utxo := &bitcoin.Utxo{
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
Vout: 0,
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
Satoshis: 1000,
}

// Add a pay-to address
payTo := &bitcoin.PayToAddress{
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
Satoshis: 500,
}

// Add some op return data
opReturn1 := bitcoin.OpReturnData{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}
opReturn2 := bitcoin.OpReturnData{[]byte("prefix2"), []byte("more example data")}

// Generate the TX
rawTx, err := bitcoin.CreateTx([]*bitcoin.Utxo{utxo}, []*bitcoin.PayToAddress{payTo}, []bitcoin.OpReturnData{opReturn1, opReturn2}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu")
if err != nil {
log.Printf("error occurred: %s", err.Error())
return
}

// Success!
log.Printf("rawTx: %s", rawTx.ToString())
}
21 changes: 21 additions & 0 deletions examples/tx_from_hex/tx_from_hex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import (
"log"

"github.com/bitcoinschema/go-bitcoin"
)

func main() {

// Example raw tx
exampleTx := "0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006b483045022100eea3d606bd1627be6459a9de4860919225db74843d2fc7f4e7caa5e01f42c2d0022017978d9c6a0e934955a70e7dda71d68cb614f7dd89eb7b9d560aea761834ddd4412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff03f4010000000000001976a9147a1980655efbfec416b2b0c663a7b3ac0b6a25d288ac00000000000000001a006a07707265666978310c6578616d706c65206461746102133700000000000000001c006a0770726566697832116d6f7265206578616d706c65206461746100000000"

rawTx, err := bitcoin.TxFromHex(exampleTx)
if err != nil {
log.Printf("error occurred: %s", err.Error())
return
}

log.Printf("tx id: %s", rawTx.GetTxID())
}
21 changes: 12 additions & 9 deletions transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ 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) {
// CreateTx will create a basic transaction and return the raw transaction (*transaction.Transaction)
//
// Get the raw hex version: tx.ToString()
// Get the tx id: tx.GetTxID()
func CreateTx(utxos []*Utxo, addresses []*PayToAddress, opReturns []OpReturnData, wif string) (*transaction.Transaction, error) {

// Missing utxos
if len(utxos) == 0 {
return "", errors.New("utxos are required to create a tx")
return nil, errors.New("utxos are required to create a tx")
}

// Start creating a new transaction
Expand All @@ -46,38 +49,38 @@ func CreateTx(utxos []*Utxo, addresses []*PayToAddress, opReturns []OpReturnData
var err error
for _, utxo := range utxos {
if err = tx.From(utxo.TxID, utxo.Vout, utxo.ScriptSig, utxo.Satoshis); err != nil {
return "", err
return nil, err
}
}

// Loop any pay addresses
for _, address := range addresses {
if err = tx.PayTo(address.Address, address.Satoshis); err != nil {
return "", err
return nil, err
}
}

// Loop any op returns
var outPut *output.Output
for _, op := range opReturns {
if outPut, err = output.NewOpReturnParts(op); err != nil {
return "", err
return nil, err
}
tx.AddOutput(outPut)
}

// Decode the WIF
var decodedWif *bsvutil.WIF
if decodedWif, err = bsvutil.DecodeWIF(wif); err != nil {
return "", err
return nil, err
}

// Sign the transaction
signer := signature.InternalSigner{PrivateKey: decodedWif.PrivKey, SigHashFlag: 0}
if err = tx.SignAuto(&signer); err != nil {
return "", err
return nil, err
}

// Return the transaction as a raw string
return tx.ToString(), nil
return tx, nil
}
183 changes: 182 additions & 1 deletion transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,186 @@ func TestCreateTx(t *testing.T) {
}

// Show the results
t.Logf("created tx: %s", rawTx)
t.Logf("created tx: %s", rawTx.ToString())
}

// ExampleCreateTx example using CreateTx()
func ExampleCreateTx() {

// 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
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{opReturn1, opReturn2}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu")
if err != nil {
fmt.Printf("error occurred: %s", err.Error())
return
}

fmt.Printf("rawTx: %s", rawTx.ToString())
// Output:rawTx: 0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006b483045022100eea3d606bd1627be6459a9de4860919225db74843d2fc7f4e7caa5e01f42c2d0022017978d9c6a0e934955a70e7dda71d68cb614f7dd89eb7b9d560aea761834ddd4412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff03f4010000000000001976a9147a1980655efbfec416b2b0c663a7b3ac0b6a25d288ac00000000000000001a006a07707265666978310c6578616d706c65206461746102133700000000000000001c006a0770726566697832116d6f7265206578616d706c65206461746100000000
}

// BenchmarkCreateTx benchmarks the method CreateTx()
func BenchmarkCreateTx(b *testing.B) {
// 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
opReturn1 := OpReturnData{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}
opReturn2 := OpReturnData{[]byte("prefix2"), []byte("more example data")}

for i := 0; i < b.N; i++ {
_, _ = CreateTx([]*Utxo{utxo}, []*PayToAddress{payTo}, []OpReturnData{opReturn1, opReturn2}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu")
}
}

// TestCreateTxErrors will test the method CreateTx()
func TestCreateTxErrors(t *testing.T) {

t.Parallel()

// Create the list of tests
var tests = []struct {
inputUtxos []*Utxo
inputAddresses []*PayToAddress
inputOpReturns []OpReturnData
inputWif string
expectedRawTx string
expectedNil bool
expectedError bool
}{
{[]*Utxo{{
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
Vout: 0,
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
Satoshis: 1000,
}},
[]*PayToAddress{{
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
Satoshis: 500,
}},
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
"0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006b483045022100bd31b3d9fbe18468086c0470e99f096e370f0c6ff41b6bb71f1a1d5c1b068ce302204f0c83d792a40337909b8b1bcea192722161f48dc475c653b7c352baa38eea6c412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff02f4010000000000001976a9147a1980655efbfec416b2b0c663a7b3ac0b6a25d288ac00000000000000001a006a07707265666978310c6578616d706c65206461746102133700000000",
false,
false,
},
{nil,
[]*PayToAddress{{
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
Satoshis: 500,
}},
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
"",
true,
true,
},
{[]*Utxo{{
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
Vout: 0,
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
Satoshis: 1000,
}}, nil,
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
"0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006a47304402205ba1a246371bf8db3fb6dfa75e1edaa18b6b86dc1775dc3f2aa3c38f22803ccc022057850f794ebf78e542228d301420d4ec896c30a2bc009b7e55c66120f6c5a57a412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff0100000000000000001a006a07707265666978310c6578616d706c65206461746102133700000000",
false,
false,
},
{[]*Utxo{{
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
Vout: 0,
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
Satoshis: 1000,
}}, nil,
nil,
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
"0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006a47304402200083bb297d53210cf9379b3f47de2eff38e6906e5982fbfeef9bf59778750f3e022046da020811e9a2d1e6db8da103d17598abc194125612be6b108d49cb60cbca95412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff0000000000",
false,
false,
},
{[]*Utxo{{
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
Vout: 0,
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
Satoshis: 1000,
}},
[]*PayToAddress{{
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
Satoshis: 500,
}},
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
"",
"",
true,
true,
},
{[]*Utxo{{
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
Vout: 0,
ScriptSig: "invalid-script",
Satoshis: 1000,
}},
[]*PayToAddress{{
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
Satoshis: 500,
}},
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
"",
true,
true,
},
{[]*Utxo{{
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
Vout: 0,
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
Satoshis: 1000,
}},
[]*PayToAddress{{
Address: "invalid-address",
Satoshis: 500,
}},
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
"",
true,
true,
},
}

// Run tests
for _, test := range tests {
if rawTx, err := CreateTx(test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif); err != nil && !test.expectedError {
t.Errorf("%s Failed: [%v] [%v] [%v] [%s] inputted and error not expected but got: %s", t.Name(), test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif, err.Error())
} else if err == nil && test.expectedError {
t.Errorf("%s Failed: [%v] [%v] [%v] [%s] inputted and error was expected", t.Name(), test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif)
} else if rawTx == nil && !test.expectedNil {
t.Errorf("%s Failed: [%v] [%v] [%v] [%s] inputted and nil was not expected", t.Name(), test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif)
} else if rawTx != nil && test.expectedNil {
t.Errorf("%s Failed: [%v] [%v] [%v] [%s] inputted and nil was expected", t.Name(), test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif)
} else if rawTx != nil && rawTx.ToString() != test.expectedRawTx {
t.Errorf("%s Failed: [%v] [%v] [%v] [%s] inputted [%s] expected but failed comparison of scripts, got: %s", t.Name(), test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif, test.expectedRawTx, rawTx.ToString())
}
}
}

0 comments on commit 177e839

Please sign in to comment.