diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index 0b19fc348..df53bb1ed 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -12724,3 +12724,369 @@ export const faqData = [ title: 'Why do I get an error for certain address lookups on my Mempool instance?', } ]; + +export const electrumApiDocsData = [ + { + type: 'category', + category: 'server', + fragment: 'server-methods', + title: 'Server Methods', + showConditions: bitcoinNetworks.concat(liquidNetworks) + }, + { + type: 'endpoint', + category: 'server', + fragment: 'server-version', + title: 'server.version', + description: { + default: 'Returns the server version and protocol version.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"server.version","params":[]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": ["mempool-electrs v3.3.0", "1.4"] +}` + }, + { + type: 'endpoint', + category: 'server', + fragment: 'server-banner', + title: 'server.banner', + description: { + default: 'Returns the server banner message.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"server.banner","params":[]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": "Welcome to mempool.space Electrum Server" +}` + }, + { + type: 'endpoint', + category: 'server', + fragment: 'server-ping', + title: 'server.ping', + description: { + default: 'A simple ping to check if the server is responsive.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"server.ping","params":[]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": null +}` + }, + { + type: 'category', + category: 'blockchain', + fragment: 'blockchain-methods', + title: 'Blockchain Methods', + showConditions: bitcoinNetworks.concat(liquidNetworks) + }, + { + type: 'endpoint', + category: 'blockchain', + fragment: 'blockchain-block-header', + title: 'blockchain.block.header', + description: { + default: 'Returns the block header at a given height. Optionally includes a merkle proof if cp_height is specified.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.block.header","params":[500000]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": "00000020b48b86f547e8ea3b1c3e8a49e2e93f6e6e1e73c3b94f16e4e7e7e7e7" +}` + }, + { + type: 'endpoint', + category: 'blockchain', + fragment: 'blockchain-block-headers', + title: 'blockchain.block.headers', + description: { + default: 'Returns a range of block headers. Maximum of 2016 headers can be requested at once.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.block.headers","params":[500000, 3]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "count": 3, + "hex": "00000020b48b86f547e8ea3b...", + "max": 2016 + } +}` + }, + { + type: 'endpoint', + category: 'blockchain', + fragment: 'blockchain-headers-subscribe', + title: 'blockchain.headers.subscribe', + description: { + default: 'Subscribe to new block headers. Returns the current best block header and sends notifications when new blocks are found.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.headers.subscribe","params":[]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "hex": "00000020b48b86f547e8ea3b1c3e8a49e2e93f6e6e1e73c3b94f16e4e7e7e7e7", + "height": 875000 + } +}` + }, + { + type: 'endpoint', + category: 'blockchain', + fragment: 'blockchain-estimatefee', + title: 'blockchain.estimatefee', + description: { + default: 'Returns the estimated transaction fee for confirmation within a given number of blocks. Returns fee in BTC/kB, or -1 if estimation is not available.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.estimatefee","params":[6]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": 0.00005000 +}` + }, + { + type: 'endpoint', + category: 'blockchain', + fragment: 'blockchain-relayfee', + title: 'blockchain.relayfee', + description: { + default: 'Returns the minimum relay fee for low-priority transactions in BTC/kB.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.relayfee","params":[]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": 0.00001000 +}` + }, + { + type: 'category', + category: 'scripthash', + fragment: 'scripthash-methods', + title: 'Script Hash Methods', + showConditions: bitcoinNetworks.concat(liquidNetworks) + }, + { + type: 'endpoint', + category: 'scripthash', + fragment: 'blockchain-scripthash-get-balance', + title: 'blockchain.scripthash.get_balance', + description: { + default: 'Returns the confirmed and unconfirmed balance for a script hash. Script hashes are the SHA256 hash of the output script (scriptPubKey), encoded as hexadecimal with the bytes reversed.

Not available on Liquid networks.' + }, + showConditions: bitcoinNetworks, + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.scripthash.get_balance","params":["8b01df4e368ea28f8dc0423bcf7a4923e3a12d307c875e47a0cfbf90b5c39161"]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "confirmed": 1000000, + "unconfirmed": 0 + } +}` + }, + { + type: 'endpoint', + category: 'scripthash', + fragment: 'blockchain-scripthash-get-history', + title: 'blockchain.scripthash.get_history', + description: { + default: 'Returns the transaction history for a script hash. Returns an array of objects containing transaction ID, height, and fee (for mempool transactions).' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.scripthash.get_history","params":["8b01df4e368ea28f8dc0423bcf7a4923e3a12d307c875e47a0cfbf90b5c39161"]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "tx_hash": "9d1e7c2e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d", + "height": 500000, + "fee": 0 + }, + { + "tx_hash": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", + "height": 0, + "fee": 226 + } + ] +}` + }, + { + type: 'endpoint', + category: 'scripthash', + fragment: 'blockchain-scripthash-listunspent', + title: 'blockchain.scripthash.listunspent', + description: { + default: 'Returns the list of unspent transaction outputs for a script hash.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.scripthash.listunspent","params":["8b01df4e368ea28f8dc0423bcf7a4923e3a12d307c875e47a0cfbf90b5c39161"]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "height": 500000, + "tx_pos": 0, + "tx_hash": "9d1e7c2e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d", + "value": 1000000 + } + ] +}` + }, + { + type: 'endpoint', + category: 'scripthash', + fragment: 'blockchain-scripthash-subscribe', + title: 'blockchain.scripthash.subscribe', + description: { + default: 'Subscribe to notifications for a script hash. Returns the current status hash and sends notifications when the history changes.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.scripthash.subscribe","params":["8b01df4e368ea28f8dc0423bcf7a4923e3a12d307c875e47a0cfbf90b5c39161"]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": "5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b" +}` + }, + { + type: 'endpoint', + category: 'scripthash', + fragment: 'blockchain-scripthash-unsubscribe', + title: 'blockchain.scripthash.unsubscribe', + description: { + default: 'Unsubscribe from notifications for a script hash.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.scripthash.unsubscribe","params":["8b01df4e368ea28f8dc0423bcf7a4923e3a12d307c875e47a0cfbf90b5c39161"]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": true +}` + }, + { + type: 'category', + category: 'transaction', + fragment: 'transaction-methods', + title: 'Transaction Methods', + showConditions: bitcoinNetworks.concat(liquidNetworks) + }, + { + type: 'endpoint', + category: 'transaction', + fragment: 'blockchain-transaction-broadcast', + title: 'blockchain.transaction.broadcast', + description: { + default: 'Broadcast a raw transaction to the network. Returns the transaction ID on success.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.transaction.broadcast","params":["0200000001..."]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": "9d1e7c2e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d" +}` + }, + { + type: 'endpoint', + category: 'transaction', + fragment: 'blockchain-transaction-get', + title: 'blockchain.transaction.get', + description: { + default: 'Returns a raw transaction by its transaction ID. Returns hex-encoded raw transaction.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.transaction.get","params":["9d1e7c2e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d"]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": "02000000019c2e0f24a03e72002a96acedb12a632e72b6119a" +}` + }, + { + type: 'endpoint', + category: 'transaction', + fragment: 'blockchain-transaction-get-merkle', + title: 'blockchain.transaction.get_merkle', + description: { + default: 'Returns the merkle proof for a transaction. Requires the transaction to be confirmed.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.transaction.get_merkle","params":["9d1e7c2e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d", 500000]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "block_height": 500000, + "merkle": [ + "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", + "2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c" + ], + "pos": 123 + } +}` + }, + { + type: 'endpoint', + category: 'transaction', + fragment: 'blockchain-transaction-id-from-pos', + title: 'blockchain.transaction.id_from_pos', + description: { + default: 'Returns the transaction ID at a given position in a block. Optionally includes the merkle proof.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"blockchain.transaction.id_from_pos","params":[500000, 123, false]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": "9d1e7c2e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d" +}` + }, + { + type: 'category', + category: 'mempool', + fragment: 'mempool-methods', + title: 'Mempool Methods', + showConditions: bitcoinNetworks.concat(liquidNetworks) + }, + { + type: 'endpoint', + category: 'mempool', + fragment: 'mempool-get-fee-histogram', + title: 'mempool.get_fee_histogram', + description: { + default: 'Returns a histogram of transaction fees in the mempool. Each entry is a pair of [fee_rate, cumulative_vsize] where fee_rate is in satoshis per virtual byte.' + }, + showConditions: bitcoinNetworks.concat(liquidNetworks), + requestExample: '{"jsonrpc":"2.0","id":1,"method":"mempool.get_fee_histogram","params":[]}', + responseExample: `{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + [10.0, 50000], + [5.0, 150000], + [2.0, 300000], + [1.0, 500000] + ] +}` + } +]; diff --git a/frontend/src/app/docs/api-docs/api-docs-nav.component.ts b/frontend/src/app/docs/api-docs/api-docs-nav.component.ts index 24ed5e996..de16c4469 100644 --- a/frontend/src/app/docs/api-docs/api-docs-nav.component.ts +++ b/frontend/src/app/docs/api-docs/api-docs-nav.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Env, StateService } from '@app/services/state.service'; import { Subject, Subscription } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { restApiDocsData, wsApiDocsData } from '@app/docs/api-docs/api-docs-data'; +import { restApiDocsData, wsApiDocsData, electrumApiDocsData } from '@app/docs/api-docs/api-docs-data'; import { faqData } from '@app/docs/api-docs/api-docs-data'; @Component({ @@ -42,6 +42,8 @@ export class ApiDocsNavComponent implements OnInit { this.tabData = wsApiDocsData; } else if (this.whichTab === 'faq') { this.tabData = faqData; + } else if (this.whichTab === 'electrs') { + this.tabData = electrumApiDocsData; } } diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 74e67dd08..f78ad27c4 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -150,16 +150,79 @@
-
-
-

Hostname

-

{{plainHostname}}

-

Port

-

{{electrsPort}}

-

SSL

-

Enabled

+ +
+ +
+ +
+ +
+

Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} Electrum JSON-RPC v2.0 API.

+ +
+

Connection Information

+
+

Hostname

+

{{plainHostname}}

+
+
+

Port

+

{{electrsPort}}

+
+
+

SSL

+

Enabled

+
+
+

Protocol

+

JSON-RPC v2.0

+
+
+

Protocol Version

+

1.4

+
+
+

Electrum RPC interface for Bitcoin Signet and Bitcoin Testnet4 is publicly available. Electrum RPC interface for all other networks is available to sponsors only—whitelisting is required.

+ +
+

Request Format

+

All requests must be valid JSON-RPC 2.0 messages. You can use a tool like curl with SSL/TLS to connect to the Electrum server.

+
{{'{'}}"jsonrpc": "2.0", "id": 1, "method": "method.name", "params": []{{'}'}}
+
+ +
+
+

{{ item.title }}

+
+ {{ item.title }} {{ item.category }} +
+
+
Description
+
+
+
+
Request Example
+
+

Using curl with SSL:

+
echo '{{item.requestExample}}' | openssl s_client -connect {{plainHostname}}:{{electrsPort}} -quiet 2>/dev/null
+
+
+

Raw JSON-RPC Request:

+
{{item.requestExample}}
+
+
+
+
Response Example
+
{{item.responseExample}}
+
+
+
+
+
+
diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index 56e94f9c8..daa593412 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -491,4 +491,69 @@ pre { fa-icon { font-size: 12px; +} + +/* Electrum API Docs Styles */ +#electrs { + .connection-info { + margin-bottom: 30px; + } + + .connection-details { + background-color: var(--bg); + padding: 20px; + border-radius: 0.25rem; + margin: 20px 0; + + h3 { + margin-top: 0; + margin-bottom: 15px; + } + + .connection-item { + display: inline-block; + margin-right: 40px; + margin-bottom: 10px; + + .subtitle { + margin-bottom: 5px; + color: var(--info); + } + + p:last-child { + margin-bottom: 0; + font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New; + } + } + } + + .protocol-info { + margin-top: 20px; + + h3 { + margin-bottom: 15px; + } + + pre { + margin-top: 10px; + } + } + + .electrum-example { + margin-top: 20px; + + .code-block { + margin-top: 10px; + + .code-label { + font-size: 12px; + color: var(--info); + margin-bottom: 5px; + } + + pre { + margin-bottom: 10px; + } + } + } } \ No newline at end of file diff --git a/frontend/src/app/docs/api-docs/api-docs.component.ts b/frontend/src/app/docs/api-docs/api-docs.component.ts index 3e2bfae8e..4a6486a33 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.ts +++ b/frontend/src/app/docs/api-docs/api-docs.component.ts @@ -3,7 +3,7 @@ import { Env, StateService } from '@app/services/state.service'; import { Observable, merge, of, Subject, Subscription } from 'rxjs'; import { tap, takeUntil } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; -import { faqData, restApiDocsData, wsApiDocsData } from '@app/docs/api-docs/api-docs-data'; +import { faqData, restApiDocsData, wsApiDocsData, electrumApiDocsData } from '@app/docs/api-docs/api-docs-data'; import { FaqTemplateDirective } from '@app/docs/faq-template/faq-template.component'; @Component({ @@ -27,6 +27,7 @@ export class ApiDocsComponent implements OnInit, AfterViewInit { faq: any[]; restDocs: any[]; wsDocs: any; + electrumDocs: any[]; screenWidth: number; officialMempoolInstance: boolean; runningElectrs: boolean; @@ -98,6 +99,7 @@ export class ApiDocsComponent implements OnInit, AfterViewInit { this.faq = faqData; this.restDocs = restApiDocsData; this.wsDocs = wsApiDocsData; + this.electrumDocs = electrumApiDocsData; this.network$.pipe(takeUntil(this.destroy$)).subscribe((network) => { this.active = (network === 'liquid' || network === 'liquidtestnet') ? 2 : 0;