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

Wallet::getBlocktrailPublicKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 11
ccs 5
cts 6
cp 0.8333
crap 2.0185
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\Bitcoin;
7
use BitWasp\Bitcoin\Key\Deterministic\HierarchicalKeyFactory;
8
use BitWasp\Bitcoin\MessageSigner\MessageSigner;
9
use BitWasp\Bitcoin\Script\P2shScript;
10
use BitWasp\Bitcoin\Script\ScriptFactory;
11
use BitWasp\Bitcoin\Script\ScriptInterface;
12
use BitWasp\Bitcoin\Transaction\Factory\SignData;
13
use BitWasp\Bitcoin\Transaction\Factory\Signer;
14
use BitWasp\Bitcoin\Transaction\Factory\TxBuilder;
15
use BitWasp\Bitcoin\Transaction\OutPoint;
16
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
17
use BitWasp\Bitcoin\Transaction\Transaction;
18
use BitWasp\Bitcoin\Transaction\TransactionInterface;
19
use BitWasp\Bitcoin\Transaction\TransactionOutput;
20
use BitWasp\Buffertools\Buffer;
21
use Blocktrail\SDK\Bitcoin\BIP32Key;
22
use Blocktrail\SDK\Bitcoin\BIP32Path;
23
use Blocktrail\SDK\Exceptions\BlocktrailSDKException;
24
25
/**
26
 * Class Wallet
27
 */
28
abstract class Wallet implements WalletInterface {
29
30
    const WALLET_VERSION_V1 = 'v1';
31
    const WALLET_VERSION_V2 = 'v2';
32
    const WALLET_VERSION_V3 = 'v3';
33
34
    const BASE_FEE = 10000;
35
36
    /**
37
     * development / debug setting
38
     *  when getting a new derivation from the API,
39
     *  will verify address / redeeemScript with the values the API provides
40
     */
41
    const VERIFY_NEW_DERIVATION = true;
42
43
    /**
44
     * @var BlocktrailSDKInterface
45
     */
46
    protected $sdk;
47
48
    /**
49
     * @var string
50
     */
51
    protected $identifier;
52
53
    /**
54
     * BIP32 master primary private key (m/)
55
     *
56
     * @var BIP32Key
57
     */
58
    protected $primaryPrivateKey;
59
60
    /**
61
     * @var BIP32Key[]
62
     */
63
    protected $primaryPublicKeys;
64
65
    /**
66
     * BIP32 master backup public key (M/)
67
68
     * @var BIP32Key
69
     */
70
    protected $backupPublicKey;
71
72
    /**
73
     * map of blocktrail BIP32 public keys
74
     *  keyed by key index
75
     *  path should be `M / key_index'`
76
     *
77
     * @var BIP32Key[]
78
     */
79
    protected $blocktrailPublicKeys;
80
81
    /**
82
     * the 'Blocktrail Key Index' that is used for new addresses
83
     *
84
     * @var int
85
     */
86
    protected $keyIndex;
87
88
    /**
89
     * 'bitcoin'
90
     *
91
     * @var string
92
     */
93
    protected $network;
94
95
    /**
96
     * testnet yes / no
97
     *
98
     * @var bool
99
     */
100
    protected $testnet;
101
102
    /**
103
     * cache of public keys, by path
104
     *
105
     * @var BIP32Key[]
106
     */
107
    protected $pubKeys = [];
108
109
    /**
110
     * cache of address / redeemScript, by path
111
     *
112
     * @var string[][]      [[address, redeemScript)], ]
113
     */
114
    protected $derivations = [];
115
116
    /**
117
     * reverse cache of paths by address
118
     *
119
     * @var string[]
120
     */
121
    protected $derivationsByAddress = [];
122
123
    /**
124
     * @var WalletPath
125
     */
126
    protected $walletPath;
127
128
    protected $checksum;
129
130
    protected $locked = true;
131
132
    protected $optimalFeePerKB;
133
    protected $lowPriorityFeePerKB;
134
    protected $feePerKBAge;
135
    protected $allowedSignModes = [SignInfo::MODE_DONTSIGN, SignInfo::MODE_SIGN];
136
137
    /**
138
     * @param BlocktrailSDKInterface        $sdk                        SDK instance used to do requests
139
     * @param string                        $identifier                 identifier of the wallet
140
     * @param BIP32Key[]                    $primaryPublicKeys
141
     * @param BIP32Key                      $backupPublicKey            should be BIP32 master public key M/
142
     * @param BIP32Key[]                    $blocktrailPublicKeys
143
     * @param int                           $keyIndex
144
     * @param string                        $network
145
     * @param bool                          $testnet
146
     * @param string                        $checksum
147
     */
148 14
    public function __construct(BlocktrailSDKInterface $sdk, $identifier, array $primaryPublicKeys, $backupPublicKey, array $blocktrailPublicKeys, $keyIndex, $network, $testnet, $checksum) {
149 14
        $this->sdk = $sdk;
150
151 14
        $this->identifier = $identifier;
152 14
        $this->backupPublicKey = BlocktrailSDK::normalizeBIP32Key($backupPublicKey);
153 14
        $this->primaryPublicKeys = BlocktrailSDK::normalizeBIP32KeyArray($primaryPublicKeys);
154 14
        $this->blocktrailPublicKeys = BlocktrailSDK::normalizeBIP32KeyArray($blocktrailPublicKeys);
155
156 14
        $this->network = $network;
157 14
        $this->testnet = $testnet;
158 14
        $this->keyIndex = $keyIndex;
159 14
        $this->checksum = $checksum;
160
161 14
        $this->walletPath = WalletPath::create($this->keyIndex);
162 14
    }
163
164
    /**
165
     * return the wallet identifier
166
     *
167
     * @return string
168
     */
169 10
    public function getIdentifier() {
170 10
        return $this->identifier;
171
    }
172
173
    /**
174
     * return list of Blocktrail co-sign extended public keys
175
     *
176
     * @return array[]      [ [xpub, path] ]
177
     */
178 5
    public function getBlocktrailPublicKeys() {
179
        return array_map(function (BIP32Key $key) {
180 5
            return $key->tuple();
181 5
        }, $this->blocktrailPublicKeys);
182
    }
183
184
    /**
185
     * check if wallet is locked
186
     *
187
     * @return bool
188
     */
189 10
    public function isLocked() {
190 10
        return $this->locked;
191
    }
192
193
    /**
194
     * upgrade wallet to different blocktrail cosign key
195
     *
196
     * @param $keyIndex
197
     * @return bool
198
     * @throws \Exception
199
     */
200 5
    public function upgradeKeyIndex($keyIndex) {
201 5
        if ($this->locked) {
202 4
            throw new \Exception("Wallet needs to be unlocked to upgrade key index");
203
        }
204
205 5
        $walletPath = WalletPath::create($keyIndex);
206
207
        // do the upgrade to the new 'key_index'
208 5
        $primaryPublicKey = $this->primaryPrivateKey->buildKey((string)$walletPath->keyIndexPath()->publicPath());
209
210
        // $primaryPublicKey = BIP32::extended_private_to_public(BIP32::build_key($this->primaryPrivateKey->tuple(), (string)$walletPath->keyIndexPath()));
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
211 5
        $result = $this->sdk->upgradeKeyIndex($this->identifier, $keyIndex, $primaryPublicKey->tuple());
212
213 5
        $this->primaryPublicKeys[$keyIndex] = $primaryPublicKey;
214
215 5
        $this->keyIndex = $keyIndex;
216 5
        $this->walletPath = $walletPath;
217
218
        // update the blocktrail public keys
219 5
        foreach ($result['blocktrail_public_keys'] as $keyIndex => $pubKey) {
220 5
            if (!isset($this->blocktrailPublicKeys[$keyIndex])) {
221 5
                $path = $pubKey[1];
222 5
                $pubKey = $pubKey[0];
223 5
                $this->blocktrailPublicKeys[$keyIndex] = BIP32Key::create(HierarchicalKeyFactory::fromExtended($pubKey), $path);
224
            }
225
        }
226
227 5
        return true;
228
    }
229
230
    /**
231
     * get a new BIP32 derivation for the next (unused) address
232
     *  by requesting it from the API
233
     *
234
     * @return string
235
     * @throws \Exception
236
     */
237 10
    protected function getNewDerivation() {
238 10
        $path = $this->walletPath->path()->last("*");
239
240 10
        if (self::VERIFY_NEW_DERIVATION) {
241 10
            $new = $this->sdk->_getNewDerivation($this->identifier, (string)$path);
242
243 10
            $path = $new['path'];
244 10
            $address = $new['address'];
245 10
            $redeemScript = $new['redeem_script'];
246
247
            /** @var ScriptInterface $checkRedeemScript */
248 10
            list($checkAddress, $checkRedeemScript) = $this->getRedeemScriptByPath($path);
249
250 10
            if ($checkAddress != $address) {
251
                throw new \Exception("Failed to verify that address from API [{$address}] matches address locally [{$checkAddress}]");
252
            }
253
254 10
            if ($checkRedeemScript->getHex() != $redeemScript) {
255 10
                throw new \Exception("Failed to verify that redeemScript from API [{$redeemScript}] matches address locally [{$checkRedeemScript->getHex()}]");
256
            }
257
        } else {
258
            $path = $this->sdk->getNewDerivation($this->identifier, (string)$path);
259
        }
260
261 10
        return (string)$path;
262
    }
263
264
    /**
265
     * @param string|BIP32Path  $path
266
     * @return BIP32Key|false
267
     * @throws \Exception
268
     *
269
     * @TODO: hmm?
270
     */
271 10
    protected function getParentPublicKey($path) {
272 10
        $path = BIP32Path::path($path)->parent()->publicPath();
273
274 10
        if ($path->count() <= 2) {
275
            return false;
276
        }
277
278 10
        if ($path->isHardened()) {
279
            return false;
280
        }
281
282 10
        if (!isset($this->pubKeys[(string)$path])) {
283 10
            $this->pubKeys[(string)$path] = $this->primaryPublicKeys[$path->getKeyIndex()]->buildKey($path);
284
        }
285
286 10
        return $this->pubKeys[(string)$path];
287
    }
288
289
    /**
290
     * get address for the specified path
291
     *
292
     * @param string|BIP32Path  $path
293
     * @return string
294
     */
295 10
    public function getAddressByPath($path) {
296 10
        $path = (string)BIP32Path::path($path)->privatePath();
297 10
        if (!isset($this->derivations[$path])) {
298 10
            list($address, ) = $this->getRedeemScriptByPath($path);
299
300 10
            $this->derivations[$path] = $address;
301 10
            $this->derivationsByAddress[$address] = $path;
302
        }
303
304 10
        return $this->derivations[$path];
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->derivations[$path]; of type string[]|string adds the type string[] to the return on line 304 which is incompatible with the return type declared by the interface Blocktrail\SDK\WalletInterface::getAddressByPath of type string.
Loading history...
305
    }
306
307
    /**
308
     * get address and redeemScript for specified path
309
     *
310
     * @param string    $path
311
     * @return array[string, ScriptInterface]     [address, redeemScript]
0 ignored issues
show
Documentation introduced by
The doc-type array[string, could not be parsed: Expected "]" at position 2, but found "string". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
312
     */
313 10
    public function getRedeemScriptByPath($path) {
314 10
        $path = BIP32Path::path($path);
315
316
        // optimization to avoid doing BitcoinLib::private_key_to_public_key too much
317 10
        if ($pubKey = $this->getParentPublicKey($path)) {
318 10
            $key = $pubKey->buildKey($path->publicPath());
319
        } else {
320
            $key = $this->primaryPublicKeys[$path->getKeyIndex()]->buildKey($path);
321
        }
322
323 10
        return $this->getRedeemScriptFromKey($key, $path);
324
    }
325
326
    /**
327
     * @param BIP32Key          $key
328
     * @param string|BIP32Path  $path
329
     * @return string
330
     */
331
    protected function getAddressFromKey(BIP32Key $key, $path) {
332
        return $this->getRedeemScriptFromKey($key, $path)[0];
333
    }
334
335
    /**
336
     * @param BIP32Key          $key
337
     * @param string|BIP32Path  $path
338
     * @return array[string, ScriptInterface]                 [address, redeemScript]
0 ignored issues
show
Documentation introduced by
The doc-type array[string, could not be parsed: Expected "]" at position 2, but found "string". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
339
     * @throws \Exception
340
     */
341 10
    protected function getRedeemScriptFromKey(BIP32Key $key, $path) {
342 10
        $path = BIP32Path::path($path)->publicPath();
343
344 10
        $blocktrailPublicKey = $this->getBlocktrailPublicKey($path);
345
346 10
        $redeemScript = ScriptFactory::scriptPubKey()->multisig(2, BlocktrailSDK::sortMultisigKeys([
347 10
            $key->buildKey($path)->publicKey(),
348 10
            $this->backupPublicKey->buildKey($path->unhardenedPath())->publicKey(),
349 10
            $blocktrailPublicKey->buildKey($path)->publicKey()
350 10
        ]), false);
351
352 10
        return [(new P2shScript($redeemScript))->getAddress()->getAddress(), $redeemScript];
353
    }
354
355
    /**
356
     * get the path (and redeemScript) to specified address
357
     *
358
     * @param string $address
359
     * @return array
360
     */
361
    public function getPathForAddress($address) {
362
        return $this->sdk->getPathForAddress($this->identifier, $address);
363
    }
364
365
    /**
366
     * @param string|BIP32Path  $path
367
     * @return BIP32Key
368
     * @throws \Exception
369
     */
370 10
    public function getBlocktrailPublicKey($path) {
371 10
        $path = BIP32Path::path($path);
372
373 10
        $keyIndex = str_replace("'", "", $path[1]);
374
375 10
        if (!isset($this->blocktrailPublicKeys[$keyIndex])) {
376
            throw new \Exception("No blocktrail publickey for key index [{$keyIndex}]");
377
        }
378
379 10
        return $this->blocktrailPublicKeys[$keyIndex];
380
    }
381
382
    /**
383
     * generate a new derived key and return the new path and address for it
384
     *
385
     * @return string[]     [path, address]
386
     */
387 10
    public function getNewAddressPair() {
388 10
        $path = $this->getNewDerivation();
389 10
        $address = $this->getAddressByPath($path);
390
391 10
        return [$path, $address];
0 ignored issues
show
Best Practice introduced by
The expression return array($path, $address); seems to be an array, but some of its elements' types (string[]) are incompatible with the return type declared by the interface Blocktrail\SDK\WalletInterface::getNewAddressPair of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
392
    }
393
394
    /**
395
     * generate a new derived private key and return the new address for it
396
     *
397
     * @return string
398
     */
399 7
    public function getNewAddress() {
400 7
        return $this->getNewAddressPair()[1];
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getNewAddressPair()[1]; of type string[]|string adds the type string[] to the return on line 400 which is incompatible with the return type declared by the interface Blocktrail\SDK\WalletInterface::getNewAddress of type string.
Loading history...
401
    }
402
403
    /**
404
     * get the balance for the wallet
405
     *
406
     * @return int[]            [confirmed, unconfirmed]
407
     */
408 9
    public function getBalance() {
409 9
        $balanceInfo = $this->sdk->getWalletBalance($this->identifier);
410
411 9
        return [$balanceInfo['confirmed'], $balanceInfo['unconfirmed']];
412
    }
413
414
    /**
415
     * do wallet discovery (slow)
416
     *
417
     * @param int   $gap        the gap setting to use for discovery
418
     * @return int[]            [confirmed, unconfirmed]
419
     */
420 2
    public function doDiscovery($gap = 200) {
421 2
        $balanceInfo = $this->sdk->doWalletDiscovery($this->identifier, $gap);
422
423 2
        return [$balanceInfo['confirmed'], $balanceInfo['unconfirmed']];
424
    }
425
426
    /**
427
     * create, sign and send a transaction
428
     *
429
     * @param array    $outputs             [address => value, ] or [[address, value], ] or [['address' => address, 'value' => value], ] coins to send
430
     *                                      value should be INT
431
     * @param string   $changeAddress       change address to use (autogenerated if NULL)
432
     * @param bool     $allowZeroConf
433
     * @param bool     $randomizeChangeIdx  randomize the location of the change (for increased privacy / anonimity)
434
     * @param string   $feeStrategy
435
     * @param null|int $forceFee            set a fixed fee instead of automatically calculating the correct fee, not recommended!
436
     * @return string the txid / transaction hash
437
     * @throws \Exception
438
     */
439 8
    public function pay(array $outputs, $changeAddress = null, $allowZeroConf = false, $randomizeChangeIdx = true, $feeStrategy = self::FEE_STRATEGY_OPTIMAL, $forceFee = null) {
440 8
        if ($this->locked) {
441 4
            throw new \Exception("Wallet needs to be unlocked to pay");
442
        }
443
444 8
        $outputs = self::normalizeOutputsStruct($outputs);
445
446 8
        $txBuilder = new TransactionBuilder();
447 8
        $txBuilder->randomizeChangeOutput($randomizeChangeIdx);
448 8
        $txBuilder->setFeeStrategy($feeStrategy);
449 8
        $txBuilder->setChangeAddress($changeAddress);
450
451 8
        foreach ($outputs as $output) {
452 8
            $txBuilder->addRecipient($output['address'], $output['value']);
453
        }
454
455 8
        $this->coinSelectionForTxBuilder($txBuilder, true, $allowZeroConf, $forceFee);
456
457 2
        $apiCheckFee = $forceFee === null;
458
459 2
        return $this->sendTx($txBuilder, $apiCheckFee);
460
    }
461
462
    /**
463
     * determine max spendable from wallet after fees
464
     *
465
     * @param bool     $allowZeroConf
466
     * @param string   $feeStrategy
467
     * @param null|int $forceFee set a fixed fee instead of automatically calculating the correct fee, not recommended!
468
     * @param int      $outputCnt
469
     * @return string
470
     * @throws BlocktrailSDKException
471
     */
472
    public function getMaxSpendable($allowZeroConf = false, $feeStrategy = self::FEE_STRATEGY_OPTIMAL, $forceFee = null, $outputCnt = 1) {
473
        return $this->sdk->walletMaxSpendable($this->identifier, $allowZeroConf, $feeStrategy, $forceFee, $outputCnt);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sdk->walle...$forceFee, $outputCnt); (array) is incompatible with the return type declared by the interface Blocktrail\SDK\WalletInterface::getMaxSpendable of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
474
    }
475
476
    /**
477
     * parse outputs into normalized struct
478
     *
479
     * @param array $outputs    [address => value, ] or [[address, value], ] or [['address' => address, 'value' => value], ]
480
     * @return array            [['address' => address, 'value' => value], ]
481
     */
482 9
    public static function normalizeOutputsStruct(array $outputs) {
483 9
        $result = [];
484
485 9
        foreach ($outputs as $k => $v) {
486 9
            if (is_numeric($k)) {
487 1
                if (!is_array($v)) {
488
                    throw new \InvalidArgumentException("outputs should be [address => value, ] or [[address, value], ] or [['address' => address, 'value' => value], ]");
489
                }
490
491 1
                if (isset($v['address']) && isset($v['value'])) {
492 1
                    $address = $v['address'];
493 1
                    $value = $v['value'];
494 1
                } elseif (count($v) == 2 && isset($v[0]) && isset($v[1])) {
495 1
                    $address = $v[0];
496 1
                    $value = $v[1];
497
                } else {
498 1
                    throw new \InvalidArgumentException("outputs should be [address => value, ] or [[address, value], ] or [['address' => address, 'value' => value], ]");
499
                }
500
            } else {
501 9
                $address = $k;
502 9
                $value = $v;
503
            }
504
505 9
            $result[] = ['address' => $address, 'value' => $value];
506
        }
507
508 9
        return $result;
509
    }
510
511
    /**
512
     * 'fund' the txBuilder with UTXOs (modified in place)
513
     *
514
     * @param TransactionBuilder    $txBuilder
515
     * @param bool|true             $lockUTXOs
516
     * @param bool|false            $allowZeroConf
517
     * @param null|int              $forceFee
518
     * @return TransactionBuilder
519
     */
520 8
    public function coinSelectionForTxBuilder(TransactionBuilder $txBuilder, $lockUTXOs = true, $allowZeroConf = false, $forceFee = null) {
521
        // get the data we should use for this transaction
522 8
        $coinSelection = $this->coinSelection($txBuilder->getOutputs(/* $json = */true), $lockUTXOs, $allowZeroConf, $txBuilder->getFeeStrategy(), $forceFee);
0 ignored issues
show
Bug introduced by
It seems like $forceFee defined by parameter $forceFee on line 520 can also be of type integer; however, Blocktrail\SDK\Wallet::coinSelection() 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...
523
        
524 2
        $utxos = $coinSelection['utxos'];
525 2
        $fee = $coinSelection['fee'];
526 2
        $change = $coinSelection['change'];
0 ignored issues
show
Unused Code introduced by
$change is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
527
528 2
        if ($forceFee !== null) {
529
            $txBuilder->setFee($forceFee);
530
        } else {
531 2
            $txBuilder->validateFee($fee);
532
        }
533
534 2
        foreach ($utxos as $utxo) {
535 2
            $signMode = SignInfo::MODE_SIGN;
536 2
            if (isset($utxo['sign_mode'])) {
537
                $signMode = $utxo['sign_mode'];
538
                if (!in_array($signMode, $this->allowedSignModes)) {
539
                    throw new \Exception("Sign mode disallowed by wallet");
540
                }
541
            }
542
543 2
            $txBuilder->spendOutput($utxo['hash'], $utxo['idx'], $utxo['value'], $utxo['address'], $utxo['scriptpubkey_hex'], $utxo['path'], $utxo['redeem_script'], $signMode);
544
        }
545
546 2
        return $txBuilder;
547
    }
548
549
    /**
550
     * build inputs and outputs lists for TransactionBuilder
551
     *
552
     * @param TransactionBuilder $txBuilder
553
     * @return [TransactionInterface, SignInfo[]]
0 ignored issues
show
Documentation introduced by
The doc-type TransactionInterface,">[TransactionInterface, could not be parsed: Unknown type name "[" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
554
     * @throws \Exception
555
     */
556 3
    public function buildTx(TransactionBuilder $txBuilder) {
557 3
        $send = $txBuilder->getOutputs();
558 3
        $utxos = $txBuilder->getUtxos();
559 3
        $signInfo = [];
560
561 3
        $txb = new TxBuilder();
562
563 3
        foreach ($utxos as $utxo) {
564 3
            if (!$utxo->address || !$utxo->value || !$utxo->scriptPubKey) {
565
                $tx = $this->sdk->transaction($utxo->hash);
566
567
                if (!$tx || !isset($tx['outputs'][$utxo->index])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tx of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
568
                    throw new \Exception("Invalid output [{$utxo->hash}][{$utxo->index}]");
569
                }
570
571
                $output = $tx['outputs'][$utxo->index];
572
573
                if (!$utxo->address) {
574
                    $utxo->address = AddressFactory::fromString($output['address']);
575
                }
576
                if (!$utxo->value) {
577
                    $utxo->value = $output['value'];
578
                }
579
                if (!$utxo->scriptPubKey) {
580
                    $utxo->scriptPubKey = ScriptFactory::fromHex($output['script_hex']);
581
                }
582
            }
583
584 3
            if (SignInfo::MODE_SIGN === $utxo->signMode) {
585 3
                if (!$utxo->path) {
586
                    $utxo->path = $this->getPathForAddress($utxo->address->getAddress());
587
                }
588
589 3
                if (!$utxo->redeemScript) {
590
                    list(, $redeemScript) = $this->getRedeemScriptByPath($utxo->path);
0 ignored issues
show
Documentation introduced by
$utxo->path is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
591
                    $utxo->redeemScript = $redeemScript;
592
                }
593
            }
594
595 3
            $signInfo[] = $utxo->getSignInfo();
596
        }
597
598
        $utxoSum = array_sum(array_map(function (UTXO $utxo) {
599 3
            return $utxo->value;
600 3
        }, $utxos));
601 3
        if ($utxoSum < array_sum(array_column($send, 'value'))) {
602 1
            throw new \Exception("Atempting to spend more than sum of UTXOs");
603
        }
604
605 3
        list($fee, $change) = $this->determineFeeAndChange($txBuilder, $this->getOptimalFeePerKB(), $this->getLowPriorityFeePerKB());
606
607 3
        if ($txBuilder->getValidateFee() !== null) {
608 2
            if (abs($txBuilder->getValidateFee() - $fee) > Wallet::BASE_FEE) {
609
                throw new \Exception("the fee suggested by the coin selection ({$txBuilder->getValidateFee()}) seems incorrect ({$fee})");
610
            }
611
        }
612
613 3
        if ($change > 0) {
614 3
            $send[] = [
615 3
                'address' => $txBuilder->getChangeAddress() ?: $this->getNewAddress(),
616 3
                'value' => $change
617
            ];
618
        }
619
620 3
        foreach ($utxos as $utxo) {
621 3
            $txb->spendOutPoint(new OutPoint(Buffer::hex($utxo->hash), $utxo->index));
622
        }
623
624
        // outputs should be randomized to make the change harder to detect
625 3
        if ($txBuilder->shouldRandomizeChangeOuput()) {
626 3
            shuffle($send);
627
        }
628
629 3
        foreach ($send as $out) {
630 3
            assert(isset($out['value']));
631
632 3
            if (isset($out['scriptPubKey'])) {
633
                $txb->output($out['value'], $out['scriptPubKey']);
634 3
            } elseif (isset($out['address'])) {
635 3
                $txb->payToAddress($out['value'], AddressFactory::fromString($out['address']));
636
            } else {
637 3
                throw new \Exception();
638
            }
639
        }
640
641 3
        return [$txb->get(), $signInfo];
642
    }
643
644 3
    public function determineFeeAndChange(TransactionBuilder $txBuilder, $optimalFeePerKB, $lowPriorityFeePerKB) {
645 3
        $send = $txBuilder->getOutputs();
646 3
        $utxos = $txBuilder->getUtxos();
647
648 3
        $fee = $txBuilder->getFee();
649 3
        $change = null;
0 ignored issues
show
Unused Code introduced by
$change is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
650
651
        // if the fee is fixed we just need to calculate the change
652 3
        if ($fee !== null) {
653 1
            $change = $this->determineChange($utxos, $send, $fee);
654
655
            // if change is not dust we need to add a change output
656 1
            if ($change > Blocktrail::DUST) {
657
                $send[] = ['address' => 'change', 'value' => $change];
658
            } else {
659
                // if change is dust we do nothing (implicitly it's added to the fee)
660 1
                $change = 0;
661
            }
662
        } else {
663 3
            $fee = $this->determineFee($utxos, $send, $txBuilder->getFeeStrategy(), $optimalFeePerKB, $lowPriorityFeePerKB);
664
665 3
            $change = $this->determineChange($utxos, $send, $fee);
666
667 3
            if ($change > 0) {
668 3
                $changeIdx = count($send);
669
                // set dummy change output
670 3
                $send[$changeIdx] = ['address' => 'change', 'value' => $change];
671
672
                // recaculate fee now that we know that we have a change output
673 3
                $fee2 = $this->determineFee($utxos, $send, $txBuilder->getFeeStrategy(), $optimalFeePerKB, $lowPriorityFeePerKB);
674
675
                // unset dummy change output
676 3
                unset($send[$changeIdx]);
677
678
                // if adding the change output made the fee bump up and the change is smaller than the fee
679
                //  then we're not doing change
680 3
                if ($fee2 > $fee && $fee2 > $change) {
681 1
                    $change = 0;
682
                } else {
683 3
                    $change = $this->determineChange($utxos, $send, $fee2);
684
685
                    // if change is not dust we need to add a change output
686 3
                    if ($change > Blocktrail::DUST) {
687 3
                        $send[$changeIdx] = ['address' => 'change', 'value' => $change];
688
                    } else {
689
                        // if change is dust we do nothing (implicitly it's added to the fee)
690
                        $change = 0;
691
                    }
692
                }
693
            }
694
        }
695
696 3
        $fee = $this->determineFee($utxos, $send, $txBuilder->getFeeStrategy(), $optimalFeePerKB, $lowPriorityFeePerKB);
697
698 3
        return [$fee, $change];
699
    }
700
701
    /**
702
     * create, sign and send transction based on TransactionBuilder
703
     *
704
     * @param TransactionBuilder $txBuilder
705
     * @param bool $apiCheckFee     let the API check if the fee is correct
706
     * @return string
707
     * @throws \Exception
708
     */
709 2
    public function sendTx(TransactionBuilder $txBuilder, $apiCheckFee = true) {
710 2
        list($tx, $signInfo) = $this->buildTx($txBuilder);
711
712 2
        return $this->_sendTx($tx, $signInfo, $apiCheckFee);
0 ignored issues
show
Compatibility introduced by
$tx of type object<BitWasp\Bitcoin\T...n\TransactionInterface> is not a sub-type of object<BitWasp\Bitcoin\Transaction\Transaction>. It seems like you assume a concrete implementation of the interface BitWasp\Bitcoin\Transaction\TransactionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
713
    }
714
715
    /**
716
     * !! INTERNAL METHOD, public for testing purposes !!
717
     * create, sign and send transction based on inputs and outputs
718
     *
719
     * @param Transaction $tx
720
     * @param SignInfo[]  $signInfo
721
     * @param bool $apiCheckFee     let the API check if the fee is correct
722
     * @return string
723
     * @throws \Exception
724
     * @internal
725
     */
726 2
    public function _sendTx(Transaction $tx, array $signInfo, $apiCheckFee = true) {
727 2
        if ($this->locked) {
728
            throw new \Exception("Wallet needs to be unlocked to pay");
729
        }
730
731
        assert(Util::all(function ($signInfo) {
732 2
            return $signInfo instanceof SignInfo;
733 2
        }, $signInfo), '$signInfo should be SignInfo[]');
734
735
        // sign the transaction with our keys
736 2
        $signed = $this->signTransaction($tx, $signInfo);
737
738
        // send the transaction
739
        return $this->sendTransaction($signed->getHex(), array_map(function (SignInfo $r) {
740 2
            return $r->path;
741 2
        }, $signInfo), $apiCheckFee);
742
    }
743
744
    /**
745
     * only supports estimating fee for 2of3 multsig UTXOs and P2PKH/P2SH outputs
746
     *
747
     * @param int $utxoCnt      number of unspent inputs in transaction
748
     * @param int $outputCnt    number of outputs in transaction
749
     * @return float
750
     * @access public           reminder that people might use this!
751
     */
752 1
    public static function estimateFee($utxoCnt, $outputCnt) {
753 1
        $size = self::estimateSize(self::estimateSizeUTXOs($utxoCnt), self::estimateSizeOutputs($outputCnt));
754
755 1
        return self::baseFeeForSize($size);
756
    }
757
758
    /**
759
     * @param int $size     size in bytes
760
     * @return int          fee in satoshi
761
     */
762 3
    public static function baseFeeForSize($size) {
763 3
        $sizeKB = (int)ceil($size / 1000);
764
765 3
        return $sizeKB * self::BASE_FEE;
766
    }
767
768
    /**
769
     * @param int $txinSize
770
     * @param int $txoutSize
771
     * @return float
772
     */
773 5
    public static function estimateSize($txinSize, $txoutSize) {
774 5
        return 4 + 4 + $txinSize + 4 + $txoutSize + 4; // version + txinVarInt + txin + txoutVarInt + txout + locktime
775
    }
776
777
    /**
778
     * only supports estimating size for P2PKH/P2SH outputs
779
     *
780
     * @param int $outputCnt    number of outputs in transaction
781
     * @return float
782
     */
783 2
    public static function estimateSizeOutputs($outputCnt) {
784 2
        return ($outputCnt * 34);
785
    }
786
787
    /**
788
     * only supports estimating size for 2of3 multsig UTXOs
789
     *
790
     * @param int $utxoCnt      number of unspent inputs in transaction
791
     * @return float
792
     */
793 5
    public static function estimateSizeUTXOs($utxoCnt) {
794 5
        $txinSize = 0;
795
796 5
        for ($i=0; $i<$utxoCnt; $i++) {
797
            // @TODO: proper size calculation, we only do multisig right now so it's hardcoded and then we guess the size ...
798 5
            $multisig = "2of3";
799
800 5
            if ($multisig) {
801 5
                $sigCnt = 2;
802 5
                $msig = explode("of", $multisig);
803 5
                if (count($msig) == 2 && is_numeric($msig[0])) {
804 5
                    $sigCnt = $msig[0];
805
                }
806
807 5
                $txinSize += array_sum([
808 5
                    32, // txhash
809 5
                    4, // idx
810 5
                    3, // scriptVarInt[>=253]
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
811 5
                    ((1 + 72) * $sigCnt), // (OP_PUSHDATA[<75] + 72) * sigCnt
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
812
                    (2 + 105) + // OP_PUSHDATA[>=75] + script
813
                    1, // OP_0
814 5
                    4, // sequence
815
                ]);
816
            } else {
817
                $txinSize += array_sum([
818
                    32, // txhash
819
                    4, // idx
820
                    73, // sig
821
                    34, // script
822
                    4, // sequence
823
                ]);
824
            }
825
        }
826
827 5
        return $txinSize;
828
    }
829
830
    /**
831
     * determine how much fee is required based on the inputs and outputs
832
     *  this is an estimation, not a proper 100% correct calculation
833
     *
834
     * @param UTXO[]  $utxos
835
     * @param array[] $outputs
836
     * @param         $feeStrategy
837
     * @param         $optimalFeePerKB
838
     * @param         $lowPriorityFeePerKB
839
     * @return int
840
     * @throws BlocktrailSDKException
841
     */
842 3
    protected function determineFee($utxos, $outputs, $feeStrategy, $optimalFeePerKB, $lowPriorityFeePerKB) {
843 3
        $outputSize = 0;
844 3
        foreach ($outputs as $output) {
845 3
            if (isset($output['scriptPubKey'])) {
846
                if ($output['scriptPubKey'] instanceof ScriptInterface) {
847
                    $outputSize += $output['scriptPubKey']->getBuffer()->getSize();
848
                } else {
849
                    $outputSize += strlen($output['scriptPubKey']) / 2; // asume HEX
850
                }
851
            } else {
852 3
                $outputSize += 34;
853
            }
854
        }
855
856 3
        $size = self::estimateSize(self::estimateSizeUTXOs(count($utxos)), $outputSize);
857
858
        switch ($feeStrategy) {
859 3
            case self::FEE_STRATEGY_BASE_FEE:
860 2
                return self::baseFeeForSize($size);
861
862 1
            case self::FEE_STRATEGY_OPTIMAL:
863 1
                return (int)round(($size / 1000) * $optimalFeePerKB);
864
865
            case self::FEE_STRATEGY_LOW_PRIORITY:
866
                return (int)round(($size / 1000) * $lowPriorityFeePerKB);
867
868
            default:
869
                throw new BlocktrailSDKException("Unknown feeStrategy [{$feeStrategy}]");
870
        }
871
    }
872
873
    /**
874
     * determine how much change is left over based on the inputs and outputs and the fee
875
     *
876
     * @param UTXO[]    $utxos
877
     * @param array[]   $outputs
878
     * @param int       $fee
879
     * @return int
880
     */
881 3
    protected function determineChange($utxos, $outputs, $fee) {
882
        $inputsTotal = array_sum(array_map(function (UTXO $utxo) {
883 3
            return $utxo->value;
884 3
        }, $utxos));
885 3
        $outputsTotal = array_sum(array_column($outputs, 'value'));
886
887 3
        return $inputsTotal - $outputsTotal - $fee;
888
    }
889
890
    /**
891
     * sign a raw transaction with the private keys that we have
892
     *
893
     * @param Transaction $tx
894
     * @param SignInfo[]  $signInfo
895
     * @return TransactionInterface
896
     * @throws \Exception
897
     */
898 2
    protected function signTransaction(Transaction $tx, array $signInfo) {
899 2
        $signer = new Signer($tx, Bitcoin::getEcAdapter());
900
901 2
        assert(Util::all(function ($signInfo) {
902 2
            return $signInfo instanceof SignInfo;
903 2
        }, $signInfo), '$signInfo should be SignInfo[]');
904
905 2
        $sigHash = SigHash::ALL;
906 2
        if ($this->network === "bitcoincash") {
907
            $sigHash |= SigHash::BITCOINCASH;
908
            $signer->redeemBitcoinCash(true);
909
        }
910
911 2
        foreach ($signInfo as $idx => $info) {
912 2
            if ($info->mode === SignInfo::MODE_SIGN) {
913
                // required SignInfo: path, redeemScript, output
914 2
                $path = BIP32Path::path($info->path)->privatePath();
915 2
                $redeemScript = $info->redeemScript;
916 2
                $output = $info->output;
917
918 2
                $key = $this->primaryPrivateKey->buildKey($path)->key()->getPrivateKey();
919
920 2
                $input = $signer->input($idx, $output, (new SignData())->p2sh($redeemScript));
921 2
                $input->sign($key, $sigHash);
922
            }
923
        }
924
925 2
        return $signer->get();
926
    }
927
928
    /**
929
     * send the transaction using the API
930
     *
931
     * @param string    $signed
932
     * @param string[]  $paths
933
     * @param bool      $checkFee
934
     * @return string           the complete raw transaction
935
     * @throws \Exception
936
     */
937 2
    protected function sendTransaction($signed, $paths, $checkFee = false) {
938 2
        return $this->sdk->sendTransaction($this->identifier, $signed, $paths, $checkFee);
939
    }
940
941
    /**
942
     * @param \array[] $outputs
943
     * @param bool $lockUTXO
944
     * @param bool $allowZeroConf
945
     * @param int|null|string $feeStrategy
946
     * @param null $forceFee
947
     * @return array
948
     */
949 8
    public function coinSelection($outputs, $lockUTXO = true, $allowZeroConf = false, $feeStrategy = self::FEE_STRATEGY_OPTIMAL, $forceFee = null) {
950 8
        $result = $this->sdk->coinSelection($this->identifier, $outputs, $lockUTXO, $allowZeroConf, $feeStrategy, $forceFee);
951
952 2
        $this->optimalFeePerKB = $result['fees'][self::FEE_STRATEGY_OPTIMAL];
953 2
        $this->lowPriorityFeePerKB = $result['fees'][self::FEE_STRATEGY_LOW_PRIORITY];
954 2
        $this->feePerKBAge = time();
955
956 2
        return $result;
957
    }
958
959 3
    public function getOptimalFeePerKB() {
960 3
        if (!$this->optimalFeePerKB || $this->feePerKBAge < time() - 60) {
961 1
            $this->updateFeePerKB();
962
        }
963
964 3
        return $this->optimalFeePerKB;
965
    }
966
967 3
    public function getLowPriorityFeePerKB() {
968 3
        if (!$this->lowPriorityFeePerKB || $this->feePerKBAge < time() - 60) {
969
            $this->updateFeePerKB();
970
        }
971
972 3
        return $this->lowPriorityFeePerKB;
973
    }
974
975 1
    public function updateFeePerKB() {
976 1
        $result = $this->sdk->feePerKB();
977
978 1
        $this->optimalFeePerKB = $result[self::FEE_STRATEGY_OPTIMAL];
979 1
        $this->lowPriorityFeePerKB = $result[self::FEE_STRATEGY_LOW_PRIORITY];
980
981 1
        $this->feePerKBAge = time();
982 1
    }
983
984
    /**
985
     * delete the wallet
986
     *
987
     * @param bool $force ignore warnings (such as non-zero balance)
988
     * @return mixed
989
     * @throws \Exception
990
     */
991 10
    public function deleteWallet($force = false) {
992 10
        if ($this->locked) {
993
            throw new \Exception("Wallet needs to be unlocked to delete wallet");
994
        }
995
996 10
        list($checksumAddress, $signature) = $this->createChecksumVerificationSignature();
997 10
        return $this->sdk->deleteWallet($this->identifier, $checksumAddress, $signature, $force)['deleted'];
998
    }
999
1000
    /**
1001
     * create checksum to verify ownership of the master primary key
1002
     *
1003
     * @return string[]     [address, signature]
1004
     */
1005 10
    protected function createChecksumVerificationSignature() {
1006 10
        $privKey = $this->primaryPrivateKey->key();
1007
1008 10
        $pubKey = $this->primaryPrivateKey->publicKey();
1009 10
        $address = $pubKey->getAddress()->getAddress();
1010
1011 10
        $signer = new MessageSigner(Bitcoin::getEcAdapter());
1012 10
        $signed = $signer->sign($address, $privKey->getPrivateKey());
1013
1014 10
        return [$address, base64_encode($signed->getCompactSignature()->getBuffer()->getBinary())];
1015
    }
1016
1017
    /**
1018
     * setup a webhook for our wallet
1019
     *
1020
     * @param string    $url            URL to receive webhook events
1021
     * @param string    $identifier     identifier for the webhook, defaults to WALLET-{$this->identifier}
1022
     * @return array
1023
     */
1024 1
    public function setupWebhook($url, $identifier = null) {
1025 1
        $identifier = $identifier ?: "WALLET-{$this->identifier}";
1026 1
        return $this->sdk->setupWalletWebhook($this->identifier, $identifier, $url);
1027
    }
1028
1029
    /**
1030
     * @param string    $identifier     identifier for the webhook, defaults to WALLET-{$this->identifier}
1031
     * @return mixed
1032
     */
1033 1
    public function deleteWebhook($identifier = null) {
1034 1
        $identifier = $identifier ?: "WALLET-{$this->identifier}";
1035 1
        return $this->sdk->deleteWalletWebhook($this->identifier, $identifier);
1036
    }
1037
1038
    /**
1039
     * lock a specific unspent output
1040
     *
1041
     * @param     $txHash
1042
     * @param     $txIdx
1043
     * @param int $ttl
1044
     * @return bool
1045
     */
1046
    public function lockUTXO($txHash, $txIdx, $ttl = 3) {
1047
        return $this->sdk->lockWalletUTXO($this->identifier, $txHash, $txIdx, $ttl);
1048
    }
1049
1050
    /**
1051
     * unlock a specific unspent output
1052
     *
1053
     * @param     $txHash
1054
     * @param     $txIdx
1055
     * @return bool
1056
     */
1057
    public function unlockUTXO($txHash, $txIdx) {
1058
        return $this->sdk->unlockWalletUTXO($this->identifier, $txHash, $txIdx);
1059
    }
1060
1061
    /**
1062
     * get all transactions for the wallet (paginated)
1063
     *
1064
     * @param  integer $page    pagination: page number
1065
     * @param  integer $limit   pagination: records per page (max 500)
1066
     * @param  string  $sortDir pagination: sort direction (asc|desc)
1067
     * @return array            associative array containing the response
1068
     */
1069 1
    public function transactions($page = 1, $limit = 20, $sortDir = 'asc') {
1070 1
        return $this->sdk->walletTransactions($this->identifier, $page, $limit, $sortDir);
1071
    }
1072
1073
    /**
1074
     * get all addresses for the wallet (paginated)
1075
     *
1076
     * @param  integer $page    pagination: page number
1077
     * @param  integer $limit   pagination: records per page (max 500)
1078
     * @param  string  $sortDir pagination: sort direction (asc|desc)
1079
     * @return array            associative array containing the response
1080
     */
1081 1
    public function addresses($page = 1, $limit = 20, $sortDir = 'asc') {
1082 1
        return $this->sdk->walletAddresses($this->identifier, $page, $limit, $sortDir);
1083
    }
1084
1085
    /**
1086
     * get all UTXOs for the wallet (paginated)
1087
     *
1088
     * @param  integer $page        pagination: page number
1089
     * @param  integer $limit       pagination: records per page (max 500)
1090
     * @param  string  $sortDir     pagination: sort direction (asc|desc)
1091
     * @param  boolean $zeroconf    include zero confirmation transactions
1092
     * @return array                associative array containing the response
1093
     */
1094 1
    public function utxos($page = 1, $limit = 20, $sortDir = 'asc', $zeroconf = true) {
1095 1
        return $this->sdk->walletUTXOs($this->identifier, $page, $limit, $sortDir, $zeroconf);
1096
    }
1097
}
1098