Completed
Pull Request — master (#118)
by Ruben de
09:49 queued 04:39
created

TransactionBuilder::getChangeAddress()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
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 8
    public function __construct(AddressReaderBase $addressReader) {
55 8
        $this->addressReader = $addressReader;
56 8
        $this->outputNormalizer = new OutputsNormalizer($this->addressReader);
57 8
    }
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 8
    public function spendOutput($txId, $index, $value = null, $address = null, $scriptPubKey = null, $path = null, $redeemScript = null, $witnessScript = null, $signMode = SignInfo::MODE_SIGN) {
71 8
        $address = $address instanceof AddressInterface ? $address : $this->addressReader->fromString($address);
72 8
        $scriptPubKey = ($scriptPubKey instanceof ScriptInterface)
73
            ? $scriptPubKey
74 8
            : (ctype_xdigit($scriptPubKey) ? ScriptFactory::fromHex($scriptPubKey) : null);
75 8
        $redeemScript = ($redeemScript instanceof ScriptInterface)
76
            ? $redeemScript
77 8
            : (ctype_xdigit($redeemScript) ? ScriptFactory::fromHex($redeemScript) : null);
78 8
        $witnessScript = ($witnessScript instanceof ScriptInterface)
79
            ? $witnessScript
80 8
            : (ctype_xdigit($witnessScript) ? ScriptFactory::fromHex($witnessScript) : null);
81
82 8
        $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 8
        return $this;
85
    }
86
87
    /**
88
     * @return UTXO[]
89
     */
90 8
    public function getUtxos() {
91 8
        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 2
    public function addRecipient($address, $value) {
113 2
        $object = $this->addressReader->fromString($address);
114 2
        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 2
        if (!is_int($value)) {
120
            throw new \Exception("Values should be in Satoshis (int)");
121
        }
122
123 2
        if ($value <= Blocktrail::DUST) {
124
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
125
        }
126
127 2
        $this->addOutput([
128 2
            'scriptPubKey' => $object->getScriptPubKey(),
129 2
            'value' => $value
130
        ]);
131
132 2
        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 8
    public function addOutput($output) {
143 8
        $output = $this->outputNormalizer->normalize([$output])[0];
144
145 8
        $this->outputs[] = $output;
146
147 8
        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 8
        return array_map(function ($output) use ($json) {
221 8
            $result = $output;
222
223 8
            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 8
            return $result;
233 8
        }, $this->outputs);
234
    }
235
236
    /**
237
     * set change address
238
     *
239
     * @param string $address
240
     * @return $this
241
     */
242 8
    public function setChangeAddress($address) {
243 8
        $this->changeAddress = $address;
244
245 8
        return $this;
246
    }
247
248
    /**
249
     * @return string|null
250
     */
251 8
    public function getChangeAddress() {
252 8
        return $this->changeAddress;
253
    }
254
255
    /**
256
     * @param string $feeStrategy
257
     * @return $this
258
     * @throws BlocktrailSDKException
259
     */
260 7
    public function setFeeStrategy($feeStrategy) {
261 7
        $this->feeStrategy = $feeStrategy;
262
263 7
        if (!in_array($feeStrategy, [
264 7
            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 7
        return $this;
272
    }
273
274
    /**
275
     * @return string
276
     */
277 8
    public function getFeeStrategy() {
278 8
        return $this->feeStrategy;
279
    }
280
281
    /**
282
     * @param bool $randomizeChangeOutput
283
     * @return $this
284
     */
285 7
    public function randomizeChangeOutput($randomizeChangeOutput = true) {
286 7
        $this->randomizeChangeOutput = $randomizeChangeOutput;
287
288 7
        return $this;
289
    }
290
291
    /**
292
     * @return bool
293
     */
294 8
    public function shouldRandomizeChangeOuput() {
295 8
        return $this->randomizeChangeOutput;
296
    }
297
298
    /**
299
     * set desired fee (normally automatically calculated)
300
     *
301
     * @param int $value
302
     * @return $this
303
     */
304 1
    public function setFee($value) {
305
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
306 1
        if (!is_int($value)) {
307
            throw new \Exception("Fee should be in Satoshis (int) - can be 0");
308
        }
309
310 1
        $this->fee = $value;
311
312 1
        return $this;
313
    }
314
315
    /**
316
     * @return int|null
317
     */
318 8
    public function getFee() {
319 8
        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 8
    public function getValidateFee() {
336 8
        return $this->validateFee;
337
    }
338
}
339