UNPKG

@ckb-ccc/core

Version:

Core of CCC - CKBer's Codebase

329 lines (328 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Client = void 0; const index_js_1 = require("../ckb/index.js"); const index_js_2 = require("../fixedPoint/index.js"); const index_js_3 = require("../hex/index.js"); const index_js_4 = require("../num/index.js"); const index_js_5 = require("../utils/index.js"); const memory_js_1 = require("./cache/memory.js"); const clientTypes_advanced_js_1 = require("./clientTypes.advanced.js"); const clientTypes_js_1 = require("./clientTypes.js"); function hasHeaderConfirmed(header) { return (0, index_js_4.numFrom)(Date.now()) - header.timestamp >= clientTypes_advanced_js_1.CONFIRMED_BLOCK_TIME; } /** * @public */ class Client { constructor(config) { this.cache = config?.cache ?? new memory_js_1.ClientCacheMemory(); } async getFeeRate(blockRange, options) { const feeRate = (0, index_js_4.numMax)((await this.getFeeRateStatistics(blockRange)).median, clientTypes_advanced_js_1.DEFAULT_MIN_FEE_RATE); const maxFeeRate = (0, index_js_4.numFrom)(options?.maxFeeRate ?? clientTypes_advanced_js_1.DEFAULT_MAX_FEE_RATE); if (maxFeeRate === index_js_2.Zero) { return feeRate; } return (0, index_js_4.numMin)(feeRate, maxFeeRate); } async getBlockByNumber(blockNumber, verbosity, withCycles) { const block = await this.cache.getBlockByNumber(blockNumber); if (block) { return block; } const res = await this.getBlockByNumberNoCache(blockNumber, verbosity, withCycles); if (res && hasHeaderConfirmed(res.header)) { await this.cache.recordBlocks(res); } return res; } async getBlockByHash(blockHash, verbosity, withCycles) { const block = await this.cache.getBlockByHash(blockHash); if (block) { return block; } const res = await this.getBlockByHashNoCache(blockHash, verbosity, withCycles); if (res && hasHeaderConfirmed(res.header)) { await this.cache.recordBlocks(res); } return res; } async getHeaderByNumber(blockNumber, verbosity) { const header = await this.cache.getHeaderByNumber(blockNumber); if (header) { return header; } const res = await this.getHeaderByNumberNoCache(blockNumber, verbosity); if (res && hasHeaderConfirmed(res)) { await this.cache.recordHeaders(res); } return res; } async getHeaderByHash(blockHash, verbosity) { const header = await this.cache.getHeaderByHash(blockHash); if (header) { return header; } const res = await this.getHeaderByHashNoCache(blockHash, verbosity); if (res && hasHeaderConfirmed(res)) { await this.cache.recordHeaders(res); } return res; } async getCell(outPointLike) { const outPoint = index_js_1.OutPoint.from(outPointLike); const cached = await this.cache.getCell(outPoint); if (cached) { return cached; } const transaction = await this.getTransaction(outPoint.txHash); if (!transaction) { return; } const output = transaction.transaction.getOutput(outPoint.index); if (!output) { return; } const cell = index_js_1.Cell.from({ outPoint, ...output, }); await this.cache.recordCells(cell); return cell; } async getCellWithHeader(outPointLike) { const outPoint = index_js_1.OutPoint.from(outPointLike); const res = await this.getTransactionWithHeader(outPoint.txHash); if (!res) { return; } const { transaction, header } = res; const output = transaction.transaction.getOutput(outPoint.index); if (!output) { return; } const cell = index_js_1.Cell.from({ outPoint, ...output, }); await this.cache.recordCells(cell); return { cell, header }; } async getCellLive(outPointLike, withData, includeTxPool) { const cell = await this.getCellLiveNoCache(outPointLike, withData, includeTxPool); if (withData && cell) { await this.cache.recordCells(cell); } return cell; } async findCellsPaged(key, order, limit, after) { const res = await this.findCellsPagedNoCache(key, order, limit, after); await this.cache.recordCells(res.cells); return res; } async *findCellsOnChain(key, order, limit = 10) { let last = undefined; while (true) { const { cells, lastCursor } = await this.findCellsPaged(key, order, limit, last); for (const cell of cells) { yield cell; } if (cells.length === 0 || cells.length < limit) { return; } last = lastCursor; } } /** * Find cells by search key designed for collectable cells. * The result also includes cached cells, the order param only works for cells fetched from RPC. * * @param keyLike - The search key. * @returns A async generator for yielding cells. */ async *findCells(keyLike, order, limit = 10) { const key = clientTypes_js_1.ClientIndexerSearchKey.from(keyLike); const foundedOutPoints = []; for await (const cell of this.cache.findCells(key)) { foundedOutPoints.push(cell.outPoint); yield cell; } for await (const cell of this.findCellsOnChain(key, order, limit)) { if ((await this.cache.isUnusable(cell.outPoint)) || foundedOutPoints.some((founded) => founded.eq(cell.outPoint))) { continue; } yield cell; } } findCellsByLock(lock, type, withData = true, order, limit = 10) { return this.findCells({ script: lock, scriptType: "lock", scriptSearchMode: "exact", filter: { script: type, }, withData, }, order, limit); } findCellsByType(type, withData = true, order, limit = 10) { return this.findCells({ script: type, scriptType: "type", scriptSearchMode: "exact", withData, }, order, limit); } async findSingletonCellByType(type, withData = false) { for await (const cell of this.findCellsByType(type, withData, undefined, 1)) { return cell; } } async getCellDeps(...cellDepsInfoLike) { return Promise.all(cellDepsInfoLike.flat().map(async (infoLike) => { const { cellDep, type } = clientTypes_js_1.CellDepInfo.from(infoLike); if (type === undefined) { return cellDep; } const found = await this.findSingletonCellByType(type); if (!found) { return cellDep; } return index_js_1.CellDep.from({ outPoint: found.outPoint, depType: cellDep.depType, }); })); } async *findTransactions(key, order, limit = 10) { let last = undefined; while (true) { const { transactions, lastCursor, } = await this.findTransactionsPaged(key, order, limit, last); for (const tx of transactions) { yield tx; } if (transactions.length === 0 || transactions.length < limit) { return; } last = lastCursor; } } findTransactionsByLock(lock, type, groupByTransaction, order, limit = 10) { return this.findTransactions({ script: lock, scriptType: "lock", scriptSearchMode: "exact", filter: { script: type, }, groupByTransaction, }, order, limit); } findTransactionsByType(type, groupByTransaction, order, limit = 10) { return this.findTransactions({ script: type, scriptType: "type", scriptSearchMode: "exact", groupByTransaction, }, order, limit); } async getBalanceSingle(lock) { return this.getCellsCapacity({ script: lock, scriptType: "lock", scriptSearchMode: "exact", filter: { scriptLenRange: [0, 1], outputDataLenRange: [0, 1], }, }); } async getBalance(locks) { return (0, index_js_5.reduceAsync)(locks, async (acc, lock) => acc + (await this.getBalanceSingle(lock)), index_js_2.Zero); } async sendTransaction(transaction, validator, options) { const tx = index_js_1.Transaction.from(transaction); const maxFeeRate = (0, index_js_4.numFrom)(options?.maxFeeRate ?? clientTypes_advanced_js_1.DEFAULT_MAX_FEE_RATE); const feeRate = await tx.getFeeRate(this); if (maxFeeRate > index_js_2.Zero && feeRate > maxFeeRate) { throw new clientTypes_js_1.ErrorClientMaxFeeRateExceeded(maxFeeRate, feeRate); } const txHash = await this.sendTransactionNoCache(tx, validator); await this.cache.markTransactions(tx); return txHash; } async getTransaction(txHashLike) { const txHash = (0, index_js_3.hexFrom)(txHashLike); const res = await this.getTransactionNoCache(txHash); if (res) { await this.cache.recordTransactionResponses(res); return res; } return this.cache.getTransactionResponse(txHash); } /** * This method gets specified transaction with its block header (if existed). * This is mainly for caching because we need the header to test if we can safely trust the cached tx status. * @param txHashLike */ async getTransactionWithHeader(txHashLike) { const txHash = (0, index_js_3.hexFrom)(txHashLike); const tx = await this.cache.getTransactionResponse(txHash); if (tx?.blockHash) { const header = await this.getHeaderByHash(tx.blockHash); if (header && hasHeaderConfirmed(header)) { return { transaction: tx, header, }; } } const res = await this.getTransactionNoCache(txHash); if (!res) { return; } await this.cache.recordTransactionResponses(res); return { transaction: res, header: res.blockHash ? await this.getHeaderByHash(res.blockHash) : undefined, }; } async waitTransaction(txHash, confirmations = 0, timeout = 60000, interval = 2000) { const startTime = Date.now(); let tx; const getTx = async () => { const res = await this.getTransaction(txHash); if (!res || res.blockNumber == null || ["sent", "pending", "proposed"].includes(res.status)) { return undefined; } tx = res; return res; }; while (true) { if (!tx) { if (await getTx()) { continue; } } else if (confirmations === 0) { return tx; } else if ((await this.getTipHeader()).number - tx.blockNumber >= confirmations) { return tx; } if (Date.now() - startTime + interval >= timeout) { throw new clientTypes_js_1.ErrorClientWaitTransactionTimeout(timeout); } await (0, index_js_5.sleep)(interval); } } } exports.Client = Client;