Completed
Branch master (65dcba)
by
unknown
05:28
created

TransactionBuilder::setFee()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 2
Metric Value
dl 0
loc 10
c 3
b 0
f 2
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
3
namespace Blocktrail\SDK;
4
5
use BitWasp\BitcoinLib\BitcoinLib;
6
use BitWasp\BitcoinLib\RawTransaction;
7
use Blocktrail\SDK\Exceptions\BlocktrailSDKException;
8
9
/**
10
 * Class TransactionBuilder
11
 *
12
 * still WIP so unsure if API remains the same, keep this in mind when updating the SDK!
13
 */
14
class TransactionBuilder {
15
16
    const OP_RETURN = '6a';
17
18
    /**
19
     * @var UTXO[]
20
     */
21
    private $utxos = [];
22
23
    /**
24
     * @var array[]
25
     */
26
    private $outputs = [];
27
28
    private $changeAddress = null;
29
    private $randomizeChangeOutput = true;
30
31
    private $fee = null;
32
33
    private $validateFee = null;
34
35
    private $feeStrategy = Wallet::FEE_STRATEGY_OPTIMAL;
36
37
    public function __construct() {
38
39
    }
40
41
    /**
42
     * @param string $txId                   transactionId (hash)
43
     * @param int    $index                  index of the output being spent
44
     * @param string $value                  when NULL we'll use the data API to fetch the value
45
     * @param string $address                when NULL we'll use the data API to fetch the address
46
     * @param string $scriptPubKey           as HEX, when NULL we'll use the data API to fetch the scriptpubkey
47
     * @param string $path                   when NULL we'll use the API to determine the path for the specified address
48
     * @param string $redeemScript           when NULL we'll use the path to determine the redeemscript
49
     * @return $this
50
     */
51
    public function spendOutput($txId, $index, $value = null, $address = null, $scriptPubKey = null, $path = null, $redeemScript = null) {
52
        $this->utxos[] = new UTXO($txId, $index, $value, $address, $scriptPubKey, $path, $redeemScript);
53
54
        return $this;
55
    }
56
57
    /**
58
     * @return UTXO[]
59
     */
60
    public function getUtxos() {
61
        return $this->utxos;
62
    }
63
64
    /**
65
     * replace the currently set UTXOs with a new set
66
     *
67
     * @param UTXO[] $utxos
68
     * @return $this
69
     */
70
    public function setUtxos(array $utxos) {
71
       $this->utxos = $utxos;
72
73
        return $this;
74
    }
75
76
    /**
77
     * @param string $address
78
     * @param int    $value
79
     * @return $this
80
     * @throws \Exception
81
     */
82
    public function addRecipient($address, $value) {
83
        if (!BitcoinLib::validate_address($address)) {
84
            throw new \Exception("Invalid address [{$address}]");
85
        }
86
87
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
88
        if (!is_int($value)) {
89
            throw new \Exception("Values should be in Satoshis (int)");
90
        }
91
92
        if ($value <= Blocktrail::DUST) {
93
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
94
        }
95
96
        $this->addOutput([
97
            'address' => $address,
98
            'value' => $value
99
        ]);
100
101
        return $this;
102
    }
103
104
    /**
105
     * add a 'raw' output, normally addRecipient or addOpReturn should be used
106
     *
107
     * @param array $output     [value => int, address => string]
108
     *                          or [value => int, scriptPubKey => string] (scriptPubKey should be hex)
109
     * @return $this
110
     */
111
    public function addOutput($output) {
112
        $this->outputs[] = $output;
113
114
        return $this;
115
    }
116
117
    /**
118
     * @param $idx
119
     * @param $output
120
     * @return $this
121
     */
122
    public function replaceOutput($idx, $output) {
123
        $this->outputs[$idx] = $output;
124
125
        return $this;
126
    }
127
128
    /**
129
     * @param $idx
130
     * @param $value
131
     * @return $this
132
     * @throws \Exception
133
     */
134
    public function updateOutputValue($idx, $value) {
135
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
136
        if (!is_int($value)) {
137
            throw new \Exception("Values should be in Satoshis (int)");
138
        }
139
140
        if ($value <= Blocktrail::DUST) {
141
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
142
        }
143
144
        if (!isset($this->outputs[$idx])) {
145
            throw new \Exception("No output for index [{$idx}]");
146
        }
147
148
        $this->outputs[$idx]['value'] = $value;
149
150
        return $this;
151
    }
152
153
    /**
154
     * add OP_RETURN output
155
     *
156
     * $data will be bin2hex and will be prefixed with a proper OP_PUSHDATA
157
     *
158
     * @param string $data
159
     * @param bool   $allowNonStandard when TRUE will allow scriptPubKey > 40 bytes (so $data > 39 bytes)
160
     * @return $this
161
     * @throws BlocktrailSDKException
162
     */
163
    public function addOpReturn($data, $allowNonStandard = false) {
164
        $pushdata = RawTransaction::pushdata(bin2hex($data));
165
166
        if (!$allowNonStandard && strlen($pushdata) / 2 > 40) {
167
            throw new BlocktrailSDKException("OP_RETURN data should be <= 39 bytes to remain standard!");
168
        }
169
170
        $this->addOutput([
171
            'scriptPubKey' => self::OP_RETURN . RawTransaction::pushdata(bin2hex($data)),
172
            'value' => 0
173
        ]);
174
175
        return $this;
176
    }
177
178
    /**
179
     * @return array
180
     */
181
    public function getOutputs() {
182
        return $this->outputs;
183
    }
184
185
    /**
186
     * set change address
187
     *
188
     * @param string $address
189
     * @return $this
190
     */
191
    public function setChangeAddress($address) {
192
        $this->changeAddress = $address;
193
194
        return $this;
195
    }
196
197
    /**
198
     * @return string|null
199
     */
200
    public function getChangeAddress() {
201
        return $this->changeAddress;
202
    }
203
204
    /**
205
     * @param string $feeStrategy
206
     * @return $this
207
     * @throws BlocktrailSDKException
208
     */
209
    public function setFeeStrategy($feeStrategy) {
210
        $this->feeStrategy = $feeStrategy;
211
212
        if (!in_array($feeStrategy, [Wallet::FEE_STRATEGY_BASE_FEE, Wallet::FEE_STRATEGY_OPTIMAL, Wallet::FEE_STRATEGY_LOW_PRIORITY])) {
213
            throw new BlocktrailSDKException("Unknown feeStrategy [{$feeStrategy}]");
214
        }
215
216
        return $this;
217
    }
218
219
    /**
220
     * @return string
221
     */
222
    public function getFeeStrategy() {
223
        return $this->feeStrategy;
224
    }
225
226
    /**
227
     * @param bool $randomizeChangeOutput
228
     * @return $this
229
     */
230
    public function randomizeChangeOutput($randomizeChangeOutput = true) {
231
        $this->randomizeChangeOutput = $randomizeChangeOutput;
232
233
        return $this;
234
    }
235
236
    /**
237
     * @return bool
238
     */
239
    public function shouldRandomizeChangeOuput() {
240
        return $this->randomizeChangeOutput;
241
    }
242
243
    /**
244
     * set desired fee (normally automatically calculated)
245
     *
246
     * @param int $value
247
     * @return $this
248
     */
249
    public function setFee($value) {
250
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
251
        if (!is_int($value)) {
252
            throw new \Exception("Fee should be in Satoshis (int) - can be 0");
253
        }
254
255
        $this->fee = $value;
256
257
        return $this;
258
    }
259
260
    /**
261
     * @return int|null
262
     */
263
    public function getFee() {
264
        return $this->fee;
265
    }
266
267
    /**
268
     * @param int $fee
269
     * @return $this
270
     */
271
    public function validateFee($fee) {
272
        $this->validateFee = $fee;
273
274
        return $this;
275
    }
276
277
    /**
278
     * @return int|null
279
     */
280
    public function getValidateFee() {
281
        return $this->validateFee;
282
    }
283
}
284