PHP WebShell

Текущая директория: /opt/BitGoJS/modules/blockapis/src/impl

Просмотр файла: BlockchairApi.ts

import { bitgo } from '@bitgo/utxo-lib';
import { BaseHttpClient, HttpClient, Response } from '../BaseHttpClient';
import { ApiNotImplementedError } from '../ApiBuilder';
import { AddressApi, AddressInfo } from '../AddressApi';
import { OutputSpend, TransactionIO, UtxoApi } from '../UtxoApi';
import { TransactionStatus } from '../TransactionApi';

type Unspent = bitgo.Unspent;
const formatOutputId = bitgo.formatOutputId;

type BlockchairResponse<T> = {
  data: T;
};

type BlockchairRecordResponse<T> = BlockchairResponse<Record<string, T>>;

class ErrorKeyNotInResponse extends Error {
  constructor(key: string) {
    super(`key ${key} not in response`);
  }
}

function unwrapRecord<T>(body: BlockchairRecordResponse<T>, key: string): T {
  if (!(key in body.data)) {
    throw new ErrorKeyNotInResponse(key);
  }
  return body.data[key];
}

// https://blockchair.com/api/docs#link_300
type BlockchairUnspent = {
  transaction_hash: string;
  index: number;
  recipient: string;
  value: number;
  block_id: number;
  script_hex: string;
  spending_transaction_hash: string;
  spending_index: number;
  address: string;
};

// https://blockchair.com/api/docs#link_300
type BlockchairAddress = {
  address: {
    transaction_hash: string;
    // vout
    index: number;
    value: number;
    block_id: number;
    transaction_count: number;
    balance: number;
  };
  utxo: BlockchairUnspent[];
};

// https://blockchair.com/api/docs#link_200
type BlockchairTransaction = {
  transaction: {
    /**
      The block number it's included in.
      If the transaction is in the mempool, data.{:hash}ᵢ.transaction.block_id yields -1
    */
    block_id: number;
    /**
     * Like https://tc39.es/ecma262/#sec-date-time-string-format but with a space instead of 'T'
     * YYYY-MM-DD HH:mm:ss
     */
    time: string;
  };
  inputs: BlockchairUnspent[];
  outputs: BlockchairUnspent[];
};

// https://blockchair.com/api/docs#link_201
type BlockchairRawTransaction = {
  raw_transaction: string;
};

export class BlockchairApi implements AddressApi, UtxoApi {
  protected readonly apiToken?: string;

  static forCoin(coinName: string, params: { apiToken?: string; httpClient?: HttpClient } = {}): BlockchairApi {
    // https://blockchair.com/api/docs#link_M0
    let blockchain;
    switch (coinName) {
      case 'btc':
        blockchain = 'bitcoin';
        break;
      case 'tbtc':
        blockchain = 'bitcoin/testnet';
        break;
      case 'bsv':
        blockchain = 'bitcoin-sv';
        break;
      case 'bch':
        blockchain = 'bitcoin-cash';
        break;
      case 'bcha':
        blockchain = 'ecash';
        break;
      case 'ltc':
        blockchain = 'litecoin';
        break;
      case 'dash':
        blockchain = 'dash';
        break;
      case 'doge':
        blockchain = 'dogecoin';
        break;
      case 'zec':
        blockchain = 'zcash';
        break;
      default:
        throw new ApiNotImplementedError(coinName);
    }
    const { httpClient = new BaseHttpClient() } = params;
    return new BlockchairApi(httpClient.withBaseUrl(`https://api.blockchair.com/${blockchain}`), params.apiToken);
  }

  constructor(public client: HttpClient, apiToken?: string) {
    this.apiToken = apiToken ?? process.env.BLOCKCHAIR_TOKEN;
  }

  get<T>(path: string): Promise<Response<T>> {
    return this.client.get(path + (this.apiToken ? `?key=${this.apiToken}` : ''));
  }

  async getAddressInfo(address: string): Promise<AddressInfo> {
    if (!address || address.length === 0) {
      throw new Error('invalid address');
    }
    // https://blockchair.com/api/docs#link_300
    return (await this.get<BlockchairRecordResponse<BlockchairAddress>>(`/dashboards/address/${address}`)).map(
      (body) => {
        return {
          txCount: body.data[address].address.transaction_count,
          balance: body.data[address].address.balance,
        };
      }
    );
  }

  async getUnspentsForAddresses(addr: string[]): Promise<Unspent[]> {
    if (addr.length > 100) {
      throw new Error(`invalid size`);
    }

    const response = await this.get<BlockchairResponse<{ utxo: BlockchairUnspent[] }>>(
      `/dashboards/addresses/${addr.join(',')}`
    );

    return response.map((body) => {
      return body.data.utxo
        .flatMap((unspent): Unspent | undefined => {
          if (addr.includes(unspent.address)) {
            return {
              id: formatOutputId({ txid: unspent.transaction_hash, vout: unspent.index }),
              address: unspent.address,
              value: unspent.value,
            };
          }
          return undefined;
        })
        .filter((unspent): unspent is Unspent => unspent !== undefined);
    });
  }

  async getTransaction(txid: string): Promise<BlockchairTransaction> {
    return (await this.get<BlockchairRecordResponse<BlockchairTransaction>>(`/dashboards/transaction/${txid}`)).map(
      (body) => {
        return unwrapRecord(body, txid);
      }
    );
  }

  async getTransactionStatus(txid: string): Promise<TransactionStatus> {
    let transaction;
    try {
      transaction = (await this.getTransaction(txid)).transaction;
    } catch (e) {
      if (e instanceof ErrorKeyNotInResponse) {
        return { found: false };
      }
      throw e;
    }
    const { block_id, time } = transaction;
    const date = new Date(Date.parse(time.replace(' ', 'T') + '.000Z' /* force UTC parsing */));
    return block_id === -1
      ? { found: true, confirmed: false }
      : {
          found: true,
          confirmed: true,
          blockHeight: block_id,
          date,
        };
  }

  async getTransactionInputs(txid: string): Promise<Unspent[]> {
    return (await this.getTransaction(txid)).inputs.map((i) => {
      return {
        id: formatOutputId({ txid: i.transaction_hash, vout: i.index }),
        address: i.recipient,
        value: i.value,
      };
    });
  }

  async getTransactionIO(txid: string): Promise<TransactionIO> {
    const tx = await this.getTransaction(txid);
    const inputs = tx.inputs.map((input) => {
      return {
        address: input.recipient,
      };
    });
    const outputs = tx.outputs.map((output) => {
      return {
        address: output.recipient,
      };
    });
    return {
      inputs,
      outputs,
    };
  }

  async getTransactionSpends(txid: string): Promise<OutputSpend[]> {
    return (await this.getTransaction(txid)).outputs.map((o) =>
      o.spending_transaction_hash
        ? {
            txid: o.spending_transaction_hash,
            vin: o.spending_index,
          }
        : { txid: undefined, vin: undefined }
    );
  }

  async getTransactionHex(txid: string): Promise<string> {
    return (await this.get<BlockchairRecordResponse<BlockchairRawTransaction>>(`/raw/transaction/${txid}`)).map(
      (body) => {
        return unwrapRecord(body, txid).raw_transaction;
      }
    );
  }
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!