Completed
Pull Request — master (#89)
by thomas
20:32
created

TransactionBuilder::getOutputs()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 6.0493

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 1
nop 1
dl 0
loc 16
ccs 8
cts 9
cp 0.8889
crap 6.0493
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace Blocktrail\SDK;
4
5
use BitWasp\Bitcoin\Address\AddressFactory;
6
use BitWasp\Bitcoin\Address\AddressInterface;
7
use BitWasp\Bitcoin\Address\SegwitAddress;
8
use BitWasp\Bitcoin\Network\NetworkInterface;
9
use BitWasp\Bitcoin\Script\Script;
10
use BitWasp\Bitcoin\Script\ScriptFactory;
11
use BitWasp\Bitcoin\Script\ScriptInterface;
12
use BitWasp\Buffertools\Buffer;
13
use Blocktrail\SDK\Exceptions\BlocktrailSDKException;
14
15
/**
16
 * Class TransactionBuilder
17
 *
18
 * still WIP so unsure if API remains the same, keep this in mind when updating the SDK!
19
 */
20
class TransactionBuilder {
21
22
    const OP_RETURN = '6a';
23
24
    /**
25
     * @var UTXO[]
26
     */
27
    private $utxos = [];
28
29
    /**
30
     * @var array[]
31
     */
32
    private $outputs = [];
33
34
    private $changeAddress = null;
35
    private $randomizeChangeOutput = true;
36
37
    private $fee = null;
38
39
    private $validateFee = null;
40
41
    private $feeStrategy = Wallet::FEE_STRATEGY_OPTIMAL;
42
43
    /**
44
     * @var NetworkInterface
45
     */
46
    private $network;
47
48 13
    public function __construct(NetworkInterface $network) {
49 13
        $this->network = $network;
50 13
    }
51
52
    /**
53
     * @param string $txId                   transactionId (hash)
54
     * @param int    $index                  index of the output being spent
55
     * @param string $value                  when NULL we'll use the data API to fetch the value
56
     * @param AddressInterface|string $address                when NULL we'll use the data API to fetch the address
57
     * @param ScriptInterface|string $scriptPubKey           as HEX, when NULL we'll use the data API to fetch the scriptpubkey
58
     * @param string $path                   when NULL we'll use the API to determine the path for the specified address
59
     * @param ScriptInterface|string $redeemScript           when NULL we'll use the path to determine the $redeemScript
60
     * @param ScriptInterface|string $witnessScript          when NULL we'll use the path to determine the $witnessScript
61
     * @return $this
62
     */
63 7
    public function spendOutput($txId, $index, $value = null, $address = null, $scriptPubKey = null, $path = null, $redeemScript = null, $witnessScript = null, $signMode = SignInfo::MODE_SIGN) {
64 7
        $address = $address instanceof AddressInterface ? $address : AddressFactory::fromString($address, $this->network);
65 7
        $scriptPubKey = ($scriptPubKey instanceof ScriptInterface)
66
            ? $scriptPubKey
67 7
            : (ctype_xdigit($scriptPubKey) ? ScriptFactory::fromHex($scriptPubKey) : null);
68 7
        $redeemScript = ($redeemScript instanceof ScriptInterface)
69
            ? $redeemScript
70 7
            : (ctype_xdigit($redeemScript) ? ScriptFactory::fromHex($redeemScript) : null);
71 7
        $witnessScript = ($witnessScript instanceof ScriptInterface)
72
            ? $witnessScript
73 7
            : (ctype_xdigit($witnessScript) ? ScriptFactory::fromHex($witnessScript) : null);
74
75 7
        $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 63 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 63 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...
76
77 7
        return $this;
78
    }
79
80
    /**
81
     * @return UTXO[]
82
     */
83 7
    public function getUtxos() {
84 7
        return $this->utxos;
85
    }
86
87
    /**
88
     * replace the currently set UTXOs with a new set
89
     *
90
     * @param UTXO[] $utxos
91
     * @return $this
92
     */
93
    public function setUtxos(array $utxos) {
94
        $this->utxos = $utxos;
95
96
        return $this;
97
    }
98
99
    /**
100
     * @param string $address
101
     * @param int    $value
102
     * @return $this
103
     * @throws \Exception
104
     */
105 13
    public function addRecipient($address, $value) {
106 13
        $object = AddressFactory::fromString($address, $this->network);
107 13
        if ($object->getAddress($this->network) != $address) {
108
            throw new \Exception("Invalid address [{$address}]");
109
        }
110
111
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
112 13
        if (!is_int($value)) {
113
            throw new \Exception("Values should be in Satoshis (int)");
114
        }
115
116 13
        if ($value <= Blocktrail::DUST) {
117
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
118
        }
119
120 13
        if ($object instanceof SegwitAddress) {
121 1
            $this->addOutput([
122 1
                'scriptPubKey' => $object->getScriptPubKey(),
123 1
                'value' => $value
124
            ]);
125
        } else {
126 12
            $this->addOutput([
127 12
                'address' => $address,
128 12
                'value' => $value
129
            ]);
130
        }
131
132 13
        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 13
    public function addOutput($output) {
143 13
        $this->outputs[] = $output;
144
145 13
        return $this;
146
    }
147
148
    /**
149
     * @param $idx
150
     * @param $output
151
     * @return $this
152
     */
153
    public function replaceOutput($idx, $output) {
154
        $this->outputs[$idx] = $output;
155
156
        return $this;
157
    }
158
159
    /**
160
     * @param $idx
161
     * @param $value
162
     * @return $this
163
     * @throws \Exception
164
     */
165
    public function updateOutputValue($idx, $value) {
166
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
167
        if (!is_int($value)) {
168
            throw new \Exception("Values should be in Satoshis (int)");
169
        }
170
171
        if ($value <= Blocktrail::DUST) {
172
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
173
        }
174
175
        if (!isset($this->outputs[$idx])) {
176
            throw new \Exception("No output for index [{$idx}]");
177
        }
178
179
        $this->outputs[$idx]['value'] = $value;
180
181
        return $this;
182
    }
183
184
    /**
185
     * add OP_RETURN output
186
     *
187
     * $data will be bin2hex and will be prefixed with a proper OP_PUSHDATA
188
     *
189
     * @param string $data
190
     * @param bool   $allowNonStandard  when TRUE will allow scriptPubKey > 80 bytes (so $data > 80 bytes)
191
     * @return $this
192
     * @throws BlocktrailSDKException
193
     */
194 1
    public function addOpReturn($data, $allowNonStandard = false) {
195 1
        if (!$allowNonStandard && strlen($data) / 2 > 79) {
196
            throw new BlocktrailSDKException("OP_RETURN data should be <= 79 bytes to remain standard!");
197
        }
198
199 1
        $script = ScriptFactory::create()
200 1
            ->op('OP_RETURN')
201 1
            ->push(new Buffer($data))
202 1
            ->getScript()
203
        ;
204
205 1
        $this->addOutput([
206 1
            'scriptPubKey' => $script,
207 1
            'value' => 0
208
        ]);
209
210 1
        return $this;
211
    }
212
213
    /**
214
     * @param bool $json return data for JSON return (so objects -> string)
215
     * @return array
216
     */
217
    public function getOutputs($json = false) {
218 13
        return array_map(function ($output) use ($json) {
219 13
            $result = $output;
220
221 13
            if ($json) {
222 11
                if (isset($result['scriptPubKey']) && $result['scriptPubKey'] instanceof ScriptInterface) {
223 2
                    $result['scriptPubKey'] = $result['scriptPubKey']->getHex();
224
                }
225 11
                if (isset($result['address']) && $result['address'] instanceof AddressInterface) {
226
                    $result['address'] = $result['address']->getAddress();
227
                }
228
            }
229
230 13
            return $result;
231 13
        }, $this->outputs);
232
    }
233
234
    /**
235
     * set change address
236
     *
237
     * @param string $address
238
     * @return $this
239
     */
240 10
    public function setChangeAddress($address) {
241 10
        $this->changeAddress = $address;
242
243 10
        return $this;
244
    }
245
246
    /**
247
     * @return string|null
248
     */
249 5
    public function getChangeAddress() {
250 5
        return $this->changeAddress;
251
    }
252
253
    /**
254
     * @param string $feeStrategy
255
     * @return $this
256
     * @throws BlocktrailSDKException
257
     */
258 13
    public function setFeeStrategy($feeStrategy) {
259 13
        $this->feeStrategy = $feeStrategy;
260
261 13
        if (!in_array($feeStrategy, [Wallet::FEE_STRATEGY_BASE_FEE, Wallet::FEE_STRATEGY_OPTIMAL, Wallet::FEE_STRATEGY_LOW_PRIORITY])) {
262
            throw new BlocktrailSDKException("Unknown feeStrategy [{$feeStrategy}]");
263
        }
264
265 13
        return $this;
266
    }
267
268
    /**
269
     * @return string
270
     */
271 13
    public function getFeeStrategy() {
272 13
        return $this->feeStrategy;
273
    }
274
275
    /**
276
     * @param bool $randomizeChangeOutput
277
     * @return $this
278
     */
279 10
    public function randomizeChangeOutput($randomizeChangeOutput = true) {
280 10
        $this->randomizeChangeOutput = $randomizeChangeOutput;
281
282 10
        return $this;
283
    }
284
285
    /**
286
     * @return bool
287
     */
288 7
    public function shouldRandomizeChangeOuput() {
289 7
        return $this->randomizeChangeOutput;
290
    }
291
292
    /**
293
     * set desired fee (normally automatically calculated)
294
     *
295
     * @param int $value
296
     * @return $this
297
     */
298 2
    public function setFee($value) {
299
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
300 2
        if (!is_int($value)) {
301
            throw new \Exception("Fee should be in Satoshis (int) - can be 0");
302
        }
303
304 2
        $this->fee = $value;
305
306 2
        return $this;
307
    }
308
309
    /**
310
     * @return int|null
311
     */
312 7
    public function getFee() {
313 7
        return $this->fee;
314
    }
315
316
    /**
317
     * @param int $fee
318
     * @return $this
319
     */
320 5
    public function validateFee($fee) {
321 5
        $this->validateFee = $fee;
322
323 5
        return $this;
324
    }
325
326
    /**
327
     * @return int|null
328
     */
329 7
    public function getValidateFee() {
330 7
        return $this->validateFee;
331
    }
332
}
333