@ckb-ccc/core
Version:
Core of CCC - CKBer's Codebase
329 lines (328 loc) • 12 kB
JavaScript
"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;