Skip to content

Commit 177e839

Browse files
committed
Finishing mvp for create tx and tx from hex
1 parent 2e6e5fd commit 177e839

File tree

5 files changed

+256
-10
lines changed

5 files changed

+256
-10
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ View the generated [documentation](https://pkg.go.dev/github.com/bitcoinschema/g
5151
- [Generate HD Keys](hd_key.go)
5252
- [Get PrivateKey by Path](hd_key.go)
5353
- [Get HD Key by Path](hd_key.go)
54+
- [Create Tx](transaction.go)
55+
- [Tx from Hex](transaction.go)
5456

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

examples/create_tx/create_tx.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"log"
5+
6+
"github.com/bitcoinschema/go-bitcoin"
7+
)
8+
9+
func main() {
10+
11+
// Use a new UTXO
12+
utxo := &bitcoin.Utxo{
13+
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
14+
Vout: 0,
15+
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
16+
Satoshis: 1000,
17+
}
18+
19+
// Add a pay-to address
20+
payTo := &bitcoin.PayToAddress{
21+
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
22+
Satoshis: 500,
23+
}
24+
25+
// Add some op return data
26+
opReturn1 := bitcoin.OpReturnData{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}
27+
opReturn2 := bitcoin.OpReturnData{[]byte("prefix2"), []byte("more example data")}
28+
29+
// Generate the TX
30+
rawTx, err := bitcoin.CreateTx([]*bitcoin.Utxo{utxo}, []*bitcoin.PayToAddress{payTo}, []bitcoin.OpReturnData{opReturn1, opReturn2}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu")
31+
if err != nil {
32+
log.Printf("error occurred: %s", err.Error())
33+
return
34+
}
35+
36+
// Success!
37+
log.Printf("rawTx: %s", rawTx.ToString())
38+
}

examples/tx_from_hex/tx_from_hex.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"log"
5+
6+
"github.com/bitcoinschema/go-bitcoin"
7+
)
8+
9+
func main() {
10+
11+
// Example raw tx
12+
exampleTx := "0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006b483045022100eea3d606bd1627be6459a9de4860919225db74843d2fc7f4e7caa5e01f42c2d0022017978d9c6a0e934955a70e7dda71d68cb614f7dd89eb7b9d560aea761834ddd4412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff03f4010000000000001976a9147a1980655efbfec416b2b0c663a7b3ac0b6a25d288ac00000000000000001a006a07707265666978310c6578616d706c65206461746102133700000000000000001c006a0770726566697832116d6f7265206578616d706c65206461746100000000"
13+
14+
rawTx, err := bitcoin.TxFromHex(exampleTx)
15+
if err != nil {
16+
log.Printf("error occurred: %s", err.Error())
17+
return
18+
}
19+
20+
log.Printf("tx id: %s", rawTx.GetTxID())
21+
}

transaction.go

+12-9
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@ func TxFromHex(rawHex string) (*transaction.Transaction, error) {
3131
return transaction.NewFromString(rawHex)
3232
}
3333

34-
// CreateTx will create a basic transaction
35-
func CreateTx(utxos []*Utxo, addresses []*PayToAddress, opReturns []OpReturnData, wif string) (string, error) {
34+
// CreateTx will create a basic transaction and return the raw transaction (*transaction.Transaction)
35+
//
36+
// Get the raw hex version: tx.ToString()
37+
// Get the tx id: tx.GetTxID()
38+
func CreateTx(utxos []*Utxo, addresses []*PayToAddress, opReturns []OpReturnData, wif string) (*transaction.Transaction, error) {
3639

3740
// Missing utxos
3841
if len(utxos) == 0 {
39-
return "", errors.New("utxos are required to create a tx")
42+
return nil, errors.New("utxos are required to create a tx")
4043
}
4144

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

5356
// Loop any pay addresses
5457
for _, address := range addresses {
5558
if err = tx.PayTo(address.Address, address.Satoshis); err != nil {
56-
return "", err
59+
return nil, err
5760
}
5861
}
5962

6063
// Loop any op returns
6164
var outPut *output.Output
6265
for _, op := range opReturns {
6366
if outPut, err = output.NewOpReturnParts(op); err != nil {
64-
return "", err
67+
return nil, err
6568
}
6669
tx.AddOutput(outPut)
6770
}
6871

6972
// Decode the WIF
7073
var decodedWif *bsvutil.WIF
7174
if decodedWif, err = bsvutil.DecodeWIF(wif); err != nil {
72-
return "", err
75+
return nil, err
7376
}
7477

7578
// Sign the transaction
7679
signer := signature.InternalSigner{PrivateKey: decodedWif.PrivKey, SigHashFlag: 0}
7780
if err = tx.SignAuto(&signer); err != nil {
78-
return "", err
81+
return nil, err
7982
}
8083

8184
// Return the transaction as a raw string
82-
return tx.ToString(), nil
85+
return tx, nil
8386
}

transaction_test.go

+182-1
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,186 @@ func TestCreateTx(t *testing.T) {
8787
}
8888

8989
// Show the results
90-
t.Logf("created tx: %s", rawTx)
90+
t.Logf("created tx: %s", rawTx.ToString())
91+
}
92+
93+
// ExampleCreateTx example using CreateTx()
94+
func ExampleCreateTx() {
95+
96+
// Use a new UTXO
97+
utxo := &Utxo{
98+
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
99+
Vout: 0,
100+
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
101+
Satoshis: 1000,
102+
}
103+
104+
// Add a pay-to address
105+
payTo := &PayToAddress{
106+
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
107+
Satoshis: 500,
108+
}
109+
110+
// Add some op return data
111+
opReturn1 := OpReturnData{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}
112+
opReturn2 := OpReturnData{[]byte("prefix2"), []byte("more example data")}
113+
114+
// Generate the TX
115+
rawTx, err := CreateTx([]*Utxo{utxo}, []*PayToAddress{payTo}, []OpReturnData{opReturn1, opReturn2}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu")
116+
if err != nil {
117+
fmt.Printf("error occurred: %s", err.Error())
118+
return
119+
}
120+
121+
fmt.Printf("rawTx: %s", rawTx.ToString())
122+
// Output:rawTx: 0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006b483045022100eea3d606bd1627be6459a9de4860919225db74843d2fc7f4e7caa5e01f42c2d0022017978d9c6a0e934955a70e7dda71d68cb614f7dd89eb7b9d560aea761834ddd4412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff03f4010000000000001976a9147a1980655efbfec416b2b0c663a7b3ac0b6a25d288ac00000000000000001a006a07707265666978310c6578616d706c65206461746102133700000000000000001c006a0770726566697832116d6f7265206578616d706c65206461746100000000
123+
}
124+
125+
// BenchmarkCreateTx benchmarks the method CreateTx()
126+
func BenchmarkCreateTx(b *testing.B) {
127+
// Use a new UTXO
128+
utxo := &Utxo{TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", Vout: 0, ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac", Satoshis: 1000}
129+
130+
// Add a pay-to address
131+
payTo := &PayToAddress{Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", Satoshis: 500}
132+
133+
// Add some op return data
134+
opReturn1 := OpReturnData{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}
135+
opReturn2 := OpReturnData{[]byte("prefix2"), []byte("more example data")}
136+
137+
for i := 0; i < b.N; i++ {
138+
_, _ = CreateTx([]*Utxo{utxo}, []*PayToAddress{payTo}, []OpReturnData{opReturn1, opReturn2}, "L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu")
139+
}
140+
}
141+
142+
// TestCreateTxErrors will test the method CreateTx()
143+
func TestCreateTxErrors(t *testing.T) {
144+
145+
t.Parallel()
146+
147+
// Create the list of tests
148+
var tests = []struct {
149+
inputUtxos []*Utxo
150+
inputAddresses []*PayToAddress
151+
inputOpReturns []OpReturnData
152+
inputWif string
153+
expectedRawTx string
154+
expectedNil bool
155+
expectedError bool
156+
}{
157+
{[]*Utxo{{
158+
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
159+
Vout: 0,
160+
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
161+
Satoshis: 1000,
162+
}},
163+
[]*PayToAddress{{
164+
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
165+
Satoshis: 500,
166+
}},
167+
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
168+
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
169+
"0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006b483045022100bd31b3d9fbe18468086c0470e99f096e370f0c6ff41b6bb71f1a1d5c1b068ce302204f0c83d792a40337909b8b1bcea192722161f48dc475c653b7c352baa38eea6c412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff02f4010000000000001976a9147a1980655efbfec416b2b0c663a7b3ac0b6a25d288ac00000000000000001a006a07707265666978310c6578616d706c65206461746102133700000000",
170+
false,
171+
false,
172+
},
173+
{nil,
174+
[]*PayToAddress{{
175+
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
176+
Satoshis: 500,
177+
}},
178+
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
179+
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
180+
"",
181+
true,
182+
true,
183+
},
184+
{[]*Utxo{{
185+
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
186+
Vout: 0,
187+
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
188+
Satoshis: 1000,
189+
}}, nil,
190+
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
191+
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
192+
"0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006a47304402205ba1a246371bf8db3fb6dfa75e1edaa18b6b86dc1775dc3f2aa3c38f22803ccc022057850f794ebf78e542228d301420d4ec896c30a2bc009b7e55c66120f6c5a57a412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff0100000000000000001a006a07707265666978310c6578616d706c65206461746102133700000000",
193+
false,
194+
false,
195+
},
196+
{[]*Utxo{{
197+
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
198+
Vout: 0,
199+
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
200+
Satoshis: 1000,
201+
}}, nil,
202+
nil,
203+
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
204+
"0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006a47304402200083bb297d53210cf9379b3f47de2eff38e6906e5982fbfeef9bf59778750f3e022046da020811e9a2d1e6db8da103d17598abc194125612be6b108d49cb60cbca95412102ea87d1fd77d169bd56a71e700628113d0f8dfe57faa0ba0e55a36f9ce8e10be3ffffffff0000000000",
205+
false,
206+
false,
207+
},
208+
{[]*Utxo{{
209+
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
210+
Vout: 0,
211+
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
212+
Satoshis: 1000,
213+
}},
214+
[]*PayToAddress{{
215+
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
216+
Satoshis: 500,
217+
}},
218+
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
219+
"",
220+
"",
221+
true,
222+
true,
223+
},
224+
{[]*Utxo{{
225+
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
226+
Vout: 0,
227+
ScriptSig: "invalid-script",
228+
Satoshis: 1000,
229+
}},
230+
[]*PayToAddress{{
231+
Address: "1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL",
232+
Satoshis: 500,
233+
}},
234+
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
235+
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
236+
"",
237+
true,
238+
true,
239+
},
240+
{[]*Utxo{{
241+
TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576",
242+
Vout: 0,
243+
ScriptSig: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
244+
Satoshis: 1000,
245+
}},
246+
[]*PayToAddress{{
247+
Address: "invalid-address",
248+
Satoshis: 500,
249+
}},
250+
[]OpReturnData{{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}}},
251+
"L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu",
252+
"",
253+
true,
254+
true,
255+
},
256+
}
257+
258+
// Run tests
259+
for _, test := range tests {
260+
if rawTx, err := CreateTx(test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif); err != nil && !test.expectedError {
261+
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())
262+
} else if err == nil && test.expectedError {
263+
t.Errorf("%s Failed: [%v] [%v] [%v] [%s] inputted and error was expected", t.Name(), test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif)
264+
} else if rawTx == nil && !test.expectedNil {
265+
t.Errorf("%s Failed: [%v] [%v] [%v] [%s] inputted and nil was not expected", t.Name(), test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif)
266+
} else if rawTx != nil && test.expectedNil {
267+
t.Errorf("%s Failed: [%v] [%v] [%v] [%s] inputted and nil was expected", t.Name(), test.inputUtxos, test.inputAddresses, test.inputOpReturns, test.inputWif)
268+
} else if rawTx != nil && rawTx.ToString() != test.expectedRawTx {
269+
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())
270+
}
271+
}
91272
}

0 commit comments

Comments
 (0)