import { Cell , CellCollector , CellCollectorResults , Hexadecimal , HexString , Indexer , QueryOptions , Script , Tip , OutPoint , HexNumber , } from '@ckb-lumos/base' ; import { RPC } from '@ckb-lumos/rpc' ; import axios from 'axios' ; import { asyncSleep } from '../../utils' ; import { logger } from '../../utils/logger' ; export enum ScriptType { type = 'type' , lock = 'lock' } export enum Order { asc = 'asc' , desc = 'desc' } export type HexadecimalRange = [ Hexadecimal , Hexadecimal ] ; export interface SearchKey { script : Script ; script_type : ScriptType ; filter ? : { script ? : Script ; output_data_len_range ? : HexadecimalRange ; output_capacity_range ? : HexadecimalRange ; block_range ? : HexadecimalRange ; } ; } export interface GetLiveCellsResult { last_cursor : string ; objects : IndexerCell [ ] ; } export interface IndexerCell { block_number : Hexadecimal ; out_point : OutPoint ; output : { capacity : HexNumber ; lock : Script ; type ? : Script ; } ; output_data : HexString ; tx_index : Hexadecimal ; } export interface TerminatorResult { stop : boolean ; push : boolean ; } export declare type Terminator = ( index : number , cell : Cell ) => TerminatorResult ; const DefaultTerminator = ( _index , _cell ) => { return { stop : false , push : true } ; } ; export type HexNum = string ; export type IOType = 'input' | 'output' ; export type Bytes32 = string ; export type GetTransactionsResult = { block_number : HexNum ; io_index : HexNum ; io_type : IOType ; tx_hash : Bytes32 ; tx_index : HexNum ; } ; export interface GetTransactionsResults { last_cursor : string ; objects : GetTransactionsResult [ ] ; } export class CkbIndexer implements Indexer { uri : string ; constructor ( public ckbRpcUrl , public ckbIndexerUrl ) { this . uri = ckbRpcUrl ; } getCkbRpc ( ) { return new RPC ( this . ckbRpcUrl ) ; } async tip ( ) { const res = await this . request ( 'get_tip' ) ; return res as Tip ; } async waitForSync ( blockDifference = 0 ) { const rpcTipNumber = parseInt ( ( await this . getCkbRpc ( ) . get_tip_header ( ) ) . number , 16 ) ; logger . debug ( 'rpcTipNumber' , rpcTipNumber ) ; let index = 0 ; while ( true ) { const indexerTipNumber = parseInt ( ( await this . tip ( ) ) . block_number , 16 ) ; logger . debug ( 'indexerTipNumber' , indexerTipNumber ) ; if ( indexerTipNumber + blockDifference >= rpcTipNumber ) { return ; } logger . debug ( `wait until indexer sync. index: ${ index ++ }` ) ; await asyncSleep ( 1000 ) ; } } collector ( queries ) { const { lock , type } = queries ; let searchKey ; if ( lock !== undefined ) { searchKey = { script : lock as Script , script_type : ScriptType . lock , } ; if ( type != undefined && type !== 'empty' ) { searchKey . filter = { script : type as Script , } ; } } else { if ( type != undefined && type != 'empty' ) { searchKey = { script : type as Script , script_type : ScriptType . type , } ; } else { throw new Error ( `should specify either type or lock in queries, queries now: ${ JSON . stringify ( queries , null , 2 ) }` ) ; } } const queryData = queries . data || '0x' ; const request = this . request ; const ckbIndexerUrl = this . ckbIndexerUrl ; return { collect ( ) { return { async * [ Symbol . asyncIterator ] ( ) { const order = 'asc' ; const sizeLimit = 100 ; let cursor = null ; for ( ; ; ) { const params = [ searchKey , order , `0x${ sizeLimit . toString ( 16 ) }` , cursor ] ; logger . debug ( 'get_cells params' , params ) ; const res = await request ( 'get_cells' , params , ckbIndexerUrl ) ; const liveCells = res . objects ; cursor = res . last_cursor ; for ( const cell of liveCells ) { if ( queryData === 'any' || queryData === cell . output_data ) { yield { cell_output : cell . output , data : cell . output_data , out_point : cell . out_point , block_number : cell . block_number , } ; } } if ( liveCells . length < sizeLimit ) { break ; } } } , } ; } , } ; } public async request ( method , params ? , ckbIndexerUrl = this . ckbIndexerUrl ) { const data = { id : 0 , jsonrpc : '2.0' , method , params , } ; const res = await axios . post ( ckbIndexerUrl , data ) ; if ( res . status !== 200 ) { throw new Error ( `indexer request failed with HTTP code ${ res . status }` ) ; } if ( res . data . error !== undefined ) { throw new Error ( `indexer request rpc failed with error: ${ JSON . stringify ( res . data . error ) }` ) ; } return res . data . result ; } public async getCells ( searchKey , terminator = DefaultTerminator , { sizeLimit = 0x100 , order = Order . asc } = { } ) { const infos = [ ] ; let cursor ; const index = 0 ; while ( true ) { const params = [ searchKey , order , `0x${ sizeLimit . toString ( 16 ) }` , cursor ] ; const res = await this . request ( 'get_cells' , params ) ; const liveCells = res . objects ; cursor = res . last_cursor ; logger . debug ( 'liveCells' , liveCells [ liveCells . length - 1 ] ) ; for ( const liveCell of liveCells ) { const cell = { cell_output : liveCell . output , data : liveCell . output_data , out_point : liveCell . out_point , block_number : liveCell . block_number , } ; const { stop , push } = terminator ( index , cell ) ; if ( push ) { infos . push ( cell ) ; } if ( stop ) { return infos ; } } if ( liveCells . length < sizeLimit ) { break ; } } return infos ; } public async getTransactions ( searchKey , { sizeLimit = 0x100 , order = Order . asc } = { } ) { let infos = [ ] ; let cursor ; for ( ; ; ) { const params = [ searchKey , order , `0x${ sizeLimit . toString ( 16 ) }` , cursor ] ; const res = await this . request ( 'get_transactions' , params ) ; const txs = res . objects ; cursor = res . last_cursor ; infos = infos . concat ( txs ) ; if ( txs . length < sizeLimit ) { break ; } } return infos ; } running ( ) { return true ; } start ( ) { logger . debug ( 'ckb indexer start' ) ; } startForever ( ) { logger . debug ( 'ckb indexer startForever' ) ; } stop ( ) { logger . debug ( 'ckb indexer stop' ) ; } subscribe ( queries ) { throw new Error ( 'unimplemented' ) ; } subscribeMedianTime ( ) { throw new Error ( 'unimplemented' ) ; } }