Completed
Pull Request — master (#85)
by thomas
15:44
created

TransactionBuilder::updateOutputValue()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 4
nop 2
dl 0
loc 18
ccs 0
cts 9
cp 0
crap 20
rs 9.2
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 13
    public function __construct() {
43 13
    }
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 13
    public function addRecipient($address, $value) {
99 13
        $object = AddressFactory::fromString($address);
100 13
        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 13
        if (!is_int($value)) {
106
            throw new \Exception("Values should be in Satoshis (int)");
107
        }
108
109 13
        if ($value <= Blocktrail::DUST) {
110
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
111
        }
112
113 13
        if ($object instanceof SegwitAddress) {
114 1
            $this->addOutput([
115 1
                'scriptPubKey' => $object->getScriptPubKey(),
116 1
                'value' => $value
117
            ]);
118
        } else {
119 12
            $this->addOutput([
120 12
                'address' => $address,
121 12
                'value' => $value
122
            ]);
123
        }
124
125 13
        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 13
    public function addOutput($output) {
136 13
        $this->outputs[] = $output;
137
138 13
        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 1
    public function addOpReturn($data, $allowNonStandard = false) {
188 1
        if (!$allowNonStandard && strlen($data) / 2 > 79) {
189
            throw new BlocktrailSDKException("OP_RETURN data should be <= 79 bytes to remain standard!");
190
        }
191
192 1
        $script = ScriptFactory::create()
193 1
            ->op('OP_RETURN')
194 1
            ->push(new Buffer($data))
195 1
            ->getScript()
196
        ;
197
198 1
        $this->addOutput([
199 1
            'scriptPubKey' => $script,
200 1
            'value' => 0
201
        ]);
202
203 1
        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 13
        return array_map(function ($output) use ($json) {
212 13
            $result = $output;
213
214 13
            if ($json) {
215 11
                if (isset($result['scriptPubKey']) && $result['scriptPubKey'] instanceof ScriptInterface) {
216 2
                    $result['scriptPubKey'] = $result['scriptPubKey']->getHex();
217
                }
218 11
                if (isset($result['address']) && $result['address'] instanceof AddressInterface) {
219
                    $result['address'] = $result['address']->getAddress();
220
                }
221
            }
222
223 13
            return $result;
224 13
        }, $this->outputs);
225
    }
226
227
    /**
228
     * set change address
229
     *
230
     * @param string $address
231
     * @return $this
232
     */
233 10
    public function setChangeAddress($address) {
234 10
        $this->changeAddress = $address;
235
236 10
        return $this;
237
    }
238
239
    /**
240
     * @return string|null
241
     */
242 6
    public function getChangeAddress() {
243 6
        return $this->changeAddress;
244
    }
245
246
    /**
247
     * @param string $feeStrategy
248
     * @return $this
249
     * @throws BlocktrailSDKException
250
     */
251 13
    public function setFeeStrategy($feeStrategy) {
252 13
        $this->feeStrategy = $feeStrategy;
253
254 13
        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 13
        return $this;
259
    }
260
261
    /**
262
     * @return string
263
     */
264 13
    public function getFeeStrategy() {
265 13
        return $this->feeStrategy;
266
    }
267
268
    /**
269
     * @param bool $randomizeChangeOutput
270
     * @return $this
271
     */
272 10
    public function randomizeChangeOutput($randomizeChangeOutput = true) {
273 10
        $this->randomizeChangeOutput = $randomizeChangeOutput;
274
275 10
        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 2
    public function setFee($value) {
292
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
293 2
        if (!is_int($value)) {
294
            throw new \Exception("Fee should be in Satoshis (int) - can be 0");
295
        }
296
297 2
        $this->fee = $value;
298
299 2
        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