Providers

Haskoin

Bitcoin Cash through Haskoin Store at api.haskoin.com. Balances, history, unspent outputs, transactions and blocks. Satoshis, no key.
provider
create("haskoin")
auth
none
chains
Bitcoin Cash
capabilities
balance, history, tx detail, utxos, block
endpoint
api.haskoin.com/bch
default chain
bitcoincash

Address it

import { Haskoin } from "@agntn/explorers/providers/haskoin";

const bch = new Haskoin(); // or create("haskoin")
await bch.getBalance("bitcoincash:qz3yjg59ypg6jqpwhaxgvjj44jm4hdx0w5wsxw2qez", "bitcoincash");
await bch.getTxHistory("bitcoincash:qz3yjg59ypg6jqpwhaxgvjj44jm4hdx0w5wsxw2qez", "bitcoincash", {
  limit: 10,
});

One chain, no key. Haskoin Store is an indexer written in Haskell, and api.haskoin.com runs a public instance of it for Bitcoin Cash. baseUrl is the network root, https://api.haskoin.com/bch by default. Before it, Bitcoin Cash had Blockchair and nothing else, and keyless Blockchair blocks an IP after a handful of reads. Now automatic selection starts on Haskoin, and with BLOCKCHAIR_API_KEY set it starts on Blockchair and falls back here. Any other chain is an UnsupportedChainError.

What it reads

OperationRoute
balance/address/{address}/balance
history/address/{address}/transactions/full
one transaction/transaction/{txid}
unspent outputs/address/{address}/unspent
block/block/height/{height}

No gas and no tokens. Haskoin keeps no fee estimates at all.

Gotchas

  • CashAddr only, with or without the bitcoincash: prefix. A legacy 1... address fails before a request goes out. Haskoin itself would take it, but @agntn/chains doesn't, and that's the check every provider here goes through. Blockchair takes the legacy form, so ask it with -p blockchair.
  • Every input comes with the address and the amount it spends. So the sender and the fee arrive with the transaction itself, no parent lookups like on WhatsOnChain.
  • Seen from an address, a transaction is a send when one of its inputs spent from that address. Then value is what went to everyone else and to is the first of them. Otherwise it's a receive and value is what landed on the address.
  • History comes newest first and only that way, so sort: "asc" throws. Haskoin bounds a listing from above only: endBlock goes out as height, and startBlock throws instead of being ignored.
  • limit up to 100, and page turns into an offset. The public instance refuses an offset above 50000, so a page that deep throws before the request.
  • Balance splits differently from Mempool. balance is Haskoin's confirmed: confirmed outputs that nothing spends yet, and a spend still in the mempool already counts. unconfirmed is the unspent outputs still waiting in the mempool, never negative. The two add up to what the address holds once the mempool confirms. funded is everything ever received, spent is the rest.
  • No block height on a balance, the endpoint doesn't send one, so blockNumber stays null.
  • A transaction still in the mempool reads as pending at block 0. One Haskoin marks as deleted, pushed out by a double spend or a reorg, reads as failed.
  • Unspent outputs come in pages of 1000, ten pages at most. The height comes along, the block hash doesn't, so blockHash stays null.
  • /block/height/{height} lists every block seen at that height, orphans too. The provider takes the main chain one. Past the tip the list is empty and that's a NotFoundError. The request skips notx, because without the list of transactions there's nothing to count.
  • A missing transaction or address is a 404, so NotFoundError. A 400 carries its reason in message, and that reason travels in a plain ExplorerError.
  • OP_RETURN outputs have no address and never become the recipient. Their payloads stay in raw, not decoded into opReturn.
  • A block has an empty miner and gas fields of "0", like every other chain without gas.

Where it lives

src/providers/haskoin.ts.

@agntn/explorers·MIT license· Read-only. Addresses you type go to a public explorer API, never to a wallet.