Completed
Branch master (5cb30d)
by
unknown
02:00
created

TransactionBuilder::getUtxos()   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\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
42 12
    public function __construct() {
43 12
    }
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
     * @return $this
55
     */
56 7
    public function spendOutput($txId, $index, $value = null, $address = null, $scriptPubKey = null, $path = null, $redeemScript = null, $witnessScript = null, $signMode = SignInfo::MODE_SIGN) {
57 7
        $address = $address instanceof AddressInterface ? $address : AddressFactory::fromString($address);
58 7
        $scriptPubKey = ($scriptPubKey instanceof ScriptInterface)
59
            ? $scriptPubKey
60 7
            : (ctype_xdigit($scriptPubKey) ? ScriptFactory::fromHex($scriptPubKey) : null);
61 7
        $redeemScript = ($redeemScript instanceof ScriptInterface)
62
            ? $redeemScript
63 7
            : (ctype_xdigit($redeemScript) ? ScriptFactory::fromHex($redeemScript) : null);
64 7
        $witnessScript = ($witnessScript instanceof ScriptInterface)
65
            ? $witnessScript
66 7
            : (ctype_xdigit($witnessScript) ? ScriptFactory::fromHex($witnessScript) : null);
67
68 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 56 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 56 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...
69
70 7
        return $this;
71
    }
72
73
    /**
74
     * @return UTXO[]
75
     */
76 7
    public function getUtxos() {
77 7
        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
     * @param string $address
94
     * @param int    $value
95
     * @return $this
96
     * @throws \Exception
97
     */
98 12
    public function addRecipient($address, $value) {
99 12
        $object = AddressFactory::fromString($address);
100 12
        if ($object->getAddress() != $address) {
101
            throw new \Exception("Invalid address [{$address}]");
102
        }
103
104
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
105 12
        if (!is_int($value)) {
106
            throw new \Exception("Values should be in Satoshis (int)");
107
        }
108
109 12
        if ($value <= Blocktrail::DUST) {
110
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
111
        }
112
113 12
        if ($object instanceof SegwitAddress) {
114 1
            $this->addOutput([
115 1
                'scriptPubKey' => $object->getScriptPubKey(),
116 1
                'value' => $value
117
            ]);
118
        } else {
119 11
            $this->addOutput([
120 11
                'address' => $address,
121 11
                'value' => $value
122
            ]);
123
        }
124
125 12
        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 12
    public function addOutput($output) {
136 12
        $this->outputs[] = $output;
137
138 12
        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
        return $this;
175
    }
176
177
    /**
178
     * add OP_RETURN output
179
     *
180
     * $data will be bin2hex and will be prefixed with a proper OP_PUSHDATA
181
     *
182
     * @param string $data
183
     * @param bool   $allowNonStandard  when TRUE will allow scriptPubKey > 80 bytes (so $data > 80 bytes)
184
     * @return $this
185
     * @throws BlocktrailSDKException
186
     */
187
    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
        }
191
192
        $script = ScriptFactory::create()
193
            ->op('OP_RETURN')
194
            ->push(new Buffer($data))
195
            ->getScript()
196
        ;
197
198
        $this->addOutput([
199
            'scriptPubKey' => $script,
200
            'value' => 0
201
        ]);
202
203
        return $this;
204
    }
205
206
    /**
207
     * @param bool $json return data for JSON return (so objects -> string)
208
     * @return array
209
     */
210
    public function getOutputs($json = false) {
211 12
        return array_map(function ($output) use ($json) {
212 12
            $result = $output;
213
214 12
            if ($json) {
215 10
                if (isset($result['scriptPubKey']) && $result['scriptPubKey'] instanceof ScriptInterface) {
216 1
                    $result['scriptPubKey'] = $result['scriptPubKey']->getHex();
217
                }
218 10
                if (isset($result['address']) && $result['address'] instanceof AddressInterface) {
219
                    $result['address'] = $result['address']->getAddress();
220
                }
221
            }
222
223 12
            return $result;
224 12
        }, $this->outputs);
225
    }
226
227
    /**
228
     * set change address
229
     *
230
     * @param string $address
231
     * @return $this
232
     */
233 9
    public function setChangeAddress($address) {
234 9
        $this->changeAddress = $address;
235
236 9
        return $this;
237
    }
238
239
    /**
240
     * @return string|null
241
     */
242 5
    public function getChangeAddress() {
243 5
        return $this->changeAddress;
244
    }
245
246
    /**
247
     * @param string $feeStrategy
248
     * @return $this
249
     * @throws BlocktrailSDKException
250
     */
251 12
    public function setFeeStrategy($feeStrategy) {
252 12
        $this->feeStrategy = $feeStrategy;
253
254 12
        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 12
        return $this;
259
    }
260
261
    /**
262
     * @return string
263
     */
264 12
    public function getFeeStrategy() {
265 12
        return $this->feeStrategy;
266
    }
267
268
    /**
269
     * @param bool $randomizeChangeOutput
270
     * @return $this
271
     */
272 9
    public function randomizeChangeOutput($randomizeChangeOutput = true) {
273 9
        $this->randomizeChangeOutput = $randomizeChangeOutput;
274
275 9
        return $this;
276
    }
277
278
    /**
279
     * @return bool
280
     */
281 7
    public function shouldRandomizeChangeOuput() {
282 7
        return $this->randomizeChangeOutput;
283
    }
284
285
    /**
286
     * set desired fee (normally automatically calculated)
287
     *
288
     * @param int $value
289
     * @return $this
290
     */
291 1
    public function setFee($value) {
292
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
293 1
        if (!is_int($value)) {
294
            throw new \Exception("Fee should be in Satoshis (int) - can be 0");
295
        }
296
297 1
        $this->fee = $value;
298
299 1
        return $this;
300
    }
301
302
    /**
303
     * @return int|null
304
     */
305 7
    public function getFee() {
306 7
        return $this->fee;
307
    }
308
309
    /**
310
     * @param int $fee
311
     * @return $this
312
     */
313 5
    public function validateFee($fee) {
314 5
        $this->validateFee = $fee;
315
316 5
        return $this;
317
    }
318
319
    /**
320
     * @return int|null
321
     */
322 7
    public function getValidateFee() {
323 7
        return $this->validateFee;
324
    }
325
}
326