Completed
Pull Request — master (#127)
by thomas
14:59
created

TransactionBuilder::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Blocktrail\SDK;
4
5
use BitWasp\Bitcoin\Address\AddressInterface;
6
use BitWasp\Bitcoin\Script\ScriptFactory;
7
use BitWasp\Bitcoin\Script\ScriptInterface;
8
use BitWasp\Buffertools\Buffer;
9
use Blocktrail\SDK\Address\AddressReaderBase;
10
use Blocktrail\SDK\Exceptions\BlocktrailSDKException;
11
12
/**
13
 * Class TransactionBuilder
14
 *
15
 * still WIP so unsure if API remains the same, keep this in mind when updating the SDK!
16
 */
17
class TransactionBuilder {
18
19
    const OP_RETURN = '6a';
20
21
    /**
22
     * @var UTXO[]
23
     */
24
    private $utxos = [];
25
26
    /**
27
     * @var array[]
28
     */
29
    private $outputs = [];
30
31
    private $changeAddress = null;
32
    private $randomizeChangeOutput = true;
33
34
    private $fee = null;
35
36
    private $validateFee = null;
37
38
    private $feeStrategy = Wallet::FEE_STRATEGY_OPTIMAL;
39
40
    /**
41
     * @var AddressReaderBase
42
     */
43
    private $addressReader;
44
45
    /**
46
     * @var OutputsNormalizer
47
     */
48
    private $outputNormalizer;
49
50
    /**
51
     * TransactionBuilder constructor.
52
     * @param AddressReaderBase $addressReader
53
     */
54 17
    public function __construct(AddressReaderBase $addressReader) {
55 17
        $this->addressReader = $addressReader;
56 17
        $this->outputNormalizer = new OutputsNormalizer($this->addressReader);
57 17
    }
58
59
    /**
60
     * @param string $txId                   transactionId (hash)
61
     * @param int    $index                  index of the output being spent
62
     * @param string $value                  when NULL we'll use the data API to fetch the value
63
     * @param AddressInterface|string $address                when NULL we'll use the data API to fetch the address
64
     * @param ScriptInterface|string $scriptPubKey           as HEX, when NULL we'll use the data API to fetch the scriptpubkey
65
     * @param string $path                   when NULL we'll use the API to determine the path for the specified address
66
     * @param ScriptInterface|string $redeemScript           when NULL we'll use the path to determine the $redeemScript
67
     * @param ScriptInterface|string $witnessScript          when NULL we'll use the path to determine the $witnessScript
68
     * @return $this
69
     */
70 17
    public function spendOutput($txId, $index, $value = null, $address = null, $scriptPubKey = null, $path = null, $redeemScript = null, $witnessScript = null, $signMode = SignInfo::MODE_SIGN) {
71 17
        $address = $address instanceof AddressInterface ? $address : $this->addressReader->fromString($address);
72 17
        $scriptPubKey = ($scriptPubKey instanceof ScriptInterface)
73
            ? $scriptPubKey
74 17
            : (ctype_xdigit($scriptPubKey) ? ScriptFactory::fromHex($scriptPubKey) : null);
75 17
        $redeemScript = ($redeemScript instanceof ScriptInterface)
76
            ? $redeemScript
77 17
            : (ctype_xdigit($redeemScript) ? ScriptFactory::fromHex($redeemScript) : null);
78 17
        $witnessScript = ($witnessScript instanceof ScriptInterface)
79
            ? $witnessScript
80 17
            : (ctype_xdigit($witnessScript) ? ScriptFactory::fromHex($witnessScript) : null);
81
82 17
        $this->utxos[] = new UTXO($txId, $index, $value, $address, $scriptPubKey, $path, $redeemScript, $witnessScript, $signMode);
0 ignored issues
show
Bug introduced by
It seems like $value defined by parameter $value on line 70 can also be of type string; however, Blocktrail\SDK\UTXO::__construct() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $path defined by parameter $path on line 70 can also be of type string; however, Blocktrail\SDK\UTXO::__construct() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
83
84 17
        return $this;
85
    }
86
87
    /**
88
     * @return UTXO[]
89
     */
90 17
    public function getUtxos() {
91 17
        return $this->utxos;
92
    }
93
94
    /**
95
     * replace the currently set UTXOs with a new set
96
     *
97
     * @param UTXO[] $utxos
98
     * @return $this
99
     */
100
    public function setUtxos(array $utxos) {
101
        $this->utxos = $utxos;
102
103
        return $this;
104
    }
105
106
    /**
107
     * @param string $address
108
     * @param int    $value
109
     * @return $this
110
     * @throws \Exception
111
     */
112 11
    public function addRecipient($address, $value) {
113 11
        $object = $this->addressReader->fromString($address);
114 11
        if ($object->getAddress() != $address) {
115
            throw new \Exception("Invalid address [{$address}]");
116
        }
117
118
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
119 11
        if (!is_int($value)) {
120
            throw new \Exception("Values should be in Satoshis (int)");
121
        }
122
123 11
        if ($value <= Blocktrail::DUST) {
124
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
125
        }
126
127 11
        $this->addOutput([
128 11
            'scriptPubKey' => $object->getScriptPubKey(),
129 11
            'value' => $value
130
        ]);
131
132 11
        return $this;
133
    }
134
135
    /**
136
     * add a 'raw' output, normally addRecipient or addOpReturn should be used
137
     *
138
     * @param array $output     [value => int, address => string]
139
     *                          or [value => int, scriptPubKey => string] (scriptPubKey should be hex)
140
     * @return $this
141
     */
142 17
    public function addOutput($output) {
143 17
        $output = $this->outputNormalizer->normalize([$output])[0];
144
145 17
        $this->outputs[] = $output;
146
147 17
        return $this;
148
    }
149
150
    /**
151
     * @param $idx
152
     * @param $output
153
     * @return $this
154
     */
155
    public function replaceOutput($idx, $output) {
156
        $this->outputs[$idx] = $output;
157
158
        return $this;
159
    }
160
161
    /**
162
     * @param $idx
163
     * @param $value
164
     * @return $this
165
     * @throws \Exception
166
     */
167
    public function updateOutputValue($idx, $value) {
168
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
169
        if (!is_int($value)) {
170
            throw new \Exception("Values should be in Satoshis (int)");
171
        }
172
173
        if ($value <= Blocktrail::DUST) {
174
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
175
        }
176
177
        if (!isset($this->outputs[$idx])) {
178
            throw new \Exception("No output for index [{$idx}]");
179
        }
180
181
        $this->outputs[$idx]['value'] = $value;
182
183
        return $this;
184
    }
185
186
    /**
187
     * add OP_RETURN output
188
     *
189
     * $data will be bin2hex and will be prefixed with a proper OP_PUSHDATA
190
     *
191
     * @param string $data
192
     * @param bool   $allowNonStandard  when TRUE will allow scriptPubKey > 80 bytes (so $data > 80 bytes)
193
     * @return $this
194
     * @throws BlocktrailSDKException
195
     */
196 1
    public function addOpReturn($data, $allowNonStandard = false) {
197 1
        if (!$allowNonStandard && strlen($data) / 2 > 79) {
198
            throw new BlocktrailSDKException("OP_RETURN data should be <= 79 bytes to remain standard!");
199
        }
200
201 1
        $script = ScriptFactory::create()
202 1
            ->op('OP_RETURN')
203 1
            ->push(new Buffer($data))
204 1
            ->getScript()
205
        ;
206
207 1
        $this->addOutput([
208 1
            'scriptPubKey' => $script,
209 1
            'value' => 0
210
        ]);
211
212 1
        return $this;
213
    }
214
215
    /**
216
     * @param bool $json return data for JSON return (so objects -> string)
217
     * @return array
218
     */
219
    public function getOutputs($json = false) {
220 17
        return array_map(function ($output) use ($json) {
221 17
            $result = $output;
222
223 17
            if ($json) {
224 8
                if (isset($result['scriptPubKey']) && $result['scriptPubKey'] instanceof ScriptInterface) {
225 8
                    $result['scriptPubKey'] = $result['scriptPubKey']->getHex();
226
                }
227 8
                if (isset($result['address']) && $result['address'] instanceof AddressInterface) {
228
                    $result['address'] = $result['address']->getAddress();
229
                }
230
            }
231
232 17
            return $result;
233 17
        }, $this->outputs);
234
    }
235
236
    /**
237
     * set change address
238
     *
239
     * @param string $address
240
     * @return $this
241
     */
242 14
    public function setChangeAddress($address) {
243 14
        $this->changeAddress = $address;
244
245 14
        return $this;
246
    }
247
248
    /**
249
     * @return string|null
250
     */
251 12
    public function getChangeAddress() {
252 12
        return $this->changeAddress;
253
    }
254
255
    /**
256
     * @param string $feeStrategy
257
     * @return $this
258
     * @throws BlocktrailSDKException
259
     */
260 16
    public function setFeeStrategy($feeStrategy) {
261 16
        $this->feeStrategy = $feeStrategy;
262
263 16
        if (!in_array($feeStrategy, [
264 16
            Wallet::FEE_STRATEGY_BASE_FEE, Wallet::FEE_STRATEGY_OPTIMAL,
265
            Wallet::FEE_STRATEGY_HIGH_PRIORITY, Wallet::FEE_STRATEGY_LOW_PRIORITY,
266
            Wallet::FEE_STRATEGY_FORCE_FEE,
267
            ])) {
268
            throw new BlocktrailSDKException("Unknown feeStrategy [{$feeStrategy}]");
269
        }
270
271 16
        return $this;
272
    }
273
274
    /**
275
     * @return string
276
     */
277 16
    public function getFeeStrategy() {
278 16
        return $this->feeStrategy;
279
    }
280
281
    /**
282
     * @param bool $randomizeChangeOutput
283
     * @return $this
284
     */
285 14
    public function randomizeChangeOutput($randomizeChangeOutput = true) {
286 14
        $this->randomizeChangeOutput = $randomizeChangeOutput;
287
288 14
        return $this;
289
    }
290
291
    /**
292
     * @return bool
293
     */
294 16
    public function shouldRandomizeChangeOuput() {
295 16
        return $this->randomizeChangeOutput;
296
    }
297
298
    /**
299
     * set desired fee (normally automatically calculated)
300
     *
301
     * @param int $value
302
     * @return $this
303
     */
304 2
    public function setFee($value) {
305
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
306 2
        if (!is_int($value)) {
307
            throw new \Exception("Fee should be in Satoshis (int) - can be 0");
308
        }
309
310 2
        $this->fee = $value;
311
312 2
        return $this;
313
    }
314
315
    /**
316
     * @return int|null
317
     */
318 16
    public function getFee() {
319 16
        return $this->fee;
320
    }
321
322
    /**
323
     * @param int $fee
324
     * @return $this
325
     */
326 7
    public function validateFee($fee) {
327 7
        $this->validateFee = $fee;
328
329 7
        return $this;
330
    }
331
332
    /**
333
     * @return int|null
334
     */
335 16
    public function getValidateFee() {
336 16
        return $this->validateFee;
337
    }
338
}
339