Completed
Branch master (ebb53c)
by
unknown
05:48
created

TransactionBuilder::setUtxos()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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