@ckb-ccc/core
Version:
Core of CCC - CKBer's Codebase
398 lines (363 loc) • 11.4 kB
text/typescript
import {
Cell,
OutPoint,
OutPointLike,
TransactionLike,
} from "../../ckb/index.js";
import { Hex, HexLike, hexFrom } from "../../hex/index.js";
import {
RequestorJsonRpc,
RequestorJsonRpcConfig,
} from "../../jsonRpc/requestor.js";
import { Num, NumLike, numFrom, numToHex } from "../../num/index.js";
import { apply } from "../../utils/index.js";
import { ClientCache } from "../cache/index.js";
import { Client } from "../client.js";
import {
ClientFindCellsResponse,
ClientIndexerSearchKeyLike,
ClientTransactionResponse,
ErrorClientBase,
ErrorClientBaseLike,
ErrorClientDuplicatedTransaction,
ErrorClientRBFRejected,
ErrorClientResolveUnknown,
ErrorClientVerification,
OutputsValidator,
} from "../clientTypes.js";
import {
JsonRpcBlock,
JsonRpcBlockHeader,
JsonRpcCellOutput,
JsonRpcTransformers,
} from "./advanced.js";
const ERROR_PARSERS: [
string,
(error: ErrorClientBaseLike, match: RegExpMatchArray) => ErrorClientBase,
][] = [
[
"Resolve\\(Unknown\\(OutPoint\\((0x.*)\\)\\)\\)",
(error, match) =>
new ErrorClientResolveUnknown(error, OutPoint.fromBytes(match[1])),
],
[
"Verification\\(Error { kind: Script, inner: TransactionScriptError { source: (Inputs|Outputs)\\[([0-9]*)\\].(Lock|Type), cause: ValidationFailure: see error code (-?[0-9])* on page https://nervosnetwork\\.github\\.io/ckb-script-error-codes/by-(type|data)-hash/(.*)\\.html",
(error, match) =>
new ErrorClientVerification(
error,
match[3] === "Lock"
? "lock"
: match[1] === "Inputs"
? "inputType"
: "outputType",
match[2],
Number(match[4]),
match[5] === "data" ? "data" : "type",
match[6],
),
],
[
"Duplicated\\(Byte32\\((0x.*)\\)\\)",
(error, match) => new ErrorClientDuplicatedTransaction(error, match[1]),
],
[
'RBFRejected\\("Tx\'s current fee is ([0-9]*), expect it to >= ([0-9]*) to replace old txs"\\)',
(error, match) => new ErrorClientRBFRejected(error, match[1], match[2]),
],
];
export type ClientJsonRpcConfig = RequestorJsonRpcConfig & {
cache?: ClientCache;
requestor?: RequestorJsonRpc;
};
/**
* An abstract class implementing JSON-RPC client functionality for a specific URL and timeout.
* Provides methods for sending transactions and building JSON-RPC payloads.
*/
export abstract class ClientJsonRpc extends Client {
public readonly requestor: RequestorJsonRpc;
/**
* Creates an instance of ClientJsonRpc.
*
* @param url_ - The URL of the JSON-RPC server.
* @param timeout - The timeout for requests in milliseconds
*/
constructor(url_: string, config?: ClientJsonRpcConfig) {
super(config);
this.requestor =
config?.requestor ??
new RequestorJsonRpc(url_, config, (errAny) => {
if (
typeof errAny !== "object" ||
errAny === null ||
!("data" in errAny) ||
typeof errAny.data !== "string"
) {
throw errAny;
}
const err = errAny as ErrorClientBaseLike;
for (const [regexp, builder] of ERROR_PARSERS) {
const match = err.data.match(regexp);
if (match) {
throw builder(err, match);
}
}
throw new ErrorClientBase(err);
});
}
/**
* Returns the URL of the JSON-RPC server.
*
* @returns The URL of the JSON-RPC server.
*/
get url(): string {
return this.requestor.url;
}
/**
* Get fee rate statistics
*
* @returns Fee rate statistics
*/
getFeeRateStatistics = this.buildSender(
"get_fee_rate_statistics",
[(n: NumLike) => apply(numFrom, n)],
({ mean, median }: { mean: NumLike; median: NumLike }) => ({
mean: numFrom(mean),
median: numFrom(median),
}),
) as Client["getFeeRateStatistics"];
/**
* Get tip block number
*
* @returns Tip block number
*/
getTip = this.buildSender(
"get_tip_block_number",
[],
numFrom,
) as () => Promise<Num>;
/**
* Get tip block header
*
* @param verbosity - result format which allows 0 and 1. (Optional, the default is 1.)
* @returns BlockHeader
*/
getTipHeader = this.buildSender(
"get_tip_header",
[],
(b: JsonRpcBlockHeader) => apply(JsonRpcTransformers.blockHeaderTo, b),
) as Client["getTipHeader"];
/**
* Get block by block number
*
* @param blockNumber - The block number.
* @param verbosity - result format which allows 0 and 2. (Optional, the default is 2.)
* @param withCycles - whether the return cycles of block transactions. (Optional, default false.)
* @returns Block
*/
getBlockByNumberNoCache = this.buildSender(
"get_block_by_number",
[(v: NumLike) => numToHex(numFrom(v))],
(b: JsonRpcBlock) => apply(JsonRpcTransformers.blockTo, b),
) as Client["getBlockByNumberNoCache"];
/**
* Get block by block hash
*
* @param blockHash - The block hash.
* @param verbosity - result format which allows 0 and 2. (Optional, the default is 2.)
* @param withCycles - whether the return cycles of block transactions. (Optional, default false.)
* @returns Block
*/
getBlockByHashNoCache = this.buildSender(
"get_block",
[hexFrom],
(b: JsonRpcBlock) => apply(JsonRpcTransformers.blockTo, b),
) as Client["getBlockByHashNoCache"];
/**
* Get header by block number
*
* @param blockNumber - The block number.
* @param verbosity - result format which allows 0 and 1. (Optional, the default is 1.)
* @returns BlockHeader
*/
getHeaderByNumberNoCache = this.buildSender(
"get_header_by_number",
[(v: NumLike) => numToHex(numFrom(v))],
(b: JsonRpcBlockHeader) => apply(JsonRpcTransformers.blockHeaderTo, b),
) as Client["getHeaderByNumberNoCache"];
/**
* Get header by block hash
*
* @param blockHash - The block hash.
* @param verbosity - result format which allows 0 and 1. (Optional, the default is 1.)
* @returns BlockHeader
*/
getHeaderByHashNoCache = this.buildSender(
"get_header",
[hexFrom],
(b: JsonRpcBlockHeader) => apply(JsonRpcTransformers.blockHeaderTo, b),
) as Client["getHeaderByHashNoCache"];
/**
* Estimate cycles of a transaction.
*
* @param transaction - The transaction to estimate.
* @returns Consumed cycles
*/
estimateCycles = this.buildSender(
"estimate_cycles",
[JsonRpcTransformers.transactionFrom],
({ cycles }: { cycles: NumLike }) => numFrom(cycles),
) as Client["estimateCycles"];
/**
* Test a transaction.
*
* @param transaction - The transaction to test.
* @param validator - "passthrough": Disable validation. "well_known_scripts_only": Only accept well known scripts in the transaction.
* @returns Consumed cycles
*/
sendTransactionDry = this.buildSender(
"test_tx_pool_accept",
[JsonRpcTransformers.transactionFrom],
({ cycles }: { cycles: NumLike }) => numFrom(cycles),
) as Client["sendTransactionDry"];
/**
* Send a transaction to node.
*
* @param transaction - The transaction to send.
* @param validator - "passthrough": Disable validation. "well_known_scripts_only": Only accept well known scripts in the transaction.
* @returns Transaction hash.
*/
sendTransactionNoCache = this.buildSender(
"send_transaction",
[JsonRpcTransformers.transactionFrom],
hexFrom,
) as (
transaction: TransactionLike,
validator?: OutputsValidator | undefined,
) => Promise<Hex>;
/**
* Get a transaction from node.
*
* @param txHash - The hash of the transaction.
* @returns The transaction with status.
*/
getTransactionNoCache = this.buildSender(
"get_transaction",
[hexFrom],
JsonRpcTransformers.transactionResponseTo,
) as (txHash: HexLike) => Promise<ClientTransactionResponse | undefined>;
/**
* Get a live cell from node.
*
* @param outPoint - The out point of the cell.
* @param withData - Include data in the response.
* @param includeTxPool - Include cells in the tx pool.
* @returns The cell
*/
getCellLiveNoCache(
outPoint: OutPointLike,
withData?: boolean | null,
includeTxPool?: boolean | null,
) {
return this.buildSender(
"get_live_cell",
[JsonRpcTransformers.outPointFrom],
({
cell,
}: {
cell?: {
output: JsonRpcCellOutput;
data?: { content: HexLike; hash: HexLike };
};
}) =>
apply(
({
output,
data,
}: {
output: JsonRpcCellOutput;
data?: { content: HexLike; hash: HexLike };
}) =>
Cell.from({
cellOutput: JsonRpcTransformers.cellOutputTo(output),
outputData: data?.content ?? "0x",
outPoint,
}),
cell,
),
)(outPoint, withData ?? true, includeTxPool) as Promise<Cell | undefined>;
}
/**
* find cells from node.
*
* @param key - The search key of cells.
* @param order - The order of cells.
* @param limit - The max return size of cells.
* @param after - Pagination parameter.
* @returns The found cells.
*/
findCellsPagedNoCache = this.buildSender(
"get_cells",
[
JsonRpcTransformers.indexerSearchKeyFrom,
(order?: "asc" | "desc") => order ?? "asc",
(limit?: NumLike) => numToHex(limit ?? 10),
],
JsonRpcTransformers.findCellsResponseTo,
) as (
key: ClientIndexerSearchKeyLike,
order?: "asc" | "desc",
limit?: NumLike,
after?: string,
) => Promise<ClientFindCellsResponse>;
/**
* find transactions from node.
*
* @param key - The search key of transactions.
* @param order - The order of transactions.
* @param limit - The max return size of transactions.
* @param after - Pagination parameter.
* @returns The found transactions.
*/
findTransactionsPaged = this.buildSender(
"get_transactions",
[
JsonRpcTransformers.indexerSearchKeyTransactionFrom,
(order?: "asc" | "desc") => order ?? "asc",
(limit?: NumLike) => numToHex(limit ?? 10),
],
JsonRpcTransformers.findTransactionsResponseTo,
) as Client["findTransactionsPaged"];
/**
* get cells capacity from node.
*
* @param key - The search key of cells.
* @returns The sum of cells capacity.
*/
getCellsCapacity = this.buildSender(
"get_cells_capacity",
[JsonRpcTransformers.indexerSearchKeyFrom],
({ capacity }: { capacity: NumLike }) => numFrom(capacity),
) as (key: ClientIndexerSearchKeyLike) => Promise<Num>;
/**
* Builds a sender function for a JSON-RPC method.
*
* @param rpcMethod - The JSON-RPC method.
* @param inTransformers - An array of input transformers.
* @param outTransformer - An output transformer function.
* @returns A function that sends a JSON-RPC request with the given method and transformed parameters.
*/
buildSender(
rpcMethod: Parameters<RequestorJsonRpc["request"]>[0],
inTransformers?: Parameters<RequestorJsonRpc["request"]>[2],
outTransformer?: Parameters<RequestorJsonRpc["request"]>[3],
): (...req: unknown[]) => Promise<unknown> {
return async (...req: unknown[]) => {
return this.requestor.request(
rpcMethod,
req,
inTransformers,
outTransformer,
);
};
}
}