eCash for Flutter

Package for interacting with eCash (XEC) blockchain.

eCashAddr

Set of functions for encoding, decoding and validating eCash Addresses.

import 'package:ecash/ecashaddr.dart';

// Encode a hash into an eCash address
final address = encodeCashAddress('ecash', AddressType.p2pkh, '76a04053bda5c88f2db1002ad94f303ab715f0eda');
// ecash:qpm2qsznhks23z7629mms6s4cwef74vcwva87rkuu2

// Decode an address into its components
final decoded = decodeCashAddress('ecash:qpm2qsznhks23z7629mms6s4cwef74vcwva87rkuu2');
// DecodedAddress(prefix: 'ecash', type: p2pkh, hash: '76a04053bda5c88f2db1002ad94f303ab715f0eda')

eCash Chronik Client

A comprehensive Dart port of the JavaScript chronik-client library for accessing the Chronik blockchain indexer on eCash (XEC).

Quick Start

import 'package:ecash/ecash.dart';

void main() async {
  // Create a client using connection strategy
  final chronik = await ChronikClient.useStrategy(
    ConnectionStrategy.closestFirst,
    [
      'https://chronik1.e.cash',
      'https://chronik2.e.cash',
    ],
  );

  // Get blockchain info
  final info = await chronik.blockchainInfo();
  print('Tip height: ${info.tipHeight}');

  // Get a transaction
  final tx = await chronik.tx('0f3c...');
  print('TX: ${tx.txid}');

  // Query script history
  final history = await chronik
      .script('p2pkh', 'hash160...')
      .history(page: 0, pageSize: 10);

  // Get UTXOs for an address
  final utxos = await chronik.address('ecash:...').utxos();

  // WebSocket subscriptions
  final ws = chronik.ws(WsConfig(
    onMessage: (msg) {
      if (msg is WsMsgBlockClient) {
        print('Block: ${msg.blockHash}');
      }
    },
  ));
  
  ws.subscribeToBlocks();

  chronik.close();
}

API Overview

Blockchain Info

// Get blockchain information
final blockchainInfo = await chronik.blockchainInfo();

// Get server information
final chronikInfo = await chronik.chronikInfo();

Blocks

// Get a block by hash or height
final block = await chronik.block('hashOrHeight');

// Get multiple blocks
final blocks = await chronik.blocks(startHeight, endHeight);

// Get transactions in a block
final blockTxs = await chronik.blockTxs('hashOrHeight', page: 0, pageSize: 25);

Transactions

// Get transaction details
final tx = await chronik.tx('txid...');

// Get raw transaction bytes
final rawBytes = await chronik.rawTx('txid...');

// Broadcast a transaction
final response = await chronik.broadcastTx(txBytes);

// Broadcast and wait for finalization
final finalResponse = await chronik.broadcastAndFinalizeTx(txBytes);

// Validate a transaction without broadcasting
final validation = await chronik.validateRawTx(txBytes);

Scripts and Addresses

// Query by script
final scriptEndpoint = chronik.script('p2pkh', 'payload...');
final history = await scriptEndpoint.history(page: 0, pageSize: 10);
final utxos = await scriptEndpoint.utxos();
final confirmed = await scriptEndpoint.confirmedTxs();
final unconfirmed = await scriptEndpoint.unconfirmedTxs();

// Query by eCash address
final addressEndpoint = chronik.address('ecash:...');

Tokens

// Get token information
final tokenInfo = await chronik.token('tokenId...');

// Get token endpoint for queries
final tokenEndpoint = chronik.tokenId('tokenId...');
final tokenHistory = await tokenEndpoint.history();
final tokenUtxos = await tokenEndpoint.utxos();

Lokad IDs

// Get lokad ID endpoint
final lokadEndpoint = chronik.lokadId('lokadId...');
final history = await lokadEndpoint.history();

Plugins

// Get plugin endpoint
final pluginEndpoint = chronik.plugin('pluginName');
final groups = await pluginEndpoint.groups();
final utxos = await pluginEndpoint.utxos('groupHex');
final history = await pluginEndpoint.history('groupHex');

WebSocket

final ws = chronik.ws(WsConfig(
  onMessage: (msg) {
    switch (msg) {
      case WsMsgBlockClient():
        print('Block update: ${msg.blockHash}');
      case WsMsgTxClient():
        print('TX update: ${msg.tx.txid}');
      case WsErrorMsg():
        print('Error: ${msg.error}');
    }
  },
  onConnect: () {
    print('Connected');
  },
  onReconnect: (error) {
    print('Reconnecting due to: $error');
  },
  keepAlive: true,
));

// Wait for connection
await ws.waitForOpen();

// Subscribe/unsubscribe
ws.subscribeToBlocks();
ws.subscribeToScript('p2pkh', 'payload...');
ws.subscribeToAddress('ecash:...');
ws.subscribeToTokenId('tokenId...');
ws.subscribeToLokadId('lokadId...');
ws.subscribeToTxid('txid...');
ws.subscribeToTxs();  // All transactions
ws.subscribeToPlugin('pluginName', 'groupHex');

// Unsubscribe
ws.unsubscribeFromBlocks();
ws.unsubscribeFromScript('p2pkh', 'payload...');

// Mobile apps can pause/resume
ws.pause();    // Stop reconnecting
ws.resume();   // Resume reconnecting

// Close
ws.close();

Connection Strategies

Selects the fastest server based on WebSocket latency measurement:

final chronik = await ChronikClient.useStrategy(
  ConnectionStrategy.closestFirst,
  ['https://chronik1.e.cash', 'https://chronik2.e.cash'],
);

The client will:

  1. Measure latency to all servers
  2. Sort by latency (fastest first)
  3. Use the fastest server
  4. Failover to the next fastest on connection failure

AsOrdered

Uses servers in the provided order:

final chronik = await ChronikClient.useStrategy(
  ConnectionStrategy.asOrdered,
  ['https://primary.e.cash', 'https://backup.e.cash'],
);

Error Handling

The client throws strongly-typed exceptions:

try {
  final tx = await chronik.tx('invalid');
} on ValidationException catch (e) {
  print('Validation error: ${e.message}');
} on ServerException catch (e) {
  print('Server error: ${e.message}');
} on AllServersFailedException catch (e) {
  print('All servers failed: ${e.failedServers}');
} catch (e) {
  print('Unknown error: $e');
}