Skip to content

Commit

Permalink
Merge pull request #2 from BitcoinSchema/feature/transactions
Browse files Browse the repository at this point in the history
Feature: Transactions (Basic)
  • Loading branch information
mrz1836 authored Oct 2, 2020
2 parents 05cd38a + 8346b19 commit f2edcf4
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
7 changes: 7 additions & 0 deletions bitcoin.go
Original file line number Diff line number Diff line change
@@ -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
83 changes: 83 additions & 0 deletions transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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 [][]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) {

// 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.NewOpReturnParts(op); 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
}
91 changes: 91 additions & 0 deletions transaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package bitcoin

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) {

// 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
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 {
t.Fatalf("error occurred: %s", err.Error())
}

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

0 comments on commit f2edcf4

Please sign in to comment.