-
Notifications
You must be signed in to change notification settings - Fork 0
/
transaction.go
174 lines (146 loc) · 4.19 KB
/
transaction.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package transaction
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/nkuba/btc-tools/pkg/chain"
)
type TransactionData struct {
// Source transaction
SourceTxHash string `json:"sourceTxHash"`
SourceTxOutputIndex uint32 `json:"sourceTxOutputIndex"`
SourceTxOutputAmount uint64 `json:"sourceTxOutputAmount"`
SourceTxOutputScript string `json:"sourceTxOutputScript"`
// Output 1
DestinationAddress1 string `json:"output1Address"`
FundingAmount uint64 `json:"output1Amount"`
// Output 2
DestinationAddress2 string `json:"output2Address"`
Fee uint64 `json:"fee"`
}
func CreateAndPublish(
txData *TransactionData,
chain *chain.BTC,
signer *Signer,
networkParams *chaincfg.Params,
) (string, error) {
// Calculate value for output 2.
outputAmount2 := txData.SourceTxOutputAmount -
txData.FundingAmount -
txData.Fee
// Create unsigned transaction
msgTx, err := CreateUnsigned(
txData.SourceTxHash,
txData.SourceTxOutputIndex,
txData.SourceTxOutputAmount,
txData.DestinationAddress1,
txData.DestinationAddress2,
txData.FundingAmount,
outputAmount2,
networkParams,
)
if err != nil {
log.Fatalf("cannot create unsigned transaction: [%s]", err)
}
// Sign transaction.
inputToSignIndex := uint32(0) // we support only transactions with one input
sourceOutputScript, err := hex.DecodeString(txData.SourceTxOutputScript)
if err != nil {
log.Fatalf("cannot decode subscript: [%s]", err)
}
if txscript.IsWitnessProgram(sourceOutputScript) {
signer.SignWitness(
msgTx,
sourceOutputScript,
inputToSignIndex,
txData.SourceTxOutputAmount,
)
} else {
signer.SignNoWitness(
msgTx,
sourceOutputScript,
inputToSignIndex,
)
}
// Publish transaction.
rawTransaction, err := Serialize(msgTx)
if err != nil {
return "", fmt.Errorf("transaction serialization failed: [%s]", err)
}
transactionHash, err := chain.PublishTransaction(rawTransaction)
if err != nil {
log.Fatalf("transaction publication failed: [%v]", err)
}
return transactionHash, nil
}
func ReadTransactionData(filePath string) (*TransactionData, error) {
var txData *TransactionData
bytes, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
json.Unmarshal(bytes, &txData)
return txData, nil
}
func CreateUnsigned(
sourceTxHashString string,
sourceTxOutputIndex uint32,
sourceTxOutputAmount uint64,
outputAddress1 string,
outputAddress2 string,
outputAmount1 uint64,
outputAmount2 uint64,
networkParams *chaincfg.Params,
) (*wire.MsgTx, error) {
var msgTx *wire.MsgTx
addTxOutput := func(address string, amount uint64) error {
addr, err := btcutil.DecodeAddress(address, networkParams)
if err != nil {
return fmt.Errorf("address decoding failed [%s]", err)
}
outScript, err := txscript.PayToAddrScript(addr)
if err != nil {
return fmt.Errorf("script to pay creation failed [%s]", err)
}
msgTx.AddTxOut(wire.NewTxOut(int64(amount), outScript))
return nil
}
// Initialize transaction message
msgTx = wire.NewMsgTx(wire.TxVersion)
// Create Transaction input based on the output of historic transaction
sourceTxHash, err := chainhash.NewHashFromStr(sourceTxHashString)
if err != nil {
return nil, fmt.Errorf("cannot create source transaction hash [%s]", err)
}
txIn := wire.NewTxIn(
wire.NewOutPoint(sourceTxHash, sourceTxOutputIndex),
nil,
nil)
msgTx.AddTxIn(txIn)
// Transaction Output 1.
if err := addTxOutput(outputAddress1, outputAmount1); err != nil {
return nil, fmt.Errorf("output 1 creation failed [%s]", err)
}
// Transaction Output 2.
if err := addTxOutput(outputAddress2, outputAmount2); err != nil {
return nil, fmt.Errorf("output 2 creation failed [%s]", err)
}
return msgTx, nil
}
// Serialize encodes a bitcoin transaction message to a hexadecimal format.
func Serialize(msgTx *wire.MsgTx) ([]byte, error) {
var buffer bytes.Buffer
err := msgTx.Serialize(&buffer)
if err != nil {
return nil, fmt.Errorf("cannot serialize transaction [%s]", err)
}
return buffer.Bytes(), nil
}