Completed
Pull Request — master (#85)
by thomas
69:21
created

TransactionBuilder::spendOutput()   B

Complexity

Conditions 8
Paths 128

Size

Total Lines 16
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 13
nc 128
nop 8
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 8
rs 7
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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