Completed
Pull Request — master (#99)
by thomas
18:19
created

TransactionBuilder::addOpReturn()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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