--- name: bitcoin-network-stats-scraper description: Take a point-in-time snapshot of Bitcoin network health via the Apify Actor arman-bd/bitcoin-network-stats-scraper. Returns one record per run carrying hash rate, mining difficulty, circulating supply, 24h transaction count and volume, average block interval, 24h fees, miner revenue, live mempool backlog and BTC spot price, with an optional conversion into one of 30 fiat currencies and the exact FX rate used. Use when a task needs network fundamentals as rows, mining-economics monitoring, fee-window timing or a scheduled time series of chain health. Not for individual blocks, addresses or transactions, not for historical backfill, and not for exchange order books. --- # Bitcoin Network Scraper Apify Actor `arman-bd/bitcoin-network-stats-scraper`. One run is one snapshot: a single dataset record describing the state of the Bitcoin network at that moment. Run it on a schedule and the dataset becomes a time series. It takes no credentials. ## When to use it - Mining economics: hash rate against difficulty against price, sampled hourly. - Fee-window timing: mempool backlog against observed block interval, sampled every few minutes. - A longitudinal chain-health dataset built by scheduling the Actor and letting the records accumulate. - Reporting in a non-USD currency where the FX rate has to be stored alongside the numbers so the series stays reproducible. - A quick current-state answer: what is the hash rate, how deep is the mempool. ## When not to use it - Individual blocks, addresses, transactions or UTXOs. None of those are returned, the Actor covers network-level aggregates only. - Historical backfill. There is no date or range input; every run reads the present. Your own accumulated dataset is the only history available. - Exchange data: order books, per-venue prices, derivatives. `marketPriceUsd` is a single blended spot figure, nothing more. - Other chains. Bitcoin only. ## Call it ```js import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('arman-bd/bitcoin-network-stats-scraper').call({ metrics: ['hashRate', 'difficulty', 'mempoolSize', 'marketPriceUsd'], includeMempool: true, currency: 'USD', }); const { items: [snapshot] } = await client.dataset(run.defaultDatasetId).listItems(); const { value: summary } = await client .keyValueStore(run.defaultKeyValueStoreId) .getRecord('RUN_SUMMARY'); ``` One-shot over HTTP, when you want the row back in the same request: ```bash curl -X POST "https://api.apify.com/v2/acts/arman-bd~bitcoin-network-stats-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"metrics":["hashRate","difficulty","mempoolSize"],"includeMempool":true}' ``` The Actor is also exposed through Apify's MCP server as `arman-bd/bitcoin-network-stats-scraper`, so an MCP-capable agent can call it with no extra wiring. ## Input Every field is optional. An empty input is a valid, complete run: all 19 fields, mempool included, in USD. | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `metrics` | string[] | no | `[]` | Restrict the record to these metric names. Empty means every metric. Valid values: `marketPriceUsd`, `hashRate`, `difficulty`, `totalBtcMined`, `blocksSize`, `minutesBetweenBlocks`, `totalTransactions`, `transactionsPerSecond`, `mempoolSize`, `minerRevenueUsd`, `totalFeesBtc`, `estimatedTransactionVolumeUsd`. `timestamp`, `currency` and `scrapedAt` survive the filter regardless. An unrecognised name aborts the run before any request, with the valid list in the error. | | `includeMempool` | boolean | no | `true` | Fetch the live unconfirmed-transaction count into `mempoolSize`. One extra request. Off gives you the daily aggregates only. | | `currency` | string | no | `"USD"` | Reporting currency for the three converted value fields. One of `USD`, `EUR`, `GBP`, `JPY`, `AUD`, `CAD`, `CHF`, `CNY`, `HKD`, `INR`, `KRW`, `SGD`, `SEK`, `NZD`, `BRL`, `RUB`, `TRY`, `PLN`, `DKK`, `ARS`, `CLP`, `CZK`, `GHS`, `HRK`, `HUF`, `ISK`, `NGN`, `RON`, `THB`, `TWD`. Fields whose name ends in `Usd` stay in USD whatever you set here. | **The one decision is how narrow to make `metrics`.** It does not save requests: the whole aggregate arrives in a single call either way, so filtering only trims the record. Filter when you are writing a long-running time series and want a stable, small row shape; leave it empty when you are exploring or when a later question might need a field you did not think to ask for. The two real request-count levers are `includeMempool` (one extra call) and a non-USD `currency` (one extra call). ## Output One record per run. | Field | Type | Notes | |---|---|---| | `timestamp` | string \| null | The source's own data timestamp, ISO 8601. This is the observation time, not the run time. | | `marketPriceUsd` | number \| null | BTC spot price in USD. | | `hashRate` | number \| null | Network hash rate in GH/s. Divide by 1e9 for EH/s. | | `difficulty` | number \| null | Current mining difficulty. | | `totalBtcMined` | number \| null | BTC in circulation. Converted from satoshis by the Actor. | | `blocksSize` | number \| null | Total size in bytes of the blocks mined in the last 24h. | | `minutesBetweenBlocks` | number \| null | Observed average block interval. | | `totalTransactions` | number \| null | Transactions confirmed in the last 24h, not an all-time total. | | `transactionsPerSecond` | number \| null | `totalTransactions` divided by 86400, rounded to 4 dp. | | `mempoolSize` | number \| null | Unconfirmed transactions waiting right now. `null` when `includeMempool` is off or that leg failed. | | `minerRevenueUsd` | number \| null | Miner revenue over 24h in USD. Frequently `0`, see below. | | `totalFeesBtc` | number \| null | Total transaction fees over 24h in BTC. Can be negative, see below. | | `estimatedTransactionVolumeUsd` | number \| null | Estimated on-chain value transacted in the last 24h, USD. | | `currency` | string | The currency the converted block is actually in. Reports `"USD"` if a requested conversion could not be applied. | | `fxRateUsd` | number | Units of `currency` per 1 USD, rounded to 8 dp. `1` when no conversion happened. | | `marketPrice` | number \| null | `marketPriceUsd` times `fxRateUsd`, 2 dp. | | `minerRevenue` | number \| null | `minerRevenueUsd` times `fxRateUsd`, 2 dp. | | `estimatedTransactionVolume` | number \| null | `estimatedTransactionVolumeUsd` times `fxRateUsd`, 2 dp. | | `scrapedAt` | string | When this Actor read it, ISO 8601. | A real record, unfiltered, with `currency: "EUR"`: ```json { "timestamp": "2026-08-06T11:26:44.000Z", "marketPriceUsd": 64565.51, "hashRate": 834575299886.082, "difficulty": 126231507121868, "totalBtcMined": 20066512.5, "blocksSize": 222048135, "minutesBetweenBlocks": 10.2727, "totalTransactions": 568358, "transactionsPerSecond": 6.5782, "mempoolSize": 49569, "minerRevenueUsd": 0, "totalFeesBtc": -415.625, "estimatedTransactionVolumeUsd": 7640022241.04, "currency": "EUR", "fxRateUsd": 0.86619497, "marketPrice": 55926.32, "minerRevenue": 0, "estimatedTransactionVolume": 6617748831.53, "scrapedAt": "2026-08-06T11:32:51.828Z" } ``` ## RUN_SUMMARY Written to the run's key-value store under the key `RUN_SUMMARY`. **Read it.** Two of its fields are chain context that appears nowhere in the dataset record. ```json { "snapshotsSaved": 1, "sourcesQueried": 3, "sourcesFailed": 0, "failures": [], "blockHeight": 961284, "nextRetargetBlock": 961631, "filters": { "metrics": [], "includeMempool": true, "currency": "EUR" }, "finishedAt": "2026-08-06T11:32:51.833Z" } ``` `snapshotsSaved` is `1` on any run that produced a record. A `sourcesFailed` above zero means the snapshot is degraded, not absent: each entry in `failures` is `{ source, error }` where `source` is `stats`, `mempool` or `ticker`. A `mempool` failure explains a `null` `mempoolSize`; a `ticker` failure explains `currency` coming back as `"USD"` when you asked for something else. If the `stats` leg fails the Actor throws, and the summary it writes on the way out has `snapshotsSaved: 0` and no `blockHeight` or `nextRetargetBlock`. `nextRetargetBlock` minus `blockHeight` is the number of blocks until the next difficulty adjustment, which is the figure miners actually watch. ## Behaviour to plan around - **`totalFeesBtc` can be negative.** The source's fee field is occasionally sign-flipped, and the Actor passes it through rather than silently correcting it. Take the absolute value if you need a magnitude, and know you are working around a source bug rather than a unit convention. - **`minerRevenueUsd` is often exactly `0`.** The source does not reliably populate it. Do not read a zero as a real collapse in revenue. Derive it from issuance times price if you need the number to be dependable. - **Three fields are 24-hour windows, not cumulative totals**: `totalTransactions`, `blocksSize` and `estimatedTransactionVolumeUsd`. Only `totalBtcMined` is an all-time figure. - **`hashRate` is in GH/s.** A value near 8.3e11 is 834 EH/s, not 834 GH/s. Divide by 1e9 before showing it to anyone. - **`currency` in the record is an outcome, not an echo of your input.** If the FX leg failed, the Actor reports `currency: "USD"` and `fxRateUsd: 1` and the converted fields mirror the USD ones. Trust the record's `currency`, not the one you sent. - **Filtering `metrics` drags fiat twins along.** Asking for `marketPriceUsd` also keeps `marketPrice` and `fxRateUsd`. Twins of metrics you did not request are dropped. `timestamp`, `currency` and `scrapedAt` are always kept, so a filtered record is never anonymous. - **`mempoolSize` in `metrics` with `includeMempool: false` yields a present but `null` field.** Set the two consistently. - **Only the primary aggregate is fatal.** Mempool and ticker failures degrade the record and are logged in `failures`; the snapshot still saves. Transient errors get three attempts with linear backoff, while a malformed response fails fast rather than burning the retry budget. - **`timestamp` and `scrapedAt` differ by minutes.** Key a time series on `timestamp` for chain semantics, on `scrapedAt` for run bookkeeping, and expect the gap. Sub-minute scheduling buys nothing: the aggregates behind `timestamp` are recomputed far more slowly than that. ## Recipes **Mining economics, hourly.** The margin picture is hash rate, difficulty and price together; the mempool is irrelevant to it, so skip that request. ```json { "metrics": ["hashRate", "difficulty", "marketPriceUsd", "minutesBetweenBlocks", "totalBtcMined"], "includeMempool": false, "currency": "USD" } ``` Plot `marketPriceUsd` divided by `difficulty` over time for hashprice compression, and read `nextRetargetBlock` minus `blockHeight` from `RUN_SUMMARY` for the countdown to the next adjustment. **Fee-window timing, every few minutes.** A narrow record, cheap to store at high frequency. ```json { "metrics": ["mempoolSize", "minutesBetweenBlocks", "totalTransactions", "transactionsPerSecond"], "includeMempool": true } ``` Rising `mempoolSize` with `minutesBetweenBlocks` above 10 means the backlog is growing faster than blocks are clearing it, so wait. Both falling means the window is open. **Daily research series in a reporting currency.** Everything, converted, once a day. ```json { "includeMempool": true, "currency": "EUR" } ``` Store `fxRateUsd` with every row. It is what lets you re-derive the USD series later, or re-base the whole thing into a third currency, without re-running anything.