Electrum Network Provider
The CashScript SDK needs to connect to the BCH network to perform certain operations, like retrieving the contract's balance, or sending transactions. The recommended network provider is the ElectrumNetworkProvider.
Creating an ElectrumNetworkProvider
The ElectrumNetworkProvider uses @electrum-cash/network library to connect to the configured electrum server. The connection uses a single, trusted electrum server so it does not have any fallback logic and does not validate SPV proofs for chain inclusion.
By default the ElectrumNetworkProvider creates a short-lived connection only when requests are pending. To configure this see the section on 'Manual Connection Management'.
Constructor
Both network and options parameters are optional, and they default to mainnet with the bch.imaginary.cash electrum server.
new ElectrumNetworkProvider(network?: Network, options?: Options)
Using the network parameter, you can specify the network to connect to. There's 4 networks supported by the ElectrumNetworkProvider:
type Network = 'mainnet' | 'chipnet' | 'testnet3' | 'testnet4';
Using the options parameter, you can specify a custom electrum client or hostname, and enable manual connection management.
type Options = OptionsBase | CustomHostNameOptions | CustomElectrumOptions;
interface OptionsBase {
manualConnectionManagement?: boolean;
}
interface CustomHostNameOptions extends OptionsBase {
hostname: string;
}
interface CustomElectrumOptions extends OptionsBase {
electrum: ElectrumClient<ElectrumClientEvents>;
}
Example
import { ElectrumNetworkProvider } from 'cashscript';
const hostname = 'chipnet.bch.ninja';
const provider = new ElectrumNetworkProvider('chipnet', { hostname });
ElectrumNetworkProvider Methods
getUtxos()
async provider.getUtxos(address: string): Promise<SpendableUtxo[]>;
Returns all UTXOs on specific address. Both confirmed and unconfirmed UTXOs are included.
interface SpendableUtxo extends Utxo {
lockingBytecode: string;
}
interface Utxo {
txid: string;
vout: number;
satoshis: bigint;
token?: TokenDetails;
lockingBytecode?: string;
}
interface TokenDetails {
amount: bigint;
category: string;
nft?: {
capability: 'none' | 'mutable' | 'minting';
commitment: string;
};
}
Example
const userUtxos = await provider.getUtxos(userAddress)
getUtxosForLockingBytecode()
async provider.getUtxosForLockingBytecode(lockingBytecode: Uint8Array | string): Promise<SpendableUtxo[]>;
Returns all UTXOs for a specific locking bytecode. Both confirmed and unconfirmed UTXOs are included.
Example
const utxos = await provider.getUtxosForLockingBytecode(lockingBytecode)
getBlockHeight()
async provider.getBlockHeight(): Promise<number>;
Get the current blockHeight.
Example
const currentBlockHeight = await provider.getBlockHeight()
getRawTransaction()
async provider.getRawTransaction(txid: string): Promise<string>;
Retrieve the Hex transaction details for a given transaction ID.
Example
const rawTransaction = await provider.getRawTransaction(txid)
sendRawTransaction()
async provider.sendRawTransaction(txHex: string): Promise<string>;
Broadcast a raw hex transaction to the network.
Example
const txId = await provider.sendRawTransaction(txHex)
performRequest()
Perform an arbitrary electrum request, refer to the docs at electrum-cash-protocol.
Example
const verbose = true // get parsed transaction as json result
const txId = await provider.performRequest('blockchain.transaction.get', txid, verbose)
Manual Connection Management
By default, the ElectrumNetworkProvider will automatically connect and disconnect to the electrum client as needed. However, you can enable manual connection management by setting the manualConnectionManagement option to true. This can be useful if you are passing a custom electrum client and are using that client for other purposes, such as subscribing to events.
const provider = new ElectrumNetworkProvider('chipnet', { manualConnectionManagement: true });
If you're providing an ElectrumClient and using it to subscribe to address or block header events, you need to enable manualConnectionManagement to overwrite the default of connecting and disconnecting for each separate request.
connect()
provider.connect(): Promise<void>;
Connects to the electrum client.
disconnect()
provider.disconnect(): Promise<boolean>;
Disconnects from the electrum client, returns true if the client was connected, false if it was already disconnected.
Using electrum-cash functionality
To use more of the electrum-specific functionality which is not exposed in the ElectrumNetworkProvider you can simply call the methods on the electrum Client itself.
Custom Electrum Client
When initializing an ElectrumNetworkProvider you have the option in the constructor to provide a custom electrum client. This way you can use one and the same indexer server for blockchain information but use it through two different interfaces. This allows you to access all underlying functionality of the @electrum-cash/network library like address and blockHeight subscriptions.
If intending to use electrum-cash subscriptions, make sure to set manualConnectionManagement to true, so the ElectrumNetworkProvider does not disconnect after each request.
The custom client must negotiate Electrum protocol 1.5.0 or later, since the provider's requests (the include_tokens UTXO filter and blockchain.headers.get_tip) were introduced in that version.
Example
import { ElectrumClient } from '@electrum-cash/network';
import { ElectrumNetworkProvider } from 'cashscript';
const electrum = new ElectrumClient('CashScript Application', '1.5.0', 'chipnet.bch.ninja');
const provider = new ElectrumNetworkProvider('chipnet', {
electrum, manualConnectionManagement: true
});
await electrum.connect();
Browser visibility and connectivity
In a browser, the underlying @electrum-cash/web-socket connection follows the page: when the page is hidden (for example when the user switches tabs) or the browser goes offline, the connection is closed, and it is opened again when the page is visible and online. A long-lived client may also reconnect earlier through its own automatic reconnection.
With the default short-lived connections this only affects a request that is in flight at that moment, which fails with a connection error. For a custom electrum client with manualConnectionManagement, subscriptions are restored when the connection is opened again, and each one then receives a notification with its current status. Updates that happened while the page was hidden are not delivered one by one, only the latest status.
To keep the connection open while the page is hidden, you need to provide a custom electrum client built on an ElectrumWebSocket with enforceConsistentBrowserBehavior set to false.
import { ElectrumClient } from '@electrum-cash/network';
import { ElectrumWebSocket } from '@electrum-cash/web-socket';
import { ElectrumNetworkProvider } from 'cashscript';
const socket = new ElectrumWebSocket('chipnet.bch.ninja', { enforceConsistentBrowserBehavior: false });
const electrum = new ElectrumClient('CashScript Application', '1.5.0', socket);
const provider = new ElectrumNetworkProvider('chipnet', {
electrum, manualConnectionManagement: true
});
await electrum.connect();
Only use enforceConsistentBrowserBehavior: false in code that runs in a browser. In Node.js the same option also disables TLS certificate verification, so the connection would accept any certificate.
Error Handling
The ElectrumNetworkProvider can throw the following errors when broadcasting a transaction:
| Error | Description |
|---|---|
NetworkProviderMissingInputsError | Transaction inputs are missing or already spent |
NetworkProviderMempoolConflictError | Transaction conflicts with an unconfirmed transaction in the mempool |
NetworkProviderTransactionAlreadySubmittedError | Transaction has already been submitted |
NetworkProviderAbsoluteTimelockError | Transaction is not yet final (nLockTime not satisfied) |
NetworkProviderRelativeTimelockError | BIP68 sequence lock not satisfied |
NetworkProviderError | Generic fallback network provider error |