@bsv/wallet-toolbox
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
690 lines (626 loc) • 20.8 kB
text/typescript
import { ArcConfig, Beef, Transaction as BsvTransaction, ChainTracker, MerklePath } from '@bsv/sdk'
import { Chain, ReqHistoryNote } from './types'
import { WalletError } from './WalletError'
import { TableOutput } from '../storage/schema/tables/TableOutput'
import { ChaintracksServiceClient } from '../services/chaintracker/chaintracks/ChaintracksServiceClient'
import { ChaintracksClientApi } from '../services/chaintracker/chaintracks/Api/ChaintracksClientApi'
/**
* Defines standard interfaces to access functionality implemented by external transaction processing services.
*/
export interface WalletServices {
/**
* The chain being serviced.
*/
chain: Chain
/**
* @returns standard `ChainTracker` service which requires `options.chaintracks` be valid.
*/
getChainTracker(): Promise<ChainTracker>
/**
* @returns serialized block header for height on active chain
* @param height
*/
getHeaderForHeight(height: number): Promise<number[]>
/**
* @returns the height of the active chain
*/
getHeight(): Promise<number>
/**
* Approximate exchange rate US Dollar / BSV, USD / BSV
*
* This is the US Dollar price of one BSV
*/
getBsvExchangeRate(): Promise<number>
/**
* Approximate exchange rate currency per base.
*/
getFiatExchangeRate(currency: 'USD' | 'GBP' | 'EUR', base?: 'USD' | 'GBP' | 'EUR'): Promise<number>
/**
* Attempts to obtain the raw transaction bytes associated with a 32 byte transaction hash (txid).
*
* Cycles through configured transaction processing services attempting to get a valid response.
*
* On success:
* Result txid is the requested transaction hash
* Result rawTx will be an array containing raw transaction bytes.
* Result name will be the responding service's identifying name.
* Returns result without incrementing active service.
*
* On failure:
* Result txid is the requested transaction hash
* Result mapi will be the first mapi response obtained (service name and response), or null
* Result error will be the first error thrown (service name and CwiError), or null
* Increments to next configured service and tries again until all services have been tried.
*
* @param txid transaction hash for which raw transaction bytes are requested
* @param useNext optional, forces skip to next service before starting service requests cycle.
*/
getRawTx(txid: string, useNext?: boolean): Promise<GetRawTxResult>
/**
* Attempts to obtain the merkle proof associated with a 32 byte transaction hash (txid).
*
* Cycles through configured transaction processing services attempting to get a valid response.
*
* On success:
* Result txid is the requested transaction hash
* Result proof will be the merkle proof.
* Result name will be the responding service's identifying name.
* Returns result without incrementing active service.
*
* On failure:
* Result txid is the requested transaction hash
* Result mapi will be the first mapi response obtained (service name and response), or null
* Result error will be the first error thrown (service name and CwiError), or null
* Increments to next configured service and tries again until all services have been tried.
*
* @param txid transaction hash for which proof is requested
* @param useNext optional, forces skip to next service before starting service requests cycle.
*/
getMerklePath(txid: string, useNext?: boolean): Promise<GetMerklePathResult>
/**
*
* @param beef
* @param txids
* @param chain
* @returns
*/
postBeef(beef: Beef, txids: string[]): Promise<PostBeefResult[]>
/**
* @param script Output script to be hashed for `getUtxoStatus` default `outputFormat`
* @returns script hash in 'hashLE' format, which is the default.
*/
hashOutputScript(script: string): string
/**
* For an array of one or more txids, returns for each wether it is a 'known', 'mined', or 'unknown' transaction.
*
* Primarily useful for determining if a recently broadcast transaction is known to the processing network.
*
* Also returns the current depth from chain tip if 'mined'.
*
* @param txids
* @param useNext
*/
getStatusForTxids(txids: string[], useNext?: boolean): Promise<GetStatusForTxidsResult>
/**
* Calls getUtxoStatus with the hash of the output's lockingScript,
* and ensures that the output's outpoint matches an unspent use of that script.
*
* @param output
* @returns true if the output appears to currently be spendable.
*/
isUtxo(output: TableOutput): Promise<boolean>
/**
* Attempts to determine the UTXO status of a transaction output.
*
* Cycles through configured transaction processing services attempting to get a valid response.
*
* @param output transaction output identifier in format determined by `outputFormat`.
* @param chain which chain to post to, all of rawTx's inputs must be unspent on this chain.
* @param outputFormat optional, supported values:
* 'hashLE' little-endian sha256 hash of output script
* 'hashBE' big-endian sha256 hash of output script
* 'script' entire transaction output script
* undefined if length of `output` is 32 hex bytes then 'hashBE`, otherwise 'script'.
* @param outpoint if valid, result isUtxo is true only if this txid and vout match an unspent occurance of output script. `${txid}.${vout}` format.
* @param useNext optional, forces skip to next service before starting service requests cycle.
*/
getUtxoStatus(
output: string,
outputFormat?: GetUtxoStatusOutputFormat,
outpoint?: string,
useNext?: boolean
): Promise<GetUtxoStatusResult>
getScriptHashHistory(hash: string, useNext?: boolean): Promise<GetScriptHashHistoryResult>
/**
* @returns a block header
* @param hash block hash
*/
hashToHeader(hash: string): Promise<BlockHeader>
/**
* @returns whether the locktime value allows the transaction to be mined at the current chain height
* @param txOrLockTime either a bitcoin locktime value or hex, binary, un-encoded Transaction
*/
nLockTimeIsFinal(txOrLockTime: string | number[] | BsvTransaction | number): Promise<boolean>
/**
* Constructs a `Beef` for the given `txid` using only external data retrieval services.
*
* In most cases, the `getBeefForTransaction` method of the `StorageProvider` class should be
* used instead to avoid redundantly retrieving data.
*
* @throws errors if txid does not correspond to a valid transaction as determined by the
* configured services.
*
* @param txid
*/
getBeefForTxid(txid: string): Promise<Beef>
/**
* @param reset if true, ends current interval and starts a new one.
* @returns a history of service calls made to the configured services.
*/
getServicesCallHistory(reset?: boolean): ServicesCallHistory
}
export type ScriptHashFormat = 'hashLE' | 'hashBE' | 'script'
export type GetUtxoStatusOutputFormat = 'hashLE' | 'hashBE' | 'script'
export interface BsvExchangeRate {
timestamp: Date
base: 'USD'
rate: number
}
export interface FiatExchangeRates {
timestamp: Date
base: 'USD'
rates: Record<string, number>
}
export interface WalletServicesOptions {
/**
* 'main' or 'test': which BSV chain to use
*/
chain: Chain
/**
* As of 2025-08-31 the `taalApiKey` is unused for default configured services.
* See `arcConfig` instead.
*/
taalApiKey?: string
/**
* Api key for use accessing Bitails API at
* mainnet: `https://api.bitails.io/`
* testnet: `https://test-api.bitails.io/`
*/
bitailsApiKey?: string
/**
* Api key for use accessing WhatsOnChain API at
* mainnet: `https://api.whatsonchain.com/v1/bsv/main`
* testnet: `https://api.whatsonchain.com/v1/bsv/test`
*/
whatsOnChainApiKey?: string
/**
* The initial approximate BSV/USD exchange rate.
*/
bsvExchangeRate: BsvExchangeRate
/**
* Update interval for BSV/USD exchange rate.
* Default is 15 minutes.
*/
bsvUpdateMsecs: number
/**
* The initial approximate fiat exchange rates with USD as base.
*/
fiatExchangeRates: FiatExchangeRates
/**
* Update interval for Fiat exchange rates.
* Default is 24 hours.
*/
fiatUpdateMsecs: number
/**
* MAPI callbacks are deprecated at this time.
*/
disableMapiCallback?: boolean
/**
* API key for use accessing fiat exchange rates API at
* `http://api.exchangeratesapi.io/v1/latest?access_key=${key}`
*
* Obtain your own api key here:
* https://manage.exchangeratesapi.io/signup/free
*/
exchangeratesapiKey?: string
/**
* Due to the default use of a free exchangeratesapiKey with low usage limits,
* the `ChaintracksService` can act as a request rate multiplier.
*
* By default the following endpoint is used:
* `https://mainnet-chaintracks.babbage.systems/getFiatExchangeRates`
*/
chaintracksFiatExchangeRatesUrl?: string
/**
* Optional Chaintracks client API instance.
* Default is a new instance of ChaintracksServiceClient configured to use:
* mainnet: `https://mainnet-chaintracks.babbage.systems`
* testnet: `https://testnet-chaintracks.babbage.systems`
*/
chaintracks?: ChaintracksClientApi
/**
* TAAL ARC service provider endpoit to use
* Default is:
* mainnet: `https://arc.taal.com`
* testnet: `https://arc-test.taal.com`
*/
arcUrl: string
/**
* TAAL ARC service configuration options.
*
* apiKey Default value is undefined.
*
* deploymentId Default value: `wallet-toolbox-${randomBytesHex(16)}`.
*
* callbackUrl Default is undefined.
* callbackToken Default is undefined.
*/
arcConfig: ArcConfig
/**
* GorillaPool ARC service provider endpoit to use
* Default is:
* mainnet: `https://arc.gorillapool.io`
* testnet: undefined
*/
arcGorillaPoolUrl?: string
/**
* GorillaPool ARC service configuration options.
*
* apiKey Default is undefined.
*
* deploymentId Default value: `wallet-toolbox-${randomBytesHex(16)}`.
*
* callbackUrl Default is undefined.
* callbackToken Default is undefined.
*/
arcGorillaPoolConfig?: ArcConfig
}
export interface GetStatusForTxidsResult {
/**
* The name of the service returning these results.
*/
name: string
status: 'success' | 'error'
/**
* The first exception error that occurred during processing, if any.
*/
error?: WalletError
results: StatusForTxidResult[]
}
export interface StatusForTxidResult {
txid: string
/**
* roughly depth of block containing txid from chain tip.
*/
depth: number | undefined
/**
* 'mined' if depth > 0
* 'known' if depth === 0
* 'unknown' if depth === undefined, txid may be old an purged or never processed.
*/
status: 'mined' | 'known' | 'unknown'
}
/**
* Properties on result returned from `WalletServices` function `getRawTx`.
*/
export interface GetRawTxResult {
/**
* Transaction hash or rawTx (and of initial request)
*/
txid: string
/**
* The name of the service returning the rawTx, or undefined if no rawTx
*/
name?: string
/**
* Multiple proofs may be returned when a transaction also appears in
* one or more orphaned blocks
*/
rawTx?: number[]
/**
* The first exception error that occurred during processing, if any.
*/
error?: WalletError
}
/**
* Properties on result returned from `WalletServices` function `getMerkleProof`.
*/
export interface GetMerklePathResult {
/**
* The name of the service returning the proof, or undefined if no proof
*/
name?: string
/**
* Multiple proofs may be returned when a transaction also appears in
* one or more orphaned blocks
*/
merklePath?: MerklePath
header?: BlockHeader
/**
* The first exception error that occurred during processing, if any.
*/
error?: WalletError
notes?: ReqHistoryNote[]
}
export interface PostTxResultForTxid {
txid: string
/**
* 'success' - The transaction was accepted for processing
*/
status: 'success' | 'error'
/**
* if true, the transaction was already known to this service. Usually treat as a success.
*
* Potentially stop posting to additional transaction processors.
*/
alreadyKnown?: boolean
/**
* service indicated this broadcast double spends at least one input
* `competingTxs` may be an array of txids that were first seen spends of at least one input.
*/
doubleSpend?: boolean
blockHash?: string
blockHeight?: number
merklePath?: MerklePath
competingTxs?: string[]
data?: object | string | PostTxResultForTxidError
notes?: ReqHistoryNote[]
/**
* true iff service was unable to process a potentially valid transaction
*/
serviceError?: boolean
}
export interface PostTxResultForTxidError {
status?: string
detail?: string
more?: object
}
export interface PostBeefResult extends PostTxsResult {}
/**
* Properties on array items of result returned from `WalletServices` function `postBeef`.
*/
export interface PostTxsResult {
/**
* The name of the service to which the transaction was submitted for processing
*/
name: string
/**
* 'success' all txids returned status of 'success'
* 'error' one or more txids returned status of 'error'. See txidResults for details.
*/
status: 'success' | 'error'
error?: WalletError
txidResults: PostTxResultForTxid[]
/**
* Service response object. Use service name and status to infer type of object.
*/
data?: object
notes?: ReqHistoryNote[]
}
export interface GetUtxoStatusDetails {
/**
* if isUtxo, the block height containing the matching unspent transaction output
*
* typically there will be only one, but future orphans can result in multiple values
*/
height?: number
/**
* if isUtxo, the transaction hash (txid) of the transaction containing the matching unspent transaction output
*
* typically there will be only one, but future orphans can result in multiple values
*/
txid?: string
/**
* if isUtxo, the output index in the transaction containing of the matching unspent transaction output
*
* typically there will be only one, but future orphans can result in multiple values
*/
index?: number
/**
* if isUtxo, the amount of the matching unspent transaction output
*
* typically there will be only one, but future orphans can result in multiple values
*/
satoshis?: number
}
export interface GetUtxoStatusResult {
/**
* The name of the service to which the transaction was submitted for processing
*/
name: string
/**
* 'success' - the operation was successful, non-error results are valid.
* 'error' - the operation failed, error may have relevant information.
*/
status: 'success' | 'error'
/**
* When status is 'error', provides code and description
*/
error?: WalletError
/**
* true if the output is associated with at least one unspent transaction output
*/
isUtxo?: boolean
/**
* Additional details about occurances of this output script as a utxo.
*
* Normally there will be one item in the array but due to the possibility of orphan races
* there could be more than one block in which it is a valid utxo.
*/
details: GetUtxoStatusDetails[]
}
export interface GetScriptHashHistory {
txid: string
height?: number
}
export interface GetScriptHashHistoryResult {
/**
* The name of the service to which the transaction was submitted for processing
*/
name: string
/**
* 'success' - the operation was successful, non-error results are valid.
* 'error' - the operation failed, error may have relevant information.
*/
status: 'success' | 'error'
/**
* When status is 'error', provides code and description
*/
error?: WalletError
/**
* Transaction txid (and height if mined) that consumes the script hash. May not be a complete history.
*/
history: GetScriptHashHistory[]
}
/**
* These are fields of 80 byte serialized header in order whose double sha256 hash is a block's hash value
* and the next block's previousHash value.
*
* All block hash values and merkleRoot values are 32 byte hex string values with the byte order reversed from the serialized byte order.
*/
export interface BaseBlockHeader {
/**
* Block header version value. Serialized length is 4 bytes.
*/
version: number
/**
* Hash of previous block's block header. Serialized length is 32 bytes.
*/
previousHash: string
/**
* Root hash of the merkle tree of all transactions in this block. Serialized length is 32 bytes.
*/
merkleRoot: string
/**
* Block header time value. Serialized length is 4 bytes.
*/
time: number
/**
* Block header bits value. Serialized length is 4 bytes.
*/
bits: number
/**
* Block header nonce value. Serialized length is 4 bytes.
*/
nonce: number
}
/**
* A `BaseBlockHeader` extended with its computed hash and height in its chain.
*/
export interface BlockHeader extends BaseBlockHeader {
/**
* Height of the header, starting from zero.
*/
height: number
/**
* The double sha256 hash of the serialized `BaseBlockHeader` fields.
*/
hash: string
}
export type GetUtxoStatusService = (
output: string,
outputFormat?: GetUtxoStatusOutputFormat,
outpoint?: string
) => Promise<GetUtxoStatusResult>
export type GetStatusForTxidsService = (txids: string[]) => Promise<GetStatusForTxidsResult>
export type GetScriptHashHistoryService = (hash: string) => Promise<GetScriptHashHistoryResult>
export type GetMerklePathService = (txid: string, services: WalletServices) => Promise<GetMerklePathResult>
export type GetRawTxService = (txid: string, chain: Chain) => Promise<GetRawTxResult>
export type PostTxsService = (beef: Beef, txids: string[], services: WalletServices) => Promise<PostTxsResult>
export type PostBeefService = (beef: Beef, txids: string[]) => Promise<PostBeefResult>
export type UpdateFiatExchangeRateService = (
targetCurrencies: string[],
options: WalletServicesOptions
) => Promise<FiatExchangeRates>
/**
* Type for the service call history returned by Services.getServicesCallHistory.
*/
export type ServicesCallHistory = {
version: number
getMerklePath: ServiceCallHistory
getRawTx: ServiceCallHistory
postBeef: ServiceCallHistory
getUtxoStatus: ServiceCallHistory
getStatusForTxids: ServiceCallHistory
getScriptHashHistory: ServiceCallHistory
updateFiatExchangeRates: ServiceCallHistory
}
/**
* Minimum data tracked for each service call.
*/
export interface ServiceCall {
/**
* string value must be Date's toISOString format.
*/
when: Date | string
msecs: number
/**
* true iff service provider successfully processed the request
* false iff service provider failed to process the request which includes thrown errors.
*/
success: boolean
/**
* Simple text summary of result. e.g. `not a valid utxo` or `valid utxo`
*/
result?: string
/**
* Error code and message iff success is false and a exception was thrown.
*/
error?: { message: string; code: string }
}
/**
* Counts of service calls over a time interval.
*/
export interface ServiceCallHistoryCounts {
/**
* count of calls returning success true.
*/
success: number
/**
* count of calls returning success false.
*/
failure: number
/**
* of failures (success false), count of calls with valid error code and message.
*/
error: number
/**
* Counts are of calls over interval `since` to `until`.
* string value must be Date's toISOString format.
*/
since: Date | string
/**
* Counts are of calls over interval `since` to `until`.
* string value must be Date's toISOString format.
*/
until: Date | string
}
/**
* History of service calls for a single service, single provider.
*/
export interface ProviderCallHistory {
providerName: string
serviceName: string
/**
* Most recent service calls.
* Array length is limited by Services configuration.
*/
calls: ServiceCall[]
/**
* Counts since creation of Services instance.
*/
totalCounts: ServiceCallHistoryCounts
/**
* Entry [0] is always the current interval being extended by new calls.
* when `getServiceCallHistory` with `reset` true is called, a new interval with zero counts is added to the start of array.
* Array length is limited by Services configuration.
*/
resetCounts: ServiceCallHistoryCounts[]
}
/**
* History of service calls for a single service, all providers.
*/
export interface ServiceCallHistory {
serviceName: string
historyByProvider: Record<string, ProviderCallHistory>
}