Completed
Branch master (f66fc6)
by
unknown
07:47
created

BlocktrailSDK::walletMaxSpendable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 12

Duplication

Lines 19
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 19
loc 19
rs 9.4285
cc 2
eloc 12
nc 2
nop 5
1
<?php
2
3
namespace Blocktrail\SDK;
4
5
use BitWasp\BitcoinLib\BIP32;
6
use BitWasp\BitcoinLib\BIP39\BIP39;
7
use BitWasp\BitcoinLib\BitcoinLib;
8
use BitWasp\BitcoinLib\RawTransaction;
9
use Blocktrail\SDK\Connection\RestClient;
10
11
/**
12
 * Class BlocktrailSDK
13
 */
14
class BlocktrailSDK implements BlocktrailSDKInterface {
15
    /**
16
     * @var Connection\RestClient
17
     */
18
    protected $client;
19
20
    /**
21
     * @var string          currently only supporting; bitcoin
22
     */
23
    protected $network;
24
25
    /**
26
     * @var bool
27
     */
28
    protected $testnet;
29
30
    /**
31
     * @param   string      $apiKey         the API_KEY to use for authentication
32
     * @param   string      $apiSecret      the API_SECRET to use for authentication
33
     * @param   string      $network        the cryptocurrency 'network' to consume, eg BTC, LTC, etc
34
     * @param   bool        $testnet        testnet yes/no
35
     * @param   string      $apiVersion     the version of the API to consume
36
     * @param   null        $apiEndpoint    overwrite the endpoint used
37
     *                                       this will cause the $network, $testnet and $apiVersion to be ignored!
38
     */
39
    public function __construct($apiKey, $apiSecret, $network = 'BTC', $testnet = false, $apiVersion = 'v1', $apiEndpoint = null) {
40
        if (is_null($apiEndpoint)) {
41
            $network = strtoupper($network);
42
43
            if ($testnet) {
44
                $network = "t{$network}";
45
            }
46
47
            $apiEndpoint = getenv('BLOCKTRAIL_SDK_API_ENDPOINT') ?: "https://api.blocktrail.com";
48
            $apiEndpoint = "{$apiEndpoint}/{$apiVersion}/{$network}/";
49
        }
50
51
        // normalize network and set bitcoinlib to the right magic-bytes
52
        list($this->network, $this->testnet) = $this->normalizeNetwork($network, $testnet);
53
        $this->setBitcoinLibMagicBytes($this->network, $this->testnet);
54
55
        $this->client = new RestClient($apiEndpoint, $apiVersion, $apiKey, $apiSecret);
56
    }
57
58
    /**
59
     * normalize network string
60
     *
61
     * @param $network
62
     * @param $testnet
63
     * @return array
64
     * @throws \Exception
65
     */
66 View Code Duplication
    protected function normalizeNetwork($network, $testnet) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
67
        switch (strtolower($network)) {
68
            case 'btc':
69
            case 'bitcoin':
70
                $network = 'bitcoin';
71
72
                break;
73
74
            case 'tbtc':
75
            case 'bitcoin-testnet':
76
                $network = 'bitcoin';
77
                $testnet = true;
78
79
                break;
80
81
            default:
82
                throw new \Exception("Unknown network [{$network}]");
83
        }
84
85
        return [$network, $testnet];
86
    }
87
88
    /**
89
     * set BitcoinLib to the correct magic-byte defaults for the selected network
90
     *
91
     * @param $network
92
     * @param $testnet
93
     */
94
    protected function setBitcoinLibMagicBytes($network, $testnet) {
95
        BitcoinLib::setMagicByteDefaults($network . ($testnet ? '-testnet' : ''));
96
    }
97
98
    /**
99
     * enable CURL debugging output
100
     *
101
     * @param   bool        $debug
102
     *
103
     * @codeCoverageIgnore
104
     */
105
    public function setCurlDebugging($debug = true) {
106
        $this->client->setCurlDebugging($debug);
107
    }
108
109
    /**
110
     * enable verbose errors
111
     *
112
     * @param   bool        $verboseErrors
113
     *
114
     * @codeCoverageIgnore
115
     */
116
    public function setVerboseErrors($verboseErrors = true) {
117
        $this->client->setVerboseErrors($verboseErrors);
118
    }
119
    
120
    /**
121
     * set cURL default option on Guzzle client
122
     * @param string    $key
123
     * @param bool      $value
124
     *
125
     * @codeCoverageIgnore
126
     */
127
    public function setCurlDefaultOption($key, $value) {
128
        $this->client->setCurlDefaultOption($key, $value);
129
    }
130
131
    /**
132
     * @return  RestClient
133
     */
134
    public function getRestClient() {
135
        return $this->client;
136
    }
137
138
    /**
139
     * get a single address
140
     * @param  string $address address hash
141
     * @return array           associative array containing the response
142
     */
143
    public function address($address) {
144
        $response = $this->client->get("address/{$address}");
145
        return self::jsonDecode($response->body(), true);
146
    }
147
148
    /**
149
     * get all transactions for an address (paginated)
150
     * @param  string  $address address hash
151
     * @param  integer $page    pagination: page number
152
     * @param  integer $limit   pagination: records per page (max 500)
153
     * @param  string  $sortDir pagination: sort direction (asc|desc)
154
     * @return array            associative array containing the response
155
     */
156
    public function addressTransactions($address, $page = 1, $limit = 20, $sortDir = 'asc') {
157
        $queryString = [
158
            'page' => $page,
159
            'limit' => $limit,
160
            'sort_dir' => $sortDir
161
        ];
162
        $response = $this->client->get("address/{$address}/transactions", $queryString);
163
        return self::jsonDecode($response->body(), true);
164
    }
165
166
    /**
167
     * get all unconfirmed transactions for an address (paginated)
168
     * @param  string  $address address hash
169
     * @param  integer $page    pagination: page number
170
     * @param  integer $limit   pagination: records per page (max 500)
171
     * @param  string  $sortDir pagination: sort direction (asc|desc)
172
     * @return array            associative array containing the response
173
     */
174
    public function addressUnconfirmedTransactions($address, $page = 1, $limit = 20, $sortDir = 'asc') {
175
        $queryString = [
176
            'page' => $page,
177
            'limit' => $limit,
178
            'sort_dir' => $sortDir
179
        ];
180
        $response = $this->client->get("address/{$address}/unconfirmed-transactions", $queryString);
181
        return self::jsonDecode($response->body(), true);
182
    }
183
184
    /**
185
     * get all unspent outputs for an address (paginated)
186
     * @param  string  $address address hash
187
     * @param  integer $page    pagination: page number
188
     * @param  integer $limit   pagination: records per page (max 500)
189
     * @param  string  $sortDir pagination: sort direction (asc|desc)
190
     * @return array            associative array containing the response
191
     */
192
    public function addressUnspentOutputs($address, $page = 1, $limit = 20, $sortDir = 'asc') {
193
        $queryString = [
194
            'page' => $page,
195
            'limit' => $limit,
196
            'sort_dir' => $sortDir
197
        ];
198
        $response = $this->client->get("address/{$address}/unspent-outputs", $queryString);
199
        return self::jsonDecode($response->body(), true);
200
    }
201
202
    /**
203
     * verify ownership of an address
204
     * @param  string  $address     address hash
205
     * @param  string  $signature   a signed message (the address hash) using the private key of the address
206
     * @return array                associative array containing the response
207
     */
208
    public function verifyAddress($address, $signature) {
209
        $postData = ['signature' => $signature];
210
211
        $response = $this->client->post("address/{$address}/verify", null, $postData, RestClient::AUTH_HTTP_SIG);
212
213
        return self::jsonDecode($response->body(), true);
214
    }
215
216
    /**
217
     * get all blocks (paginated)
218
     * @param  integer $page    pagination: page number
219
     * @param  integer $limit   pagination: records per page
220
     * @param  string  $sortDir pagination: sort direction (asc|desc)
221
     * @return array            associative array containing the response
222
     */
223
    public function allBlocks($page = 1, $limit = 20, $sortDir = 'asc') {
224
        $queryString = [
225
            'page' => $page,
226
            'limit' => $limit,
227
            'sort_dir' => $sortDir
228
        ];
229
        $response = $this->client->get("all-blocks", $queryString);
230
        return self::jsonDecode($response->body(), true);
231
    }
232
233
    /**
234
     * get the latest block
235
     * @return array            associative array containing the response
236
     */
237
    public function blockLatest() {
238
        $response = $this->client->get("block/latest");
239
        return self::jsonDecode($response->body(), true);
240
    }
241
242
    /**
243
     * get an individual block
244
     * @param  string|integer $block    a block hash or a block height
245
     * @return array                    associative array containing the response
246
     */
247
    public function block($block) {
248
        $response = $this->client->get("block/{$block}");
249
        return self::jsonDecode($response->body(), true);
250
    }
251
252
    /**
253
     * get all transaction in a block (paginated)
254
     * @param  string|integer   $block   a block hash or a block height
255
     * @param  integer          $page    pagination: page number
256
     * @param  integer          $limit   pagination: records per page
257
     * @param  string           $sortDir pagination: sort direction (asc|desc)
258
     * @return array                     associative array containing the response
259
     */
260
    public function blockTransactions($block, $page = 1, $limit = 20, $sortDir = 'asc') {
261
        $queryString = [
262
            'page' => $page,
263
            'limit' => $limit,
264
            'sort_dir' => $sortDir
265
        ];
266
        $response = $this->client->get("block/{$block}/transactions", $queryString);
267
        return self::jsonDecode($response->body(), true);
268
    }
269
270
    /**
271
     * get a single transaction
272
     * @param  string $txhash transaction hash
273
     * @return array          associative array containing the response
274
     */
275
    public function transaction($txhash) {
276
        $response = $this->client->get("transaction/{$txhash}");
277
        return self::jsonDecode($response->body(), true);
278
    }
279
    
280
    /**
281
     * get a paginated list of all webhooks associated with the api user
282
     * @param  integer          $page    pagination: page number
283
     * @param  integer          $limit   pagination: records per page
284
     * @return array                     associative array containing the response
285
     */
286
    public function allWebhooks($page = 1, $limit = 20) {
287
        $queryString = [
288
            'page' => $page,
289
            'limit' => $limit
290
        ];
291
        $response = $this->client->get("webhooks", $queryString);
292
        return self::jsonDecode($response->body(), true);
293
    }
294
295
    /**
296
     * get an existing webhook by it's identifier
297
     * @param string    $identifier     a unique identifier associated with the webhook
298
     * @return array                    associative array containing the response
299
     */
300
    public function getWebhook($identifier) {
301
        $response = $this->client->get("webhook/".$identifier);
302
        return self::jsonDecode($response->body(), true);
303
    }
304
305
    /**
306
     * create a new webhook
307
     * @param  string  $url        the url to receive the webhook events
308
     * @param  string  $identifier a unique identifier to associate with this webhook
309
     * @return array               associative array containing the response
310
     */
311
    public function setupWebhook($url, $identifier = null) {
312
        $postData = [
313
            'url'        => $url,
314
            'identifier' => $identifier
315
        ];
316
        $response = $this->client->post("webhook", null, $postData, RestClient::AUTH_HTTP_SIG);
317
        return self::jsonDecode($response->body(), true);
318
    }
319
320
    /**
321
     * update an existing webhook
322
     * @param  string  $identifier      the unique identifier of the webhook to update
323
     * @param  string  $newUrl          the new url to receive the webhook events
324
     * @param  string  $newIdentifier   a new unique identifier to associate with this webhook
325
     * @return array                    associative array containing the response
326
     */
327
    public function updateWebhook($identifier, $newUrl = null, $newIdentifier = null) {
328
        $putData = [
329
            'url'        => $newUrl,
330
            'identifier' => $newIdentifier
331
        ];
332
        $response = $this->client->put("webhook/{$identifier}", null, $putData, RestClient::AUTH_HTTP_SIG);
333
        return self::jsonDecode($response->body(), true);
334
    }
335
336
    /**
337
     * deletes an existing webhook and any event subscriptions associated with it
338
     * @param  string  $identifier      the unique identifier of the webhook to delete
339
     * @return boolean                  true on success
340
     */
341
    public function deleteWebhook($identifier) {
342
        $response = $this->client->delete("webhook/{$identifier}", null, null, RestClient::AUTH_HTTP_SIG);
343
        return self::jsonDecode($response->body(), true);
344
    }
345
346
    /**
347
     * get a paginated list of all the events a webhook is subscribed to
348
     * @param  string  $identifier  the unique identifier of the webhook
349
     * @param  integer $page        pagination: page number
350
     * @param  integer $limit       pagination: records per page
351
     * @return array                associative array containing the response
352
     */
353
    public function getWebhookEvents($identifier, $page = 1, $limit = 20) {
354
        $queryString = [
355
            'page' => $page,
356
            'limit' => $limit
357
        ];
358
        $response = $this->client->get("webhook/{$identifier}/events", $queryString);
359
        return self::jsonDecode($response->body(), true);
360
    }
361
    
362
    /**
363
     * subscribes a webhook to transaction events of one particular transaction
364
     * @param  string  $identifier      the unique identifier of the webhook to be triggered
365
     * @param  string  $transaction     the transaction hash
366
     * @param  integer $confirmations   the amount of confirmations to send.
367
     * @return array                    associative array containing the response
368
     */
369 View Code Duplication
    public function subscribeTransaction($identifier, $transaction, $confirmations = 6) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
370
        $postData = [
371
            'event_type'    => 'transaction',
372
            'transaction'   => $transaction,
373
            'confirmations' => $confirmations,
374
        ];
375
        $response = $this->client->post("webhook/{$identifier}/events", null, $postData, RestClient::AUTH_HTTP_SIG);
376
        return self::jsonDecode($response->body(), true);
377
    }
378
379
    /**
380
     * subscribes a webhook to transaction events on a particular address
381
     * @param  string  $identifier      the unique identifier of the webhook to be triggered
382
     * @param  string  $address         the address hash
383
     * @param  integer $confirmations   the amount of confirmations to send.
384
     * @return array                    associative array containing the response
385
     */
386 View Code Duplication
    public function subscribeAddressTransactions($identifier, $address, $confirmations = 6) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
387
        $postData = [
388
            'event_type'    => 'address-transactions',
389
            'address'       => $address,
390
            'confirmations' => $confirmations,
391
        ];
392
        $response = $this->client->post("webhook/{$identifier}/events", null, $postData, RestClient::AUTH_HTTP_SIG);
393
        return self::jsonDecode($response->body(), true);
394
    }
395
396
    /**
397
     * batch subscribes a webhook to multiple transaction events
398
     *
399
     * @param  string $identifier   the unique identifier of the webhook
400
     * @param  array  $batchData    A 2D array of event data:
401
     *                              [address => $address, confirmations => $confirmations]
402
     *                              where $address is the address to subscibe to
403
     *                              and optionally $confirmations is the amount of confirmations
404
     * @return boolean              true on success
405
     */
406
    public function batchSubscribeAddressTransactions($identifier, $batchData) {
407
        $postData = [];
408
        foreach ($batchData as $record) {
409
            $postData[] = [
410
                'event_type' => 'address-transactions',
411
                'address' => $record['address'],
412
                'confirmations' => isset($record['confirmations']) ? $record['confirmations'] : 6,
413
            ];
414
        }
415
        $response = $this->client->post("webhook/{$identifier}/events/batch", null, $postData, RestClient::AUTH_HTTP_SIG);
416
        return self::jsonDecode($response->body(), true);
417
    }
418
419
    /**
420
     * subscribes a webhook to a new block event
421
     * @param  string  $identifier  the unique identifier of the webhook to be triggered
422
     * @return array                associative array containing the response
423
     */
424
    public function subscribeNewBlocks($identifier) {
425
        $postData = [
426
            'event_type'    => 'block',
427
        ];
428
        $response = $this->client->post("webhook/{$identifier}/events", null, $postData, RestClient::AUTH_HTTP_SIG);
429
        return self::jsonDecode($response->body(), true);
430
    }
431
432
    /**
433
     * removes an transaction event subscription from a webhook
434
     * @param  string  $identifier      the unique identifier of the webhook associated with the event subscription
435
     * @param  string  $transaction     the transaction hash of the event subscription
436
     * @return boolean                  true on success
437
     */
438
    public function unsubscribeTransaction($identifier, $transaction) {
439
        $response = $this->client->delete("webhook/{$identifier}/transaction/{$transaction}", null, null, RestClient::AUTH_HTTP_SIG);
440
        return self::jsonDecode($response->body(), true);
441
    }
442
443
    /**
444
     * removes an address transaction event subscription from a webhook
445
     * @param  string  $identifier      the unique identifier of the webhook associated with the event subscription
446
     * @param  string  $address         the address hash of the event subscription
447
     * @return boolean                  true on success
448
     */
449
    public function unsubscribeAddressTransactions($identifier, $address) {
450
        $response = $this->client->delete("webhook/{$identifier}/address-transactions/{$address}", null, null, RestClient::AUTH_HTTP_SIG);
451
        return self::jsonDecode($response->body(), true);
452
    }
453
454
    /**
455
     * removes a block event subscription from a webhook
456
     * @param  string  $identifier      the unique identifier of the webhook associated with the event subscription
457
     * @return boolean                  true on success
458
     */
459
    public function unsubscribeNewBlocks($identifier) {
460
        $response = $this->client->delete("webhook/{$identifier}/block", null, null, RestClient::AUTH_HTTP_SIG);
461
        return self::jsonDecode($response->body(), true);
462
    }
463
464
    /**
465
     * create a new wallet
466
     *   - will generate a new primary seed (with password) and backup seed (without password)
467
     *   - send the primary seed (BIP39 'encrypted') and backup public key to the server
468
     *   - receive the blocktrail co-signing public key from the server
469
     *
470
     * Either takes one argument:
471
     * @param array $options
472
     *
473
     * Or takes three arguments (old, deprecated syntax):
474
     * (@nonPHP-doc) @param      $identifier
475
     * (@nonPHP-doc) @param      $password
476
     * (@nonPHP-doc) @param int  $keyIndex         override for the blocktrail cosigning key to use
0 ignored issues
show
Bug introduced by
There is no parameter named $keyIndex. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
477
     *
478
     * @return array[WalletInterface, (string)primaryMnemonic, (string)backupMnemonic]
0 ignored issues
show
Documentation introduced by
The doc-type array[WalletInterface, could not be parsed: Expected "]" at position 2, but found "WalletInterface". (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...
479
     * @throws \Exception
480
     */
481
    public function createNewWallet($options) {
482
        if (!is_array($options)) {
483
            $args = func_get_args();
484
            $options = [
485
                "identifier" => $args[0],
486
                "password" => $args[1],
487
                "key_index" => isset($args[2]) ? $args[2] : null,
488
            ];
489
        }
490
491
        $identifier = $options['identifier'];
492
        $password = isset($options['passphrase']) ? $options['passphrase'] : (isset($options['password']) ? $options['password'] : null);
493
        $keyIndex = isset($options['key_index']) ? $options['key_index'] : 0;
494
495
        $walletPath = WalletPath::create($keyIndex);
496
497
        $storePrimaryMnemonic = isset($options['store_primary_mnemonic']) ? $options['store_primary_mnemonic'] : null;
498
499
        if (isset($options['primary_mnemonic']) && $options['primary_private_key']) {
500
            throw new \InvalidArgumentException("Can't specify Primary Mnemonic and Primary PrivateKey");
501
        }
502
503
        $primaryMnemonic = null;
504
        $primaryPrivateKey = null;
505
        if (!isset($options['primary_mnemonic']) && !isset($options['primary_private_key'])) {
506
            if (!$password) {
507
                throw new \InvalidArgumentException("Can't generate Primary Mnemonic without a passphrase");
508
            } else {
509
                // create new primary seed
510
                list($primaryMnemonic, $primarySeed, $primaryPrivateKey) = $this->newPrimarySeed($password);
0 ignored issues
show
Unused Code introduced by
The assignment to $primarySeed is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
511
                if ($storePrimaryMnemonic !== false) {
512
                    $storePrimaryMnemonic = true;
513
                }
514
            }
515
        } else if (isset($options['primary_mnemonic'])) {
516
            $primaryMnemonic = $options['primary_mnemonic'];
517
        } else if (isset($options['primary_private_key'])) {
518
            $primaryPrivateKey = $options['primary_private_key'];
519
        }
520
521
        if ($storePrimaryMnemonic && $primaryMnemonic && !$password) {
522
            throw new \InvalidArgumentException("Can't store Primary Mnemonic on server without a passphrase");
523
        }
524
525 View Code Duplication
        if ($primaryPrivateKey) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
526
            if (is_string($primaryPrivateKey)) {
527
                $primaryPrivateKey = [$primaryPrivateKey, "m"];
528
            }
529
        } else {
530
            $primaryPrivateKey = BIP32::master_key(BIP39::mnemonicToSeedHex($primaryMnemonic, $password), 'bitcoin', $this->testnet);
0 ignored issues
show
Bug introduced by
It seems like \BitWasp\BitcoinLib\BIP3...aryMnemonic, $password) targeting BitWasp\BitcoinLib\BIP39...39::mnemonicToSeedHex() can also be of type false or null; however, BitWasp\BitcoinLib\BIP32::master_key() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
531
        }
532
533
        if (!$storePrimaryMnemonic) {
534
            $primaryMnemonic = false;
535
        }
536
537
        // create primary public key from the created private key
538
        $primaryPublicKey = BIP32::build_key($primaryPrivateKey, (string)$walletPath->keyIndexPath()->publicPath());
539
540
        if (isset($options['backup_mnemonic']) && $options['backup_public_key']) {
541
            throw new \InvalidArgumentException("Can't specify Backup Mnemonic and Backup PublicKey");
542
        }
543
544
        $backupMnemonic = null;
545
        $backupPublicKey = null;
546
        if (!isset($options['backup_mnemonic']) && !isset($options['backup_public_key'])) {
547
            list($backupMnemonic, $backupSeed, $backupPrivateKey) = $this->newBackupSeed();
0 ignored issues
show
Unused Code introduced by
The assignment to $backupSeed is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $backupPrivateKey is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
548
        } else if (isset($options['backup_mnemonic'])) {
549
            $backupMnemonic = $options['backup_mnemonic'];
550
        } else if (isset($options['backup_public_key'])) {
551
            $backupPublicKey = $options['backup_public_key'];
552
        }
553
554 View Code Duplication
        if ($backupPublicKey) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
555
            if (is_string($backupPublicKey)) {
556
                $backupPublicKey = [$backupPublicKey, "m"];
557
            }
558
        } else {
559
            $backupPublicKey = BIP32::extended_private_to_public(BIP32::master_key(BIP39::mnemonicToSeedHex($backupMnemonic, ""), 'bitcoin', $this->testnet));
0 ignored issues
show
Bug introduced by
It seems like \BitWasp\BitcoinLib\BIP3...ex($backupMnemonic, '') targeting BitWasp\BitcoinLib\BIP39...39::mnemonicToSeedHex() can also be of type false or null; however, BitWasp\BitcoinLib\BIP32::master_key() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
560
        }
561
562
        // create a checksum of our private key which we'll later use to verify we used the right password
563
        $checksum = BIP32::key_to_address($primaryPrivateKey[0]);
564
565
        // send the public keys to the server to store them
566
        //  and the mnemonic, which is safe because it's useless without the password
567
        $data = $this->_createNewWallet($identifier, $primaryPublicKey, $backupPublicKey, $primaryMnemonic, $checksum, $keyIndex);
0 ignored issues
show
Bug introduced by
It seems like $primaryPublicKey defined by \BitWasp\BitcoinLib\BIP3...exPath()->publicPath()) on line 538 can also be of type string; however, Blocktrail\SDK\BlocktrailSDK::_createNewWallet() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
568
        // received the blocktrail public keys
569
        $blocktrailPublicKeys = $data['blocktrail_public_keys'];
570
571
        $wallet = new Wallet($this, $identifier, $primaryMnemonic, [$keyIndex => $primaryPublicKey], $backupPublicKey, $blocktrailPublicKeys, $keyIndex, $this->network, $this->testnet, $checksum);
572
573
        $wallet->unlock($options);
574
575
        // return wallet and backup mnemonic
576
        return [
577
            $wallet,
578
            $primaryMnemonic,
579
            $backupMnemonic,
580
            $blocktrailPublicKeys
581
        ];
582
    }
583
584
    /**
585
     * create wallet using the API
586
     *
587
     * @param string    $identifier             the wallet identifier to create
588
     * @param array     $primaryPublicKey       BIP32 extended public key - [key, path]
589
     * @param string    $backupPublicKey        plain public key
590
     * @param string    $primaryMnemonic        mnemonic to store
591
     * @param string    $checksum               checksum to store
592
     * @param int       $keyIndex               account that we expect to use
593
     * @return mixed
594
     */
595
    public function _createNewWallet($identifier, $primaryPublicKey, $backupPublicKey, $primaryMnemonic, $checksum, $keyIndex) {
596
        $data = [
597
            'identifier' => $identifier,
598
            'primary_public_key' => $primaryPublicKey,
599
            'backup_public_key' => $backupPublicKey,
600
            'primary_mnemonic' => $primaryMnemonic,
601
            'checksum' => $checksum,
602
            'key_index' => $keyIndex
603
        ];
604
605
        $response = $this->client->post("wallet", null, $data, RestClient::AUTH_HTTP_SIG);
606
        return self::jsonDecode($response->body(), true);
607
    }
608
609
    /**
610
     * upgrade wallet to use a new account number
611
     *  the account number specifies which blocktrail cosigning key is used
612
     *
613
     * @param string    $identifier             the wallet identifier to be upgraded
614
     * @param int       $keyIndex               the new account to use
615
     * @param array     $primaryPublicKey       BIP32 extended public key - [key, path]
616
     * @return mixed
617
     */
618
    public function upgradeKeyIndex($identifier, $keyIndex, $primaryPublicKey) {
619
        $data = [
620
            'key_index' => $keyIndex,
621
            'primary_public_key' => $primaryPublicKey
622
        ];
623
624
        $response = $this->client->post("wallet/{$identifier}/upgrade", null, $data, RestClient::AUTH_HTTP_SIG);
625
        return self::jsonDecode($response->body(), true);
626
    }
627
628
    /**
629
     * initialize a previously created wallet
630
     *
631
     * Either takes one argument:
632
     * @param array $options
633
     *
634
     * Or takes two arguments (old, deprecated syntax):
635
     * (@nonPHP-doc) @param string    $identifier             the wallet identifier to be initialized
0 ignored issues
show
Bug introduced by
There is no parameter named $identifier. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
636
     * (@nonPHP-doc) @param string    $password               the password to decrypt the mnemonic with
0 ignored issues
show
Bug introduced by
There is no parameter named $password. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
637
     *
638
     * @return WalletInterface
639
     * @throws \Exception
640
     */
641
    public function initWallet($options) {
642
        if (!is_array($options)) {
643
            $args = func_get_args();
644
            $options = [
645
                "identifier" => $args[0],
646
                "password" => $args[1],
647
            ];
648
        }
649
650
        $identifier = $options['identifier'];
651
        $readonly = isset($options['readonly']) ? $options['readonly'] :
652
                    (isset($options['readOnly']) ? $options['readOnly'] :
653
                        (isset($options['read-only']) ? $options['read-only'] :
654
                            false));
655
656
        // get the wallet data from the server
657
        $data = $this->getWallet($identifier);
658
659
        if (!$data) {
660
            throw new \Exception("Failed to get wallet");
661
        }
662
663
        // explode the wallet data
664
        $primaryMnemonic = isset($options['primary_mnemonic']) ? $options['primary_mnemonic'] : $data['primary_mnemonic'];
665
        $checksum = $data['checksum'];
666
        $backupPublicKey = $data['backup_public_key'];
667
        $primaryPublicKeys = $data['primary_public_keys'];
668
        $blocktrailPublicKeys = $data['blocktrail_public_keys'];
669
        $keyIndex = isset($options['key_index']) ? $options['key_index'] : $data['key_index'];
670
671
        $wallet = new Wallet($this, $identifier, $primaryMnemonic, $primaryPublicKeys, $backupPublicKey, $blocktrailPublicKeys, $keyIndex, $this->network, $this->testnet, $checksum);
672
673
        if (!$readonly) {
674
            $wallet->unlock($options);
675
        }
676
677
        return $wallet;
678
    }
679
680
    /**
681
     * get the wallet data from the server
682
     *
683
     * @param string    $identifier             the identifier of the wallet
684
     * @return mixed
685
     */
686
    public function getWallet($identifier) {
687
        $response = $this->client->get("wallet/{$identifier}", null, RestClient::AUTH_HTTP_SIG);
688
        return self::jsonDecode($response->body(), true);
689
    }
690
691
    /**
692
     * delete a wallet from the server
693
     *  the checksum address and a signature to verify you ownership of the key of that checksum address
694
     *  is required to be able to delete a wallet
695
     *
696
     * @param string    $identifier             the identifier of the wallet
697
     * @param string    $checksumAddress        the address for your master private key (and the checksum used when creating the wallet)
698
     * @param string    $signature              a signature of the checksum address as message signed by the private key matching that address
699
     * @param bool      $force                  ignore warnings (such as a non-zero balance)
700
     * @return mixed
701
     */
702
    public function deleteWallet($identifier, $checksumAddress, $signature, $force = false) {
703
        $response = $this->client->delete("wallet/{$identifier}", ['force' => $force], [
704
            'checksum' => $checksumAddress,
705
            'signature' => $signature
706
        ], RestClient::AUTH_HTTP_SIG, 360);
707
        return self::jsonDecode($response->body(), true);
708
    }
709
710
    /**
711
     * create new backup key;
712
     *  1) a BIP39 mnemonic
713
     *  2) a seed from that mnemonic with a blank password
714
     *  3) a private key from that seed
715
     *
716
     * @return array [mnemonic, seed, key]
717
     */
718
    protected function newBackupSeed() {
719
        list($backupMnemonic, $backupSeed, $backupPrivateKey) = $this->generateNewSeed("");
720
721
        return [$backupMnemonic, $backupSeed, $backupPrivateKey];
722
    }
723
724
    /**
725
     * create new primary key;
726
     *  1) a BIP39 mnemonic
727
     *  2) a seed from that mnemonic with the password
728
     *  3) a private key from that seed
729
     *
730
     * @param string    $passphrase             the password to use in the BIP39 creation of the seed
731
     * @return array [mnemonic, seed, key]
732
     * @TODO: require a strong password?
733
     */
734
    protected function newPrimarySeed($passphrase) {
735
        list($primaryMnemonic, $primarySeed, $primaryPrivateKey) = $this->generateNewSeed($passphrase);
736
737
        return [$primaryMnemonic, $primarySeed, $primaryPrivateKey];
738
    }
739
740
    /**
741
     * create a new key;
742
     *  1) a BIP39 mnemonic
743
     *  2) a seed from that mnemonic with the password
744
     *  3) a private key from that seed
745
     *
746
     * @param string    $passphrase             the password to use in the BIP39 creation of the seed
747
     * @param string    $forceEntropy           forced entropy instead of random entropy for testing purposes
748
     * @return array
749
     */
750
    protected function generateNewSeed($passphrase = "", $forceEntropy = null) {
751
        // generate master seed, retry if the generated private key isn't valid (FALSE is returned)
752
        do {
753
            $mnemonic = $this->generateNewMnemonic($forceEntropy);
754
755
            $seed = BIP39::mnemonicToSeedHex($mnemonic, $passphrase);
756
757
            $key = BIP32::master_key($seed, $this->network, $this->testnet);
0 ignored issues
show
Bug introduced by
It seems like $seed defined by \BitWasp\BitcoinLib\BIP3...$mnemonic, $passphrase) on line 755 can also be of type false or null; however, BitWasp\BitcoinLib\BIP32::master_key() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
758
        } while (!$key);
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type string[] 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...
759
760
        return [$mnemonic, $seed, $key];
761
    }
762
763
    /**
764
     * generate a new mnemonic from some random entropy (512 bit)
765
     *
766
     * @param string    $forceEntropy           forced entropy instead of random entropy for testing purposes
767
     * @return string
768
     * @throws \Exception
769
     */
770
    protected function generateNewMnemonic($forceEntropy = null) {
771
        if ($forceEntropy === null) {
772
            $entropy = BIP39::generateEntropy(512);
773
        } else {
774
            $entropy = $forceEntropy;
775
        }
776
777
        return BIP39::entropyToMnemonic($entropy);
778
    }
779
780
    /**
781
     * get the balance for the wallet
782
     *
783
     * @param string    $identifier             the identifier of the wallet
784
     * @return array
785
     */
786
    public function getWalletBalance($identifier) {
787
        $response = $this->client->get("wallet/{$identifier}/balance", null, RestClient::AUTH_HTTP_SIG);
788
        return self::jsonDecode($response->body(), true);
789
    }
790
791
    /**
792
     * do HD wallet discovery for the wallet
793
     *
794
     * this can be REALLY slow, so we've set the timeout to 120s ...
795
     *
796
     * @param string    $identifier             the identifier of the wallet
797
     * @param int       $gap                    the gap setting to use for discovery
798
     * @return mixed
799
     */
800
    public function doWalletDiscovery($identifier, $gap = 200) {
801
        $response = $this->client->get("wallet/{$identifier}/discovery", ['gap' => $gap], RestClient::AUTH_HTTP_SIG, 360.0);
802
        return self::jsonDecode($response->body(), true);
803
    }
804
805
    /**
806
     * get a new derivation number for specified parent path
807
     *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
808
     *
809
     * returns the path
810
     *
811
     * @param string    $identifier             the identifier of the wallet
812
     * @param string    $path                   the parent path for which to get a new derivation
813
     * @return string
814
     */
815
    public function getNewDerivation($identifier, $path) {
816
        $result = $this->_getNewDerivation($identifier, $path);
817
        return $result['path'];
818
    }
819
820
    /**
821
     * get a new derivation number for specified parent path
822
     *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
823
     *
824
     * @param string    $identifier             the identifier of the wallet
825
     * @param string    $path                   the parent path for which to get a new derivation
826
     * @return mixed
827
     */
828
    public function _getNewDerivation($identifier, $path) {
829
        $response = $this->client->post("wallet/{$identifier}/path", null, ['path' => $path], RestClient::AUTH_HTTP_SIG);
830
        return self::jsonDecode($response->body(), true);
831
    }
832
833
    /**
834
     * get the path (and redeemScript) to specified address
835
     *
836
     * @param string $identifier
837
     * @param string $address
838
     * @return array
839
     * @throws \Exception
840
     */
841
    public function getPathForAddress($identifier, $address) {
842
        $response = $this->client->post("wallet/{$identifier}/path_for_address", null, ['address' => $address], RestClient::AUTH_HTTP_SIG);
843
        return self::jsonDecode($response->body(), true)['path'];
844
    }
845
846
    /**
847
     * send the transaction using the API
848
     *
849
     * @param string    $identifier             the identifier of the wallet
850
     * @param string    $rawTransaction         raw hex of the transaction (should be partially signed)
851
     * @param array     $paths                  list of the paths that were used for the UTXO
852
     * @param bool      $checkFee               let the server verify the fee after signing
853
     * @return string                           the complete raw transaction
854
     * @throws \Exception
855
     */
856
    public function sendTransaction($identifier, $rawTransaction, $paths, $checkFee = false) {
857
        $data = [
858
            'raw_transaction' => $rawTransaction,
859
            'paths' => $paths
860
        ];
861
862
        // dynamic TTL for when we're signing really big transactions
863
        $ttl = max(5.0, count($paths) * 0.25) + 4.0;
864
865
        $response = $this->client->post("wallet/{$identifier}/send", ['check_fee' => (int)!!$checkFee], $data, RestClient::AUTH_HTTP_SIG, $ttl);
866
        $signed = self::jsonDecode($response->body(), true);
867
868
        if (!$signed['complete'] || $signed['complete'] == 'false') {
869
            throw new \Exception("Failed to completely sign transaction");
870
        }
871
872
        // create TX hash from the raw signed hex
873
        return RawTransaction::txid_from_raw($signed['hex']);
874
    }
875
876
    /**
877
     * use the API to get the best inputs to use based on the outputs
878
     *
879
     * the return array has the following format:
880
     * [
881
     *  "utxos" => [
882
     *      [
883
     *          "hash" => "<txHash>",
884
     *          "idx" => "<index of the output of that <txHash>",
885
     *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
886
     *          "value" => 32746327,
887
     *          "address" => "1address",
888
     *          "path" => "m/44'/1'/0'/0/13",
889
     *          "redeem_script" => "<redeemScript-hex>",
890
     *      ],
891
     *  ],
892
     *  "fee"   => 10000,
893
     *  "change"=> 1010109201,
894
     * ]
895
     *
896
     * @param string   $identifier              the identifier of the wallet
897
     * @param array    $outputs                 the outputs you want to create - array[address => satoshi-value]
898
     * @param bool     $lockUTXO                when TRUE the UTXOs selected will be locked for a few seconds
899
     *                                          so you have some time to spend them without race-conditions
900
     * @param bool     $allowZeroConf
901
     * @param string   $feeStrategy
902
     * @param null|int $forceFee
903
     * @return array
904
     * @throws \Exception
905
     */
906 View Code Duplication
    public function coinSelection($identifier, $outputs, $lockUTXO = false, $allowZeroConf = false, $feeStrategy = Wallet::FEE_STRATEGY_OPTIMAL, $forceFee = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
907
        $args = [
908
            'lock' => (int)!!$lockUTXO,
909
            'zeroconf' => (int)!!$allowZeroConf,
910
            'fee_strategy' => $feeStrategy,
911
        ];
912
913
        if ($forceFee !== null) {
914
            $args['forcefee'] = (int)$forceFee;
915
        }
916
917
        $response = $this->client->post(
918
            "wallet/{$identifier}/coin-selection",
919
            $args,
920
            $outputs,
921
            RestClient::AUTH_HTTP_SIG
922
        );
923
924
        return self::jsonDecode($response->body(), true);
925
    }
926
927
    /**
928
     *
929
     * @param string   $identifier the identifier of the wallet
930
     * @param bool     $allowZeroConf
931
     * @param string   $feeStrategy
932
     * @param null|int $forceFee
933
     * @param int      $outputCnt
934
     * @return array
935
     * @throws \Exception
936
     */
937 View Code Duplication
    public function walletMaxSpendable($identifier, $allowZeroConf = false, $feeStrategy = Wallet::FEE_STRATEGY_OPTIMAL, $forceFee = null, $outputCnt = 1) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
938
        $args = [
939
            'zeroconf' => (int)!!$allowZeroConf,
940
            'fee_strategy' => $feeStrategy,
941
            'outputs' => $outputCnt,
942
        ];
943
944
        if ($forceFee !== null) {
945
            $args['forcefee'] = (int)$forceFee;
946
        }
947
948
        $response = $this->client->get(
949
            "wallet/{$identifier}/max-spendable",
950
            $args,
951
            RestClient::AUTH_HTTP_SIG
952
        );
953
954
        return self::jsonDecode($response->body(), true);
955
    }
956
957
    /**
958
     * @return array        ['optimal_fee' => 10000, 'low_priority_fee' => 5000]
959
     */
960
    public function feePerKB() {
961
        $response = $this->client->get("fee-per-kb");
962
        return self::jsonDecode($response->body(), true);
963
    }
964
965
    /**
966
     * get the current price index
967
     *
968
     * @return array        eg; ['USD' => 287.30]
969
     */
970
    public function price() {
971
        $response = $this->client->get("price");
972
        return self::jsonDecode($response->body(), true);
973
    }
974
975
    /**
976
     * setup webhook for wallet
977
     *
978
     * @param string    $identifier         the wallet identifier for which to create the webhook
979
     * @param string    $webhookIdentifier  the webhook identifier to use
980
     * @param string    $url                the url to receive the webhook events
981
     * @return array
982
     */
983
    public function setupWalletWebhook($identifier, $webhookIdentifier, $url) {
984
        $response = $this->client->post("wallet/{$identifier}/webhook", null, ['url' => $url, 'identifier' => $webhookIdentifier], RestClient::AUTH_HTTP_SIG);
985
        return self::jsonDecode($response->body(), true);
986
    }
987
988
    /**
989
     * delete webhook for wallet
990
     *
991
     * @param string    $identifier         the wallet identifier for which to delete the webhook
992
     * @param string    $webhookIdentifier  the webhook identifier to delete
993
     * @return array
994
     */
995
    public function deleteWalletWebhook($identifier, $webhookIdentifier) {
996
        $response = $this->client->delete("wallet/{$identifier}/webhook/{$webhookIdentifier}", null, null, RestClient::AUTH_HTTP_SIG);
997
        return self::jsonDecode($response->body(), true);
998
    }
999
1000
    /**
1001
     * lock a specific unspent output
1002
     *
1003
     * @param     $identifier
1004
     * @param     $txHash
1005
     * @param     $txIdx
1006
     * @param int $ttl
1007
     * @return bool
1008
     */
1009
    public function lockWalletUTXO($identifier, $txHash, $txIdx, $ttl = 3) {
1010
        $response = $this->client->post("wallet/{$identifier}/lock-utxo", null, ['hash' => $txHash, 'idx' => $txIdx, 'ttl' => $ttl], RestClient::AUTH_HTTP_SIG);
1011
        return self::jsonDecode($response->body(), true)['locked'];
1012
    }
1013
1014
    /**
1015
     * unlock a specific unspent output
1016
     *
1017
     * @param     $identifier
1018
     * @param     $txHash
1019
     * @param     $txIdx
1020
     * @return bool
1021
     */
1022
    public function unlockWalletUTXO($identifier, $txHash, $txIdx) {
1023
        $response = $this->client->post("wallet/{$identifier}/unlock-utxo", null, ['hash' => $txHash, 'idx' => $txIdx], RestClient::AUTH_HTTP_SIG);
1024
        return self::jsonDecode($response->body(), true)['unlocked'];
1025
    }
1026
1027
    /**
1028
     * get all transactions for wallet (paginated)
1029
     *
1030
     * @param  string  $identifier  the wallet identifier for which to get transactions
1031
     * @param  integer $page        pagination: page number
1032
     * @param  integer $limit       pagination: records per page (max 500)
1033
     * @param  string  $sortDir     pagination: sort direction (asc|desc)
1034
     * @return array                associative array containing the response
1035
     */
1036 View Code Duplication
    public function walletTransactions($identifier, $page = 1, $limit = 20, $sortDir = 'asc') {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1037
        $queryString = [
1038
            'page' => $page,
1039
            'limit' => $limit,
1040
            'sort_dir' => $sortDir
1041
        ];
1042
        $response = $this->client->get("wallet/{$identifier}/transactions", $queryString, RestClient::AUTH_HTTP_SIG);
1043
        return self::jsonDecode($response->body(), true);
1044
    }
1045
1046
    /**
1047
     * get all addresses for wallet (paginated)
1048
     *
1049
     * @param  string  $identifier  the wallet identifier for which to get addresses
1050
     * @param  integer $page        pagination: page number
1051
     * @param  integer $limit       pagination: records per page (max 500)
1052
     * @param  string  $sortDir     pagination: sort direction (asc|desc)
1053
     * @return array                associative array containing the response
1054
     */
1055 View Code Duplication
    public function walletAddresses($identifier, $page = 1, $limit = 20, $sortDir = 'asc') {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1056
        $queryString = [
1057
            'page' => $page,
1058
            'limit' => $limit,
1059
            'sort_dir' => $sortDir
1060
        ];
1061
        $response = $this->client->get("wallet/{$identifier}/addresses", $queryString, RestClient::AUTH_HTTP_SIG);
1062
        return self::jsonDecode($response->body(), true);
1063
    }
1064
1065
    /**
1066
     * get all UTXOs for wallet (paginated)
1067
     *
1068
     * @param  string  $identifier  the wallet identifier for which to get addresses
1069
     * @param  integer $page        pagination: page number
1070
     * @param  integer $limit       pagination: records per page (max 500)
1071
     * @param  string  $sortDir     pagination: sort direction (asc|desc)
1072
     * @return array                associative array containing the response
1073
     */
1074 View Code Duplication
    public function walletUTXOs($identifier, $page = 1, $limit = 20, $sortDir = 'asc') {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1075
        $queryString = [
1076
            'page' => $page,
1077
            'limit' => $limit,
1078
            'sort_dir' => $sortDir
1079
        ];
1080
        $response = $this->client->get("wallet/{$identifier}/utxos", $queryString, RestClient::AUTH_HTTP_SIG);
1081
        return self::jsonDecode($response->body(), true);
1082
    }
1083
1084
    /**
1085
     * get a paginated list of all wallets associated with the api user
1086
     *
1087
     * @param  integer          $page    pagination: page number
1088
     * @param  integer          $limit   pagination: records per page
1089
     * @return array                     associative array containing the response
1090
     */
1091
    public function allWallets($page = 1, $limit = 20) {
1092
        $queryString = [
1093
            'page' => $page,
1094
            'limit' => $limit
1095
        ];
1096
        $response = $this->client->get("wallets", $queryString, RestClient::AUTH_HTTP_SIG);
1097
        return self::jsonDecode($response->body(), true);
1098
    }
1099
1100
    /**
1101
     * verify a message signed bitcoin-core style
1102
     *
1103
     * @param  string           $message
1104
     * @param  string           $address
1105
     * @param  string           $signature
1106
     * @return boolean
1107
     */
1108
    public function verifyMessage($message, $address, $signature) {
1109
        // we could also use the API instead of the using BitcoinLib to verify
1110
        // $this->client->post("verify_message", null, ['message' => $message, 'address' => $address, 'signature' => $signature])['result'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
1111
1112
        try {
1113
            return BitcoinLib::verifyMessage($address, $signature, $message);
1114
        } catch (\Exception $e) {
1115
            return false;
1116
        }
1117
    }
1118
1119
    /**
1120
     * convert a Satoshi value to a BTC value
1121
     *
1122
     * @param int       $satoshi
1123
     * @return float
1124
     */
1125
    public static function toBTC($satoshi) {
1126
        return bcdiv((int)(string)$satoshi, 100000000, 8);
1127
    }
1128
1129
    /**
1130
     * convert a Satoshi value to a BTC value and return it as a string
1131
1132
     * @param int       $satoshi
1133
     * @return string
1134
     */
1135
    public static function toBTCString($satoshi) {
1136
        return sprintf("%.8f", self::toBTC($satoshi));
1137
    }
1138
1139
    /**
1140
     * convert a BTC value to a Satoshi value
1141
     *
1142
     * @param float     $btc
1143
     * @return string
1144
     */
1145
    public static function toSatoshiString($btc) {
1146
        return bcmul(sprintf("%.8f", (float)$btc), 100000000, 0);
1147
    }
1148
1149
    /**
1150
     * convert a BTC value to a Satoshi value
1151
     *
1152
     * @param float     $btc
1153
     * @return string
1154
     */
1155
    public static function toSatoshi($btc) {
1156
        return (int)self::toSatoshiString($btc);
1157
    }
1158
1159
    /**
1160
     * json_decode helper that throws exceptions when it fails to decode
1161
     *
1162
     * @param      $json
1163
     * @param bool $assoc
1164
     * @return mixed
1165
     * @throws \Exception
1166
     */
1167
    protected static function jsonDecode($json, $assoc = false) {
1168
        if (!$json) {
1169
            throw new \Exception("Can't json_decode empty string [{$json}]");
1170
        }
1171
1172
        $data = json_decode($json, $assoc);
1173
1174
        if ($data === null) {
1175
            throw new \Exception("Failed to json_decode [{$json}]");
1176
        }
1177
1178
        return $data;
1179
    }
1180
1181
    /**
1182
     * sort public keys for multisig script
1183
     *
1184
     * @param string[] $pubKeys
1185
     * @return string[]
1186
     */
1187
    public static function sortMultisigKeys(array $pubKeys) {
1188
        $sortedKeys = $pubKeys;
1189
1190
        sort($sortedKeys);
1191
1192
        return $sortedKeys;
1193
    }
1194
1195
    /**
1196
     * read and decode the json payload from a webhook's POST request.
1197
     *
1198
     * @param bool $returnObject    flag to indicate if an object or associative array should be returned
1199
     * @return mixed|null
1200
     * @throws \Exception
1201
     */
1202
    public static function getWebhookPayload($returnObject = false) {
1203
        $data = file_get_contents("php://input");
1204
        if ($data) {
1205
            return self::jsonDecode($data, !$returnObject);
1206
        } else {
1207
            return null;
1208
        }
1209
    }
1210
}
1211