Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
60.71% covered (warning)
60.71%
17 / 28
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Blockstream_Info_API
60.71% covered (warning)
60.71%
17 / 28
33.33% covered (danger)
33.33%
1 / 3
13.91
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_transactions_received
69.57% covered (warning)
69.57%
16 / 23
0.00% covered (danger)
0.00%
0 / 1
5.70
 get_blockchain_height
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * @see https://github.com/Blockstream/esplora/blob/master/API.md
4 * @see https://dashboard.blockstream.info
5 *
6 * {
7 * "error" : "Too Many Requests",
8 * "message" : "Blockstream Explorer API NOTICE: Your request rate exceeds the current limit. Starting July 15 2025, monthly unauthenticated usage will be capped at 500,000 requests/month and 700 requests/hour per IP. To maintain uninterrupted access, get your API key at: https://dashboard.blockstream.info"
9 * }
10 *
11 * @see https://github.com/Blockstream/esplora/issues/519
12 * @see https://github.com/Blockstream/esplora/issues/449
13 *
14 * @package    brianhenryie/bh-wp-bitcoin-gateway
15 */
16
17namespace BrianHenryIE\WP_Bitcoin_Gateway\API\Clients\Blockchain;
18
19use BrianHenryIE\WP_Bitcoin_Gateway\API\Clients\Blockchain_API_Interface;
20use BrianHenryIE\WP_Bitcoin_Gateway\API\Clients\Blockchain\Adapters\BlockStream_Info_API_Transaction_Adapter;
21use BrianHenryIE\WP_Bitcoin_Gateway\API\Model\Exceptions\BH_WP_Bitcoin_Gateway_Exception;
22use BrianHenryIE\WP_Bitcoin_Gateway\API\Model\Exceptions\Rate_Limit_Exception;
23use BrianHenryIE\WP_Bitcoin_Gateway\API\Model\Payments\Transaction_Interface;
24use JsonException;
25use Psr\Log\LoggerAwareInterface;
26use Psr\Log\LoggerAwareTrait;
27use Psr\Log\LoggerInterface;
28
29/**
30 * TODO: Complete the transaction in/out array shapes.
31 *
32 * @phpstan-type BlockStreamApiTransactionVInArray array{txid:string, vout:int, prevout:array{scriptpubkey:string, scriptpubkey_asm:string, scriptpubkey_type:string, scriptpubkey_address:string, value:int}, scriptsig:string, scriptsig_asm:string, witness:array<string>, is_coinbase:bool, sequence:int, inner_redeemscript_asm:string, inner_witnewssscript_asm:string}
33 * @phpstan-type BlockStreamApiTransactionVOutArray array{scriptpubkey:string, scriptpubkey_asm:string, scriptpubkey_type:string, scriptpubkey_address:string, value:int}
34 * @phpstan-type BlockStreamApiTransactionArray array{txid:string, version:int, locktime:int, vin:BlockStreamApiTransactionVInArray, vout:BlockStreamApiTransactionVOutArray, size:int, weight:int, fee:int, status:array{confirmed:bool, block_height:int, block_hash:string, block_time:int}}
35 * @phpstan-type Stats array{funded_txo_count:int, funded_txo_sum:int, spent_txo_count:int, spent_txo_sum:int, tx_count:int}
36 */
37class Blockstream_Info_API implements Blockchain_API_Interface, LoggerAwareInterface {
38    use LoggerAwareTrait;
39
40    /**
41     * Constructor
42     *
43     * @param LoggerInterface $logger Logger instance for debug logging API calls.
44     */
45    public function __construct(
46        LoggerInterface $logger
47    ) {
48        $this->setLogger( $logger );
49    }
50
51    /**
52     * Get all transactions received for a Bitcoin address.
53     *
54     * @param string $btc_address The Bitcoin address to query.
55     *
56     * @return array<string, Transaction_Interface> Transactions keyed by txid.
57     *
58     * @throws JsonException When JSON decoding of the API response fails.
59     * @throws Rate_Limit_Exception When HTTP 429 is returned, indicating the rate limit has been exceeded.
60     * @throws BH_WP_Bitcoin_Gateway_Exception When the API request fails or returns an unexpected response code.
61     */
62    public function get_transactions_received( string $btc_address ): array {
63
64        $address_info_url_bs = "https://blockstream.info/api/address/{$btc_address}/txs";
65
66        $this->logger->debug( 'URL: {url}', array( 'url' => $address_info_url_bs ) );
67
68        $request_response = wp_remote_get( $address_info_url_bs );
69
70        if ( is_wp_error( $request_response ) ) {
71            throw new BH_WP_Bitcoin_Gateway_Exception( $request_response->get_error_message() );
72        }
73        if ( 429 === $request_response['response']['code'] ) {
74            /** @var array{error:string, message:string} $blockstream_rate_limit_response */
75            $blockstream_rate_limit_response = json_decode( (string) $request_response['body'], true, 512, JSON_THROW_ON_ERROR );
76            throw new Rate_Limit_Exception(
77                reset_time: null,
78                message: $blockstream_rate_limit_response['message']
79            );
80        }
81
82        if ( 200 !== $request_response['response']['code'] ) {
83            throw new BH_WP_Bitcoin_Gateway_Exception( 'Unexpected response received.' );
84        }
85
86        /**
87         * @var BlockStreamApiTransactionArray[] $blockstream_transactions
88         */
89        $blockstream_transactions = json_decode( (string) $request_response['body'], true, 512, JSON_THROW_ON_ERROR );
90
91        $adapter = new BlockStream_Info_API_Transaction_Adapter();
92
93        /**
94         * `block_time` is in unix-time.
95         *
96         * @param array{txid:string, version:int, locktime:int, vin:array, vout:array, size:int, weight:int, fee:int, status:array{confirmed:bool, block_height:int, block_hash:string, block_time:int}} $blockstream_transaction
97         *
98         * @var Transaction_Interface[] $transactions
99         */
100        $transactions = array_map(
101            $adapter->adapt( ... ),
102            $blockstream_transactions
103        );
104
105        $keyed_transactions = array();
106        foreach ( $transactions as $transaction ) {
107            $keyed_transactions[ $transaction->get_txid() ] = $transaction;
108        }
109
110        return $keyed_transactions;
111    }
112
113    /**
114     * Get the current Bitcoin blockchain height.
115     *
116     * @return int The current block height from Blockstream's API.
117     * @throws BH_WP_Bitcoin_Gateway_Exception When the API request fails or returns a non-200 status code.
118     */
119    public function get_blockchain_height(): int {
120        $blocks_url_bs    = 'https://blockstream.info/api/blocks/tip/height';
121        $request_response = wp_remote_get( $blocks_url_bs );
122        if ( is_wp_error( $request_response ) || 200 !== $request_response['response']['code'] ) {
123            throw new BH_WP_Bitcoin_Gateway_Exception();
124        }
125        return intval( $request_response['body'] );
126    }
127}