Documentation
¶
Overview ¶
Package nodeapi talks to a Teranode node over HTTP: JSON-RPC for mining and direct submission, the asset API for reading what got mined.
It is the funding and settlement side of a producer, and nothing a reader depends on: a reader verifies against a header source, never against a node it would have to trust.
Every response body is bounded before it is parsed and every declared count in a binary proof is checked against the bytes present before the SDK is allowed to allocate for it. The node is trusted to be honest about the chain, which is not the same as trusting it to be well behaved about response size (the same rule the headers package states).
The JSON shapes decoded here are the ones a Teranode node's RPC and asset API answer. The tests' response bodies are written to those shapes, not captured from a live node.
Index ¶
- Constants
- Variables
- func CheckProof(ctx context.Context, mp *transaction.MerklePath, txid string, ...) error
- func IsHTTP(err error, status int) bool
- func IsNotFound(err error) bool
- func ProofFor(raw []byte, txid string) (*transaction.MerklePath, error)
- func SpentElsewhereIn(ctx context.Context, s SpendSource, tx *transaction.Transaction) error
- func WaitMined(ctx context.Context, asset *Asset, txid string, poll time.Duration) (*transaction.MerklePath, uint32, error)
- func WaitMinedOn(ctx context.Context, p ProofSource, txid string, poll time.Duration) (*transaction.MerklePath, uint32, error)
- func WaitSettled(ctx context.Context, asset *Asset, tx *transaction.Transaction, ...) (*transaction.MerklePath, uint32, error)
- func WaitSettledOn(ctx context.Context, p ProofSource, spends SpendSource, ...) (*transaction.MerklePath, uint32, error)
- type Asset
- func (a *Asset) BestHeader(ctx context.Context) (*Header, error)
- func (a *Asset) Block(ctx context.Context, hash string) (*Block, error)
- func (a *Asset) BlockTime(ctx context.Context, height uint32) (time.Time, error)
- func (a *Asset) HashAtHeight(ctx context.Context, height uint32) (string, error)
- func (a *Asset) Header(ctx context.Context, hash string) (*Header, error)
- func (a *Asset) Known(ctx context.Context, txid string) (bool, error)
- func (a *Asset) MerkleProof(ctx context.Context, txid string) (*transaction.MerklePath, error)
- func (a *Asset) Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
- func (a *Asset) Spender(ctx context.Context, txid string, vout uint32) (string, error)
- func (a *Asset) SpentElsewhere(ctx context.Context, tx *transaction.Transaction) error
- func (a *Asset) TxMeta(ctx context.Context, txid string) (*TxMeta, error)
- func (a *Asset) TxRaw(ctx context.Context, txid string) ([]byte, error)
- type Block
- type Chain
- type ChainOptions
- type Checked
- type HTTPError
- type Header
- type Info
- type KnownSource
- type ProofSource
- type RPC
- func (r *RPC) Call(ctx context.Context, method string, params []any, out any) error
- func (r *RPC) GenerateToAddress(ctx context.Context, n int, addr string) ([]string, error)
- func (r *RPC) GetInfo(ctx context.Context) (*Info, error)
- func (r *RPC) SendRawTransaction(ctx context.Context, rawHex string) (string, error)
- type Sources
- func (s *Sources) Known(ctx context.Context, txid string) (bool, error)
- func (s *Sources) Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
- func (s *Sources) Spender(ctx context.Context, txid string, vout uint32) (string, error)
- func (s *Sources) SpentElsewhere(ctx context.Context, tx *transaction.Transaction) error
- func (s *Sources) TxRaw(ctx context.Context, txid string) ([]byte, error)
- type SpendSource
- type SpentError
- type TSCProof
- type TxMeta
- type TxSource
- type WoC
- func (w *WoC) Known(ctx context.Context, txid string) (bool, error)
- func (w *WoC) Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
- func (w *WoC) Spender(ctx context.Context, txid string, vout uint32) (string, error)
- func (w *WoC) SpentElsewhere(ctx context.Context, tx *transaction.Transaction) error
- func (w *WoC) TxBEEF(ctx context.Context, txid string) ([]byte, error)
- func (w *WoC) TxRaw(ctx context.Context, txid string) ([]byte, error)
Examples ¶
Constants ¶
const WoCBase = "https://api.whatsonchain.com/v1/bsv/"
WoCBase is the WhatsOnChain API root, before the network.
const WoCFreeRate = 3
WoCFreeRate is WhatsOnChain's free tier: "Up to 3 requests/sec".
Variables ¶
var ErrBodyTooLarge = errors.New("nodeapi: response body exceeds the bound")
ErrBodyTooLarge refuses a response above the bound.
var ErrChainSpec = errors.New("nodeapi: bad chain specification")
ErrChainSpec is a chain view specification that cannot be used.
var ErrDoubleSpent = errors.New("an input is spent by another transaction")
ErrDoubleSpent is a transaction one of whose inputs the node shows spent by another transaction. It never mines. Every error this package and the packages built on it return for that case wraps it, so errors.Is finds it whatever the broadcaster said.
var ErrHeader = errors.New("nodeapi: header does not hash to its hash")
ErrHeader is a header answer whose fields do not hash to its hash.
var ErrNoID = errors.New("nodeapi: rpc: no request id")
ErrNoID refuses a call on an RPC with no request id. The library has no id of its own to fall back on, and any default would put one application's name on another's requests.
var ErrNotMined = errors.New("nodeapi: not mined")
ErrNotMined reports a transaction the node has not placed in a main-chain block yet, or a proof it cannot build yet. It is a state, not a failure: callers poll again after the next block.
var ErrProofRefused = errors.New("nodeapi: proof does not verify against the headers")
ErrProofRefused is a proof that does not verify against the caller's headers. It is the source lying or on another chain, never "not yet".
var ErrSpendUnknown = errors.New("the node does not say whether the output is spent")
ErrSpendUnknown is the node's UTXO view saying neither that an output is unspent nor who spent it. Every error Spender returns wraps it, so a caller that must not mistake "no answer" for "unspent" tests one error. An output the answer leaves out, a status other than OK or SPENT, a SPENT with no spender, an answer that does not decode, a transaction the node does not serve (a 404, which a node also answers for a fully spent transaction it has pruned) and a failed read are all this. The status NOT_FOUND is one of them: a node answers it for an output it holds no record of, which it has pruned or could not read, as well as for one it never stored.
var ErrTxNotFound = errors.New("nodeapi: transaction not known")
ErrTxNotFound is a transaction a source does not hold. *Asset answers it as an *HTTPError with status 404; IsNotFound reads both.
Functions ¶
func CheckProof ¶ added in v0.14.0
func CheckProof(ctx context.Context, mp *transaction.MerklePath, txid string, headers chaintracker.ChainTracker) error
CheckProof verifies that mp proves txid against headers: txid at the leaf level, and the root it computes is the one headers hold at its height.
func IsNotFound ¶ added in v0.14.0
IsNotFound reports a TxSource's "not known": ErrTxNotFound, or a 404.
func ProofFor ¶
func ProofFor(raw []byte, txid string) (*transaction.MerklePath, error)
ProofFor parses a BUMP someone else supplied as the proof for txid, and refuses one that does not name txid at its leaf level. Every source of a proof goes through here: the bytes come from a service, and a path for some other transaction would build a BEEF that no host accepts. The proof is held to the same bound as every answer this package reads.
Example ¶
ProofFor is where every proof a service supplies enters: the guard walks it before the SDK parses it, and a proof that does not name the transaction it was asked for is refused, because it would verify perfectly and prove nothing.
package main
import (
"crypto/sha256"
"fmt"
"github.com/bsv-blockchain/go-sdk/chainhash"
"github.com/bsv-blockchain/go-sdk/transaction"
"github.com/lightwebinc/bcommon/nodeapi"
)
func main() {
txid := chainhash.Hash(sha256.Sum256([]byte("a transaction")))
sibling := chainhash.Hash(sha256.Sum256([]byte("its neighbour")))
isTxid := true
raw := transaction.NewMerklePath(90, [][]*transaction.PathElement{{
{Offset: 0, Hash: &sibling},
{Offset: 1, Hash: &txid, Txid: &isTxid},
}}).Bytes()
mp, err := nodeapi.ProofFor(raw, txid.String())
fmt.Println("asked for:", mp != nil, err)
unrelated := chainhash.Hash(sha256.Sum256([]byte("some other transaction")))
_, err = nodeapi.ProofFor(raw, unrelated.String())
fmt.Println("asked for another:", err)
_, err = nodeapi.ProofFor(raw[:len(raw)-1], txid.String())
fmt.Println("truncated:", err)
}
Output: asked for: true <nil> asked for another: proof does not contain the txid truncated: bump ends mid-structure
func SpentElsewhereIn ¶ added in v0.14.0
func SpentElsewhereIn(ctx context.Context, s SpendSource, tx *transaction.Transaction) error
SpentElsewhereIn is Asset.SpentElsewhere over any SpendSource: a *SpentError for the first input of tx the source shows spent by another transaction, or nil. An input the source cannot answer for is passed over, so nil is not evidence that the inputs are unspent.
func WaitMined ¶
func WaitMined(ctx context.Context, asset *Asset, txid string, poll time.Duration) (*transaction.MerklePath, uint32, error)
WaitMined polls until txid is placed in a main-chain block and its proof is served, or ctx ends. It returns the proof and the block height.
func WaitMinedOn ¶ added in v0.14.0
func WaitMinedOn(ctx context.Context, p ProofSource, txid string, poll time.Duration) (*transaction.MerklePath, uint32, error)
WaitMinedOn is WaitMined over any ProofSource: it polls until txid's proof is served, or ctx ends.
func WaitSettled ¶ added in v0.5.4
func WaitSettled(ctx context.Context, asset *Asset, tx *transaction.Transaction, poll time.Duration) (*transaction.MerklePath, uint32, error)
WaitSettled is WaitMined for a transaction the caller holds: while tx has not mined, each poll also asks the node whether one of its inputs is spent by another transaction, and returns that *SpentError (errors.Is ErrDoubleSpent) at once rather than waiting until ctx ends.
func WaitSettledOn ¶ added in v0.14.0
func WaitSettledOn(ctx context.Context, p ProofSource, spends SpendSource, tx *transaction.Transaction, poll time.Duration) (*transaction.MerklePath, uint32, error)
WaitSettledOn is WaitSettled over any ProofSource and SpendSource: while tx has not mined, each poll also asks spends (when not nil) whether one of its inputs is spent by another transaction, and returns that *SpentError (errors.Is ErrDoubleSpent) at once.
Types ¶
type Asset ¶
type Asset struct {
// Base is the API root with no path suffix, e.g. http://node.example.com:20090.
Base string
// Client is optional; the default has a 30s timeout.
Client *http.Client
}
Asset is a Teranode asset-HTTP-API client.
func (*Asset) BestHeader ¶
BestHeader reads the tip.
func (*Asset) BlockTime ¶ added in v0.11.0
BlockTime is the time of the main-chain block at height, from its header, which must hash to the hash the node names for that height (Check). The node is trusted for which block is at the height; a caller that must not trust it checks the header's hash or merkle root against its headers.
func (*Asset) HashAtHeight ¶
HashAtHeight resolves a main-chain block hash by height through the paged block list, where offset counts back from the tip. The tip can move between the two reads, so the answer is checked against the height asked for and the read is retried when it does.
func (*Asset) Known ¶ added in v0.14.0
Known reports whether the node holds txid, mined or not (/api/v1/txmeta/{txid}/json answers it; a 404 is not known).
func (*Asset) MerkleProof ¶
func (a *Asset) MerkleProof(ctx context.Context, txid string) (*transaction.MerklePath, error)
MerkleProof returns the BRC-74 BUMP for a mined transaction, parsed.
The node answers 404 for a transaction it has not placed in a main-chain block and 500 for a proof with an empty path (a single-transaction block); both are ErrNotMined to a caller that will ask again after the next block.
The bytes are walked by the guard package before the SDK parses them, and the parse runs under a recover, so a malformed proof is an error and never a crash. The parsed path must also contain txid at its leaf level: a proof the node served for the wrong transaction verifies perfectly and proves nothing.
func (*Asset) Proof ¶
func (a *Asset) Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
Proof asks once whether txid is mined and returns its proof and height if so, or ErrNotMined. It is WaitMined without the waiting, for a caller that collects proofs later rather than blocking on them.
func (*Asset) Spender ¶ added in v0.5.4
Spender reads the node's UTXO view of txid (/api/v1/utxos/{txid}/json) and names the transaction that spent output vout, or "" with a nil error when the node shows the output unspent (status OK). Only those two answers are evidence. Anything else is an error wrapping ErrSpendUnknown, never "": see ErrSpendUnknown for what the node answers that is not evidence. A transaction the node does not serve is also an *HTTPError with status 404 (IsHTTP).
A caller that acts on "unspent", such as returning a coin to a pool, acts only on a nil error and "", and treats an error as undecided: it changes nothing and asks again later.
func (*Asset) SpentElsewhere ¶ added in v0.5.4
func (a *Asset) SpentElsewhere(ctx context.Context, tx *transaction.Transaction) error
SpentElsewhere returns a *SpentError for the first input of tx the node shows spent by another transaction, or nil when there is none: then tx can still mine as far as its inputs go. An input the node cannot answer for (Spender's ErrSpendUnknown: a parent it does not serve, a status that is neither OK nor SPENT, a read that failed) is passed over, so this refuses only on the node's positive word. nil is therefore not evidence that tx's inputs are unspent; a caller that needs that asks Spender per input.
func (*Asset) TxMeta ¶
TxMeta reads a transaction's placement. A 404 is ErrNotMined: for a submission in flight, "never seen" and "not placed yet" are one state.
func (*Asset) TxRaw ¶
TxRaw returns a transaction's raw serialisation. A node may answer in the raw form or in Extended Format (BRC-30), the raw form with each input's previous output added, as a Teranode asset API does; either is walked by guard.RawTransaction and the raw form returned, so a caller always gets bytes guard.ParseTransaction reads. The previous outputs an Extended Format answer carries are dropped.
type Block ¶
type Block struct {
Hash string `json:"hash"`
Height uint32 `json:"height"`
CoinbaseTx struct {
TxID string `json:"txid"`
Outputs []struct {
Satoshis uint64 `json:"satoshis"`
LockingScript string `json:"lockingScript"`
} `json:"outputs"`
} `json:"coinbase_tx"`
}
Block is the subset of /api/v1/block/{hash}/json read here: the coinbase outputs are what fund the pool.
type Chain ¶ added in v0.14.0
type Chain interface {
TxSource
ProofSource
SpendSource
}
Chain is the three views a producer needs to fund, prove and refuse.
type ChainOptions ¶ added in v0.14.0
type ChainOptions struct {
// WoCKey is the WhatsOnChain API key, if any, and WoCRate its
// requests a second (zero is WoCFreeRate).
WoCKey string
WoCRate float64
// Client, when set, is every backend's HTTP client.
Client *http.Client
// Headers is required: every proof a backend answers is checked
// against it (Checked) before the Sources return it.
Headers chaintracker.ChainTracker
}
ChainOptions are the settings a chain specification does not carry: secrets and transport.
type Checked ¶ added in v0.14.0
type Checked struct {
Source ProofSource
Headers chaintracker.ChainTracker
}
Checked is a ProofSource whose every proof is verified against Headers before it is returned: it must name txid at its leaf level (ProofFor's rule) and its root must be the one Headers holds at its height. Wrap any third party's proofs in it; a node's own are checked the same way.
func (Checked) Proof ¶ added in v0.14.0
func (c Checked) Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
Proof is Source's proof once Headers confirm it, ErrNotMined as Source answers it, and an error wrapping ErrProofRefused for a proof Headers do not hold. A header source that cannot answer is an error, not a refusal.
type HTTPError ¶
HTTPError is a non-2xx answer, kept so callers can distinguish a 404 from a transport failure.
type Header ¶
type Header struct {
Hash string `json:"hash"`
Prev string `json:"previousblockhash"`
MerkleRoot string `json:"merkleroot"`
Height uint32 `json:"height"`
// Version, Time (Unix seconds), Bits (hex) and Nonce are the rest of
// the 80 bytes the hash is over; Check rebuilds them.
Version uint32 `json:"version"`
Time uint32 `json:"time"`
Bits string `json:"bits"`
Nonce uint32 `json:"nonce"`
}
Header is the subset of a block header answer a caller needs.
func (*Header) Check ¶ added in v0.11.0
Check rebuilds the 80-byte header from h's fields and refuses, as ErrHeader, one whose double SHA-256 is not h.Hash. A header that passes carries the fields its hash commits to, so its Time is the block's own once the hash is known to be on the chain (through headers, say); before that it is the node's word.
type Info ¶
type Info struct {
Blocks int64 `json:"blocks"`
Connections int64 `json:"connections"`
Version int64 `json:"version"`
}
Info is the subset of getinfo used here. Teranode registers getblockcount but answers "Command unimplemented", so the height comes from here.
type KnownSource ¶ added in v0.14.0
KnownSource reports whether the source holds a transaction at all, mined or not: true for "known", false with a nil error for "not known", an error when it cannot say. A transaction known and not mined is one to wait for; one not known after it was sent is one to send again.
type ProofSource ¶ added in v0.14.0
type ProofSource interface {
Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
}
ProofSource returns a mined transaction's proof and block height, or ErrNotMined while it has not mined (or the source does not know it).
type RPC ¶
type RPC struct {
// URL is the RPC endpoint, e.g. http://node.example.com:21292/.
URL string
User string
Pass string
// ID is the JSON-RPC request id, sent verbatim on every call. It is the
// application's to choose and it is required: Call refuses an empty one
// before any request is sent rather than choosing one itself.
ID string
// Client is optional; the default has a 60s timeout, which is long
// because generatetoaddress mines inline. The lever for a slow node is
// the batch size, not this.
Client *http.Client
}
RPC is a Teranode JSON-RPC client.
func (*RPC) Call ¶
Call issues one JSON-RPC call and unmarshals the result into out, which may be nil. An RPC-level error is returned as the node phrased it. An RPC with no ID is refused with ErrNoID before anything is sent.
func (*RPC) GenerateToAddress ¶
GenerateToAddress mines n blocks paying the coinbase to addr and returns the block hashes. Keep n small: a large call can outlast the RPC timeout, which is why FundFromCoinbase mines in batches. Coinbase: only on a regtest chain you run (development and tests).
type Sources ¶ added in v0.14.0
type Sources struct {
Tx []TxSource
Proofs []ProofSource
Spends SpendSource
Knows KnownSource
}
Sources is a Chain built from a backend per method, which is how an application picks them in configuration (ParseChain). Presence answers may come from any backend in a list, tried in order, since each is checked: a transaction must hash to its txid, and a proof should be wrapped in Checked by the caller who holds the headers. Absence answers come from the one Spends and the one Knows backend only.
func ParseChain ¶ added in v0.14.0
func ParseChain(spec string, opt ChainOptions) (*Sources, error)
ParseChain reads a chain view specification, in the style of headers.Parse: comma-separated backends, each
woc:main | woc:test the public WhatsOnChain API (*WoC) asset:http://node:8090 a Teranode asset API (*Asset)
with every proof checked against opt.Headers (Checked), optionally qualified by the one method it serves: tx=, proof=, spend= or known=. An unqualified backend serves every method. Transactions and proofs are asked of every backend that serves them, in order, since each answer is checked. "Spent", "unspent" and "known" come from one backend only: the first that serves spend (or known), so that an absence answer is never a fall-through. Arcade, which proves only what it was sent, is not named here; put a publish.Arcade first in Proofs, as producer.Proofs does.
woc:main no node, mainnet asset:http://node:8090 a node for everything asset:http://node:8090,woc:main a node, WhatsOnChain for what it lacks woc:test,spend=asset:http://node:8090 WhatsOnChain, a node's spend view
func (*Sources) Known ¶ added in v0.14.0
Known is the Knows backend's answer; with none configured, an error.
func (*Sources) Proof ¶ added in v0.14.0
func (s *Sources) Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
Proof asks each ProofSource in order and returns the first proof. When every source answers ErrNotMined, so does Proof; a source that failed otherwise does not stop the next from being asked.
func (*Sources) Spender ¶ added in v0.14.0
Spender is the Spends backend's answer; with none configured, every answer is ErrSpendUnknown.
func (*Sources) SpentElsewhere ¶ added in v0.14.0
func (s *Sources) SpentElsewhere(ctx context.Context, tx *transaction.Transaction) error
SpentElsewhere is SpentElsewhereIn over the Spends backend.
type SpendSource ¶ added in v0.14.0
type SpendSource interface {
Spender(ctx context.Context, txid string, vout uint32) (string, error)
}
SpendSource names the transaction that spent an output, "" with a nil error only when the source shows it unspent, and an error wrapping ErrSpendUnknown for every other answer.
type SpentError ¶ added in v0.5.4
type SpentError struct {
Txid string
// Input is the index of the input in Txid; Outpoint is what it spends,
// txid.vout.
Input int
Outpoint string
By string
}
SpentError names the input of Txid that the node shows spent by By.
func (*SpentError) Error ¶ added in v0.5.4
func (e *SpentError) Error() string
func (*SpentError) Unwrap ¶ added in v0.5.4
func (e *SpentError) Unwrap() error
Unwrap makes a *SpentError errors.Is ErrDoubleSpent.
type TSCProof ¶ added in v0.14.0
type TSCProof struct {
Index uint64 `json:"index"`
TxOrID string `json:"txOrId"`
Target string `json:"target"`
Nodes []string `json:"nodes"`
}
TSCProof is one entry of WhatsOnChain's /tx/{txid}/proof/tsc answer: a TSC merkle proof (the transaction's index in its block, the block hash as target, and the sibling at each level, "*" for a duplicate).
func (*TSCProof) MerklePath ¶ added in v0.14.0
func (p *TSCProof) MerklePath(txid string, height uint32) (*transaction.MerklePath, error)
MerklePath converts a TSC proof of txid into a BRC-74 BUMP at height.
type TxMeta ¶
type TxMeta struct {
BlockHashes []string `json:"blockHashes"`
BlockHeights []uint32 `json:"blockHeights"`
SubtreeIdxs []int `json:"subtreeIdxs"`
MainChainIndex int `json:"mainChainIndex"`
IsCoinbase bool `json:"isCoinbase"`
}
TxMeta is the placement subset of /api/v1/txmeta/{txid}/json. The slices are parallel, one entry per block the transaction landed in, and MainChainIndex selects the main-chain one.
type TxSource ¶ added in v0.14.0
TxSource returns a transaction's raw bytes. A transaction the source does not hold is an error for which IsNotFound is true.
type WoC ¶ added in v0.14.0
type WoC struct {
// Network is "main" or "test".
Network string
// Base overrides the API root (default
// https://api.whatsonchain.com/v1/bsv/<Network>), for a mirror or a
// test.
Base string
// Key, when set, is sent as the Authorization header, as WhatsOnChain
// documents for an API key.
Key string
// Client is optional; the default has a 30 s timeout and takes no
// proxy from the environment.
Client *http.Client
// Rate is requests a second, shared by every call on this value; zero
// is WoCFreeRate.
Rate float64
// MaxTx bounds a transaction's size in bytes; zero is
// guard.DefaultBound.
MaxTx int
// contains filtered or unexported fields
}
WoC is the public WhatsOnChain API for one network, as a TxSource, ProofSource, SpendSource and KnownSource: a chain view that needs no node.
What it answers is held to the same rules as a node's: a transaction must hash to the txid asked for, a proof must name it (and should be checked against the caller's headers, Checked), and a spender must be a txid. Its absence answers are its word: "unspent" is a 404 from the spent endpoint, which WhatsOnChain documents as "known but spent details are not found", and a 400, which it documents as an unknown output (and answers for an unspendable one), is ErrSpendUnknown, never unspent.
The free tier allows 3 requests a second; Rate paces requests to it, and a 429 is retried with backoff. A key (Key) raises the limit on a paid plan; set Rate to match it.
Safe for concurrent use.
func (*WoC) Known ¶ added in v0.14.0
Known is GET /tx/hash/{txid}: true for a transaction WhatsOnChain holds, mined or in its mempool, false for a 404.
func (*WoC) Proof ¶ added in v0.14.0
func (w *WoC) Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
Proof is txid's proof and height, or ErrNotMined while WhatsOnChain does not hold it mined. It reads the BEEF first and falls back to the raw transaction's TSC proof (/tx/{txid}/proof/tsc) when the BEEF endpoint fails for any other reason, since that endpoint is undocumented. The proof names txid; check it against your headers (Checked).
func (*WoC) Spender ¶ added in v0.14.0
Spender is GET /tx/{txid}/{vout}/spent. A 200 names the spender, mined or not; a 404 is unspent ("" and nil), which WhatsOnChain documents as an output it knows with no spend; a 400, its "UTXO is unknown" (also its answer for an unspendable output), and every other answer is an error wrapping ErrSpendUnknown.
func (*WoC) SpentElsewhere ¶ added in v0.14.0
func (w *WoC) SpentElsewhere(ctx context.Context, tx *transaction.Transaction) error
SpentElsewhere is SpentElsewhereIn over WhatsOnChain's spends.
func (*WoC) TxBEEF ¶ added in v0.14.0
TxBEEF is GET /tx/{txid}/beef, which WhatsOnChain serves but does not document: the transaction as BEEF, with its proof when mined. A 422 (it declines some unmined transactions) is ErrNotMined; a 404, or a 500 saying the transaction is unknown, is ErrTxNotFound. The bytes are returned as served; ParseBEEF them through guard before use.