Completed
Pull Request — master (#88)
by thomas
05:05
created

TransactionBuilder::getFeeStrategy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
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
    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
     * @return $this
55
     */
56
    public function spendOutput($txId, $index, $value = null, $address = null, $scriptPubKey = null, $path = null, $redeemScript = null, $witnessScript = null, $signMode = SignInfo::MODE_SIGN) {
57
        $address = $address instanceof AddressInterface ? $address : AddressFactory::fromString($address);
58
        $scriptPubKey = ($scriptPubKey instanceof ScriptInterface)
59
            ? $scriptPubKey
60
            : (ctype_xdigit($scriptPubKey) ? ScriptFactory::fromHex($scriptPubKey) : null);
61
        $redeemScript = ($redeemScript instanceof ScriptInterface)
62
            ? $redeemScript
63
            : (ctype_xdigit($redeemScript) ? ScriptFactory::fromHex($redeemScript) : null);
64
        $witnessScript = ($witnessScript instanceof ScriptInterface)
65
            ? $witnessScript
66
            : (ctype_xdigit($witnessScript) ? ScriptFactory::fromHex($witnessScript) : null);
67
68
        $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
        return $this;
71
    }
72
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
     * @param string $address
94
     * @param int    $value
95
     * @return $this
96
     * @throws \Exception
97
     */
98
    public function addRecipient($address, $value) {
99
        $object = AddressFactory::fromString($address);
100
        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
        if (!is_int($value)) {
106
            throw new \Exception("Values should be in Satoshis (int)");
107
        }
108
109
        if ($value <= Blocktrail::DUST) {
110
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
111
        }
112
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
            ]);
123
        }
124
125
        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
        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
        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
                }
221
            }
222
223
            return $result;
224
        }, $this->outputs);
225
    }
226
227
    /**
228
     * set change address
229
     *
230
     * @param string $address
231
     * @return $this
232
     */
233
    public function setChangeAddress($address) {
234
        $this->changeAddress = $address;
235
236
        return $this;
237
    }
238
239
    /**
240
     * @return string|null
241
     */
242
    public function getChangeAddress() {
243
        return $this->changeAddress;
244
    }
245
246
    /**
247
     * @param string $feeStrategy
248
     * @return $this
249
     * @throws BlocktrailSDKException
250
     */
251
    public function setFeeStrategy($feeStrategy) {
252
        $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
    }
260
261
    /**
262
     * @return string
263
     */
264
    public function getFeeStrategy() {
265
        return $this->feeStrategy;
266
    }
267
268
    /**
269
     * @param bool $randomizeChangeOutput
270
     * @return $this
271
     */
272
    public function randomizeChangeOutput($randomizeChangeOutput = true) {
273
        $this->randomizeChangeOutput = $randomizeChangeOutput;
274
275
        return $this;
276
    }
277
278
    /**
279
     * @return bool
280
     */
281
    public function shouldRandomizeChangeOuput() {
282
        return $this->randomizeChangeOutput;
283
    }
284
285
    /**
286
     * set desired fee (normally automatically calculated)
287
     *
288
     * @param int $value
289
     * @return $this
290
     */
291
    public function setFee($value) {
292
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
293
        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
    }
301
302
    /**
303
     * @return int|null
304
     */
305
    public function getFee() {
306
        return $this->fee;
307
    }
308
309
    /**
310
     * @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