From 0faa3a44eaabfe1fdc6b91f18dbe71730a550fdf Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Mon, 31 Mar 2025 23:05:55 +0800 Subject: [PATCH 01/15] feat: add fiber-sdk module --- packages/fiber/.npmignore | 21 ++ packages/fiber/.prettierignore | 13 + packages/fiber/.prettierrc | 5 + packages/fiber/CHANGELOG.md | 0 packages/fiber/README.md | 43 +++ packages/fiber/eslint.config.mjs | 47 +++ .../misc/basedirs/dist.commonjs/package.json | 3 + .../fiber/misc/basedirs/dist/package.json | 3 + packages/fiber/package.json | 54 +++ packages/fiber/src/client.ts | 76 +++++ packages/fiber/src/core/client.ts | 91 +++++ packages/fiber/src/core/types.ts | 79 +++++ packages/fiber/src/index.ts | 32 ++ packages/fiber/src/modules/channel.ts | 93 +++++ packages/fiber/src/modules/info.ts | 73 ++++ packages/fiber/src/modules/invoice.ts | 51 +++ packages/fiber/src/modules/payment.ts | 38 +++ packages/fiber/src/modules/peer.ts | 22 ++ packages/fiber/src/types.ts | 102 ++++++ packages/fiber/tsconfig.base.json | 22 ++ packages/fiber/tsconfig.commonjs.json | 8 + packages/fiber/tsconfig.json | 8 + packages/fiber/typedoc.json | 6 + pnpm-lock.yaml | 321 +++++++++++++++--- 24 files changed, 1164 insertions(+), 47 deletions(-) create mode 100644 packages/fiber/.npmignore create mode 100644 packages/fiber/.prettierignore create mode 100644 packages/fiber/.prettierrc create mode 100644 packages/fiber/CHANGELOG.md create mode 100644 packages/fiber/README.md create mode 100644 packages/fiber/eslint.config.mjs create mode 100644 packages/fiber/misc/basedirs/dist.commonjs/package.json create mode 100644 packages/fiber/misc/basedirs/dist/package.json create mode 100644 packages/fiber/package.json create mode 100644 packages/fiber/src/client.ts create mode 100644 packages/fiber/src/core/client.ts create mode 100644 packages/fiber/src/core/types.ts create mode 100644 packages/fiber/src/index.ts create mode 100644 packages/fiber/src/modules/channel.ts create mode 100644 packages/fiber/src/modules/info.ts create mode 100644 packages/fiber/src/modules/invoice.ts create mode 100644 packages/fiber/src/modules/payment.ts create mode 100644 packages/fiber/src/modules/peer.ts create mode 100644 packages/fiber/src/types.ts create mode 100644 packages/fiber/tsconfig.base.json create mode 100644 packages/fiber/tsconfig.commonjs.json create mode 100644 packages/fiber/tsconfig.json create mode 100644 packages/fiber/typedoc.json diff --git a/packages/fiber/.npmignore b/packages/fiber/.npmignore new file mode 100644 index 00000000..7a88408a --- /dev/null +++ b/packages/fiber/.npmignore @@ -0,0 +1,21 @@ +node_modules/ +misc/ + +*test.js +*test.ts +*test.d.ts +*test.d.ts.map +*spec.js +*spec.ts +*spec.d.ts +*spec.d.ts.map + +tsconfig.json +tsconfig.*.json +eslint.config.mjs +.prettierrc +.prettierignore + +tsconfig.tsbuildinfo +tsconfig.*.tsbuildinfo +.github/ diff --git a/packages/fiber/.prettierignore b/packages/fiber/.prettierignore new file mode 100644 index 00000000..e7ce6f62 --- /dev/null +++ b/packages/fiber/.prettierignore @@ -0,0 +1,13 @@ +node_modules/ + +dist/ +dist.commonjs/ + +.npmignore +.prettierrc +tsconfig.json +eslint.config.mjs +.prettierrc + +tsconfig.tsbuildinfo +.github/ diff --git a/packages/fiber/.prettierrc b/packages/fiber/.prettierrc new file mode 100644 index 00000000..6390af08 --- /dev/null +++ b/packages/fiber/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": false, + "trailingComma": "all", + "plugins": ["prettier-plugin-organize-imports"] +} diff --git a/packages/fiber/CHANGELOG.md b/packages/fiber/CHANGELOG.md new file mode 100644 index 00000000..e69de29b diff --git a/packages/fiber/README.md b/packages/fiber/README.md new file mode 100644 index 00000000..652fc5c5 --- /dev/null +++ b/packages/fiber/README.md @@ -0,0 +1,43 @@ +

+ + Logo + +

+ +

+ CCC's support for Fiber SDK +

+ +

+ NPM Version + GitHub commit activity + GitHub last commit + GitHub branch check runs + Playground + App + Docs +

+ +

+ CCC - CKBers' Codebase is a one-stop solution for your CKB JS/TS ecosystem development. +
+ Empower yourself with CCC to discover the unlimited potential of CKB. +
+ Interoperate with wallets from different chain ecosystems. +
+ Fully enabling CKB's Turing completeness and cryptographic freedom power. +

+ +For non-developers, you can also [try CCC's app now here](https://app.ckbccc.com/). It showcases how to use CCC for some basic scenarios in CKB. + +

+ Read more about CCC on our website or GitHub Repo. +

diff --git a/packages/fiber/eslint.config.mjs b/packages/fiber/eslint.config.mjs new file mode 100644 index 00000000..8dae5ed1 --- /dev/null +++ b/packages/fiber/eslint.config.mjs @@ -0,0 +1,47 @@ +// @ts-check + +import eslint from "@eslint/js"; +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; +import tseslint from "typescript-eslint"; + +import { dirname } from "path"; +import { fileURLToPath } from "url"; + +export default [ + ...tseslint.config({ + files: ["**/*.ts"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ], + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], + "@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }], + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/require-await": "off", + "no-empty": "off", + "prefer-const": [ + "error", + { ignoreReadBeforeAssign: true, destructuring: "all" }, + ], + }, + languageOptions: { + parserOptions: { + project: true, + tsconfigRootDir: dirname(fileURLToPath(import.meta.url)), + }, + }, + }), + eslintPluginPrettierRecommended, +]; diff --git a/packages/fiber/misc/basedirs/dist.commonjs/package.json b/packages/fiber/misc/basedirs/dist.commonjs/package.json new file mode 100644 index 00000000..5bbefffb --- /dev/null +++ b/packages/fiber/misc/basedirs/dist.commonjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/packages/fiber/misc/basedirs/dist/package.json b/packages/fiber/misc/basedirs/dist/package.json new file mode 100644 index 00000000..aead43de --- /dev/null +++ b/packages/fiber/misc/basedirs/dist/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file diff --git a/packages/fiber/package.json b/packages/fiber/package.json new file mode 100644 index 00000000..0de2b64a --- /dev/null +++ b/packages/fiber/package.json @@ -0,0 +1,54 @@ +{ + "name": "@ckb-ccc/fiber", + "version": "1.0.0", + "description": "CCC - CKBer's Codebase. Common Chains Connector's support for Fiber SDK", + "author": "author: Jack ", + "license": "MIT", + "private": false, + "homepage": "https://github.com/ckb-devrel/ccc", + "repository": { + "type": "git", + "url": "git://github.com/ckb-devrel/ccc.git" + }, + "sideEffects": false, + "main": "dist.commonjs/index.js", + "module": "dist/index.js", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist.commonjs/index.js", + "default": "./dist.commonjs/index.js" + } + }, + "scripts": { + "build": "rimraf ./dist && rimraf ./dist.commonjs && tsc && tsc --project tsconfig.commonjs.json && copyfiles -u 2 misc/basedirs/**/* .", + "lint": "eslint ./src", + "format": "prettier --write . && eslint --fix ./src" + }, + "devDependencies": { + "@eslint/js": "^9.1.1", + "@types/node": "^22.10.0", + "@types/chai": "^5.2.0", + "@types/mocha": "^10.0.10", + "chai": "^5.2.0", + "mocha": "^11.1.0", + "zod": "^3.22.4", + "copyfiles": "^2.4.1", + "dotenv": "^16.4.5", + "eslint": "^9.1.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "prettier": "^3.2.5", + "prettier-plugin-organize-imports": "^3.2.4", + "rimraf": "^5.0.5", + "typescript": "^5.4.5", + "typescript-eslint": "^7.7.0" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@ckb-ccc/core": "workspace:*", + "axios": "^1.7.7" + } +} diff --git a/packages/fiber/src/client.ts b/packages/fiber/src/client.ts new file mode 100644 index 00000000..96c26031 --- /dev/null +++ b/packages/fiber/src/client.ts @@ -0,0 +1,76 @@ +interface ClientConfig { + endpoint: string; + timeout?: number; +} + +interface AcceptChannelParams { + temporary_channel_id: string; + funding_amount: bigint; + max_tlc_value_in_flight: bigint; + max_tlc_number_in_flight: bigint; + tlc_min_value: bigint; + tlc_fee_proportional_millionths: bigint; + tlc_expiry_delta: bigint; +} + +interface AcceptChannelResponse { + channel_id: string; +} + +export class Client { + private endpoint: string; + private timeout: number; + private id: number; + + constructor(config: ClientConfig) { + this.endpoint = config.endpoint; + this.timeout = config.timeout || 5000; + this.id = 1; + } + + async call(method: string, params: any[]): Promise { + const response = await fetch(this.endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + method, + params, + id: this.id++, + }), + signal: AbortSignal.timeout(this.timeout), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + if (data.error) { + throw new Error( + `[RPC Error ${data.error.code}] ${data.error.message}\nDetails: ${data.error.data}`, + ); + } + + return data.result; + } + + async acceptChannel( + params: AcceptChannelParams, + ): Promise { + const response = await this.call("accept_channel", [ + { + temporary_channel_id: params.temporary_channel_id, + funding_amount: `0x${params.funding_amount.toString(16)}`, + max_tlc_value_in_flight: `0x${params.max_tlc_value_in_flight.toString(16)}`, + max_tlc_number_in_flight: `0x${params.max_tlc_number_in_flight.toString(16)}`, + tlc_min_value: `0x${params.tlc_min_value.toString(16)}`, + tlc_fee_proportional_millionths: `0x${params.tlc_fee_proportional_millionths.toString(16)}`, + tlc_expiry_delta: `0x${params.tlc_expiry_delta.toString(16)}`, + }, + ]); + return response; + } +} diff --git a/packages/fiber/src/core/client.ts b/packages/fiber/src/core/client.ts new file mode 100644 index 00000000..fa46cc4f --- /dev/null +++ b/packages/fiber/src/core/client.ts @@ -0,0 +1,91 @@ +import axios, { AxiosInstance } from "axios"; + +export interface FiberClientConfig { + baseURL: string; + timeout?: number; +} + +export class FiberClient { + private client: AxiosInstance; + + constructor(config: FiberClientConfig) { + this.client = axios.create({ + baseURL: config.baseURL, + timeout: config.timeout || 5000, + headers: { + "Content-Type": "application/json", + }, + }); + } + + private serializeBigInt(obj: any): any { + if (typeof obj === "bigint") { + const hex = obj.toString(16); + return "0x" + hex; + } + if (typeof obj === "number") { + const hex = obj.toString(16); + return "0x" + hex; + } + if (Array.isArray(obj)) { + return obj.map((item) => this.serializeBigInt(item)); + } + if (obj !== null && typeof obj === "object") { + const result: any = {}; + for (const key in obj) { + if (key === "peer_id") { + result[key] = obj[key]; + } else if (key === "channel_id") { + result[key] = obj[key]; + } else if ( + typeof obj[key] === "bigint" || + typeof obj[key] === "number" + ) { + result[key] = "0x" + obj[key].toString(16); + } else { + result[key] = this.serializeBigInt(obj[key]); + } + } + return result; + } + return obj; + } + + async call(method: string, params?: any): Promise { + const serializedParams = params ? this.serializeBigInt(params) : undefined; + const payload = { + jsonrpc: "2.0", + method, + params: serializedParams ? [serializedParams] : [], + id: 1, + }; + + console.log("发送 RPC 请求:", JSON.stringify(payload, null, 2)); + + const response = await this.client.post("", payload); + + if (response.data.error) { + throw new Error( + `[RPC Error ${response.data.error.code}] ${response.data.error.message}${ + response.data.error.data + ? "\nDetails: " + response.data.error.data + : "" + }`, + ); + } + + return response.data.result; + } +} + +export class RPCError extends Error { + constructor( + public error: { + code: number; + message: string; + data?: any; + }, + ) { + super(`[RPC Error ${error.code}] ${error.message}`); + } +} diff --git a/packages/fiber/src/core/types.ts b/packages/fiber/src/core/types.ts new file mode 100644 index 00000000..e94bd253 --- /dev/null +++ b/packages/fiber/src/core/types.ts @@ -0,0 +1,79 @@ +export type Hash256 = string; +export type Pubkey = string; + +export interface Channel { + channel_id: Hash256; + is_public: boolean; + channel_outpoint?: any; + peer_id: string; + funding_udt_type_script?: any; + state: string; + local_balance: bigint; + offered_tlc_balance: bigint; + remote_balance: bigint; + received_tlc_balance: bigint; + latest_commitment_transaction_hash?: Hash256; + created_at: bigint; + enabled: boolean; + tlc_expiry_delta: bigint; + tlc_fee_proportional_millionths: bigint; +} + +export interface ChannelInfo { + channel_outpoint: any; + node1: Pubkey; + node2: Pubkey; + created_timestamp: bigint; + last_updated_timestamp_of_node1?: bigint; + last_updated_timestamp_of_node2?: bigint; + fee_rate_of_node1?: bigint; + fee_rate_of_node2?: bigint; + capacity: bigint; + chain_hash: Hash256; + udt_type_script?: any; +} + +export interface NodeInfo { + node_name: string; + addresses: string[]; + node_id: Pubkey; + timestamp: bigint; + chain_hash: Hash256; + auto_accept_min_ckb_funding_amount: bigint; + udt_cfg_infos: any; +} + +export interface PaymentSessionStatus { + status: "Created" | "Inflight" | "Success" | "Failed"; + payment_hash: Hash256; + created_at: bigint; + last_updated_at: bigint; + failed_error?: string; + fee: bigint; + custom_records?: any; + router: any; +} + +export interface CkbInvoice { + currency: "Fibb" | "Fibt" | "Fibd"; + amount?: bigint; + signature?: any; + data: any; +} + +export interface CkbInvoiceStatus { + status: "Open" | "Cancelled" | "Expired" | "Received" | "Paid"; + invoice_address: string; + invoice: CkbInvoice; +} + +export interface RPCResponse { + jsonrpc: string; + id: string; + result?: T; + error?: { + code: number; + message: string; + data?: any; + }; +} diff --git a/packages/fiber/src/index.ts b/packages/fiber/src/index.ts new file mode 100644 index 00000000..4bc4aca1 --- /dev/null +++ b/packages/fiber/src/index.ts @@ -0,0 +1,32 @@ +import { FiberClient } from "./core/client"; +import { ChannelModule } from "./modules/channel"; +import { InfoModule } from "./modules/info"; +import { InvoiceModule } from "./modules/invoice"; +import { PaymentModule } from "./modules/payment"; +import { PeerModule } from "./modules/peer"; + +export interface FiberSDKConfig { + endpoint: string; + timeout?: number; +} + +export class FiberSDK { + public channel: ChannelModule; + public payment: PaymentModule; + public invoice: InvoiceModule; + public peer: PeerModule; + public info: InfoModule; + + constructor(config: FiberSDKConfig) { + const client = new FiberClient({ + baseURL: config.endpoint, + timeout: config.timeout, + }); + + this.channel = new ChannelModule(client); + this.payment = new PaymentModule(client); + this.invoice = new InvoiceModule(client); + this.peer = new PeerModule(client); + this.info = new InfoModule(client); + } +} diff --git a/packages/fiber/src/modules/channel.ts b/packages/fiber/src/modules/channel.ts new file mode 100644 index 00000000..efc2f1fb --- /dev/null +++ b/packages/fiber/src/modules/channel.ts @@ -0,0 +1,93 @@ +import { FiberClient } from "../core/client"; +import { Channel, Hash256 } from "../types"; + +export class ChannelModule { + constructor(private client: FiberClient) {} + + /** + * 打开通道 + */ + async openChannel(params: { + peer_id: string; + funding_amount: bigint; + public?: boolean; + funding_udt_type_script?: any; + shutdown_script?: any; + commitment_delay_epoch?: bigint; + commitment_fee_rate?: bigint; + funding_fee_rate?: bigint; + tlc_expiry_delta?: bigint; + tlc_min_value?: bigint; + tlc_fee_proportional_millionths?: bigint; + max_tlc_value_in_flight?: bigint; + max_tlc_number_in_flight?: bigint; + }): Promise { + return this.client.call("open_channel", params); + } + + /** + * 接受通道 + */ + async acceptChannel(params: { + temporary_channel_id: string; + funding_amount: bigint; + max_tlc_value_in_flight: bigint; + max_tlc_number_in_flight: bigint; + tlc_min_value: bigint; + tlc_fee_proportional_millionths: bigint; + tlc_expiry_delta: bigint; + }): Promise { + const serializedParams = { + temporary_channel_id: params.temporary_channel_id, + funding_amount: params.funding_amount, + max_tlc_value_in_flight: params.max_tlc_value_in_flight, + max_tlc_number_in_flight: params.max_tlc_number_in_flight, + tlc_min_value: params.tlc_min_value, + tlc_fee_proportional_millionths: params.tlc_fee_proportional_millionths, + tlc_expiry_delta: params.tlc_expiry_delta, + }; + return this.client.call("accept_channel", serializedParams); + } + + /** + * 放弃通道 + */ + async abandonChannel(channelId: Hash256): Promise { + return this.client.call("abandon_channel", { channel_id: channelId }); + } + + /** + * 列出所有通道 + */ + async listChannels(params?: { + peer_id?: string; + include_closed?: boolean; + }): Promise { + return this.client.call("list_channels", params || {}); + } + + /** + * 关闭通道 + */ + async shutdownChannel(params: { + channel_id: Hash256; + close_script: any; + force?: boolean; + fee_rate: bigint; + }): Promise { + return this.client.call("shutdown_channel", params); + } + + /** + * 更新通道 + */ + async updateChannel(params: { + channel_id: Hash256; + enabled?: boolean; + tlc_expiry_delta?: bigint; + tlc_minimum_value?: bigint; + tlc_fee_proportional_millionths?: bigint; + }): Promise { + return this.client.call("update_channel", params); + } +} diff --git a/packages/fiber/src/modules/info.ts b/packages/fiber/src/modules/info.ts new file mode 100644 index 00000000..23a1aa21 --- /dev/null +++ b/packages/fiber/src/modules/info.ts @@ -0,0 +1,73 @@ +import { FiberClient } from "../core/client"; +import { NodeInfo } from "../types"; + +export interface NodeStatus { + is_online: boolean; + last_sync_time: bigint; + connected_peers: number; + total_channels: number; +} + +export interface NodeVersion { + version: string; + commit_hash: string; + build_time: string; +} + +export interface NetworkInfo { + network_type: "mainnet" | "testnet" | "devnet"; + chain_hash: string; + block_height: bigint; + block_hash: string; +} + +export class InfoModule { + constructor(private client: FiberClient) {} + + /** + * 获取节点基本信息 + * + * @returns {Promise} 包含节点详细信息的对象,包括: + * - version: 节点软件版本 + * - commit_hash: 节点软件的提交哈希 + * - node_id: 节点的身份公钥 + * - node_name: 节点名称(可选) + * - addresses: 节点的多地址列表 + * - chain_hash: 节点连接的区块链哈希 + * - open_channel_auto_accept_min_ckb_funding_amount: 自动接受开放通道请求的最小 CKB 资金金额 + * - auto_accept_channel_ckb_funding_amount: 自动接受通道请求的 CKB 资金金额 + * - default_funding_lock_script: 节点的默认资金锁定脚本 + * - tlc_expiry_delta: 时间锁定合约(TLC)的过期增量 + * - tlc_min_value: 可以发送的 TLC 最小值 + * - tlc_max_value: 可以发送的 TLC 最大值(0 表示无限制) + * - tlc_fee_proportional_millionths: TLC 转发支付的费用比例(以百万分之一为单位) + * - channel_count: 节点关联的通道数量 + * - pending_channel_count: 节点关联的待处理通道数量 + * - peers_count: 连接到节点的对等节点数量 + * - udt_cfg_infos: 节点关联的用户自定义代币(UDT)配置信息 + */ + async nodeInfo(): Promise { + return this.client.call("node_info"); + } + + /** + * 获取节点状态信息 + */ + async nodeStatus(): Promise { + return this.client.call("node_status"); + } + + /** + * 获取节点版本信息 + */ + async nodeVersion(): Promise { + return this.client.call("node_version"); + } + + /** + * 获取网络信息 + */ + async networkInfo(): Promise { + return this.client.call("network_info"); + } +} diff --git a/packages/fiber/src/modules/invoice.ts b/packages/fiber/src/modules/invoice.ts new file mode 100644 index 00000000..40023e66 --- /dev/null +++ b/packages/fiber/src/modules/invoice.ts @@ -0,0 +1,51 @@ +import { FiberClient } from "../core/client"; +import { CkbInvoice, CkbInvoiceStatus, Hash256 } from "../types"; + +export class InvoiceModule { + constructor(private client: FiberClient) {} + + /** + * 创建新发票 + */ + async newInvoice(params: { + amount: bigint; + description?: string; + currency: "Fibb" | "Fibt" | "Fibd"; + payment_preimage: Hash256; + expiry?: bigint; + fallback_address?: string; + final_expiry_delta?: bigint; + udt_type_script?: any; + hash_algorithm?: "CkbHash" | "Sha256"; + }): Promise<{ + invoice_address: string; + invoice: CkbInvoice; + }> { + return this.client.call("invoice.new_invoice", params); + } + + /** + * 解析发票 + */ + async parseInvoice(invoice: string): Promise { + return this.client.call("invoice.parse_invoice", { invoice }); + } + + /** + * 获取发票 + */ + async getInvoice(paymentHash: Hash256): Promise { + return this.client.call("invoice.get_invoice", { + payment_hash: paymentHash, + }); + } + + /** + * 取消发票 + */ + async cancelInvoice(paymentHash: Hash256): Promise { + return this.client.call("invoice.cancel_invoice", { + payment_hash: paymentHash, + }); + } +} diff --git a/packages/fiber/src/modules/payment.ts b/packages/fiber/src/modules/payment.ts new file mode 100644 index 00000000..9266fdbb --- /dev/null +++ b/packages/fiber/src/modules/payment.ts @@ -0,0 +1,38 @@ +import { FiberClient } from "../core/client"; +import { Hash256, PaymentSessionStatus, Pubkey } from "../types"; + +export class PaymentModule { + constructor(private client: FiberClient) {} + + /** + * 发送支付 + */ + async sendPayment(params: { + target_pubkey?: Pubkey; + amount?: bigint; + payment_hash?: Hash256; + final_tlc_expiry_delta?: bigint; + tlc_expiry_limit?: bigint; + invoice?: string; + timeout?: bigint; + max_fee_amount?: bigint; + max_parts?: bigint; + keysend?: boolean; + udt_type_script?: any; + allow_self_payment?: boolean; + custom_records?: Record; + hop_hints?: any[]; + dry_run?: boolean; + }): Promise { + return this.client.call("payment.send_payment", params); + } + + /** + * 获取支付状态 + */ + async getPayment(paymentHash: Hash256): Promise { + return this.client.call("payment.get_payment", { + payment_hash: paymentHash, + }); + } +} diff --git a/packages/fiber/src/modules/peer.ts b/packages/fiber/src/modules/peer.ts new file mode 100644 index 00000000..1e6a2216 --- /dev/null +++ b/packages/fiber/src/modules/peer.ts @@ -0,0 +1,22 @@ +import { FiberClient } from "../core/client"; + +export class PeerModule { + constructor(private client: FiberClient) {} + + /** + * 连接到对等节点 + */ + async connectPeer(params: { + address: string; + save?: boolean; + }): Promise { + return this.client.call("connect_peer", params); + } + + /** + * 断开对等节点连接 + */ + async disconnectPeer(peerId: string): Promise { + return this.client.call("disconnect_peer", [peerId]); + } +} diff --git a/packages/fiber/src/types.ts b/packages/fiber/src/types.ts new file mode 100644 index 00000000..452f827e --- /dev/null +++ b/packages/fiber/src/types.ts @@ -0,0 +1,102 @@ +export type Hash256 = string; +export type Pubkey = string; + +export interface Channel { + channel_id: Hash256; + is_public: boolean; + channel_outpoint?: any; + peer_id: string; + funding_udt_type_script?: any; + state: string; + local_balance: bigint; + offered_tlc_balance: bigint; + remote_balance: bigint; + received_tlc_balance: bigint; + latest_commitment_transaction_hash?: Hash256; + created_at: bigint; + enabled: boolean; + tlc_expiry_delta: bigint; + tlc_fee_proportional_millionths: bigint; +} + +export interface ChannelInfo { + channel_outpoint: any; + node1: Pubkey; + node2: Pubkey; + created_timestamp: bigint; + last_updated_timestamp_of_node1?: bigint; + last_updated_timestamp_of_node2?: bigint; + fee_rate_of_node1?: bigint; + fee_rate_of_node2?: bigint; + capacity: bigint; + chain_hash: Hash256; + udt_type_script?: any; +} + +export interface NodeInfo { + version: string; + commit_hash: string; + node_id: Pubkey; + node_name: string | null; + addresses: string[]; + chain_hash: Hash256; + open_channel_auto_accept_min_ckb_funding_amount: bigint; + auto_accept_channel_ckb_funding_amount: bigint; + default_funding_lock_script: { + code_hash: string; + hash_type: string; + args: string; + }; + tlc_expiry_delta: bigint; + tlc_min_value: bigint; + tlc_max_value: bigint; + tlc_fee_proportional_millionths: bigint; + channel_count: number; + pending_channel_count: number; + peers_count: number; + udt_cfg_infos: any; +} + +export interface NodeStatus { + is_online: boolean; + last_sync_time: bigint; + connected_peers: number; + total_channels: number; +} + +export interface NodeVersion { + version: string; + commit_hash: string; + build_time: string; +} + +export interface NetworkInfo { + network_type: "mainnet" | "testnet" | "devnet"; + chain_hash: string; + block_height: bigint; + block_hash: string; +} + +export interface PaymentSessionStatus { + status: "Created" | "Inflight" | "Success" | "Failed"; + payment_hash: Hash256; + created_at: bigint; + last_updated_at: bigint; + failed_error?: string; + fee: bigint; + custom_records?: any; + router: any; +} + +export interface CkbInvoice { + currency: "Fibb" | "Fibt" | "Fibd"; + amount?: bigint; + signature?: any; + data: any; +} + +export interface CkbInvoiceStatus { + status: "Open" | "Cancelled" | "Expired" | "Received" | "Paid"; + invoice_address: string; + invoice: CkbInvoice; +} diff --git a/packages/fiber/tsconfig.base.json b/packages/fiber/tsconfig.base.json new file mode 100644 index 00000000..7e5ac952 --- /dev/null +++ b/packages/fiber/tsconfig.base.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es2020", + "incremental": true, + "allowJs": true, + "importHelpers": false, + "declaration": true, + "declarationMap": true, + "experimentalDecorators": true, + "useDefineForClassFields": false, + "esModuleInterop": true, + "strict": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "strictNullChecks": true, + "alwaysStrict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/packages/fiber/tsconfig.commonjs.json b/packages/fiber/tsconfig.commonjs.json new file mode 100644 index 00000000..76a25e98 --- /dev/null +++ b/packages/fiber/tsconfig.commonjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "./dist.commonjs" + } +} diff --git a/packages/fiber/tsconfig.json b/packages/fiber/tsconfig.json new file mode 100644 index 00000000..df22faec --- /dev/null +++ b/packages/fiber/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "outDir": "./dist", + } +} diff --git a/packages/fiber/typedoc.json b/packages/fiber/typedoc.json new file mode 100644 index 00000000..63f8fa3f --- /dev/null +++ b/packages/fiber/typedoc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./src/index.ts", "./src/advanced.ts"], + "extends": ["../../typedoc.base.json"], + "name": "@ckb-ccc fiber" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37d82b22..65a20be5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -223,7 +223,7 @@ importers: dependencies: '@joyid/ckb': specifier: ^1.1.1 - version: 1.1.1(typescript@5.7.3) + version: 1.1.1(typescript@5.7.3)(zod@3.24.2) '@noble/ciphers': specifier: ^0.5.3 version: 0.5.3 @@ -584,6 +584,67 @@ importers: specifier: ^5.1.3 version: 5.7.3 + packages/fiber: + dependencies: + '@ckb-ccc/core': + specifier: workspace:* + version: link:../core + axios: + specifier: ^1.7.7 + version: 1.7.9 + devDependencies: + '@eslint/js': + specifier: ^9.1.1 + version: 9.20.0 + '@types/chai': + specifier: ^5.2.0 + version: 5.2.1 + '@types/mocha': + specifier: ^10.0.10 + version: 10.0.10 + '@types/node': + specifier: ^22.10.0 + version: 22.13.1 + chai: + specifier: ^5.2.0 + version: 5.2.0 + copyfiles: + specifier: ^2.4.1 + version: 2.4.1 + dotenv: + specifier: ^16.4.5 + version: 16.4.7 + eslint: + specifier: ^9.1.0 + version: 9.20.0(jiti@1.21.7) + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@9.20.0(jiti@1.21.7)) + eslint-plugin-prettier: + specifier: ^5.1.3 + version: 5.2.3(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.20.0(jiti@1.21.7)))(eslint@9.20.0(jiti@1.21.7))(prettier@3.5.1) + mocha: + specifier: ^11.1.0 + version: 11.1.0 + prettier: + specifier: ^3.2.5 + version: 3.5.1 + prettier-plugin-organize-imports: + specifier: ^3.2.4 + version: 3.2.4(prettier@3.5.1)(typescript@5.7.3) + rimraf: + specifier: ^5.0.5 + version: 5.0.10 + typescript: + specifier: ^5.4.5 + version: 5.7.3 + typescript-eslint: + specifier: ^7.7.0 + version: 7.18.0(eslint@9.20.0(jiti@1.21.7))(typescript@5.7.3) + zod: + specifier: ^3.22.4 + version: 3.24.2 + packages/joy-id: dependencies: '@ckb-ccc/core': @@ -591,10 +652,10 @@ importers: version: link:../core '@joyid/ckb': specifier: ^1.1.1 - version: 1.1.1(typescript@5.7.3) + version: 1.1.1(typescript@5.7.3)(zod@3.24.2) '@joyid/common': specifier: ^0.2.0 - version: 0.2.0(typescript@5.7.3) + version: 0.2.0(typescript@5.7.3)(zod@3.24.2) devDependencies: '@eslint/js': specifier: ^9.1.1 @@ -649,7 +710,7 @@ importers: version: 0.24.0-next.2 '@joyid/ckb': specifier: ^1.1.1 - version: 1.1.1(typescript@5.7.3) + version: 1.1.1(typescript@5.7.3)(zod@3.24.2) devDependencies: '@eslint/js': specifier: ^9.1.1 @@ -2184,12 +2245,18 @@ packages: '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + '@types/chai@5.2.1': + resolution: {integrity: sha512-iu1JLYmGmITRzUgNiLMZD3WCoFzpYtueuyAgHTXqgwSRAMIlFTnZqG6/xenkpUGRJEzSfklUTI4GNSzks/dc0w==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/deep-freeze-strict@1.1.2': resolution: {integrity: sha512-VvMETBojHvhX4f+ocYTySQlXMZfxKV3Jyb7iCWlWaC+exbedkv6Iv2bZZqI736qXjVguH6IH7bzwMBMfTT+zuQ==} @@ -2256,6 +2323,9 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/mocha@10.0.10': + resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -2661,6 +2731,10 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -2812,6 +2886,9 @@ packages: brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + browserify-aes@1.2.0: resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} @@ -2903,6 +2980,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2924,6 +3005,10 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -3164,6 +3249,10 @@ packages: supports-color: optional: true + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + dedent@1.5.3: resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: @@ -3172,6 +3261,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-freeze-strict@1.1.1: resolution: {integrity: sha512-QemROZMM2IvhAcCFvahdX2Vbm4S/txeq5rFYU9fh4mQP79WTMW5c/HkQ2ICl1zuzcDZdPZ6zarDxQeQMsVYoNA==} @@ -3242,6 +3335,10 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3694,6 +3791,10 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} @@ -3901,6 +4002,10 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + hex-rgb@4.3.0: resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} engines: {node: '>=6'} @@ -4068,6 +4173,10 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -4598,6 +4707,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -4748,6 +4860,11 @@ packages: engines: {node: '>=10'} hasBin: true + mocha@11.1.0: + resolution: {integrity: sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + monaco-editor@0.51.0: resolution: {integrity: sha512-xaGwVV1fq343cM7aOYB6lVE4Ugf0UyimdD/x5PWcWBMKENwectaEu77FAN7c5sFiyumqeJdX1RPTh1ocioyDjw==} @@ -5020,6 +5137,10 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -6105,6 +6226,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -6182,6 +6306,10 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} @@ -6201,6 +6329,9 @@ packages: yoga-wasm-web@0.3.3: resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} + zod@3.24.2: + resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -6269,7 +6400,7 @@ snapshots: '@babel/types': 7.26.8 '@types/gensync': 1.0.4 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -6427,7 +6558,7 @@ snapshots: '@babel/parser': 7.26.8 '@babel/template': 7.26.8 '@babel/types': 7.26.8 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -6705,7 +6836,7 @@ snapshots: '@eslint/config-array@0.19.2': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -6721,7 +6852,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -6735,7 +6866,7 @@ snapshots: '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 @@ -6778,7 +6909,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -7184,9 +7315,9 @@ snapshots: '@types/yargs': 17.0.33 chalk: 4.1.2 - '@joyid/ckb@1.1.1(typescript@5.7.3)': + '@joyid/ckb@1.1.1(typescript@5.7.3)(zod@3.24.2)': dependencies: - '@joyid/common': 0.2.0(typescript@5.7.3) + '@joyid/common': 0.2.0(typescript@5.7.3)(zod@3.24.2) '@nervosnetwork/ckb-sdk-utils': 0.109.5 cross-fetch: 4.0.0 uncrypto: 0.1.3 @@ -7195,9 +7326,9 @@ snapshots: - typescript - zod - '@joyid/common@0.2.0(typescript@5.7.3)': + '@joyid/common@0.2.0(typescript@5.7.3)(zod@3.24.2)': dependencies: - abitype: 0.8.7(typescript@5.7.3) + abitype: 0.8.7(typescript@5.7.3)(zod@3.24.2) type-fest: 4.6.0 transitivePeerDependencies: - typescript @@ -7663,14 +7794,20 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.17.17 + '@types/node': 22.13.1 + + '@types/chai@5.2.1': + dependencies: + '@types/deep-eql': 4.0.2 '@types/connect@3.4.38': dependencies: - '@types/node': 20.17.17 + '@types/node': 22.13.1 '@types/cookiejar@2.1.5': {} + '@types/deep-eql@4.0.2': {} + '@types/deep-freeze-strict@1.1.2': {} '@types/eslint-scope@3.7.7': @@ -7687,7 +7824,7 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 20.17.17 + '@types/node': 22.13.1 '@types/qs': 6.9.18 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -7746,6 +7883,8 @@ snapshots: '@types/mime@1.3.5': {} + '@types/mocha@10.0.10': {} + '@types/node@12.20.55': {} '@types/node@20.17.17': @@ -7780,12 +7919,12 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.17.17 + '@types/node': 22.13.1 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.17.17 + '@types/node': 22.13.1 '@types/send': 0.17.4 '@types/stack-utils@2.0.3': {} @@ -7794,7 +7933,7 @@ snapshots: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 20.17.17 + '@types/node': 22.13.1 form-data: 4.0.1 '@types/supertest@6.0.2': @@ -7864,7 +8003,7 @@ snapshots: '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/utils': 7.2.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 @@ -7882,7 +8021,7 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.1 optionalDependencies: typescript: 5.7.3 @@ -7895,7 +8034,7 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 9.20.0(jiti@1.21.7) optionalDependencies: typescript: 5.7.3 @@ -7908,7 +8047,7 @@ snapshots: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.7.3) '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.1 optionalDependencies: typescript: 5.7.3 @@ -7929,7 +8068,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.7.3) - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.1 ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: @@ -7941,7 +8080,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) '@typescript-eslint/utils': 7.18.0(eslint@9.20.0(jiti@1.21.7))(typescript@5.7.3) - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 9.20.0(jiti@1.21.7) ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: @@ -7953,7 +8092,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.7.3) '@typescript-eslint/utils': 7.2.0(eslint@8.57.1)(typescript@5.7.3) - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.1 ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: @@ -7969,7 +8108,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 @@ -7984,7 +8123,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -8129,9 +8268,11 @@ snapshots: '@xtuc/long@4.2.2': {} - abitype@0.8.7(typescript@5.7.3): + abitype@0.8.7(typescript@5.7.3)(zod@3.24.2): dependencies: typescript: 5.7.3 + optionalDependencies: + zod: 3.24.2 abort-controller@3.0.0: dependencies: @@ -8303,6 +8444,8 @@ snapshots: asap@2.0.6: {} + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} async-function@1.0.0: {} @@ -8512,6 +8655,8 @@ snapshots: brorand@1.1.0: {} + browser-stdout@1.3.1: {} + browserify-aes@1.2.0: dependencies: buffer-xor: 1.0.3 @@ -8612,6 +8757,14 @@ snapshots: ccount@2.0.1: {} + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -8627,6 +8780,8 @@ snapshots: chardet@0.7.0: {} + check-error@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -8892,12 +9047,18 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.0: + debug@4.4.0(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} dedent@1.5.3: {} + deep-eql@5.0.2: {} + deep-freeze-strict@1.1.1: {} deep-is@0.1.4: {} @@ -8954,6 +9115,8 @@ snapshots: diff@4.0.2: {} + diff@5.2.0: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -9158,7 +9321,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.4(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -9178,7 +9341,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.4(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -9212,7 +9375,7 @@ snapshots: eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) enhanced-resolve: 5.18.1 eslint: 8.57.1 fast-glob: 3.3.3 @@ -9221,14 +9384,14 @@ snapshots: is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) enhanced-resolve: 5.18.1 eslint: 8.57.1 fast-glob: 3.3.3 @@ -9237,7 +9400,18 @@ snapshots: is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.7.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -9252,7 +9426,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -9263,7 +9437,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -9281,7 +9455,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -9369,6 +9543,16 @@ snapshots: '@types/eslint': 9.6.1 eslint-config-prettier: 9.1.0(eslint@9.20.0(jiti@1.21.7)) + eslint-plugin-prettier@5.2.3(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.20.0(jiti@1.21.7)))(eslint@9.20.0(jiti@1.21.7))(prettier@3.5.1): + dependencies: + eslint: 9.20.0(jiti@1.21.7) + prettier: 3.5.1 + prettier-linter-helpers: 1.0.0 + synckit: 0.9.2 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 9.1.0(eslint@9.20.0(jiti@1.21.7)) + eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -9427,7 +9611,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -9474,7 +9658,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 eslint-visitor-keys: 4.2.0 @@ -9712,6 +9896,8 @@ snapshots: flatted: 3.3.2 keyv: 4.5.4 + flat@5.0.2: {} + flatted@3.3.2: {} follow-redirects@1.15.9: {} @@ -9963,6 +10149,8 @@ snapshots: dependencies: '@types/hast': 3.0.4 + he@1.2.0: {} + hex-rgb@4.3.0: {} hexoid@2.0.0: {} @@ -10148,6 +10336,8 @@ snapshots: is-path-inside@3.0.3: {} + is-plain-obj@2.1.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.3 @@ -10239,7 +10429,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -10248,7 +10438,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -10953,7 +11143,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.17.17 + '@types/node': 22.13.1 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -11154,6 +11344,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.1.3: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -11293,6 +11485,29 @@ snapshots: mkdirp@1.0.4: {} + mocha@11.1.0: + dependencies: + ansi-colors: 4.1.3 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.4.0(supports-color@8.1.1) + diff: 5.2.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 10.4.5 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.1.6 + ms: 2.1.3 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.5.1 + yargs: 17.7.2 + yargs-parser: 21.1.1 + yargs-unparser: 2.0.0 + monaco-editor@0.51.0: {} mri@1.2.0: {} @@ -11576,6 +11791,8 @@ snapshots: path-type@4.0.0: {} + pathval@2.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -11659,7 +11876,6 @@ snapshots: dependencies: prettier: 3.5.1 typescript: 5.7.3 - optional: true prettier-plugin-tailwindcss@0.5.14(prettier-plugin-organize-imports@3.2.4(prettier@3.4.2)(typescript@5.7.3))(prettier@3.4.2): dependencies: @@ -12261,7 +12477,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) fast-safe-stringify: 2.1.1 form-data: 4.0.1 formidable: 3.5.2 @@ -12814,6 +13030,8 @@ snapshots: word-wrap@1.2.5: {} + workerpool@6.5.1: {} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -12869,6 +13087,13 @@ snapshots: yargs-parser@21.1.1: {} + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + yargs@16.2.0: dependencies: cliui: 7.0.4 @@ -12895,4 +13120,6 @@ snapshots: yoga-wasm-web@0.3.3: {} + zod@3.24.2: {} + zwitch@2.0.4: {} From 06a61b6ef9b85c35058ed6d3e6fef336dc578655 Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Wed, 2 Apr 2025 13:46:51 +0800 Subject: [PATCH 02/15] chore: add framework of fiber json rpc client --- packages/fiber/src/rpc.ts | 86 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 packages/fiber/src/rpc.ts diff --git a/packages/fiber/src/rpc.ts b/packages/fiber/src/rpc.ts new file mode 100644 index 00000000..5bdea9d8 --- /dev/null +++ b/packages/fiber/src/rpc.ts @@ -0,0 +1,86 @@ +import { RequestorJsonRpc, RequestorJsonRpcConfig } from "@ckb-ccc/core/barrel"; + +export type JsonRpcConfig = RequestorJsonRpcConfig & { + requestor?: RequestorJsonRpc; +}; + +export interface ErrorRpcBaseLike { + message?: string; + code?: number; + data: string; +} + +export class ErrorRpcBase extends Error { + public readonly code?: number; + public readonly data: string; + + constructor(origin: ErrorRpcBaseLike) { + super(`Client request error ${origin.message}`); + this.code = origin.code; + this.data = origin.data; + } +} + +const ERROR_PARSERS: [ + string, + (error: ErrorRpcBaseLike, match: RegExpMatchArray) => ErrorRpcBase, +][] = [ + // TODO: add error parsers +]; + +/** + * An abstract class implementing JSON-RPC client functionality for a specific URL and timeout. + * Provides methods for interacting with the Fiber JSON-RPC server. + */ +export abstract class FiberJsonRpc { + public readonly requestor: RequestorJsonRpc; + + /** + * Creates an instance of FiberJsonRpc. + * + * @param url_ - The URL of the JSON-RPC server. + * @param timeout - The timeout for requests in milliseconds + */ + + constructor(url_: string, config?: JsonRpcConfig) { + 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 ErrorRpcBaseLike; + + for (const [regexp, builder] of ERROR_PARSERS) { + const match = err.data.match(regexp); + if (match) { + throw builder(err, match); + } + } + + throw new ErrorRpcBase(err); + }); + } + + // TODO: add methods + + buildSender( + rpcMethod: Parameters[0], + inTransformers?: Parameters[2], + outTransformer?: Parameters[3], + ): (...req: unknown[]) => Promise { + return async (...req: unknown[]) => { + return this.requestor.request( + rpcMethod, + req, + inTransformers, + outTransformer, + ); + }; + } +} From 355632e60a8790e42e7b9d554fce5c2b71346868 Mon Sep 17 00:00:00 2001 From: Jack <6464072@qq.com> Date: Wed, 2 Apr 2025 21:35:51 +0800 Subject: [PATCH 03/15] feat:add fiber method and test --- packages/fiber/package.json | 1 + packages/fiber/src/client.ts | 142 ++++++++++++++------- packages/fiber/src/core/client.ts | 91 -------------- packages/fiber/src/index.ts | 24 +++- packages/fiber/src/modules/cch.ts | 69 +++++++++++ packages/fiber/src/modules/channel.ts | 38 ++---- packages/fiber/src/modules/dev.ts | 58 +++++++++ packages/fiber/src/modules/graph.ts | 20 +++ packages/fiber/src/modules/info.ts | 53 ++------ packages/fiber/src/modules/invoice.ts | 36 +++--- packages/fiber/src/modules/payment.ts | 50 ++++---- packages/fiber/src/modules/peer.ts | 12 +- packages/fiber/src/types.ts | 135 ++++++++++++-------- packages/fiber/test.mjs | 172 ++++++++++++++++++++++++++ 14 files changed, 592 insertions(+), 309 deletions(-) delete mode 100644 packages/fiber/src/core/client.ts create mode 100644 packages/fiber/src/modules/cch.ts create mode 100644 packages/fiber/src/modules/dev.ts create mode 100644 packages/fiber/src/modules/graph.ts create mode 100644 packages/fiber/test.mjs diff --git a/packages/fiber/package.json b/packages/fiber/package.json index 0de2b64a..8830a1ff 100644 --- a/packages/fiber/package.json +++ b/packages/fiber/package.json @@ -1,6 +1,7 @@ { "name": "@ckb-ccc/fiber", "version": "1.0.0", + "type": "commonjs", "description": "CCC - CKBer's Codebase. Common Chains Connector's support for Fiber SDK", "author": "author: Jack ", "license": "MIT", diff --git a/packages/fiber/src/client.ts b/packages/fiber/src/client.ts index 96c26031..40d99a3b 100644 --- a/packages/fiber/src/client.ts +++ b/packages/fiber/src/client.ts @@ -1,6 +1,7 @@ -interface ClientConfig { +import { RequestorJsonRpc, RequestorJsonRpcConfig } from "@ckb-ccc/core"; + +interface ClientConfig extends RequestorJsonRpcConfig { endpoint: string; - timeout?: number; } interface AcceptChannelParams { @@ -17,60 +18,113 @@ interface AcceptChannelResponse { channel_id: string; } -export class Client { - private endpoint: string; - private timeout: number; - private id: number; - - constructor(config: ClientConfig) { - this.endpoint = config.endpoint; - this.timeout = config.timeout || 5000; - this.id = 1; +export class RPCError extends Error { + constructor( + public error: { + code: number; + message: string; + data?: unknown; + }, + ) { + super(`[RPC Error ${error.code}] ${error.message}`); + this.name = "RPCError"; } +} - async call(method: string, params: any[]): Promise { - const response = await fetch(this.endpoint, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - method, - params, - id: this.id++, - }), - signal: AbortSignal.timeout(this.timeout), +export class FiberClient { + private requestor: RequestorJsonRpc; + + constructor(config: ClientConfig) { + this.requestor = new RequestorJsonRpc(config.endpoint, { + timeout: config.timeout, + maxConcurrent: config.maxConcurrent, + fallbacks: config.fallbacks, + transport: config.transport, }); + } - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + private serializeBigInt(obj: unknown): unknown { + if (typeof obj === "bigint") { + const hex = obj.toString(16); + return "0x" + hex; } - - const data = await response.json(); - if (data.error) { - throw new Error( - `[RPC Error ${data.error.code}] ${data.error.message}\nDetails: ${data.error.data}`, - ); + if (typeof obj === "number") { + const hex = obj.toString(16); + return "0x" + hex; } + if (Array.isArray(obj)) { + return obj.map((item) => this.serializeBigInt(item)); + } + if (obj !== null && typeof obj === "object") { + if (Object.keys(obj).length === 0) { + return obj; + } + const result: Record = {}; + const typedObj = obj as Record; + for (const key in typedObj) { + if (key === "peer_id") { + result[key] = typedObj[key]; + } else if (key === "channel_id") { + result[key] = typedObj[key]; + } else if ( + typeof typedObj[key] === "bigint" || + typeof typedObj[key] === "number" + ) { + result[key] = "0x" + typedObj[key].toString(16); + } else { + result[key] = this.serializeBigInt(typedObj[key]); + } + } + return result; + } + return obj; + } - return data.result; + async call(method: string, params: unknown[]): Promise { + const serializedParams = params.map((param) => { + if (param === null || param === undefined) { + return {}; + } + return this.serializeBigInt(param); + }); + + try { + const result = await this.requestor.request(method, serializedParams); + if (!result) { + throw new RPCError({ + code: -1, + message: "Unknown RPC error", + data: undefined, + }); + } + return result as T; + } catch (error) { + if (error instanceof Error) { + throw new RPCError({ + code: -1, + message: error.message, + data: undefined, + }); + } + throw error; + } } async acceptChannel( params: AcceptChannelParams, ): Promise { - const response = await this.call("accept_channel", [ - { - temporary_channel_id: params.temporary_channel_id, - funding_amount: `0x${params.funding_amount.toString(16)}`, - max_tlc_value_in_flight: `0x${params.max_tlc_value_in_flight.toString(16)}`, - max_tlc_number_in_flight: `0x${params.max_tlc_number_in_flight.toString(16)}`, - tlc_min_value: `0x${params.tlc_min_value.toString(16)}`, - tlc_fee_proportional_millionths: `0x${params.tlc_fee_proportional_millionths.toString(16)}`, - tlc_expiry_delta: `0x${params.tlc_expiry_delta.toString(16)}`, - }, + const transformedParams = { + temporary_channel_id: params.temporary_channel_id, + funding_amount: params.funding_amount, + max_tlc_value_in_flight: params.max_tlc_value_in_flight, + max_tlc_number_in_flight: params.max_tlc_number_in_flight, + tlc_min_value: params.tlc_min_value, + tlc_fee_proportional_millionths: params.tlc_fee_proportional_millionths, + tlc_expiry_delta: params.tlc_expiry_delta, + }; + + return this.call("accept_channel", [ + transformedParams, ]); - return response; } } diff --git a/packages/fiber/src/core/client.ts b/packages/fiber/src/core/client.ts deleted file mode 100644 index fa46cc4f..00000000 --- a/packages/fiber/src/core/client.ts +++ /dev/null @@ -1,91 +0,0 @@ -import axios, { AxiosInstance } from "axios"; - -export interface FiberClientConfig { - baseURL: string; - timeout?: number; -} - -export class FiberClient { - private client: AxiosInstance; - - constructor(config: FiberClientConfig) { - this.client = axios.create({ - baseURL: config.baseURL, - timeout: config.timeout || 5000, - headers: { - "Content-Type": "application/json", - }, - }); - } - - private serializeBigInt(obj: any): any { - if (typeof obj === "bigint") { - const hex = obj.toString(16); - return "0x" + hex; - } - if (typeof obj === "number") { - const hex = obj.toString(16); - return "0x" + hex; - } - if (Array.isArray(obj)) { - return obj.map((item) => this.serializeBigInt(item)); - } - if (obj !== null && typeof obj === "object") { - const result: any = {}; - for (const key in obj) { - if (key === "peer_id") { - result[key] = obj[key]; - } else if (key === "channel_id") { - result[key] = obj[key]; - } else if ( - typeof obj[key] === "bigint" || - typeof obj[key] === "number" - ) { - result[key] = "0x" + obj[key].toString(16); - } else { - result[key] = this.serializeBigInt(obj[key]); - } - } - return result; - } - return obj; - } - - async call(method: string, params?: any): Promise { - const serializedParams = params ? this.serializeBigInt(params) : undefined; - const payload = { - jsonrpc: "2.0", - method, - params: serializedParams ? [serializedParams] : [], - id: 1, - }; - - console.log("发送 RPC 请求:", JSON.stringify(payload, null, 2)); - - const response = await this.client.post("", payload); - - if (response.data.error) { - throw new Error( - `[RPC Error ${response.data.error.code}] ${response.data.error.message}${ - response.data.error.data - ? "\nDetails: " + response.data.error.data - : "" - }`, - ); - } - - return response.data.result; - } -} - -export class RPCError extends Error { - constructor( - public error: { - code: number; - message: string; - data?: any; - }, - ) { - super(`[RPC Error ${error.code}] ${error.message}`); - } -} diff --git a/packages/fiber/src/index.ts b/packages/fiber/src/index.ts index 4bc4aca1..e6d5edc1 100644 --- a/packages/fiber/src/index.ts +++ b/packages/fiber/src/index.ts @@ -1,10 +1,24 @@ -import { FiberClient } from "./core/client"; +import { FiberClient } from "./client"; +import { CchModule } from "./modules/cch"; import { ChannelModule } from "./modules/channel"; +import { DevModule } from "./modules/dev"; +import { GraphModule } from "./modules/graph"; import { InfoModule } from "./modules/info"; import { InvoiceModule } from "./modules/invoice"; import { PaymentModule } from "./modules/payment"; import { PeerModule } from "./modules/peer"; +export { FiberClient } from "./client"; +export { CchModule } from "./modules/cch"; +export { ChannelModule } from "./modules/channel"; +export { DevModule } from "./modules/dev"; +export { GraphModule } from "./modules/graph"; +export { InfoModule } from "./modules/info"; +export { InvoiceModule } from "./modules/invoice"; +export { PaymentModule } from "./modules/payment"; +export { PeerModule } from "./modules/peer"; +export * from "./types"; + export interface FiberSDKConfig { endpoint: string; timeout?: number; @@ -16,10 +30,13 @@ export class FiberSDK { public invoice: InvoiceModule; public peer: PeerModule; public info: InfoModule; + public graph: GraphModule; + public dev: DevModule; + public cch: CchModule; constructor(config: FiberSDKConfig) { const client = new FiberClient({ - baseURL: config.endpoint, + endpoint: config.endpoint, timeout: config.timeout, }); @@ -28,5 +45,8 @@ export class FiberSDK { this.invoice = new InvoiceModule(client); this.peer = new PeerModule(client); this.info = new InfoModule(client); + this.graph = new GraphModule(client); + this.dev = new DevModule(client); + this.cch = new CchModule(client); } } diff --git a/packages/fiber/src/modules/cch.ts b/packages/fiber/src/modules/cch.ts new file mode 100644 index 00000000..6a545339 --- /dev/null +++ b/packages/fiber/src/modules/cch.ts @@ -0,0 +1,69 @@ +import { FiberClient } from "../client"; +import { Currency, Script } from "../types"; + +export class CchModule { + constructor(private client: FiberClient) {} + + /** + * 发送 BTC + */ + async sendBtc(params: { + btc_pay_req: string; + currency: Currency; + }): Promise<{ + timestamp: bigint; + expiry: bigint; + ckb_final_tlc_expiry_delta: bigint; + currency: Currency; + wrapped_btc_type_script: Script; + btc_pay_req: string; + ckb_pay_req: string; + payment_hash: string; + amount_sats: bigint; + fee_sats: bigint; + status: string; + }> { + return this.client.call("send_btc", [params]); + } + + /** + * 接收 BTC + */ + async receiveBtc(params: { + payment_hash: string; + channel_id: string; + amount_sats: bigint; + final_tlc_expiry: bigint; + }): Promise<{ + timestamp: bigint; + expiry: bigint; + ckb_final_tlc_expiry_delta: bigint; + wrapped_btc_type_script: Script; + btc_pay_req: string; + payment_hash: string; + channel_id: string; + tlc_id?: bigint; + amount_sats: bigint; + status: string; + }> { + return this.client.call("receive_btc", [params]); + } + + /** + * 获取接收 BTC 订单 + */ + async getReceiveBtcOrder(payment_hash: string): Promise<{ + timestamp: bigint; + expiry: bigint; + ckb_final_tlc_expiry_delta: bigint; + wrapped_btc_type_script: Script; + btc_pay_req: string; + payment_hash: string; + channel_id: string; + tlc_id?: bigint; + amount_sats: bigint; + status: string; + }> { + return this.client.call("get_receive_btc_order", [payment_hash]); + } +} \ No newline at end of file diff --git a/packages/fiber/src/modules/channel.ts b/packages/fiber/src/modules/channel.ts index efc2f1fb..6771ce53 100644 --- a/packages/fiber/src/modules/channel.ts +++ b/packages/fiber/src/modules/channel.ts @@ -1,5 +1,5 @@ -import { FiberClient } from "../core/client"; -import { Channel, Hash256 } from "../types"; +import { FiberClient } from "../client"; +import { Channel, Hash256, Script } from "../types"; export class ChannelModule { constructor(private client: FiberClient) {} @@ -11,8 +11,8 @@ export class ChannelModule { peer_id: string; funding_amount: bigint; public?: boolean; - funding_udt_type_script?: any; - shutdown_script?: any; + funding_udt_type_script?: Script; + shutdown_script?: Script; commitment_delay_epoch?: bigint; commitment_fee_rate?: bigint; funding_fee_rate?: bigint; @@ -22,7 +22,7 @@ export class ChannelModule { max_tlc_value_in_flight?: bigint; max_tlc_number_in_flight?: bigint; }): Promise { - return this.client.call("open_channel", params); + return this.client.call("open_channel", [params]); } /** @@ -37,33 +37,21 @@ export class ChannelModule { tlc_fee_proportional_millionths: bigint; tlc_expiry_delta: bigint; }): Promise { - const serializedParams = { - temporary_channel_id: params.temporary_channel_id, - funding_amount: params.funding_amount, - max_tlc_value_in_flight: params.max_tlc_value_in_flight, - max_tlc_number_in_flight: params.max_tlc_number_in_flight, - tlc_min_value: params.tlc_min_value, - tlc_fee_proportional_millionths: params.tlc_fee_proportional_millionths, - tlc_expiry_delta: params.tlc_expiry_delta, - }; - return this.client.call("accept_channel", serializedParams); + return this.client.call("accept_channel", [params]); } /** * 放弃通道 */ async abandonChannel(channelId: Hash256): Promise { - return this.client.call("abandon_channel", { channel_id: channelId }); + return this.client.call("abandon_channel", [channelId]); } /** - * 列出所有通道 + * 列出通道 */ - async listChannels(params?: { - peer_id?: string; - include_closed?: boolean; - }): Promise { - return this.client.call("list_channels", params || {}); + async listChannels(): Promise { + return this.client.call("list_channels", []); } /** @@ -71,11 +59,11 @@ export class ChannelModule { */ async shutdownChannel(params: { channel_id: Hash256; - close_script: any; + close_script: Script; force?: boolean; fee_rate: bigint; }): Promise { - return this.client.call("shutdown_channel", params); + return this.client.call("shutdown_channel", [params]); } /** @@ -88,6 +76,6 @@ export class ChannelModule { tlc_minimum_value?: bigint; tlc_fee_proportional_millionths?: bigint; }): Promise { - return this.client.call("update_channel", params); + return this.client.call("update_channel", [params]); } } diff --git a/packages/fiber/src/modules/dev.ts b/packages/fiber/src/modules/dev.ts new file mode 100644 index 00000000..ea528589 --- /dev/null +++ b/packages/fiber/src/modules/dev.ts @@ -0,0 +1,58 @@ +import { FiberClient } from "../client"; +import { Hash256, RemoveTlcReason } from "../types"; + +export class DevModule { + constructor(private client: FiberClient) {} + + /** + * 提交承诺交易 + */ + async commitmentSigned(params: { + channel_id: Hash256; + commitment_transaction: string; + }): Promise { + return this.client.call("commitment_signed", [params]); + } + + /** + * 添加时间锁定合约 + */ + async addTlc(params: { + channel_id: Hash256; + amount: bigint; + payment_hash: string; + expiry: bigint; + }): Promise { + return this.client.call("add_tlc", [params]); + } + + /** + * 移除时间锁定合约 + */ + async removeTlc(params: { + channel_id: Hash256; + tlc_id: bigint; + reason: RemoveTlcReason; + payment_preimage?: string; + failure_message?: string; + }): Promise { + return this.client.call("remove_tlc", [params]); + } + + /** + * 提交承诺交易 + */ + async submitCommitmentTransaction(params: { + channel_id: Hash256; + commitment_transaction: string; + }): Promise { + return this.client.call("submit_commitment_transaction", [params]); + } + + /** + * 移除监视通道 + */ + async removeWatchChannel(channel_id: Hash256): Promise { + return this.client.call("remove_watch_channel", [channel_id]); + } +} \ No newline at end of file diff --git a/packages/fiber/src/modules/graph.ts b/packages/fiber/src/modules/graph.ts new file mode 100644 index 00000000..67a9a0ce --- /dev/null +++ b/packages/fiber/src/modules/graph.ts @@ -0,0 +1,20 @@ +import { FiberClient } from "../client"; +import { ChannelInfo, Pubkey } from "../types"; + +export class GraphModule { + constructor(private client: FiberClient) {} + + /** + * 获取节点列表 + */ + async graphNodes(): Promise { + return this.client.call("graph_nodes", []); + } + + /** + * 获取通道列表 + */ + async graphChannels(): Promise { + return this.client.call("graph_channels", []); + } +} \ No newline at end of file diff --git a/packages/fiber/src/modules/info.ts b/packages/fiber/src/modules/info.ts index 23a1aa21..7e46740b 100644 --- a/packages/fiber/src/modules/info.ts +++ b/packages/fiber/src/modules/info.ts @@ -1,73 +1,34 @@ -import { FiberClient } from "../core/client"; -import { NodeInfo } from "../types"; - -export interface NodeStatus { - is_online: boolean; - last_sync_time: bigint; - connected_peers: number; - total_channels: number; -} - -export interface NodeVersion { - version: string; - commit_hash: string; - build_time: string; -} - -export interface NetworkInfo { - network_type: "mainnet" | "testnet" | "devnet"; - chain_hash: string; - block_height: bigint; - block_hash: string; -} +import { FiberClient } from "../client"; +import { NetworkInfo, NodeInfo, NodeStatus, NodeVersion } from "../types"; export class InfoModule { constructor(private client: FiberClient) {} /** - * 获取节点基本信息 - * - * @returns {Promise} 包含节点详细信息的对象,包括: - * - version: 节点软件版本 - * - commit_hash: 节点软件的提交哈希 - * - node_id: 节点的身份公钥 - * - node_name: 节点名称(可选) - * - addresses: 节点的多地址列表 - * - chain_hash: 节点连接的区块链哈希 - * - open_channel_auto_accept_min_ckb_funding_amount: 自动接受开放通道请求的最小 CKB 资金金额 - * - auto_accept_channel_ckb_funding_amount: 自动接受通道请求的 CKB 资金金额 - * - default_funding_lock_script: 节点的默认资金锁定脚本 - * - tlc_expiry_delta: 时间锁定合约(TLC)的过期增量 - * - tlc_min_value: 可以发送的 TLC 最小值 - * - tlc_max_value: 可以发送的 TLC 最大值(0 表示无限制) - * - tlc_fee_proportional_millionths: TLC 转发支付的费用比例(以百万分之一为单位) - * - channel_count: 节点关联的通道数量 - * - pending_channel_count: 节点关联的待处理通道数量 - * - peers_count: 连接到节点的对等节点数量 - * - udt_cfg_infos: 节点关联的用户自定义代币(UDT)配置信息 + * 获取节点信息 */ async nodeInfo(): Promise { - return this.client.call("node_info"); + return this.client.call("node_info", []); } /** * 获取节点状态信息 */ async nodeStatus(): Promise { - return this.client.call("node_status"); + return this.client.call("node_status", []); } /** * 获取节点版本信息 */ async nodeVersion(): Promise { - return this.client.call("node_version"); + return this.client.call("node_version", []); } /** * 获取网络信息 */ async networkInfo(): Promise { - return this.client.call("network_info"); + return this.client.call("network_info", []); } } diff --git a/packages/fiber/src/modules/invoice.ts b/packages/fiber/src/modules/invoice.ts index 40023e66..b6d0b926 100644 --- a/packages/fiber/src/modules/invoice.ts +++ b/packages/fiber/src/modules/invoice.ts @@ -1,5 +1,5 @@ -import { FiberClient } from "../core/client"; -import { CkbInvoice, CkbInvoiceStatus, Hash256 } from "../types"; +import { FiberClient } from "../client"; +import { CkbInvoice, CkbInvoiceStatus } from "../types"; export class InvoiceModule { constructor(private client: FiberClient) {} @@ -10,42 +10,34 @@ export class InvoiceModule { async newInvoice(params: { amount: bigint; description?: string; - currency: "Fibb" | "Fibt" | "Fibd"; - payment_preimage: Hash256; expiry?: bigint; - fallback_address?: string; - final_expiry_delta?: bigint; - udt_type_script?: any; - hash_algorithm?: "CkbHash" | "Sha256"; - }): Promise<{ - invoice_address: string; - invoice: CkbInvoice; - }> { - return this.client.call("invoice.new_invoice", params); + payment_secret?: string; + }): Promise { + return this.client.call("new_invoice", [params]); } /** * 解析发票 */ async parseInvoice(invoice: string): Promise { - return this.client.call("invoice.parse_invoice", { invoice }); + return this.client.call("parse_invoice", [invoice]); } /** * 获取发票 */ - async getInvoice(paymentHash: Hash256): Promise { - return this.client.call("invoice.get_invoice", { - payment_hash: paymentHash, - }); + async getInvoice(payment_hash: string): Promise<{ + status: CkbInvoiceStatus; + invoice_address: string; + invoice: CkbInvoice; + }> { + return this.client.call("get_invoice", [payment_hash]); } /** * 取消发票 */ - async cancelInvoice(paymentHash: Hash256): Promise { - return this.client.call("invoice.cancel_invoice", { - payment_hash: paymentHash, - }); + async cancelInvoice(payment_hash: string): Promise { + return this.client.call("cancel_invoice", [payment_hash]); } } diff --git a/packages/fiber/src/modules/payment.ts b/packages/fiber/src/modules/payment.ts index 9266fdbb..da2f147a 100644 --- a/packages/fiber/src/modules/payment.ts +++ b/packages/fiber/src/modules/payment.ts @@ -1,5 +1,10 @@ -import { FiberClient } from "../core/client"; -import { Hash256, PaymentSessionStatus, Pubkey } from "../types"; +import { FiberClient } from "../client"; +import { + Hash256, + PaymentCustomRecords, + PaymentSessionStatus, + SessionRoute, +} from "../types"; export class PaymentModule { constructor(private client: FiberClient) {} @@ -8,31 +13,28 @@ export class PaymentModule { * 发送支付 */ async sendPayment(params: { - target_pubkey?: Pubkey; - amount?: bigint; - payment_hash?: Hash256; - final_tlc_expiry_delta?: bigint; - tlc_expiry_limit?: bigint; - invoice?: string; - timeout?: bigint; - max_fee_amount?: bigint; - max_parts?: bigint; - keysend?: boolean; - udt_type_script?: any; - allow_self_payment?: boolean; - custom_records?: Record; - hop_hints?: any[]; - dry_run?: boolean; - }): Promise { - return this.client.call("payment.send_payment", params); + payment_hash: string; + amount: bigint; + fee_rate: bigint; + custom_records?: PaymentCustomRecords; + route?: SessionRoute; + }): Promise { + return this.client.call("send_payment", [params]); } /** - * 获取支付状态 + * 获取支付 */ - async getPayment(paymentHash: Hash256): Promise { - return this.client.call("payment.get_payment", { - payment_hash: paymentHash, - }); + async getPayment(payment_hash: string): Promise<{ + status: PaymentSessionStatus; + payment_hash: Hash256; + created_at: bigint; + last_updated_at: bigint; + failed_error?: string; + fee: bigint; + custom_records?: PaymentCustomRecords; + route: SessionRoute; + }> { + return this.client.call("get_payment", [payment_hash]); } } diff --git a/packages/fiber/src/modules/peer.ts b/packages/fiber/src/modules/peer.ts index 1e6a2216..b2010070 100644 --- a/packages/fiber/src/modules/peer.ts +++ b/packages/fiber/src/modules/peer.ts @@ -1,22 +1,22 @@ -import { FiberClient } from "../core/client"; +import { FiberClient } from "../client"; export class PeerModule { constructor(private client: FiberClient) {} /** - * 连接到对等节点 + * 连接节点 */ async connectPeer(params: { address: string; save?: boolean; }): Promise { - return this.client.call("connect_peer", params); + return this.client.call("connect_peer", [params.address]); } /** - * 断开对等节点连接 + * 断开节点连接 */ - async disconnectPeer(peerId: string): Promise { - return this.client.call("disconnect_peer", [peerId]); + async disconnectPeer(peer_id: string): Promise { + return this.client.call("disconnect_peer", [peer_id]); } } diff --git a/packages/fiber/src/types.ts b/packages/fiber/src/types.ts index 452f827e..89ee6303 100644 --- a/packages/fiber/src/types.ts +++ b/packages/fiber/src/types.ts @@ -1,12 +1,42 @@ export type Hash256 = string; export type Pubkey = string; +export enum Currency { + Fibb = "Fibb", + Fibt = "Fibt", + Fibd = "Fibd", +} + +export enum CkbInvoiceStatus { + Open = "Open", + Cancelled = "Cancelled", + Expired = "Expired", + Received = "Received", + Paid = "Paid", +} + +export enum PaymentSessionStatus { + Created = "Created", + Inflight = "Inflight", + Success = "Success", + Failed = "Failed", +} + +export enum RemoveTlcReason { + RemoveTlcFulfill = "RemoveTlcFulfill", + RemoveTlcFail = "RemoveTlcFail", +} + +export interface Script { + code_hash: string; + hash_type: string; + args: string[]; +} + export interface Channel { channel_id: Hash256; - is_public: boolean; - channel_outpoint?: any; - peer_id: string; - funding_udt_type_script?: any; + peer_id: Pubkey; + funding_udt_type_script?: Script; state: string; local_balance: bigint; offered_tlc_balance: bigint; @@ -20,7 +50,10 @@ export interface Channel { } export interface ChannelInfo { - channel_outpoint: any; + channel_outpoint: { + tx_hash: Hash256; + index: bigint; + }; node1: Pubkey; node2: Pubkey; created_timestamp: bigint; @@ -30,31 +63,59 @@ export interface ChannelInfo { fee_rate_of_node2?: bigint; capacity: bigint; chain_hash: Hash256; - udt_type_script?: any; + udt_type_script?: Script; +} + +export interface CkbInvoice { + currency: Currency; + amount?: bigint; + signature?: { + pubkey: Pubkey; + signature: string; + }; + data: { + payment_hash: string; + timestamp: bigint; + expiry?: bigint; + description?: string; + description_hash?: string; + payment_secret?: string; + features?: bigint; + route_hints?: Array<{ + pubkey: Pubkey; + channel_outpoint: { + tx_hash: Hash256; + index: bigint; + }; + fee_rate: bigint; + tlc_expiry_delta: bigint; + }>; + }; } export interface NodeInfo { - version: string; - commit_hash: string; - node_id: Pubkey; - node_name: string | null; + node_name: string; addresses: string[]; + node_id: Pubkey; + timestamp: bigint; chain_hash: Hash256; - open_channel_auto_accept_min_ckb_funding_amount: bigint; - auto_accept_channel_ckb_funding_amount: bigint; - default_funding_lock_script: { - code_hash: string; - hash_type: string; - args: string; - }; - tlc_expiry_delta: bigint; - tlc_min_value: bigint; - tlc_max_value: bigint; - tlc_fee_proportional_millionths: bigint; - channel_count: number; - pending_channel_count: number; - peers_count: number; - udt_cfg_infos: any; + auto_accept_min_ckb_funding_amount: bigint; + udt_cfg_infos: Record; +} + +export interface PaymentCustomRecords { + data: Record; +} + +export interface SessionRoute { + nodes: Array<{ + pubkey: Pubkey; + amount: bigint; + channel_outpoint?: { + tx_hash: Hash256; + index: bigint; + }; + }>; } export interface NodeStatus { @@ -76,27 +137,3 @@ export interface NetworkInfo { block_height: bigint; block_hash: string; } - -export interface PaymentSessionStatus { - status: "Created" | "Inflight" | "Success" | "Failed"; - payment_hash: Hash256; - created_at: bigint; - last_updated_at: bigint; - failed_error?: string; - fee: bigint; - custom_records?: any; - router: any; -} - -export interface CkbInvoice { - currency: "Fibb" | "Fibt" | "Fibd"; - amount?: bigint; - signature?: any; - data: any; -} - -export interface CkbInvoiceStatus { - status: "Open" | "Cancelled" | "Expired" | "Received" | "Paid"; - invoice_address: string; - invoice: CkbInvoice; -} diff --git a/packages/fiber/test.mjs b/packages/fiber/test.mjs new file mode 100644 index 00000000..cf9aefe0 --- /dev/null +++ b/packages/fiber/test.mjs @@ -0,0 +1,172 @@ +import pkg from "./dist.commonjs/index.js"; +const { FiberSDK } = pkg; + +async function testNodeInfo() { + try { + // 初始化 SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", // Fiber 节点的默认 RPC 地址 + timeout: 5000, + }); + console.log("开始获取节点信息...\n"); + + // 调用 nodeInfo 方法 + const nodeInfo = await sdk.info.nodeInfo(); + + // 打印节点信息 + console.log("节点信息:"); + console.log("节点名称:", nodeInfo.node_name); + console.log("节点地址:", nodeInfo.addresses); + console.log("节点ID:", nodeInfo.node_id); + console.log("链哈希:", nodeInfo.chain_hash); + if (nodeInfo.auto_accept_min_ckb_funding_amount) { + console.log( + "自动接受最小资金金额:", + nodeInfo.auto_accept_min_ckb_funding_amount.toString(), + ); + } + console.log("UDT配置信息:", nodeInfo.udt_cfg_infos); + + console.log("\n测试完成!"); + } catch (error) { + console.error("获取节点信息时发生错误:", error.message); + } +} + +async function testChannelManagement() { + console.log("开始测试通道管理...\n"); + + try { + // 初始化 SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + // 尝试打开新通道 + // console.log('尝试打开新通道:'); + + // 先连接对等节点 + const peerAddress = + "/ip4/18.162.235.225/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; + const targetPeerId = "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; + + try { + // 先检查节点状态 + const status = await sdk.info.nodeStatus(); + console.log("当前节点状态:", status); + + // 尝试连接对等节点 + console.log("正在连接到对等节点:", peerAddress); + await sdk.peer.connectPeer({ + address: peerAddress, + save: true, + }); + console.log("成功连接到对等节点:", peerAddress); + + // 等待更长时间确保连接完全建立 + console.log("等待连接稳定..."); + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // // 再次检查节点状态 + // const newStatus = await sdk.info.nodeStatus(); + // console.log('连接后的节点状态:', newStatus); + + // 检查连接是否成功 + const updatedChannels = await sdk.channel.listChannels(); + console.log("连接后的通道列表:", updatedChannels); + + const openChannelParams = { + peer_id: targetPeerId, + funding_amount: BigInt("0x1717918000"), // 62 CKB = 6200000000 shannon + public: true, + commitment_delay_epoch: BigInt("0x54"), // 84 epochs + commitment_fee_rate: BigInt("0x3e8"), + funding_fee_rate: BigInt("0x3e8"), + tlc_expiry_delta: BigInt(900000), + tlc_min_value: BigInt("0x3e8"), + tlc_fee_proportional_millionths: BigInt("0x3e8"), + max_tlc_value_in_flight: BigInt("0x1717918000"), + max_tlc_number_in_flight: BigInt("0x64"), + }; + + console.log("准备打开通道,参数:", openChannelParams); + + try { + const result = await sdk.channel.openChannel(openChannelParams); + console.log("成功打开通道,临时通道ID:", result.temporary_channel_id); + + // 测试接受通道 + console.log("\n尝试接受通道:", result.temporary_channel_id); + try { + const acceptResult = await sdk.channel.acceptChannel({ + temporary_channel_id: result.temporary_channel_id, + funding_amount: BigInt("390000000000"), + max_tlc_value_in_flight: BigInt("390000000000"), + max_tlc_number_in_flight: BigInt("100"), + tlc_min_value: BigInt("1000"), + tlc_fee_proportional_millionths: BigInt("1000"), + tlc_expiry_delta: BigInt("900000"), + }); + console.log("通道接受成功,最终通道ID:", acceptResult.channel_id); + + // 测试关闭通道 + console.log("\n尝试关闭通道:", acceptResult.channel_id); + try { + await sdk.channel.shutdownChannel({ + channel_id: acceptResult.channel_id, + close_script: null, // 使用默认的关闭脚本 + force: false, + fee_rate: BigInt(1000), + }); + console.log("通道关闭成功"); + + // 测试更新通道参数 + console.log("\n尝试更新通道参数:", acceptResult.channel_id); + try { + await sdk.channel.updateChannel({ + channel_id: acceptResult.channel_id, + enabled: true, + tlc_expiry_delta: BigInt(200), + tlc_minimum_value: BigInt(200000000), // 0.2 CKB + tlc_fee_proportional_millionths: BigInt(200), + }); + console.log("通道参数更新成功"); + } catch (error) { + console.log("更新通道参数失败:", error.message); + } + + // 测试放弃通道 + console.log("\n尝试放弃通道:", acceptResult.channel_id); + try { + await sdk.channel.abandonChannel(acceptResult.channel_id); + console.log("通道放弃成功"); + } catch (error) { + console.log("放弃通道失败:", error.message); + } + } catch (error) { + console.log("关闭通道失败:", error.message); + } + } catch (error) { + console.log("接受通道失败:", error.message); + } + } catch (error) { + console.log("打开通道失败:", error.message); + } + } catch (error) { + console.log("连接对等节点失败:", error.message); + } + } catch (error) { + console.error("测试过程中出错:", error.message); + } + + console.log("\n通道管理测试完成!"); +} + +// 运行测试 +async function runTests() { + // await testNodeInfo(); + await testChannelManagement(); +} + +runTests().catch(console.error); From db8b74f43ef5fbac9a4944126a887bac8a66ff62 Mon Sep 17 00:00:00 2001 From: Jack <6464072@qq.com> Date: Tue, 8 Apr 2025 10:31:20 +0800 Subject: [PATCH 04/15] feat:add channel,info test code --- packages/fiber/src/client.ts | 13 +- packages/fiber/src/modules/channel.ts | 41 ++- packages/fiber/src/modules/info.ts | 25 +- packages/fiber/src/modules/peer.ts | 12 +- packages/fiber/test.mjs | 172 ---------- packages/fiber/test/abandon.cjs | 40 +++ packages/fiber/test/channel.cjs | 449 ++++++++++++++++++++++++++ packages/fiber/test/info.cjs | 108 +++++++ packages/fiber/test/invoice.cjs | 184 +++++++++++ packages/fiber/test/payment.cjs | 129 ++++++++ packages/fiber/test/peer.cjs | 198 ++++++++++++ 11 files changed, 1167 insertions(+), 204 deletions(-) delete mode 100644 packages/fiber/test.mjs create mode 100644 packages/fiber/test/abandon.cjs create mode 100644 packages/fiber/test/channel.cjs create mode 100644 packages/fiber/test/info.cjs create mode 100644 packages/fiber/test/invoice.cjs create mode 100644 packages/fiber/test/payment.cjs create mode 100644 packages/fiber/test/peer.cjs diff --git a/packages/fiber/src/client.ts b/packages/fiber/src/client.ts index 40d99a3b..ff22a610 100644 --- a/packages/fiber/src/client.ts +++ b/packages/fiber/src/client.ts @@ -81,6 +81,10 @@ export class FiberClient { } async call(method: string, params: unknown[]): Promise { + if (params.length === 0 || (params.length === 1 && params[0] === null)) { + params = []; + } + const serializedParams = params.map((param) => { if (param === null || param === undefined) { return {}; @@ -93,13 +97,20 @@ export class FiberClient { if (!result) { throw new RPCError({ code: -1, - message: "Unknown RPC error", + message: `RPC method "${method}" failed`, data: undefined, }); } return result as T; } catch (error) { if (error instanceof Error) { + if (error.message.includes("Method not found")) { + throw new RPCError({ + code: -32601, + message: `RPC method "${method}" not found`, + data: undefined, + }); + } throw new RPCError({ code: -1, message: error.message, diff --git a/packages/fiber/src/modules/channel.ts b/packages/fiber/src/modules/channel.ts index 6771ce53..c7eaa6ae 100644 --- a/packages/fiber/src/modules/channel.ts +++ b/packages/fiber/src/modules/channel.ts @@ -42,16 +42,53 @@ export class ChannelModule { /** * 放弃通道 + * @param channelId - 通道ID,必须是有效的 Hash256 格式 + * @throws {Error} 当通道ID无效或通道不存在时抛出错误 + * @returns Promise */ async abandonChannel(channelId: Hash256): Promise { - return this.client.call("abandon_channel", [channelId]); + if (!channelId) { + throw new Error("通道ID不能为空"); + } + + if (!channelId.startsWith("0x")) { + throw new Error("通道ID必须以0x开头"); + } + + if (channelId.length !== 66) { + // 0x + 64位哈希 + throw new Error("通道ID长度无效"); + } + + try { + // 先检查通道是否存在 + const channels = await this.listChannels(); + const channelExists = channels.some( + (channel) => channel.channel_id === channelId, + ); + + if (!channelExists) { + throw new Error(`找不到ID为 ${channelId} 的通道`); + } + + return this.client.call("abandon_channel", [channelId]); + } catch (error) { + if (error instanceof Error) { + throw new Error(`放弃通道失败: ${error.message}`); + } + throw error; + } } /** * 列出通道 */ async listChannels(): Promise { - return this.client.call("list_channels", []); + const response = await this.client.call<{ channels: Channel[] }>( + "list_channels", + [{}], + ); + return response.channels; } /** diff --git a/packages/fiber/src/modules/info.ts b/packages/fiber/src/modules/info.ts index 7e46740b..3be23e7c 100644 --- a/packages/fiber/src/modules/info.ts +++ b/packages/fiber/src/modules/info.ts @@ -1,34 +1,15 @@ import { FiberClient } from "../client"; -import { NetworkInfo, NodeInfo, NodeStatus, NodeVersion } from "../types"; +import { NodeInfo } from "../types"; export class InfoModule { constructor(private client: FiberClient) {} /** * 获取节点信息 + * @returns 返回节点的详细信息,包括节点名称、地址、ID等 + * @throws {Error} 当无法获取节点信息时抛出错误 */ async nodeInfo(): Promise { return this.client.call("node_info", []); } - - /** - * 获取节点状态信息 - */ - async nodeStatus(): Promise { - return this.client.call("node_status", []); - } - - /** - * 获取节点版本信息 - */ - async nodeVersion(): Promise { - return this.client.call("node_version", []); - } - - /** - * 获取网络信息 - */ - async networkInfo(): Promise { - return this.client.call("network_info", []); - } } diff --git a/packages/fiber/src/modules/peer.ts b/packages/fiber/src/modules/peer.ts index b2010070..721c4e62 100644 --- a/packages/fiber/src/modules/peer.ts +++ b/packages/fiber/src/modules/peer.ts @@ -4,17 +4,15 @@ export class PeerModule { constructor(private client: FiberClient) {} /** - * 连接节点 + * 连接对等节点 + * @param address 节点地址 */ - async connectPeer(params: { - address: string; - save?: boolean; - }): Promise { - return this.client.call("connect_peer", [params.address]); + async connectPeer(address: string): Promise { + return this.client.call("connect_peer", [address]); } /** - * 断开节点连接 + * 断开对等节点连接 */ async disconnectPeer(peer_id: string): Promise { return this.client.call("disconnect_peer", [peer_id]); diff --git a/packages/fiber/test.mjs b/packages/fiber/test.mjs deleted file mode 100644 index cf9aefe0..00000000 --- a/packages/fiber/test.mjs +++ /dev/null @@ -1,172 +0,0 @@ -import pkg from "./dist.commonjs/index.js"; -const { FiberSDK } = pkg; - -async function testNodeInfo() { - try { - // 初始化 SDK - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", // Fiber 节点的默认 RPC 地址 - timeout: 5000, - }); - console.log("开始获取节点信息...\n"); - - // 调用 nodeInfo 方法 - const nodeInfo = await sdk.info.nodeInfo(); - - // 打印节点信息 - console.log("节点信息:"); - console.log("节点名称:", nodeInfo.node_name); - console.log("节点地址:", nodeInfo.addresses); - console.log("节点ID:", nodeInfo.node_id); - console.log("链哈希:", nodeInfo.chain_hash); - if (nodeInfo.auto_accept_min_ckb_funding_amount) { - console.log( - "自动接受最小资金金额:", - nodeInfo.auto_accept_min_ckb_funding_amount.toString(), - ); - } - console.log("UDT配置信息:", nodeInfo.udt_cfg_infos); - - console.log("\n测试完成!"); - } catch (error) { - console.error("获取节点信息时发生错误:", error.message); - } -} - -async function testChannelManagement() { - console.log("开始测试通道管理...\n"); - - try { - // 初始化 SDK - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", - timeout: 5000, - }); - - // 尝试打开新通道 - // console.log('尝试打开新通道:'); - - // 先连接对等节点 - const peerAddress = - "/ip4/18.162.235.225/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; - const targetPeerId = "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; - - try { - // 先检查节点状态 - const status = await sdk.info.nodeStatus(); - console.log("当前节点状态:", status); - - // 尝试连接对等节点 - console.log("正在连接到对等节点:", peerAddress); - await sdk.peer.connectPeer({ - address: peerAddress, - save: true, - }); - console.log("成功连接到对等节点:", peerAddress); - - // 等待更长时间确保连接完全建立 - console.log("等待连接稳定..."); - await new Promise((resolve) => setTimeout(resolve, 2000)); - - // // 再次检查节点状态 - // const newStatus = await sdk.info.nodeStatus(); - // console.log('连接后的节点状态:', newStatus); - - // 检查连接是否成功 - const updatedChannels = await sdk.channel.listChannels(); - console.log("连接后的通道列表:", updatedChannels); - - const openChannelParams = { - peer_id: targetPeerId, - funding_amount: BigInt("0x1717918000"), // 62 CKB = 6200000000 shannon - public: true, - commitment_delay_epoch: BigInt("0x54"), // 84 epochs - commitment_fee_rate: BigInt("0x3e8"), - funding_fee_rate: BigInt("0x3e8"), - tlc_expiry_delta: BigInt(900000), - tlc_min_value: BigInt("0x3e8"), - tlc_fee_proportional_millionths: BigInt("0x3e8"), - max_tlc_value_in_flight: BigInt("0x1717918000"), - max_tlc_number_in_flight: BigInt("0x64"), - }; - - console.log("准备打开通道,参数:", openChannelParams); - - try { - const result = await sdk.channel.openChannel(openChannelParams); - console.log("成功打开通道,临时通道ID:", result.temporary_channel_id); - - // 测试接受通道 - console.log("\n尝试接受通道:", result.temporary_channel_id); - try { - const acceptResult = await sdk.channel.acceptChannel({ - temporary_channel_id: result.temporary_channel_id, - funding_amount: BigInt("390000000000"), - max_tlc_value_in_flight: BigInt("390000000000"), - max_tlc_number_in_flight: BigInt("100"), - tlc_min_value: BigInt("1000"), - tlc_fee_proportional_millionths: BigInt("1000"), - tlc_expiry_delta: BigInt("900000"), - }); - console.log("通道接受成功,最终通道ID:", acceptResult.channel_id); - - // 测试关闭通道 - console.log("\n尝试关闭通道:", acceptResult.channel_id); - try { - await sdk.channel.shutdownChannel({ - channel_id: acceptResult.channel_id, - close_script: null, // 使用默认的关闭脚本 - force: false, - fee_rate: BigInt(1000), - }); - console.log("通道关闭成功"); - - // 测试更新通道参数 - console.log("\n尝试更新通道参数:", acceptResult.channel_id); - try { - await sdk.channel.updateChannel({ - channel_id: acceptResult.channel_id, - enabled: true, - tlc_expiry_delta: BigInt(200), - tlc_minimum_value: BigInt(200000000), // 0.2 CKB - tlc_fee_proportional_millionths: BigInt(200), - }); - console.log("通道参数更新成功"); - } catch (error) { - console.log("更新通道参数失败:", error.message); - } - - // 测试放弃通道 - console.log("\n尝试放弃通道:", acceptResult.channel_id); - try { - await sdk.channel.abandonChannel(acceptResult.channel_id); - console.log("通道放弃成功"); - } catch (error) { - console.log("放弃通道失败:", error.message); - } - } catch (error) { - console.log("关闭通道失败:", error.message); - } - } catch (error) { - console.log("接受通道失败:", error.message); - } - } catch (error) { - console.log("打开通道失败:", error.message); - } - } catch (error) { - console.log("连接对等节点失败:", error.message); - } - } catch (error) { - console.error("测试过程中出错:", error.message); - } - - console.log("\n通道管理测试完成!"); -} - -// 运行测试 -async function runTests() { - // await testNodeInfo(); - await testChannelManagement(); -} - -runTests().catch(console.error); diff --git a/packages/fiber/test/abandon.cjs b/packages/fiber/test/abandon.cjs new file mode 100644 index 00000000..dabf22fe --- /dev/null +++ b/packages/fiber/test/abandon.cjs @@ -0,0 +1,40 @@ +const { FiberSDK } = require('../dist.commonjs'); + +const sdk = new FiberSDK({ + endpoint: 'http://ec2-18-162-235-225.ap-east-1.compute.amazonaws.com:8119', + timeout: 30000 +}); + +async function abandonNegotiatingChannels() { + try { + console.log('开始放弃处于 NEGOTIATING_FUNDING 状态的通道...\n'); + + // 首先获取所有通道 + const channels = await sdk.channel.listChannels(); + console.log(`找到 ${channels.length} 个通道`); + + // 筛选出处于 NEGOTIATING_FUNDING 状态的通道 + const negotiatingChannels = channels.filter( + channel => channel.state.state_name === 'NEGOTIATING_FUNDING' + ); + + console.log(`找到 ${negotiatingChannels.length} 个处于 NEGOTIATING_FUNDING 状态的通道`); + + // 遍历并放弃这些通道 + for (const channel of negotiatingChannels) { + console.log(`正在放弃通道: ${channel.channel_id}`); + try { + await sdk.channel.abandonChannel(channel.channel_id); + console.log(`成功放弃通道: ${channel.channel_id}`); + } catch (error) { + console.error(`放弃通道失败: ${channel.channel_id}`, error); + } + } + + console.log('\n放弃通道完成!'); + } catch (error) { + console.error('发生错误:', error); + } +} + +abandonNegotiatingChannels(); \ No newline at end of file diff --git a/packages/fiber/test/channel.cjs b/packages/fiber/test/channel.cjs new file mode 100644 index 00000000..5898f4be --- /dev/null +++ b/packages/fiber/test/channel.cjs @@ -0,0 +1,449 @@ +const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); + +// 自定义错误处理函数 +function handleRPCError(error) { + if (error.error && error.error.code === -32601) { + console.error("错误: 节点可能未运行或 RPC 方法不存在"); + console.error("请确保:"); + console.error("1. Fiber 节点已启动"); + console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); + console.error("3. 节点 RPC 接口可用"); + } else if (error.error && error.error.code === -32602) { + console.error("错误: 参数无效"); + console.error("请检查:"); + console.error("1. 参数类型是否正确"); + console.error("2. 参数值是否在有效范围内"); + console.error("3. 必填参数是否都已提供"); + } else { + console.error("RPC 错误:", error.message); + if (error.error && error.error.data) { + console.error("错误详情:", error.error.data); + } + } +} + +// 将十六进制字符串转换为数字 +function hexToNumber(hex) { + if (!hex) return 0; + return parseInt(hex.replace("0x", ""), 16); +} + +async function testListChannels() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("开始测试列出通道...\n"); + + try { + // 列出通道 + console.log("正在调用 listChannels 方法..."); + const channels = await sdk.channel.listChannels(); + + // 输出原始数据 + console.log("原始数据:", JSON.stringify(channels, null, 2)); + + // 类型检查 + if (!Array.isArray(channels)) { + throw new Error("返回的通道列表格式不正确"); + } + + // 输出详细信息 + if (channels.length > 0) { + console.log("\n通道详细信息:"); + channels.forEach((channel, index) => { + console.log(`\n通道 ${index + 1}:`); + console.log("通道ID:", channel.channel_id); + console.log("对等节点ID:", channel.peer_id); + console.log("状态:", channel.state); + console.log("本地余额:", hexToNumber(channel.local_balance)); + console.log("远程余额:", hexToNumber(channel.remote_balance)); + console.log( + "创建时间:", + new Date(hexToNumber(channel.created_at)).toLocaleString(), + ); + console.log("是否公开:", channel.is_public ? "是" : "否"); + console.log("是否启用:", channel.is_enabled ? "是" : "否"); + console.log("TLC 过期增量:", hexToNumber(channel.tlc_expiry_delta)); + console.log("TLC 最小金额:", hexToNumber(channel.tlc_min_value)); + console.log( + "TLC 费用比例:", + hexToNumber(channel.tlc_fee_proportional_millionths), + ); + }); + } else { + console.log("当前没有通道"); + } + + return channels; + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("列出通道失败:", error.message); + } + return []; + } + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("测试过程中发生错误:", error.message); + } + return []; + } +} + +async function testUpdateAndShutdownChannel() { + console.log("\n开始测试更新和关闭通道..."); + + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 30000, + }); + + try { + // 获取可用通道列表 + const channels = await sdk.channel.listChannels(); + + if (!channels || channels.length === 0) { + console.log("没有可用的通道"); + return; + } + + // 选择第一个通道进行测试 + const channel = channels[0]; + console.log("\n准备更新的通道信息:"); + console.log("通道ID:", channel.channel_id); + console.log("对等节点ID:", channel.peer_id); + console.log("状态:", channel.state); + + console.log("\n正在调用 updateChannel 方法禁用通道..."); + await sdk.channel.updateChannel({ + channel_id: channel.channel_id, + enabled: false, + tlc_expiry_delta: 1000000, // 设置大于 900000 的值 + tlc_min_amount: 0, + tlc_fee_rate: 0, + }); + console.log("通道已成功禁用"); + + console.log("\n正在调用 shutdownChannel 方法关闭通道..."); + await sdk.channel.shutdownChannel({ + channel_id: channel.channel_id, + close_script: "", + force: true, + fee_rate: 1000, + }); + console.log("通道已成功关闭"); + } catch (error) { + console.log("更新和关闭通道失败:", error.message); + } +} + +async function testAcceptChannel() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("\n开始测试接受通道...\n"); + + try { + // 获取可用的通道列表 + const channels = await testListChannels(); + + if (channels.length === 0) { + console.log("没有可用的通道可以接受"); + return; + } + + // 选择第一个通道进行接受操作 + const channelToAccept = channels[0]; + console.log("\n准备接受的通道信息:"); + console.log("通道ID:", channelToAccept.channel_id); + console.log("对等节点ID:", channelToAccept.peer_id); + console.log("状态:", channelToAccept.state); + + // 检查通道状态是否适合接受 + if (channelToAccept.state.state_name !== "NEGOTIATING_FUNDING") { + console.log("通道不处于资金协商状态,无法接受"); + return; + } + + // 调用接受通道方法 + console.log("\n正在调用 acceptChannel 方法..."); + await sdk.channel.acceptChannel({ + temporary_channel_id: channelToAccept.channel_id, + funding_amount: BigInt(100000000), + max_tlc_value_in_flight: BigInt(100000000), + max_tlc_number_in_flight: BigInt(10), + tlc_min_value: BigInt(1000), + tlc_fee_proportional_millionths: BigInt(1000), + tlc_expiry_delta: BigInt(100), + }); + console.log("通道接受成功"); + + // 验证通道状态 + console.log("\n验证通道状态..."); + const updatedChannels = await sdk.channel.listChannels(); + const acceptedChannel = updatedChannels.find( + (c) => c.channel_id === channelToAccept.channel_id, + ); + + if (acceptedChannel) { + console.log("通道状态:", acceptedChannel.state); + } + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("接受通道失败:", error.message); + } + } + } catch (error) { + console.error("测试接受通道时发生错误:", error); + } +} + +async function testAbandonChannel() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("\n开始测试放弃通道...\n"); + + try { + // 获取可用的通道列表 + const channels = await testListChannels(); + + if (channels.length === 0) { + console.log("没有可用的通道可以放弃"); + return; + } + + // 选择第一个通道进行放弃操作 + const channelToAbandon = channels[0]; + console.log("\n准备放弃的通道信息:"); + console.log("通道ID:", channelToAbandon.channel_id); + console.log("对等节点ID:", channelToAbandon.peer_id); + console.log("状态:", channelToAbandon.state); + + // 调用放弃通道方法 + console.log("\n正在调用 abandonChannel 方法..."); + await sdk.channel.abandonChannel(channelToAbandon.channel_id); + console.log("通道放弃成功"); + + // 验证通道状态 + console.log("\n验证通道状态..."); + const updatedChannels = await sdk.channel.listChannels(); + const abandonedChannel = updatedChannels.find( + (c) => c.channel_id === channelToAbandon.channel_id, + ); + + if (!abandonedChannel) { + console.log("验证成功:通道已被放弃"); + } else { + console.log("通道状态:", abandonedChannel.state); + } + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("放弃通道失败:", error.message); + } + } + } catch (error) { + console.error("测试放弃通道时发生错误:", error); + } +} + +async function testRemoveWatchChannel() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("\n开始测试移除监视通道...\n"); + + // 获取可用的通道列表 + const channels = await testListChannels(); + + if (channels.length === 0) { + console.log("没有可用的通道可以移除"); + return; + } + + // 选择第一个通道进行移除操作 + const channelToRemove = channels[0]; + console.log("\n准备移除的通道信息:"); + console.log("通道ID:", channelToRemove.channel_id); + console.log("对等节点ID:", channelToRemove.peer_id); + console.log("状态:", channelToRemove.state); + + // 调用移除监视通道方法 + console.log("\n正在调用 removeWatchChannel 方法..."); + await sdk.dev.removeWatchChannel(channelToRemove.channel_id); + console.log("移除监视通道成功"); + + // 验证通道状态 + console.log("\n验证通道状态..."); + const updatedChannels = await sdk.channel.listChannels(); + const removedChannel = updatedChannels.find( + (c) => c.channel_id === channelToRemove.channel_id, + ); + + if (!removedChannel) { + console.log("验证成功:通道已被移除"); + } else { + console.log("通道仍然存在,状态:", removedChannel.state); + } + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("移除监视通道时发生错误:", error); + } + } +} + +async function testSubmitCommitmentTransaction() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("\n开始测试提交承诺交易...\n"); + + // 获取可用的通道列表 + const channels = await testListChannels(); + + if (channels.length === 0) { + console.log("没有可用的通道"); + return; + } + + // 选择第一个通道 + const channel = channels[0]; + console.log("\n准备提交承诺交易的通道信息:"); + console.log("通道ID:", channel.channel_id); + console.log("对等节点ID:", channel.peer_id); + console.log("状态:", channel.state); + + // 创建承诺交易 + const commitmentTransaction = "0x" + "00".repeat(32); // 示例交易数据 + + // 调用提交承诺交易方法 + console.log("\n正在调用 submitCommitmentTransaction 方法..."); + await sdk.dev.submitCommitmentTransaction({ + channel_id: channel.channel_id, + commitment_transaction: commitmentTransaction, + }); + console.log("承诺交易提交成功"); + + // 验证通道状态 + console.log("\n验证通道状态..."); + const updatedChannels = await sdk.channel.listChannels(); + const updatedChannel = updatedChannels.find( + (c) => c.channel_id === channel.channel_id, + ); + + if (updatedChannel) { + console.log("通道新状态:", updatedChannel.state); + } else { + console.log("找不到通道,可能已被关闭"); + } + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("提交承诺交易时发生错误:", error); + } + } +} + +async function testUpdateChannel() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("\n开始测试更新通道...\n"); + + try { + // 获取可用的通道列表 + const channels = await testListChannels(); + + if (channels.length === 0) { + console.log("没有可用的通道可以更新"); + return; + } + + // 选择第一个通道进行更新操作 + const channelToUpdate = channels[0]; + console.log("\n准备更新的通道信息:"); + console.log("通道ID:", channelToUpdate.channel_id); + console.log("对等节点ID:", channelToUpdate.peer_id); + console.log("状态:", channelToUpdate.state); + + // 调用更新通道方法,禁用通道 + console.log("\n正在调用 updateChannel 方法..."); + await sdk.channel.updateChannel({ + channel_id: channelToUpdate.channel_id, + enabled: false, + }); + console.log("通道更新成功"); + + // 验证通道状态 + console.log("\n验证通道状态..."); + const updatedChannels = await sdk.channel.listChannels(); + const updatedChannel = updatedChannels.find( + (c) => c.channel_id === channelToUpdate.channel_id, + ); + + if (updatedChannel) { + console.log("通道状态:", updatedChannel.state); + console.log("是否启用:", updatedChannel.enabled ? "是" : "否"); + } + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("更新通道失败:", error.message); + } + } + } catch (error) { + console.error("测试更新通道时发生错误:", error); + } +} + +async function main() { + try { + await testListChannels(); + await testUpdateAndShutdownChannel(); + console.log("\n所有测试完成!"); + } catch (error) { + console.error("测试过程中发生错误:", error); + } +} + +// 运行测试 +console.log("开始运行通道相关测试...\n"); + +main() + .then(() => console.log("\n所有测试完成!")) + .catch(console.error); diff --git a/packages/fiber/test/info.cjs b/packages/fiber/test/info.cjs new file mode 100644 index 00000000..b7fabf52 --- /dev/null +++ b/packages/fiber/test/info.cjs @@ -0,0 +1,108 @@ +const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); + +// 自定义错误处理函数 +function handleRPCError(error) { + if (error.error && error.error.code === -32601) { + console.error("错误: 节点可能未运行或 RPC 方法不存在"); + console.error("请确保:"); + console.error("1. Fiber 节点已启动"); + console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); + console.error("3. 节点 RPC 接口可用"); + } else if (error.error && error.error.code === -32602) { + console.error("错误: 参数无效"); + console.error("请检查:"); + console.error("1. 参数类型是否正确"); + console.error("2. 参数值是否在有效范围内"); + console.error("3. 必填参数是否都已提供"); + } else { + console.error("RPC 错误:", error.message); + if (error.error && error.error.data) { + console.error("错误详情:", error.error.data); + } + } +} + +// 将十六进制字符串转换为数字 +function hexToNumber(hex) { + if (!hex) return 0; + return parseInt(hex.replace("0x", ""), 16); +} + +async function testNodeInfo() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("开始测试获取节点信息...\n"); + + try { + // 获取节点信息 + console.log("正在调用 nodeInfo 方法..."); + const nodeInfo = await sdk.info.nodeInfo(); + + // 类型检查 + if (!nodeInfo || typeof nodeInfo !== "object") { + throw new Error("返回的节点信息格式不正确"); + } + + // 输出详细信息 + console.log("\n节点详细信息:"); + console.log("版本:", nodeInfo.version); + console.log("提交哈希:", nodeInfo.commit_hash); + console.log("节点ID:", nodeInfo.node_id); + console.log("节点名称:", nodeInfo.node_name || "未设置"); + console.log( + "地址列表:", + nodeInfo.addresses.length > 0 ? nodeInfo.addresses.join(", ") : "无", + ); + console.log("链哈希:", nodeInfo.chain_hash); + console.log( + "自动接受最小CKB资金金额:", + hexToNumber(nodeInfo.open_channel_auto_accept_min_ckb_funding_amount), + ); + console.log( + "自动接受通道CKB资金金额:", + hexToNumber(nodeInfo.auto_accept_channel_ckb_funding_amount), + ); + console.log("通道数量:", hexToNumber(nodeInfo.channel_count)); + console.log( + "待处理通道数量:", + hexToNumber(nodeInfo.pending_channel_count), + ); + console.log("对等节点数量:", hexToNumber(nodeInfo.peers_count)); + + if (nodeInfo.udt_cfg_infos && nodeInfo.udt_cfg_infos.length > 0) { + console.log("\nUDT配置信息:"); + nodeInfo.udt_cfg_infos.forEach((udt, index) => { + console.log(`\nUDT ${index + 1}:`); + console.log("名称:", udt.name); + console.log("自动接受金额:", hexToNumber(udt.auto_accept_amount)); + }); + } + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("获取节点信息失败:", error.message); + } + } + + console.log("\n测试完成!"); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("测试过程中发生错误:", error.message); + } + } +} + +// 运行测试 +console.log("开始运行节点信息相关测试...\n"); + +testNodeInfo() + .then(() => console.log("\n所有测试完成!")) + .catch(console.error); diff --git a/packages/fiber/test/invoice.cjs b/packages/fiber/test/invoice.cjs new file mode 100644 index 00000000..caed7d55 --- /dev/null +++ b/packages/fiber/test/invoice.cjs @@ -0,0 +1,184 @@ +const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); + +// 自定义错误处理函数 +function handleRPCError(error) { + if (error.error && error.error.code === -32601) { + console.error("错误: 节点可能未运行或 RPC 方法不存在"); + console.error("请确保:"); + console.error("1. Fiber 节点已启动"); + console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); + console.error("3. 节点 RPC 接口可用"); + } else if (error.error && error.error.code === -32602) { + console.error("错误: 参数无效"); + console.error("请检查:"); + console.error("1. 参数类型是否正确"); + console.error("2. 参数值是否在有效范围内"); + console.error("3. 必填参数是否都已提供"); + } else { + console.error("RPC 错误:", error.message); + if (error.error && error.error.data) { + console.error("错误详情:", error.error.data); + } + } +} + +// 将十六进制字符串转换为数字 +function hexToNumber(hex) { + return parseInt(hex.replace("0x", ""), 16); +} + +async function testNewInvoice() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("开始测试创建新发票...\n"); + + try { + // 创建新发票 + console.log("正在调用 newInvoice 方法..."); + const invoice = await sdk.invoice.newInvoice({ + amount: BigInt(1000), + description: "测试发票", + expiry: BigInt(3600), // 1小时过期 + payment_secret: "secret", // 可选 + }); + console.log("发票信息:", JSON.stringify(invoice, null, 2)); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("创建发票失败:", error.message); + } + } + + console.log("\n测试完成!"); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("测试过程中发生错误:", error.message); + } + } +} + +async function testParseInvoice() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("开始测试解析发票...\n"); + + try { + // 解析发票 + console.log("正在调用 parseInvoice 方法..."); + const invoiceString = "invoice_string"; // 替换为实际的发票字符串 + const invoice = await sdk.invoice.parseInvoice(invoiceString); + console.log("解析结果:", JSON.stringify(invoice, null, 2)); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("解析发票失败:", error.message); + } + } + + console.log("\n测试完成!"); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("测试过程中发生错误:", error.message); + } + } +} + +async function testGetInvoice() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("开始测试获取发票...\n"); + + try { + // 获取发票 + console.log("正在调用 getInvoice 方法..."); + const paymentHash = "payment_hash"; // 替换为实际的 payment_hash + const invoice = await sdk.invoice.getInvoice(paymentHash); + console.log("发票信息:", JSON.stringify(invoice, null, 2)); + + // 输出详细信息 + console.log("\n发票详细信息:"); + console.log("状态:", invoice.status); + console.log("发票地址:", invoice.invoice_address); + console.log("发票内容:", JSON.stringify(invoice.invoice, null, 2)); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("获取发票失败:", error.message); + } + } + + console.log("\n测试完成!"); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("测试过程中发生错误:", error.message); + } + } +} + +async function testCancelInvoice() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("开始测试取消发票...\n"); + + try { + // 取消发票 + console.log("正在调用 cancelInvoice 方法..."); + const paymentHash = "payment_hash"; // 替换为实际的 payment_hash + await sdk.invoice.cancelInvoice(paymentHash); + console.log("发票取消成功"); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("取消发票失败:", error.message); + } + } + + console.log("\n测试完成!"); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("测试过程中发生错误:", error.message); + } + } +} + +// 运行所有测试 +console.log("开始运行发票相关测试...\n"); + +testNewInvoice() + .then(() => testParseInvoice()) + .then(() => testGetInvoice()) + .then(() => testCancelInvoice()) + .then(() => console.log("\n所有测试完成!")) + .catch(console.error); \ No newline at end of file diff --git a/packages/fiber/test/payment.cjs b/packages/fiber/test/payment.cjs new file mode 100644 index 00000000..b1bbe586 --- /dev/null +++ b/packages/fiber/test/payment.cjs @@ -0,0 +1,129 @@ +const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); + +// 自定义错误处理函数 +function handleRPCError(error) { + if (error.error && error.error.code === -32601) { + console.error("错误: 节点可能未运行或 RPC 方法不存在"); + console.error("请确保:"); + console.error("1. Fiber 节点已启动"); + console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); + console.error("3. 节点 RPC 接口可用"); + } else if (error.error && error.error.code === -32602) { + console.error("错误: 参数无效"); + console.error("请检查:"); + console.error("1. 参数类型是否正确"); + console.error("2. 参数值是否在有效范围内"); + console.error("3. 必填参数是否都已提供"); + } else { + console.error("RPC 错误:", error.message); + if (error.error && error.error.data) { + console.error("错误详情:", error.error.data); + } + } +} + +// 将十六进制字符串转换为数字 +function hexToNumber(hex) { + return parseInt(hex.replace("0x", ""), 16); +} + +async function testSendPayment() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("开始测试发送支付...\n"); + + try { + // 发送支付 + console.log("正在调用 sendPayment 方法..."); + await sdk.payment.sendPayment({ + payment_hash: "payment_hash", // 替换为实际的 payment_hash + amount: BigInt(1000), + fee_rate: BigInt(100), + custom_records: { + // 自定义记录 + "key1": "value1", + "key2": "value2", + }, + }); + console.log("支付发送成功"); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("发送支付失败:", error.message); + } + } + + console.log("\n测试完成!"); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("测试过程中发生错误:", error.message); + } + } +} + +async function testGetPayment() { + try { + // 初始化SDK + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + + console.log("开始测试获取支付...\n"); + + try { + // 获取支付 + console.log("正在调用 getPayment 方法..."); + const paymentHash = "payment_hash"; // 替换为实际的 payment_hash + const payment = await sdk.payment.getPayment(paymentHash); + console.log("支付信息:", JSON.stringify(payment, null, 2)); + + // 输出详细信息 + console.log("\n支付详细信息:"); + console.log("状态:", payment.status); + console.log("支付哈希:", payment.payment_hash); + console.log( + "创建时间:", + new Date(hexToNumber(payment.created_at)).toLocaleString(), + ); + console.log( + "最后更新时间:", + new Date(hexToNumber(payment.last_updated_at)).toLocaleString(), + ); + if (payment.failed_error) { + console.log("失败原因:", payment.failed_error); + } + console.log("手续费:", hexToNumber(payment.fee)); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("获取支付失败:", error.message); + } + } + + console.log("\n测试完成!"); + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("测试过程中发生错误:", error.message); + } + } +} + +// 运行所有测试 +console.log("开始运行支付相关测试...\n"); + +testSendPayment() + .then(() => testGetPayment()) + .then(() => console.log("\n所有测试完成!")) + .catch(console.error); \ No newline at end of file diff --git a/packages/fiber/test/peer.cjs b/packages/fiber/test/peer.cjs new file mode 100644 index 00000000..7406fbaa --- /dev/null +++ b/packages/fiber/test/peer.cjs @@ -0,0 +1,198 @@ +const { FiberSDK } = require("../dist.commonjs/index.js"); + +// 自定义错误处理函数 +function handleRPCError(error) { + if (error.error && error.error.code === -32601) { + console.error("错误: 节点可能未运行或 RPC 方法不存在"); + console.error("请确保:"); + console.error("1. Fiber 节点已启动"); + console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); + console.error("3. 节点 RPC 接口可用"); + } else if (error.error && error.error.code === -32602) { + console.error("错误: 参数无效"); + console.error("请检查:"); + console.error("1. 参数类型是否正确"); + console.error("2. 参数值是否在有效范围内"); + console.error("3. 必填参数是否都已提供"); + } else { + console.error("RPC 错误:", error.message); + if (error.error && error.error.data) { + console.error("错误详情:", error.error.data); + } + } +} + +async function testConnectPeer() { + console.log("\n开始测试连接节点...\n"); + + try { + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 30000, + }); + + // 使用文档中的测试节点地址 + const peerAddress = "/ip4/18.162.235.225/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; + console.log("正在调用 connect_peer 方法,连接地址:", peerAddress); + + await sdk.peer.connectPeer({ address: peerAddress }); + console.log("连接节点成功"); + } catch (error) { + console.error("连接节点失败:", error.message); + handleRPCError(error); + } + + console.log("\n测试完成!"); +} + +async function testOpenChannel() { + console.log("\n开始测试打开通道...\n"); + + try { + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 30000, + }); + + const peerId = "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; + console.log("正在调用 open_channel 方法,节点 ID:", peerId); + + const result = await sdk.channel.openChannel({ + peer_id: peerId, + funding_amount: "0x174876e800", // 100 CKB + public: true, + }); + + console.log("打开通道结果:", result); + } catch (error) { + console.error("打开通道失败:", error.message); + handleRPCError(error); + } + + console.log("\n测试完成!"); +} + +async function testListChannels() { + console.log("\n开始测试列出通道...\n"); + + try { + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 30000, + }); + + const peerId = "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; + console.log("正在调用 list_channels 方法,节点 ID:", peerId); + + const result = await sdk.channel.listChannels({ + peer_id: peerId, + }); + + console.log("通道列表:", result); + } catch (error) { + console.error("列出通道失败:", error.message); + handleRPCError(error); + } + + console.log("\n测试完成!"); +} + +async function testCloseChannel() { + console.log("\n开始测试关闭通道...\n"); + + try { + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 30000, + }); + + // 获取通道列表 + const channels = await sdk.channel.listChannels({ + peer_id: "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo", + }); + + // 关闭所有通道 + for (const channel of channels) { + console.log("正在关闭通道:", channel.channel_id); + await sdk.channel.shutdownChannel({ + channel_id: channel.channel_id, + close_script: { + code_hash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: "0xea076cd91e879a3c189d94068e1584c3fbcc1876" + }, + fee_rate: "0x3FC" + }); + console.log("通道关闭成功:", channel.channel_id); + } + } catch (error) { + console.error("关闭通道失败:", error.message); + handleRPCError(error); + } + + console.log("\n测试完成!"); +} + +async function cleanupNegotiatingChannels() { + console.log("\n开始清理处于 NEGOTIATING_FUNDING 状态的通道...\n"); + + try { + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 30000, + }); + + // 获取通道列表 + const channels = await sdk.channel.listChannels({ + peer_id: "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo", + }); + + // 过滤出处于 NEGOTIATING_FUNDING 状态的通道 + const negotiatingChannels = channels.filter( + channel => channel.state.state_name === "NEGOTIATING_FUNDING" + ); + + console.log(`找到 ${negotiatingChannels.length} 个处于 NEGOTIATING_FUNDING 状态的通道`); + + // 关闭这些通道 + for (const channel of negotiatingChannels) { + console.log("正在关闭通道:", channel.channel_id); + try { + await sdk.channel.shutdownChannel({ + channel_id: channel.channel_id, + close_script: { + code_hash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: "0xea076cd91e879a3c189d94068e1584c3fbcc1876" + }, + fee_rate: "0x3FC", + force: true + }); + console.log("通道关闭成功:", channel.channel_id); + } catch (closeError) { + console.error("关闭通道失败:", channel.channel_id, closeError.message); + } + } + } catch (error) { + console.error("清理通道失败:", error.message); + handleRPCError(error); + } + + console.log("\n清理完成!"); +} + +async function main() { + // 1. 首先清理处于 NEGOTIATING_FUNDING 状态的通道 + await cleanupNegotiatingChannels(); + + // 2. 然后建立网络连接 + await testConnectPeer(); + + // 3. 打开新通道 + await testOpenChannel(); + + // 4. 最后查询通道状态 + await testListChannels(); +} + +main().catch(console.error); From d6b9a9d723713bd837862f81665f070fef676df0 Mon Sep 17 00:00:00 2001 From: Jack <6464072@qq.com> Date: Wed, 9 Apr 2025 15:59:16 +0800 Subject: [PATCH 05/15] feat:fiber sdk demo init --- .env | 2 + packages/demo/package.json | 1 + packages/demo/src/app/fiber/page.tsx | 48 +++ packages/demo/src/app/page.tsx | 7 + packages/fiber/examples/basic-usage.html | 1 + packages/fiber/package.json | 12 +- packages/fiber/src/client.ts | 5 +- packages/fiber/src/modules/channel.ts | 33 +- packages/fiber/src/modules/info.ts | 6 +- packages/fiber/src/modules/invoice.ts | 14 +- packages/fiber/src/modules/peer.ts | 10 +- packages/fiber/src/rpc.ts | 4 +- packages/fiber/test/abandon.cjs | 40 -- packages/fiber/test/channel.cjs | 474 +++++++---------------- packages/fiber/test/info.cjs | 94 ++--- packages/fiber/test/invoice.cjs | 164 ++++---- packages/fiber/test/payment.cjs | 15 +- packages/fiber/test/peer.cjs | 219 ++++------- packages/fiber/tsconfig.json | 6 +- pnpm-lock.yaml | 17 +- 20 files changed, 466 insertions(+), 706 deletions(-) create mode 100644 .env create mode 100644 packages/demo/src/app/fiber/page.tsx create mode 100644 packages/fiber/examples/basic-usage.html delete mode 100644 packages/fiber/test/abandon.cjs diff --git a/.env b/.env new file mode 100644 index 00000000..34dc658f --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +process.env.PRIVATE_KEY = + "0x0000000000000000000000000000000000000000000000000000000000000000"; diff --git a/packages/demo/package.json b/packages/demo/package.json index 47c0d551..96993112 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -27,6 +27,7 @@ "@ckb-ccc/lumos-patches": "workspace:*", "@ckb-ccc/ssri": "workspace:*", "@ckb-ccc/udt": "workspace:*", + "@ckb-lumos/ckb-indexer": "^0.24.0-next.1", "@ckb-lumos/common-scripts": "^0.24.0-next.1", "@ckb-lumos/config-manager": "^0.24.0-next.1", diff --git a/packages/demo/src/app/fiber/page.tsx b/packages/demo/src/app/fiber/page.tsx new file mode 100644 index 00000000..f3d214e1 --- /dev/null +++ b/packages/demo/src/app/fiber/page.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { Button } from "@/src/components/Button"; +import { useEffect, useState } from "react"; +import { TextInput } from "@/src/components/Input"; +import { ccc } from "@ckb-ccc/connector-react"; +import { useRouter } from "next/navigation"; +import { ButtonsPanel } from "@/src/components/ButtonsPanel"; +import { FiberSDK } from "@ckb-ccc/fiber"; + +export default function Page() { + const router = useRouter(); + const { client } = ccc.useCcc(); + const [endpoint, setEndpoint] = useState(""); + + const initSdk = () => { + const fiber = new FiberSDK({ + endpoint: endpoint, + timeout: 5000, + }); + if (fiber) { + console.log(fiber); + console.log("Fiber SDK initialized"); + } else { + console.log("Fiber SDK initialization failed"); + } + }; + return ( +
+ + + + + +
+ ); +} diff --git a/packages/demo/src/app/page.tsx b/packages/demo/src/app/page.tsx index c20210d6..de07b731 100644 --- a/packages/demo/src/app/page.tsx +++ b/packages/demo/src/app/page.tsx @@ -38,6 +38,13 @@ export default function Home() { > Private Key + router.push("/fiber")} + iconName="Key" + className="text-emerald-500" + > + Fiber + */ async abandonChannel(channelId: Hash256): Promise { + console.log(channelId); if (!channelId) { - throw new Error("通道ID不能为空"); + throw new Error("Channel ID cannot be empty"); } if (!channelId.startsWith("0x")) { - throw new Error("通道ID必须以0x开头"); + throw new Error("Channel ID must start with 0x"); } if (channelId.length !== 66) { - // 0x + 64位哈希 - throw new Error("通道ID长度无效"); + // 0x + 64-bit hash + throw new Error("Invalid channel ID length"); } try { - // 先检查通道是否存在 + // Check if channel exists const channels = await this.listChannels(); const channelExists = channels.some( (channel) => channel.channel_id === channelId, ); if (!channelExists) { - throw new Error(`找不到ID为 ${channelId} 的通道`); + throw new Error(`Channel with ID ${channelId} not found`); } - return this.client.call("abandon_channel", [channelId]); + return this.client.call("abandon_channel", [{ channel_id: channelId }]); } catch (error) { if (error instanceof Error) { - throw new Error(`放弃通道失败: ${error.message}`); + throw new Error(`Failed to abandon channel: ${error.message}`); } throw error; } } /** - * 列出通道 + * List channels */ async listChannels(): Promise { const response = await this.client.call<{ channels: Channel[] }>( @@ -92,7 +93,7 @@ export class ChannelModule { } /** - * 关闭通道 + * Shutdown channel */ async shutdownChannel(params: { channel_id: Hash256; @@ -104,7 +105,7 @@ export class ChannelModule { } /** - * 更新通道 + * Update channel */ async updateChannel(params: { channel_id: Hash256; diff --git a/packages/fiber/src/modules/info.ts b/packages/fiber/src/modules/info.ts index 3be23e7c..a919cd0a 100644 --- a/packages/fiber/src/modules/info.ts +++ b/packages/fiber/src/modules/info.ts @@ -5,9 +5,9 @@ export class InfoModule { constructor(private client: FiberClient) {} /** - * 获取节点信息 - * @returns 返回节点的详细信息,包括节点名称、地址、ID等 - * @throws {Error} 当无法获取节点信息时抛出错误 + * Get node information + * @returns Returns detailed node information, including node name, address, ID, etc. + * @throws {Error} Throws error when unable to get node information */ async nodeInfo(): Promise { return this.client.call("node_info", []); diff --git a/packages/fiber/src/modules/invoice.ts b/packages/fiber/src/modules/invoice.ts index b6d0b926..1e4c7343 100644 --- a/packages/fiber/src/modules/invoice.ts +++ b/packages/fiber/src/modules/invoice.ts @@ -5,7 +5,7 @@ export class InvoiceModule { constructor(private client: FiberClient) {} /** - * 创建新发票 + * Create a new invoice */ async newInvoice(params: { amount: bigint; @@ -17,27 +17,27 @@ export class InvoiceModule { } /** - * 解析发票 + * Parse an invoice */ async parseInvoice(invoice: string): Promise { - return this.client.call("parse_invoice", [invoice]); + return this.client.call("parse_invoice", [{ invoice }]); } /** - * 获取发票 + * Get invoice details */ async getInvoice(payment_hash: string): Promise<{ status: CkbInvoiceStatus; invoice_address: string; invoice: CkbInvoice; }> { - return this.client.call("get_invoice", [payment_hash]); + return this.client.call("get_invoice", [{ payment_hash }]); } /** - * 取消发票 + * Cancel an invoice */ async cancelInvoice(payment_hash: string): Promise { - return this.client.call("cancel_invoice", [payment_hash]); + return this.client.call("cancel_invoice", [{ payment_hash }]); } } diff --git a/packages/fiber/src/modules/peer.ts b/packages/fiber/src/modules/peer.ts index 721c4e62..76158e13 100644 --- a/packages/fiber/src/modules/peer.ts +++ b/packages/fiber/src/modules/peer.ts @@ -4,17 +4,17 @@ export class PeerModule { constructor(private client: FiberClient) {} /** - * 连接对等节点 - * @param address 节点地址 + * Connect to a peer node + * @param address Node address */ async connectPeer(address: string): Promise { - return this.client.call("connect_peer", [address]); + return this.client.call("connect_peer", [{ address }]); } /** - * 断开对等节点连接 + * Disconnect from a peer node */ async disconnectPeer(peer_id: string): Promise { - return this.client.call("disconnect_peer", [peer_id]); + return this.client.call("disconnect_peer", [{ peer_id }]); } } diff --git a/packages/fiber/src/rpc.ts b/packages/fiber/src/rpc.ts index 5bdea9d8..2e55cd39 100644 --- a/packages/fiber/src/rpc.ts +++ b/packages/fiber/src/rpc.ts @@ -1,4 +1,4 @@ -import { RequestorJsonRpc, RequestorJsonRpcConfig } from "@ckb-ccc/core/barrel"; +import { RequestorJsonRpc, RequestorJsonRpcConfig } from "@ckb-ccc/core"; export type JsonRpcConfig = RequestorJsonRpcConfig & { requestor?: RequestorJsonRpc; @@ -45,7 +45,7 @@ export abstract class FiberJsonRpc { constructor(url_: string, config?: JsonRpcConfig) { this.requestor = config?.requestor ?? - new RequestorJsonRpc(url_, config, (errAny) => { + new RequestorJsonRpc(url_, config, (errAny: unknown) => { if ( typeof errAny !== "object" || errAny === null || diff --git a/packages/fiber/test/abandon.cjs b/packages/fiber/test/abandon.cjs deleted file mode 100644 index dabf22fe..00000000 --- a/packages/fiber/test/abandon.cjs +++ /dev/null @@ -1,40 +0,0 @@ -const { FiberSDK } = require('../dist.commonjs'); - -const sdk = new FiberSDK({ - endpoint: 'http://ec2-18-162-235-225.ap-east-1.compute.amazonaws.com:8119', - timeout: 30000 -}); - -async function abandonNegotiatingChannels() { - try { - console.log('开始放弃处于 NEGOTIATING_FUNDING 状态的通道...\n'); - - // 首先获取所有通道 - const channels = await sdk.channel.listChannels(); - console.log(`找到 ${channels.length} 个通道`); - - // 筛选出处于 NEGOTIATING_FUNDING 状态的通道 - const negotiatingChannels = channels.filter( - channel => channel.state.state_name === 'NEGOTIATING_FUNDING' - ); - - console.log(`找到 ${negotiatingChannels.length} 个处于 NEGOTIATING_FUNDING 状态的通道`); - - // 遍历并放弃这些通道 - for (const channel of negotiatingChannels) { - console.log(`正在放弃通道: ${channel.channel_id}`); - try { - await sdk.channel.abandonChannel(channel.channel_id); - console.log(`成功放弃通道: ${channel.channel_id}`); - } catch (error) { - console.error(`放弃通道失败: ${channel.channel_id}`, error); - } - } - - console.log('\n放弃通道完成!'); - } catch (error) { - console.error('发生错误:', error); - } -} - -abandonNegotiatingChannels(); \ No newline at end of file diff --git a/packages/fiber/test/channel.cjs b/packages/fiber/test/channel.cjs index 5898f4be..33aa2dcd 100644 --- a/packages/fiber/test/channel.cjs +++ b/packages/fiber/test/channel.cjs @@ -1,28 +1,32 @@ const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); -// 自定义错误处理函数 +// Custom error handling function function handleRPCError(error) { if (error.error && error.error.code === -32601) { - console.error("错误: 节点可能未运行或 RPC 方法不存在"); - console.error("请确保:"); - console.error("1. Fiber 节点已启动"); - console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); - console.error("3. 节点 RPC 接口可用"); + console.error( + "Error: Node may not be running or RPC method does not exist", + ); + console.error("Please ensure:"); + console.error("1. Fiber node is started"); + console.error( + "2. Node RPC address is correct (current: http://127.0.0.1:8227)", + ); + console.error("3. Node RPC interface is available"); } else if (error.error && error.error.code === -32602) { - console.error("错误: 参数无效"); - console.error("请检查:"); - console.error("1. 参数类型是否正确"); - console.error("2. 参数值是否在有效范围内"); - console.error("3. 必填参数是否都已提供"); + console.error("Error: Invalid parameters"); + console.error("Please check:"); + console.error("1. Parameter types are correct"); + console.error("2. Parameter values are within valid range"); + console.error("3. All required parameters are provided"); } else { - console.error("RPC 错误:", error.message); + console.error("RPC Error:", error.message); if (error.error && error.error.data) { - console.error("错误详情:", error.error.data); + console.error("Error details:", error.error.data); } } } -// 将十六进制字符串转换为数字 +// Convert hexadecimal string to number function hexToNumber(hex) { if (!hex) return 0; return parseInt(hex.replace("0x", ""), 16); @@ -30,52 +34,56 @@ function hexToNumber(hex) { async function testListChannels() { try { - // 初始化SDK + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - console.log("开始测试列出通道...\n"); + console.log("Starting channel listing test...\n"); try { - // 列出通道 - console.log("正在调用 listChannels 方法..."); + // List channels + console.log("Calling listChannels method..."); const channels = await sdk.channel.listChannels(); - // 输出原始数据 - console.log("原始数据:", JSON.stringify(channels, null, 2)); + // Output raw data + console.log("Raw data:", JSON.stringify(channels, null, 2)); - // 类型检查 + // Type check if (!Array.isArray(channels)) { - throw new Error("返回的通道列表格式不正确"); + throw new Error("Invalid channel list format"); } - // 输出详细信息 + // Output detailed information if (channels.length > 0) { - console.log("\n通道详细信息:"); + console.log("\nChannel details:"); channels.forEach((channel, index) => { - console.log(`\n通道 ${index + 1}:`); - console.log("通道ID:", channel.channel_id); - console.log("对等节点ID:", channel.peer_id); - console.log("状态:", channel.state); - console.log("本地余额:", hexToNumber(channel.local_balance)); - console.log("远程余额:", hexToNumber(channel.remote_balance)); + console.log(`\nChannel ${index + 1}:`); + console.log("Channel ID:", channel.channel_id); + console.log("Peer ID:", channel.peer_id); + console.log("State:", channel.state_name); + console.log("State Flags:", channel.state_flags); + console.log("Local Balance:", hexToNumber(channel.local_balance)); + console.log("Remote Balance:", hexToNumber(channel.remote_balance)); console.log( - "创建时间:", + "Created At:", new Date(hexToNumber(channel.created_at)).toLocaleString(), ); - console.log("是否公开:", channel.is_public ? "是" : "否"); - console.log("是否启用:", channel.is_enabled ? "是" : "否"); - console.log("TLC 过期增量:", hexToNumber(channel.tlc_expiry_delta)); - console.log("TLC 最小金额:", hexToNumber(channel.tlc_min_value)); + console.log("Is Public:", channel.is_public ? "Yes" : "No"); + console.log("Is Enabled:", channel.is_enabled ? "Yes" : "No"); console.log( - "TLC 费用比例:", + "TLC Expiry Delta:", + hexToNumber(channel.tlc_expiry_delta), + ); + console.log("TLC Min Value:", hexToNumber(channel.tlc_min_value)); + console.log( + "TLC Fee Proportion:", hexToNumber(channel.tlc_fee_proportional_millionths), ); }); } else { - console.log("当前没有通道"); + console.log("No channels available"); } return channels; @@ -83,7 +91,7 @@ async function testListChannels() { if (error.error) { handleRPCError(error); } else { - console.error("列出通道失败:", error.message); + console.error("Failed to list channels:", error.message); } return []; } @@ -91,359 +99,141 @@ async function testListChannels() { if (error.error) { handleRPCError(error); } else { - console.error("测试过程中发生错误:", error.message); + console.error("Error during test:", error.message); } return []; } } -async function testUpdateAndShutdownChannel() { - console.log("\n开始测试更新和关闭通道..."); - - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", - timeout: 30000, - }); - - try { - // 获取可用通道列表 - const channels = await sdk.channel.listChannels(); - - if (!channels || channels.length === 0) { - console.log("没有可用的通道"); - return; - } - - // 选择第一个通道进行测试 - const channel = channels[0]; - console.log("\n准备更新的通道信息:"); - console.log("通道ID:", channel.channel_id); - console.log("对等节点ID:", channel.peer_id); - console.log("状态:", channel.state); - - console.log("\n正在调用 updateChannel 方法禁用通道..."); - await sdk.channel.updateChannel({ - channel_id: channel.channel_id, - enabled: false, - tlc_expiry_delta: 1000000, // 设置大于 900000 的值 - tlc_min_amount: 0, - tlc_fee_rate: 0, - }); - console.log("通道已成功禁用"); - - console.log("\n正在调用 shutdownChannel 方法关闭通道..."); - await sdk.channel.shutdownChannel({ - channel_id: channel.channel_id, - close_script: "", - force: true, - fee_rate: 1000, - }); - console.log("通道已成功关闭"); - } catch (error) { - console.log("更新和关闭通道失败:", error.message); - } -} - -async function testAcceptChannel() { +async function testCloseChannels() { try { - // 初始化SDK + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - console.log("\n开始测试接受通道...\n"); + console.log("\nStarting channel closure test...\n"); try { - // 获取可用的通道列表 + // Get available channels const channels = await testListChannels(); if (channels.length === 0) { - console.log("没有可用的通道可以接受"); - return; - } - - // 选择第一个通道进行接受操作 - const channelToAccept = channels[0]; - console.log("\n准备接受的通道信息:"); - console.log("通道ID:", channelToAccept.channel_id); - console.log("对等节点ID:", channelToAccept.peer_id); - console.log("状态:", channelToAccept.state); - - // 检查通道状态是否适合接受 - if (channelToAccept.state.state_name !== "NEGOTIATING_FUNDING") { - console.log("通道不处于资金协商状态,无法接受"); + console.log("No channels available to close"); return; } - // 调用接受通道方法 - console.log("\n正在调用 acceptChannel 方法..."); - await sdk.channel.acceptChannel({ - temporary_channel_id: channelToAccept.channel_id, - funding_amount: BigInt(100000000), - max_tlc_value_in_flight: BigInt(100000000), - max_tlc_number_in_flight: BigInt(10), - tlc_min_value: BigInt(1000), - tlc_fee_proportional_millionths: BigInt(1000), - tlc_expiry_delta: BigInt(100), + // Select first channel for closure + const channelToClose = channels[0]; + console.log("\nChannel to close:"); + console.log("Channel ID:", channelToClose.channel_id); + console.log("Peer ID:", channelToClose.peer_id); + console.log("State:", channelToClose.state); + + // Call shutdown channel method + console.log("\nCalling shutdownChannel method..."); + await sdk.channel.shutdownChannel({ + channel_id: channelToClose.channel_id, + close_script: { + code_hash: + "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: "0xea076cd91e879a3c189d94068e1584c3fbcc1876", + }, + fee_rate: "0x3FC", + force: false, }); - console.log("通道接受成功"); + console.log("Channel closed successfully"); - // 验证通道状态 - console.log("\n验证通道状态..."); - const updatedChannels = await sdk.channel.listChannels(); - const acceptedChannel = updatedChannels.find( - (c) => c.channel_id === channelToAccept.channel_id, - ); - - if (acceptedChannel) { - console.log("通道状态:", acceptedChannel.state); - } + // Verify channel status + console.log("\nVerifying channel status..."); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("接受通道失败:", error.message); + console.error("Failed to close channel:", error.message); } } } catch (error) { - console.error("测试接受通道时发生错误:", error); + console.error("Error during channel closure test:", error); } } -async function testAbandonChannel() { - try { - // 初始化SDK - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", - timeout: 5000, - }); - - console.log("\n开始测试放弃通道...\n"); - - try { - // 获取可用的通道列表 - const channels = await testListChannels(); - - if (channels.length === 0) { - console.log("没有可用的通道可以放弃"); - return; - } - - // 选择第一个通道进行放弃操作 - const channelToAbandon = channels[0]; - console.log("\n准备放弃的通道信息:"); - console.log("通道ID:", channelToAbandon.channel_id); - console.log("对等节点ID:", channelToAbandon.peer_id); - console.log("状态:", channelToAbandon.state); - - // 调用放弃通道方法 - console.log("\n正在调用 abandonChannel 方法..."); - await sdk.channel.abandonChannel(channelToAbandon.channel_id); - console.log("通道放弃成功"); - - // 验证通道状态 - console.log("\n验证通道状态..."); - const updatedChannels = await sdk.channel.listChannels(); - const abandonedChannel = updatedChannels.find( - (c) => c.channel_id === channelToAbandon.channel_id, - ); - - if (!abandonedChannel) { - console.log("验证成功:通道已被放弃"); - } else { - console.log("通道状态:", abandonedChannel.state); - } - } catch (error) { - if (error.error) { - handleRPCError(error); - } else { - console.error("放弃通道失败:", error.message); - } - } - } catch (error) { - console.error("测试放弃通道时发生错误:", error); - } +async function testNewChannel() { + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + const peerId = "QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89"; + console.log("Calling open_channel method, Peer ID:", peerId); + const result = await sdk.channel.openChannel({ + peer_id: peerId, + funding_amount: "0xba43b7400", // 100 CKB + public: true, + }); + console.log("Open channel result:", result); } -async function testRemoveWatchChannel() { - try { - // 初始化SDK - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", - timeout: 5000, - }); - - console.log("\n开始测试移除监视通道...\n"); - - // 获取可用的通道列表 - const channels = await testListChannels(); - - if (channels.length === 0) { - console.log("没有可用的通道可以移除"); - return; - } - - // 选择第一个通道进行移除操作 - const channelToRemove = channels[0]; - console.log("\n准备移除的通道信息:"); - console.log("通道ID:", channelToRemove.channel_id); - console.log("对等节点ID:", channelToRemove.peer_id); - console.log("状态:", channelToRemove.state); - - // 调用移除监视通道方法 - console.log("\n正在调用 removeWatchChannel 方法..."); - await sdk.dev.removeWatchChannel(channelToRemove.channel_id); - console.log("移除监视通道成功"); - - // 验证通道状态 - console.log("\n验证通道状态..."); - const updatedChannels = await sdk.channel.listChannels(); - const removedChannel = updatedChannels.find( - (c) => c.channel_id === channelToRemove.channel_id, - ); +async function testUpdateAndShutdownChannel() { + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 5000, + }); + const channels = await testListChannels(); - if (!removedChannel) { - console.log("验证成功:通道已被移除"); - } else { - console.log("通道仍然存在,状态:", removedChannel.state); - } - } catch (error) { - if (error.error) { - handleRPCError(error); - } else { - console.error("移除监视通道时发生错误:", error); - } + if (channels.length === 0) { + console.log("No channels available to close"); + return; } -} - -async function testSubmitCommitmentTransaction() { - try { - // 初始化SDK - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", - timeout: 5000, - }); - - console.log("\n开始测试提交承诺交易...\n"); - - // 获取可用的通道列表 - const channels = await testListChannels(); - - if (channels.length === 0) { - console.log("没有可用的通道"); - return; - } - // 选择第一个通道 - const channel = channels[0]; - console.log("\n准备提交承诺交易的通道信息:"); - console.log("通道ID:", channel.channel_id); - console.log("对等节点ID:", channel.peer_id); - console.log("状态:", channel.state); + // Select first channel for closure + const channelToClose = channels[0]; + const channelId = channelToClose.channel_id; - // 创建承诺交易 - const commitmentTransaction = "0x" + "00".repeat(32); // 示例交易数据 - - // 调用提交承诺交易方法 - console.log("\n正在调用 submitCommitmentTransaction 方法..."); - await sdk.dev.submitCommitmentTransaction({ - channel_id: channel.channel_id, - commitment_transaction: commitmentTransaction, - }); - console.log("承诺交易提交成功"); - - // 验证通道状态 - console.log("\n验证通道状态..."); - const updatedChannels = await sdk.channel.listChannels(); - const updatedChannel = updatedChannels.find( - (c) => c.channel_id === channel.channel_id, - ); - - if (updatedChannel) { - console.log("通道新状态:", updatedChannel.state); - } else { - console.log("找不到通道,可能已被关闭"); - } - } catch (error) { - if (error.error) { - handleRPCError(error); - } else { - console.error("提交承诺交易时发生错误:", error); - } + // Check channel state + if (channelToClose.state_name === "NEGOTIATING_FUNDING") { + console.log("Channel is in funding negotiation stage, cannot close"); + return; } -} -async function testUpdateChannel() { - try { - // 初始化SDK - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", - timeout: 5000, - }); - - console.log("\n开始测试更新通道...\n"); - - try { - // 获取可用的通道列表 - const channels = await testListChannels(); - - if (channels.length === 0) { - console.log("没有可用的通道可以更新"); - return; - } - - // 选择第一个通道进行更新操作 - const channelToUpdate = channels[0]; - console.log("\n准备更新的通道信息:"); - console.log("通道ID:", channelToUpdate.channel_id); - console.log("对等节点ID:", channelToUpdate.peer_id); - console.log("状态:", channelToUpdate.state); - - // 调用更新通道方法,禁用通道 - console.log("\n正在调用 updateChannel 方法..."); - await sdk.channel.updateChannel({ - channel_id: channelToUpdate.channel_id, - enabled: false, - }); - console.log("通道更新成功"); - - // 验证通道状态 - console.log("\n验证通道状态..."); - const updatedChannels = await sdk.channel.listChannels(); - const updatedChannel = updatedChannels.find( - (c) => c.channel_id === channelToUpdate.channel_id, - ); - - if (updatedChannel) { - console.log("通道状态:", updatedChannel.state); - console.log("是否启用:", updatedChannel.enabled ? "是" : "否"); - } - } catch (error) { - if (error.error) { - handleRPCError(error); - } else { - console.error("更新通道失败:", error.message); - } - } - } catch (error) { - console.error("测试更新通道时发生错误:", error); + // Ensure channelId is string type + if (typeof channelId !== "string") { + console.error("Invalid channel ID format:", channelId); + return; } + + console.log("Calling shutdown_channel method, Channel ID:", channelId); + const result2 = await sdk.channel.shutdownChannel({ + channel_id: channelId, + close_script: { + code_hash: + "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: "0xcc015401df73a3287d8b2b19f0cc23572ac8b14d", + }, + fee_rate: "0x3FC", + force: false, + }); + console.log("Channel closure result:", result2); } async function main() { try { await testListChannels(); - await testUpdateAndShutdownChannel(); - console.log("\n所有测试完成!"); + // await testCloseChannels(); + // await testNewChannel(); + // await testUpdateAndShutdownChannel(); + // await testListChannels(); + console.log("\nAll tests completed!"); } catch (error) { - console.error("测试过程中发生错误:", error); + console.error("Error during test:", error); } } -// 运行测试 -console.log("开始运行通道相关测试...\n"); +// Run tests +console.log("Starting channel-related tests...\n"); main() - .then(() => console.log("\n所有测试完成!")) + .then(() => console.log("\nAll tests completed!")) .catch(console.error); diff --git a/packages/fiber/test/info.cjs b/packages/fiber/test/info.cjs index b7fabf52..517b4c4e 100644 --- a/packages/fiber/test/info.cjs +++ b/packages/fiber/test/info.cjs @@ -1,23 +1,23 @@ const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); -// 自定义错误处理函数 +// Custom error handling function function handleRPCError(error) { if (error.error && error.error.code === -32601) { - console.error("错误: 节点可能未运行或 RPC 方法不存在"); - console.error("请确保:"); - console.error("1. Fiber 节点已启动"); - console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); - console.error("3. 节点 RPC 接口可用"); + console.error("Error: Node may not be running or RPC method does not exist"); + console.error("Please ensure:"); + console.error("1. Fiber node is started"); + console.error("2. Node RPC address is correct (current: http://127.0.0.1:8227)"); + console.error("3. Node RPC interface is available"); } else if (error.error && error.error.code === -32602) { - console.error("错误: 参数无效"); - console.error("请检查:"); - console.error("1. 参数类型是否正确"); - console.error("2. 参数值是否在有效范围内"); - console.error("3. 必填参数是否都已提供"); + console.error("Error: Invalid parameters"); + console.error("Please check:"); + console.error("1. Parameter types are correct"); + console.error("2. Parameter values are within valid range"); + console.error("3. All required parameters are provided"); } else { - console.error("RPC 错误:", error.message); + console.error("RPC Error:", error.message); if (error.error && error.error.data) { - console.error("错误详情:", error.error.data); + console.error("Error details:", error.error.data); } } } @@ -30,79 +30,55 @@ function hexToNumber(hex) { async function testNodeInfo() { try { - // 初始化SDK + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - console.log("开始测试获取节点信息...\n"); + console.log("Starting node info test...\n"); try { - // 获取节点信息 - console.log("正在调用 nodeInfo 方法..."); - const nodeInfo = await sdk.info.nodeInfo(); + // Get node information + console.log("Calling node_info method..."); + const info = await sdk.info.nodeInfo(); - // 类型检查 - if (!nodeInfo || typeof nodeInfo !== "object") { - throw new Error("返回的节点信息格式不正确"); - } - - // 输出详细信息 - console.log("\n节点详细信息:"); - console.log("版本:", nodeInfo.version); - console.log("提交哈希:", nodeInfo.commit_hash); - console.log("节点ID:", nodeInfo.node_id); - console.log("节点名称:", nodeInfo.node_name || "未设置"); - console.log( - "地址列表:", - nodeInfo.addresses.length > 0 ? nodeInfo.addresses.join(", ") : "无", - ); - console.log("链哈希:", nodeInfo.chain_hash); + // Output node information + console.log("\nNode Information:"); + console.log("Node Name:", info.node_name); + console.log("Node ID:", info.node_id); + console.log("Addresses:", info.addresses); + console.log("Chain Hash:", info.chain_hash); console.log( - "自动接受最小CKB资金金额:", - hexToNumber(nodeInfo.open_channel_auto_accept_min_ckb_funding_amount), + "Auto Accept Min CKB Funding Amount:", + info.auto_accept_min_ckb_funding_amount, ); + console.log("UDT Config Info:", info.udt_cfg_infos); console.log( - "自动接受通道CKB资金金额:", - hexToNumber(nodeInfo.auto_accept_channel_ckb_funding_amount), + "Timestamp:", + new Date(Number(info.timestamp)).toLocaleString(), ); - console.log("通道数量:", hexToNumber(nodeInfo.channel_count)); - console.log( - "待处理通道数量:", - hexToNumber(nodeInfo.pending_channel_count), - ); - console.log("对等节点数量:", hexToNumber(nodeInfo.peers_count)); - - if (nodeInfo.udt_cfg_infos && nodeInfo.udt_cfg_infos.length > 0) { - console.log("\nUDT配置信息:"); - nodeInfo.udt_cfg_infos.forEach((udt, index) => { - console.log(`\nUDT ${index + 1}:`); - console.log("名称:", udt.name); - console.log("自动接受金额:", hexToNumber(udt.auto_accept_amount)); - }); - } } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("获取节点信息失败:", error.message); + console.error("Failed to get node info:", error.message); } } - console.log("\n测试完成!"); + console.log("\nTest completed!"); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("测试过程中发生错误:", error.message); + console.error("Error during test:", error.message); } } } -// 运行测试 -console.log("开始运行节点信息相关测试...\n"); +// Run tests +console.log("Starting node info tests...\n"); testNodeInfo() - .then(() => console.log("\n所有测试完成!")) + .then(() => console.log("\nAll tests completed!")) .catch(console.error); diff --git a/packages/fiber/test/invoice.cjs b/packages/fiber/test/invoice.cjs index caed7d55..bd33332b 100644 --- a/packages/fiber/test/invoice.cjs +++ b/packages/fiber/test/invoice.cjs @@ -1,23 +1,30 @@ const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); +const crypto = require("crypto"); -// 自定义错误处理函数 +// 生成随机的 payment_preimage +function generatePaymentPreimage() { + const randomBytes = crypto.randomBytes(32); + return "0x" + randomBytes.toString("hex"); +} + +// Custom error handling function function handleRPCError(error) { if (error.error && error.error.code === -32601) { - console.error("错误: 节点可能未运行或 RPC 方法不存在"); - console.error("请确保:"); - console.error("1. Fiber 节点已启动"); - console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); - console.error("3. 节点 RPC 接口可用"); + console.error("Error: Node may not be running or RPC method does not exist"); + console.error("Please ensure:"); + console.error("1. Fiber node is started"); + console.error("2. Node RPC address is correct (current: http://127.0.0.1:8227)"); + console.error("3. Node RPC interface is available"); } else if (error.error && error.error.code === -32602) { - console.error("错误: 参数无效"); - console.error("请检查:"); - console.error("1. 参数类型是否正确"); - console.error("2. 参数值是否在有效范围内"); - console.error("3. 必填参数是否都已提供"); + console.error("Error: Invalid parameters"); + console.error("Please check:"); + console.error("1. Parameter types are correct"); + console.error("2. Parameter values are within valid range"); + console.error("3. All required parameters are provided"); } else { - console.error("RPC 错误:", error.message); + console.error("RPC Error:", error.message); if (error.error && error.error.data) { - console.error("错误详情:", error.error.data); + console.error("Error details:", error.error.data); } } } @@ -29,39 +36,50 @@ function hexToNumber(hex) { async function testNewInvoice() { try { - // 初始化SDK + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - console.log("开始测试创建新发票...\n"); + console.log("Starting new invoice test...\n"); try { - // 创建新发票 - console.log("正在调用 newInvoice 方法..."); - const invoice = await sdk.invoice.newInvoice({ - amount: BigInt(1000), - description: "测试发票", - expiry: BigInt(3600), // 1小时过期 - payment_secret: "secret", // 可选 + // Create new invoice + console.log("Calling new_invoice method..."); + const amount = 100000000; // 100,000,000 + const response = await sdk.invoice.newInvoice({ + amount: `0x${amount.toString(16)}`, // 将金额转换为十六进制 + currency: "Fibt", + description: "test invoice generated by node2", + expiry: "0xe10", + final_cltv: "0x28", + payment_preimage: generatePaymentPreimage(), + hash_algorithm: "sha256", }); - console.log("发票信息:", JSON.stringify(invoice, null, 2)); + console.log("Invoice created successfully:"); + console.log("Payment Hash:", response.payment_hash); + console.log("Amount:", response.amount); + console.log("Description:", response.description); + console.log("Expiry:", response.expiry); + console.log("Created At:", new Date(response.created_at).toLocaleString()); + + return response; } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("创建发票失败:", error.message); + console.error("Failed to create invoice:", error.message); } + return null; } - - console.log("\n测试完成!"); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("测试过程中发生错误:", error.message); + console.error("Error during test:", error.message); } + return null; } } @@ -76,10 +94,10 @@ async function testParseInvoice() { console.log("开始测试解析发票...\n"); try { - // 解析发票 console.log("正在调用 parseInvoice 方法..."); - const invoiceString = "invoice_string"; // 替换为实际的发票字符串 - const invoice = await sdk.invoice.parseInvoice(invoiceString); + const invoice = await sdk.invoice.parseInvoice( + "fibt1000000001pcsaug0p0exgfw0pnm6vk0rnt4xefskmrz0k2vqxr4lnrms60qasvc54jagg2hk8v40k88exmp04pn5cpcnrcsw5lk9w0w6l0m3k84e2ax4v6gq9ne2n77u4p8h3npx6tuufqftq8eyqxw9t4upaw4f89xukcee79rm0p0jv92d5ckq7pmvm09ma3psheu3rfyy9atlrdr4el6ys8yqurl2m74msuykljp35j0s47vpw8h3crfp5ldp8kp4xlusqk6rad3ssgwn2a429qlpgfgjrtj3gzy26w50cy7gypgjm6mjgaz2ff5q4am0avf6paxja2gh2wppjagqlg466yzty0r0pfz8qpuzqgq43mkgx", + ); console.log("解析结果:", JSON.stringify(invoice, null, 2)); } catch (error) { if (error.error) { @@ -101,84 +119,98 @@ async function testParseInvoice() { async function testGetInvoice() { try { - // 初始化SDK + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - console.log("开始测试获取发票...\n"); + console.log("\nStarting get invoice test...\n"); try { - // 获取发票 - console.log("正在调用 getInvoice 方法..."); - const paymentHash = "payment_hash"; // 替换为实际的 payment_hash - const invoice = await sdk.invoice.getInvoice(paymentHash); - console.log("发票信息:", JSON.stringify(invoice, null, 2)); - - // 输出详细信息 - console.log("\n发票详细信息:"); - console.log("状态:", invoice.status); - console.log("发票地址:", invoice.invoice_address); - console.log("发票内容:", JSON.stringify(invoice.invoice, null, 2)); + // Get invoice + const invoice = await testNewInvoice(); + if (!invoice) { + console.log("No invoice available to retrieve"); + return; + } + + console.log("Calling get_invoice method..."); + const invoiceInfo = await sdk.invoice.getInvoice(invoice.payment_hash); + + // Output invoice information + console.log("\nInvoice details:"); + console.log("Status:", invoiceInfo.status); + console.log("Invoice Address:", invoiceInfo.invoice_address); + console.log("Payment Hash:", invoiceInfo.invoice.payment_hash); + console.log("Amount:", invoiceInfo.invoice.amount); + console.log("Description:", invoiceInfo.invoice.description); + console.log("Expiry:", invoiceInfo.invoice.expiry); + console.log( + "Created At:", + new Date(invoiceInfo.invoice.created_at).toLocaleString(), + ); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("获取发票失败:", error.message); + console.error("Failed to get invoice:", error.message); } } - - console.log("\n测试完成!"); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("测试过程中发生错误:", error.message); + console.error("Error during test:", error.message); } } } async function testCancelInvoice() { try { - // 初始化SDK + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - console.log("开始测试取消发票...\n"); + console.log("\nStarting cancel invoice test...\n"); try { - // 取消发票 - console.log("正在调用 cancelInvoice 方法..."); - const paymentHash = "payment_hash"; // 替换为实际的 payment_hash - await sdk.invoice.cancelInvoice(paymentHash); - console.log("发票取消成功"); + // Get invoice to cancel + const invoice = await testNewInvoice(); + if (!invoice) { + console.log("No invoice available to cancel"); + return; + } + + console.log("Calling cancel_invoice method..."); + await sdk.invoice.cancelInvoice(invoice.payment_hash); + console.log("Invoice cancelled successfully"); + + // Verify invoice status + console.log("\nVerifying invoice status..."); + const cancelledInvoice = await sdk.invoice.getInvoice(invoice.payment_hash); + console.log("Invoice status after cancellation:", cancelledInvoice.status); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("取消发票失败:", error.message); + console.error("Failed to cancel invoice:", error.message); } } - - console.log("\n测试完成!"); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("测试过程中发生错误:", error.message); + console.error("Error during test:", error.message); } } } -// 运行所有测试 -console.log("开始运行发票相关测试...\n"); +// Run tests +console.log("Starting invoice-related tests...\n"); -testNewInvoice() - .then(() => testParseInvoice()) - .then(() => testGetInvoice()) - .then(() => testCancelInvoice()) - .then(() => console.log("\n所有测试完成!")) - .catch(console.error); \ No newline at end of file +main() + .then(() => console.log("\nAll tests completed!")) + .catch(console.error); diff --git a/packages/fiber/test/payment.cjs b/packages/fiber/test/payment.cjs index b1bbe586..1f321b8e 100644 --- a/packages/fiber/test/payment.cjs +++ b/packages/fiber/test/payment.cjs @@ -41,14 +41,8 @@ async function testSendPayment() { // 发送支付 console.log("正在调用 sendPayment 方法..."); await sdk.payment.sendPayment({ - payment_hash: "payment_hash", // 替换为实际的 payment_hash - amount: BigInt(1000), - fee_rate: BigInt(100), - custom_records: { - // 自定义记录 - "key1": "value1", - "key2": "value2", - }, + invoice: + "fibt1000000001pcsaug0p0exgfw0pnm6vkkya5ul6wxurhh09qf9tuwwaufqnr3uzwpplgcrjpeuhe6w4rudppfkytvm4jekf6ymmwqk2h0ajvr5uhjpwfd9aga09ahpy88hz2um4l9t0xnpk3m9wlf22m2yjcshv3k4g5x7c68fn0gs6a35dw5r56cc3uztyf96l55ayeuvnd9fl4yrt68y086xn6qgjhf4n7xkml62gz5ecypm3xz0wdd59tfhtrhwvp5qlps959vmpf4jygdkspxn8xalparwj8h9ts6v6v0rf7vvhhku40z9sa4txxmgsjzwqzme4ddazxrfrlkc9m4uysh27zgqlx7jrfgvjw7rcqpmsrlga", }); console.log("支付发送成功"); } catch (error) { @@ -123,7 +117,4 @@ async function testGetPayment() { // 运行所有测试 console.log("开始运行支付相关测试...\n"); -testSendPayment() - .then(() => testGetPayment()) - .then(() => console.log("\n所有测试完成!")) - .catch(console.error); \ No newline at end of file +testSendPayment().catch(console.error); diff --git a/packages/fiber/test/peer.cjs b/packages/fiber/test/peer.cjs index 7406fbaa..7775c142 100644 --- a/packages/fiber/test/peer.cjs +++ b/packages/fiber/test/peer.cjs @@ -1,75 +1,85 @@ -const { FiberSDK } = require("../dist.commonjs/index.js"); +const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); -// 自定义错误处理函数 +// Custom error handling function function handleRPCError(error) { if (error.error && error.error.code === -32601) { - console.error("错误: 节点可能未运行或 RPC 方法不存在"); - console.error("请确保:"); - console.error("1. Fiber 节点已启动"); - console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); - console.error("3. 节点 RPC 接口可用"); + console.error("Error: Node may not be running or RPC method does not exist"); + console.error("Please ensure:"); + console.error("1. Fiber node is started"); + console.error("2. Node RPC address is correct (current: http://127.0.0.1:8227)"); + console.error("3. Node RPC interface is available"); } else if (error.error && error.error.code === -32602) { - console.error("错误: 参数无效"); - console.error("请检查:"); - console.error("1. 参数类型是否正确"); - console.error("2. 参数值是否在有效范围内"); - console.error("3. 必填参数是否都已提供"); + console.error("Error: Invalid parameters"); + console.error("Please check:"); + console.error("1. Parameter types are correct"); + console.error("2. Parameter values are within valid range"); + console.error("3. All required parameters are provided"); } else { - console.error("RPC 错误:", error.message); + console.error("RPC Error:", error.message); if (error.error && error.error.data) { - console.error("错误详情:", error.error.data); + console.error("Error details:", error.error.data); } } } async function testConnectPeer() { - console.log("\n开始测试连接节点...\n"); - try { + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", - timeout: 30000, + timeout: 5000, }); - // 使用文档中的测试节点地址 - const peerAddress = "/ip4/18.162.235.225/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; - console.log("正在调用 connect_peer 方法,连接地址:", peerAddress); - - await sdk.peer.connectPeer({ address: peerAddress }); - console.log("连接节点成功"); - } catch (error) { - console.error("连接节点失败:", error.message); - handleRPCError(error); - } - - console.log("\n测试完成!"); -} - -async function testOpenChannel() { - console.log("\n开始测试打开通道...\n"); + console.log("Starting peer connection test...\n"); - try { - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", - timeout: 30000, - }); - - const peerId = "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; - console.log("正在调用 open_channel 方法,节点 ID:", peerId); - - const result = await sdk.channel.openChannel({ - peer_id: peerId, - funding_amount: "0x174876e800", // 100 CKB - public: true, - }); + try { + // Connect to peer + console.log("Calling connect_peer method..."); + const peerAddress = "/ip4/18.162.235.225/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; + const [address, peerId] = peerAddress.split("/p2p/"); + + try { + const response = await sdk.peer.connectPeer({ + peer_id: peerId, + address: address, + }); + console.log("Successfully connected to peer:", response); + } catch (error) { + // Check error message, if already connected, consider it a success + if (error.message && error.message.includes("already connected")) { + console.log("Peer is already connected, proceeding with next steps"); + } else { + throw error; + } + } + } catch (error) { + if (error.error) { + handleRPCError(error); + } else { + console.error("Failed to connect to peer:", error.message); + } + } - console.log("打开通道结果:", result); + console.log("\nTest completed!"); } catch (error) { - console.error("打开通道失败:", error.message); - handleRPCError(error); + if (error.error) { + handleRPCError(error); + } else { + console.error("Error during test:", error.message); + } } +} - console.log("\n测试完成!"); +async function testDisconnectPeer() { + console.log("\nStarting peer disconnection test...\n"); + const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", + timeout: 30000, + }); + const peerId = "QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89"; + console.log("正在调用 disconnect_peer 方法,节点 ID:", peerId); + const result = await sdk.peer.disconnectPeer(peerId); + console.log("断开链接结果:", result); } async function testListChannels() { @@ -97,102 +107,23 @@ async function testListChannels() { console.log("\n测试完成!"); } -async function testCloseChannel() { - console.log("\n开始测试关闭通道...\n"); - - try { - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", - timeout: 30000, - }); - - // 获取通道列表 - const channels = await sdk.channel.listChannels({ - peer_id: "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo", - }); - - // 关闭所有通道 - for (const channel of channels) { - console.log("正在关闭通道:", channel.channel_id); - await sdk.channel.shutdownChannel({ - channel_id: channel.channel_id, - close_script: { - code_hash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - hash_type: "type", - args: "0xea076cd91e879a3c189d94068e1584c3fbcc1876" - }, - fee_rate: "0x3FC" - }); - console.log("通道关闭成功:", channel.channel_id); - } - } catch (error) { - console.error("关闭通道失败:", error.message); - handleRPCError(error); - } - - console.log("\n测试完成!"); -} - -async function cleanupNegotiatingChannels() { - console.log("\n开始清理处于 NEGOTIATING_FUNDING 状态的通道...\n"); - - try { - const sdk = new FiberSDK({ - endpoint: "http://127.0.0.1:8227", - timeout: 30000, - }); - - // 获取通道列表 - const channels = await sdk.channel.listChannels({ - peer_id: "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo", - }); - - // 过滤出处于 NEGOTIATING_FUNDING 状态的通道 - const negotiatingChannels = channels.filter( - channel => channel.state.state_name === "NEGOTIATING_FUNDING" - ); - - console.log(`找到 ${negotiatingChannels.length} 个处于 NEGOTIATING_FUNDING 状态的通道`); - - // 关闭这些通道 - for (const channel of negotiatingChannels) { - console.log("正在关闭通道:", channel.channel_id); - try { - await sdk.channel.shutdownChannel({ - channel_id: channel.channel_id, - close_script: { - code_hash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - hash_type: "type", - args: "0xea076cd91e879a3c189d94068e1584c3fbcc1876" - }, - fee_rate: "0x3FC", - force: true - }); - console.log("通道关闭成功:", channel.channel_id); - } catch (closeError) { - console.error("关闭通道失败:", channel.channel_id, closeError.message); - } - } - } catch (error) { - console.error("清理通道失败:", error.message); - handleRPCError(error); - } - - console.log("\n清理完成!"); -} - async function main() { // 1. 首先清理处于 NEGOTIATING_FUNDING 状态的通道 - await cleanupNegotiatingChannels(); - + await testListChannels(); + // 2. 然后建立网络连接 - await testConnectPeer(); - - // 3. 打开新通道 - await testOpenChannel(); - + // await testConnectPeer(); + + // 3. 断开链接 + await testDisconnectPeer(); + // 4. 最后查询通道状态 - await testListChannels(); + // await testListChannels(); } -main().catch(console.error); +// 运行测试 +console.log("开始运行节点连接测试...\n"); + +main() + .then(() => console.log("\n所有测试完成!")) + .catch(console.error); diff --git a/packages/fiber/tsconfig.json b/packages/fiber/tsconfig.json index df22faec..d9937dbd 100644 --- a/packages/fiber/tsconfig.json +++ b/packages/fiber/tsconfig.json @@ -1,8 +1,10 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "module": "ESNext", - "moduleResolution": "Bundler", + "module": "CommonJS", + "moduleResolution": "node", "outDir": "./dist", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65a20be5..353960da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -525,7 +525,7 @@ importers: version: 10.2.3(chokidar@3.6.0)(typescript@5.7.3) '@nestjs/testing': specifier: ^10.0.0 - version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)) + version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(@nestjs/platform-express@10.4.15) '@types/express': specifier: ^4.17.17 version: 4.17.21 @@ -599,6 +599,9 @@ importers: '@types/chai': specifier: ^5.2.0 version: 5.2.1 + '@types/jest': + specifier: ^29.5.0 + version: 29.5.14 '@types/mocha': specifier: ^10.0.10 version: 10.0.10 @@ -623,6 +626,9 @@ importers: eslint-plugin-prettier: specifier: ^5.1.3 version: 5.2.3(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.20.0(jiti@1.21.7)))(eslint@9.20.0(jiti@1.21.7))(prettier@3.5.1) + jest: + specifier: ^29.5.0 + version: 29.7.0(@types/node@22.13.1)(ts-node@10.9.2(@types/node@22.13.1)(typescript@5.7.3)) mocha: specifier: ^11.1.0 version: 11.1.0 @@ -635,6 +641,9 @@ importers: rimraf: specifier: ^5.0.5 version: 5.0.10 + ts-jest: + specifier: ^29.1.0 + version: 29.2.5(@babel/core@7.26.8)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.8))(jest@29.7.0(@types/node@22.13.1)(ts-node@10.9.2(@types/node@22.13.1)(typescript@5.7.3)))(typescript@5.7.3) typescript: specifier: ^5.4.5 version: 5.7.3 @@ -7518,7 +7527,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/testing@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15))': + '@nestjs/testing@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(@nestjs/platform-express@10.4.15)': dependencies: '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -9415,7 +9424,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -9466,7 +9475,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 From fa996747bea8a832b24a2d2b4a60e40ff0f18fcf Mon Sep 17 00:00:00 2001 From: Jack <6464072@qq.com> Date: Wed, 9 Apr 2025 17:42:21 +0800 Subject: [PATCH 06/15] =?UTF-8?q?feat=EF=BC=9Afiber=20sdk=20&demo=20v0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/fiber/package.json | 2 +- packages/fiber/src/client.ts | 2 + packages/fiber/src/index.ts | 179 ++++++++++++++++++++++---- packages/fiber/src/modules/cch.ts | 4 +- packages/fiber/src/modules/channel.ts | 4 +- packages/fiber/src/modules/dev.ts | 4 +- packages/fiber/src/modules/graph.ts | 4 +- packages/fiber/src/modules/info.ts | 44 ++++++- packages/fiber/src/modules/invoice.ts | 4 +- packages/fiber/src/modules/payment.ts | 8 +- packages/fiber/src/modules/peer.ts | 3 +- packages/fiber/src/types.ts | 94 ++++++++++++++ packages/fiber/test/channel.cjs | 2 +- packages/fiber/test/info.cjs | 5 +- packages/fiber/test/invoice.cjs | 20 ++- packages/fiber/test/payment.cjs | 10 +- packages/fiber/test/peer.cjs | 10 +- packages/fiber/tsconfig.commonjs.json | 4 +- packages/fiber/tsconfig.json | 4 +- 19 files changed, 347 insertions(+), 60 deletions(-) diff --git a/packages/fiber/package.json b/packages/fiber/package.json index 97e296d3..ebd02c53 100644 --- a/packages/fiber/package.json +++ b/packages/fiber/package.json @@ -1,7 +1,7 @@ { "name": "@ckb-ccc/fiber", "version": "1.0.0", - "type": "commonjs", + "type": "module", "description": "CCC - CKBer's Codebase. Common Chains Connector's support for Fiber SDK", "author": "author: Jack ", "license": "MIT", diff --git a/packages/fiber/src/client.ts b/packages/fiber/src/client.ts index 3de5043a..81c73960 100644 --- a/packages/fiber/src/client.ts +++ b/packages/fiber/src/client.ts @@ -1,3 +1,5 @@ +import axios from "axios"; +import { RPCRequest, RPCResponse } from "./types.js"; import { RequestorJsonRpc, RequestorJsonRpcConfig } from "@ckb-ccc/core"; export interface ClientConfig extends RequestorJsonRpcConfig { diff --git a/packages/fiber/src/index.ts b/packages/fiber/src/index.ts index e6d5edc1..6c2e8a3c 100644 --- a/packages/fiber/src/index.ts +++ b/packages/fiber/src/index.ts @@ -1,23 +1,25 @@ -import { FiberClient } from "./client"; -import { CchModule } from "./modules/cch"; -import { ChannelModule } from "./modules/channel"; -import { DevModule } from "./modules/dev"; -import { GraphModule } from "./modules/graph"; -import { InfoModule } from "./modules/info"; -import { InvoiceModule } from "./modules/invoice"; -import { PaymentModule } from "./modules/payment"; -import { PeerModule } from "./modules/peer"; - -export { FiberClient } from "./client"; -export { CchModule } from "./modules/cch"; -export { ChannelModule } from "./modules/channel"; -export { DevModule } from "./modules/dev"; -export { GraphModule } from "./modules/graph"; -export { InfoModule } from "./modules/info"; -export { InvoiceModule } from "./modules/invoice"; -export { PaymentModule } from "./modules/payment"; -export { PeerModule } from "./modules/peer"; -export * from "./types"; +import { FiberClient } from "./client.js"; +import { ChannelModule } from "./modules/channel.js"; +import { InfoModule } from "./modules/info.js"; +import { InvoiceModule } from "./modules/invoice.js"; +import { PaymentModule } from "./modules/payment.js"; +import { PeerModule } from "./modules/peer.js"; +import { + Channel, + CkbInvoice, + Hash256, + NodeInfo, + PaymentSessionStatus, + Script, +} from "./types.js"; + +export { FiberClient } from "./client.js"; +export { ChannelModule } from "./modules/channel.js"; +export { InfoModule } from "./modules/info.js"; +export { InvoiceModule } from "./modules/invoice.js"; +export { PaymentModule } from "./modules/payment.js"; +export { PeerModule } from "./modules/peer.js"; +export * from "./types.js"; export interface FiberSDKConfig { endpoint: string; @@ -30,9 +32,9 @@ export class FiberSDK { public invoice: InvoiceModule; public peer: PeerModule; public info: InfoModule; - public graph: GraphModule; - public dev: DevModule; - public cch: CchModule; + // public graph: GraphModule; + // public dev: DevModule; + // public cch: CchModule; constructor(config: FiberSDKConfig) { const client = new FiberClient({ @@ -45,8 +47,133 @@ export class FiberSDK { this.invoice = new InvoiceModule(client); this.peer = new PeerModule(client); this.info = new InfoModule(client); - this.graph = new GraphModule(client); - this.dev = new DevModule(client); - this.cch = new CchModule(client); + // this.graph = new GraphModule(client); + // this.dev = new DevModule(client); + // this.cch = new CchModule(client); + } + + /** + * 列出所有通道 + */ + async listChannels(): Promise { + return this.channel.listChannels(); + } + + /** + * 获取节点信息 + */ + async nodeInfo(): Promise { + return this.info.nodeInfo(); + } + + /** + * 打开通道 + */ + async openChannel(params: { + peer_id: string; + funding_amount: bigint; + public?: boolean; + funding_udt_type_script?: Script; + shutdown_script?: Script; + commitment_delay_epoch?: bigint; + commitment_fee_rate?: bigint; + funding_fee_rate?: bigint; + tlc_expiry_delta?: bigint; + tlc_min_value?: bigint; + tlc_fee_proportional_millionths?: bigint; + max_tlc_value_in_flight?: bigint; + max_tlc_number_in_flight?: bigint; + }): Promise { + return this.channel.openChannel(params); + } + + /** + * 关闭通道 + */ + async shutdownChannel(params: { + channel_id: Hash256; + close_script: Script; + force?: boolean; + fee_rate: bigint; + }): Promise { + return this.channel.shutdownChannel(params); + } + + /** + * 发送支付 + */ + async sendPayment(params: { + payment_hash: string; + amount: bigint; + fee_rate: bigint; + }): Promise { + return this.payment.sendPayment(params); + } + + /** + * 解析发票 + */ + async parseInvoice(invoice: string): Promise { + return this.invoice.parseInvoice(invoice); + } + + /** + * 创建新发票 + */ + async newInvoice(params: { + amount: bigint; + description?: string; + expiry?: bigint; + payment_secret?: string; + }): Promise { + return this.invoice.newInvoice(params); + } + + /** + * 获取发票信息 + */ + async getInvoice(payment_hash: string): Promise<{ + status: string; + invoice_address: string; + invoice: CkbInvoice; + }> { + return this.invoice.getInvoice(payment_hash); + } + + /** + * 取消发票 + */ + async cancelInvoice(payment_hash: string): Promise { + return this.invoice.cancelInvoice(payment_hash); + } + + /** + * 获取支付信息 + */ + async getPayment(payment_hash: string): Promise<{ + status: PaymentSessionStatus; + payment_hash: Hash256; + created_at: bigint; + last_updated_at: bigint; + failed_error?: string; + fee: bigint; + }> { + return this.payment.getPayment(payment_hash); + } + + /** + * 连接节点 + */ + async connectPeer(peer_address: string): Promise { + return this.peer.connectPeer(peer_address); + } + + /** + * 断开节点连接 + */ + async disconnectPeer(peer_id: string): Promise { + return this.peer.disconnectPeer(peer_id); } } + +export default FiberSDK; diff --git a/packages/fiber/src/modules/cch.ts b/packages/fiber/src/modules/cch.ts index 6a545339..6b4ff7a3 100644 --- a/packages/fiber/src/modules/cch.ts +++ b/packages/fiber/src/modules/cch.ts @@ -1,5 +1,5 @@ -import { FiberClient } from "../client"; -import { Currency, Script } from "../types"; +import { FiberClient } from "../client.js"; +import { Currency, Script } from "../types.js"; export class CchModule { constructor(private client: FiberClient) {} diff --git a/packages/fiber/src/modules/channel.ts b/packages/fiber/src/modules/channel.ts index eb28a8ff..841e1f63 100644 --- a/packages/fiber/src/modules/channel.ts +++ b/packages/fiber/src/modules/channel.ts @@ -1,5 +1,5 @@ -import { FiberClient } from "../client"; -import { Channel, Hash256, Script } from "../types"; +import { FiberClient } from "../client.js"; +import { Channel, Hash256, Script } from "../types.js"; export class ChannelModule { constructor(private client: FiberClient) {} diff --git a/packages/fiber/src/modules/dev.ts b/packages/fiber/src/modules/dev.ts index ea528589..dcd2d9c7 100644 --- a/packages/fiber/src/modules/dev.ts +++ b/packages/fiber/src/modules/dev.ts @@ -1,5 +1,5 @@ -import { FiberClient } from "../client"; -import { Hash256, RemoveTlcReason } from "../types"; +import { FiberClient } from "../client.js"; +import { Hash256, RemoveTlcReason } from "../types.js"; export class DevModule { constructor(private client: FiberClient) {} diff --git a/packages/fiber/src/modules/graph.ts b/packages/fiber/src/modules/graph.ts index 67a9a0ce..954b0422 100644 --- a/packages/fiber/src/modules/graph.ts +++ b/packages/fiber/src/modules/graph.ts @@ -1,5 +1,5 @@ -import { FiberClient } from "../client"; -import { ChannelInfo, Pubkey } from "../types"; +import { FiberClient } from "../client.js"; +import { ChannelInfo, Pubkey } from "../types.js"; export class GraphModule { constructor(private client: FiberClient) {} diff --git a/packages/fiber/src/modules/info.ts b/packages/fiber/src/modules/info.ts index a919cd0a..1da89aa6 100644 --- a/packages/fiber/src/modules/info.ts +++ b/packages/fiber/src/modules/info.ts @@ -1,5 +1,5 @@ -import { FiberClient } from "../client"; -import { NodeInfo } from "../types"; +import { FiberClient } from "../client.js"; +import { NodeInfo } from "../types.js"; export class InfoModule { constructor(private client: FiberClient) {} @@ -10,6 +10,44 @@ export class InfoModule { * @throws {Error} Throws error when unable to get node information */ async nodeInfo(): Promise { - return this.client.call("node_info", []); + //转换nodeInfo中的十六进制数字为十进制数字 + //包括channel_count,peers_count,pending_channel_count,tlc_expiry_delta,tlc_fee_proportional_millionths,tlc_max_value,tlc_min_value,open_channel_auto_accept_min_ckb_funding_amount,auto_accept_channel_ckb_funding_amount + const nodeInfo = await this.client.call<{ + node_name: string; + addresses: string[]; + node_id: string; + timestamp: string; + chain_hash: string; + auto_accept_min_ckb_funding_amount: string; + udt_cfg_infos: Record; + default_funding_lock_script?: { + code_hash: string; + hash_type: string; + args: string; + }; + }>("node_info", []); + if (nodeInfo) { + const nodeInfoWithDecimal: NodeInfo = { + node_name: nodeInfo.node_name || "", + addresses: nodeInfo.addresses || [], + node_id: nodeInfo.node_id || "", + timestamp: nodeInfo.timestamp ? BigInt(nodeInfo.timestamp) : BigInt(0), + chain_hash: nodeInfo.chain_hash || "", + auto_accept_min_ckb_funding_amount: + nodeInfo.auto_accept_min_ckb_funding_amount + ? BigInt(nodeInfo.auto_accept_min_ckb_funding_amount) + : BigInt(0), + udt_cfg_infos: nodeInfo.udt_cfg_infos || {}, + default_funding_lock_script: nodeInfo.default_funding_lock_script + ? { + code_hash: nodeInfo.default_funding_lock_script.code_hash || "", + hash_type: nodeInfo.default_funding_lock_script.hash_type || "", + args: nodeInfo.default_funding_lock_script.args || "", + } + : undefined, + }; + return nodeInfoWithDecimal; + } + throw new Error("无法获取节点信息"); } } diff --git a/packages/fiber/src/modules/invoice.ts b/packages/fiber/src/modules/invoice.ts index 1e4c7343..c15f4400 100644 --- a/packages/fiber/src/modules/invoice.ts +++ b/packages/fiber/src/modules/invoice.ts @@ -1,5 +1,5 @@ -import { FiberClient } from "../client"; -import { CkbInvoice, CkbInvoiceStatus } from "../types"; +import { FiberClient } from "../client.js"; +import { CkbInvoice, CkbInvoiceStatus } from "../types.js"; export class InvoiceModule { constructor(private client: FiberClient) {} diff --git a/packages/fiber/src/modules/payment.ts b/packages/fiber/src/modules/payment.ts index da2f147a..67722c48 100644 --- a/packages/fiber/src/modules/payment.ts +++ b/packages/fiber/src/modules/payment.ts @@ -1,10 +1,14 @@ -import { FiberClient } from "../client"; +import { FiberClient } from "../client.js"; import { Hash256, PaymentCustomRecords, PaymentSessionStatus, SessionRoute, -} from "../types"; + Payment, + PaymentStatus, + PaymentType, + PaymentResult, +} from "../types.js"; export class PaymentModule { constructor(private client: FiberClient) {} diff --git a/packages/fiber/src/modules/peer.ts b/packages/fiber/src/modules/peer.ts index 76158e13..d7447fc9 100644 --- a/packages/fiber/src/modules/peer.ts +++ b/packages/fiber/src/modules/peer.ts @@ -1,4 +1,5 @@ -import { FiberClient } from "../client"; +import { FiberClient } from "../client.js"; +import { Peer } from "../types.js"; export class PeerModule { constructor(private client: FiberClient) {} diff --git a/packages/fiber/src/types.ts b/packages/fiber/src/types.ts index 89ee6303..7f0205fb 100644 --- a/packages/fiber/src/types.ts +++ b/packages/fiber/src/types.ts @@ -1,6 +1,24 @@ export type Hash256 = string; export type Pubkey = string; +export interface RPCRequest { + jsonrpc: string; + method: string; + params: T[]; + id: number; +} + +export interface RPCResponse { + jsonrpc: string; + result?: T; + error?: { + code: number; + message: string; + data?: unknown; + }; + id: number; +} + export enum Currency { Fibb = "Fibb", Fibt = "Fibt", @@ -15,6 +33,41 @@ export enum CkbInvoiceStatus { Paid = "Paid", } +export enum PaymentStatus { + Pending = "Pending", + Succeeded = "Succeeded", + Failed = "Failed", +} + +export enum PaymentType { + Send = "Send", + Receive = "Receive", +} + +export interface Payment { + payment_hash: string; + payment_preimage: string; + amount: bigint; + fee: bigint; + status: PaymentStatus; + type: PaymentType; + created_at: bigint; + completed_at?: bigint; +} + +export interface PaymentResult { + payment_hash: string; + status: PaymentStatus; + fee: bigint; +} + +export interface Peer { + node_id: Pubkey; + address: string; + is_connected: boolean; + last_connected_at?: bigint; +} + export enum PaymentSessionStatus { Created = "Created", Inflight = "Inflight", @@ -101,6 +154,11 @@ export interface NodeInfo { chain_hash: Hash256; auto_accept_min_ckb_funding_amount: bigint; udt_cfg_infos: Record; + default_funding_lock_script?: { + code_hash: string; + hash_type: string; + args: string; + }; } export interface PaymentCustomRecords { @@ -137,3 +195,39 @@ export interface NetworkInfo { block_height: bigint; block_hash: string; } + +export interface CchOrder { + timestamp: bigint; + expiry: bigint; + ckb_final_tlc_expiry_delta: bigint; + currency: Currency; + wrapped_btc_type_script?: Script; + btc_pay_req: string; + ckb_pay_req: string; + payment_hash: string; + amount_sats: bigint; + fee_sats: bigint; + status: CchOrderStatus; +} + +export enum CchOrderStatus { + Pending = "Pending", + Processing = "Processing", + Completed = "Completed", + Failed = "Failed", +} + +export enum HashAlgorithm { + CkbHash = "CkbHash", + Sha256 = "Sha256", +} + +export interface HopHint { + pubkey: Pubkey; + channel_outpoint: { + tx_hash: Hash256; + index: bigint; + }; + fee_rate: bigint; + tlc_expiry_delta: bigint; +} diff --git a/packages/fiber/test/channel.cjs b/packages/fiber/test/channel.cjs index 33aa2dcd..40e5047c 100644 --- a/packages/fiber/test/channel.cjs +++ b/packages/fiber/test/channel.cjs @@ -1,4 +1,4 @@ -const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); +const { FiberSDK } = require("../dist.commonjs/index.js"); // Custom error handling function function handleRPCError(error) { diff --git a/packages/fiber/test/info.cjs b/packages/fiber/test/info.cjs index 517b4c4e..17f7cfcf 100644 --- a/packages/fiber/test/info.cjs +++ b/packages/fiber/test/info.cjs @@ -1,5 +1,4 @@ -const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); - +const { FiberSDK } = require("../dist.commonjs/index.js"); // Custom error handling function function handleRPCError(error) { if (error.error && error.error.code === -32601) { @@ -41,7 +40,7 @@ async function testNodeInfo() { try { // Get node information console.log("Calling node_info method..."); - const info = await sdk.info.nodeInfo(); + const info = await sdk.nodeInfo(); // Output node information console.log("\nNode Information:"); diff --git a/packages/fiber/test/invoice.cjs b/packages/fiber/test/invoice.cjs index bd33332b..fd0abbde 100644 --- a/packages/fiber/test/invoice.cjs +++ b/packages/fiber/test/invoice.cjs @@ -136,7 +136,9 @@ async function testGetInvoice() { } console.log("Calling get_invoice method..."); - const invoiceInfo = await sdk.invoice.getInvoice(invoice.payment_hash); + const invoiceInfo = await sdk.invoice.getInvoice( + invoice.payment_hash, + ); // Output invoice information console.log("\nInvoice details:"); @@ -190,8 +192,13 @@ async function testCancelInvoice() { // Verify invoice status console.log("\nVerifying invoice status..."); - const cancelledInvoice = await sdk.invoice.getInvoice(invoice.payment_hash); - console.log("Invoice status after cancellation:", cancelledInvoice.status); + const cancelledInvoice = await sdk.invoice.getInvoice( + invoice.payment_hash, + ); + console.log( + "Invoice status after cancellation:", + cancelledInvoice.status, + ); } catch (error) { if (error.error) { handleRPCError(error); @@ -211,6 +218,13 @@ async function testCancelInvoice() { // Run tests console.log("Starting invoice-related tests...\n"); +async function main() { + await testNewInvoice(); + await testParseInvoice(); + await testGetInvoice(); + await testCancelInvoice(); +} + main() .then(() => console.log("\nAll tests completed!")) .catch(console.error); diff --git a/packages/fiber/test/payment.cjs b/packages/fiber/test/payment.cjs index 1f321b8e..cb458122 100644 --- a/packages/fiber/test/payment.cjs +++ b/packages/fiber/test/payment.cjs @@ -1,12 +1,16 @@ -const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); +const { FiberSDK } = require("../dist.commonjs/index.js"); // 自定义错误处理函数 function handleRPCError(error) { if (error.error && error.error.code === -32601) { - console.error("错误: 节点可能未运行或 RPC 方法不存在"); + console.error( + "错误: 节点可能未运行或 RPC 方法不存在", + ); console.error("请确保:"); console.error("1. Fiber 节点已启动"); - console.error("2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)"); + console.error( + "2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)", + ); console.error("3. 节点 RPC 接口可用"); } else if (error.error && error.error.code === -32602) { console.error("错误: 参数无效"); diff --git a/packages/fiber/test/peer.cjs b/packages/fiber/test/peer.cjs index 7775c142..e42a24eb 100644 --- a/packages/fiber/test/peer.cjs +++ b/packages/fiber/test/peer.cjs @@ -1,12 +1,16 @@ -const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); +const { FiberSDK } = require("../dist.commonjs/index.js"); // Custom error handling function function handleRPCError(error) { if (error.error && error.error.code === -32601) { - console.error("Error: Node may not be running or RPC method does not exist"); + console.error( + "Error: Node may not be running or RPC method does not exist", + ); console.error("Please ensure:"); console.error("1. Fiber node is started"); - console.error("2. Node RPC address is correct (current: http://127.0.0.1:8227)"); + console.error( + "2. Node RPC address is correct (current: http://127.0.0.1:8227)", + ); console.error("3. Node RPC interface is available"); } else if (error.error && error.error.code === -32602) { console.error("Error: Invalid parameters"); diff --git a/packages/fiber/tsconfig.commonjs.json b/packages/fiber/tsconfig.commonjs.json index 76a25e98..baad27db 100644 --- a/packages/fiber/tsconfig.commonjs.json +++ b/packages/fiber/tsconfig.commonjs.json @@ -1,8 +1,8 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", + "module": "CommonJS", + "moduleResolution": "node", "outDir": "./dist.commonjs" } } diff --git a/packages/fiber/tsconfig.json b/packages/fiber/tsconfig.json index d9937dbd..c8645909 100644 --- a/packages/fiber/tsconfig.json +++ b/packages/fiber/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "module": "CommonJS", - "moduleResolution": "node", + "module": "Node16", + "moduleResolution": "node16", "outDir": "./dist", "esModuleInterop": true, "allowSyntheticDefaultImports": true From a46abf2847133ec7b16898ea1e270fc284174504 Mon Sep 17 00:00:00 2001 From: Jack <6464072@qq.com> Date: Wed, 9 Apr 2025 17:48:53 +0800 Subject: [PATCH 07/15] feat:add readme --- packages/fiber/README.md | 333 ++++++++++++++++++++++++++++++++++----- 1 file changed, 290 insertions(+), 43 deletions(-) diff --git a/packages/fiber/README.md b/packages/fiber/README.md index 652fc5c5..5a34b11e 100644 --- a/packages/fiber/README.md +++ b/packages/fiber/README.md @@ -1,43 +1,290 @@ -

- - Logo - -

- -

- CCC's support for Fiber SDK -

- -

- NPM Version - GitHub commit activity - GitHub last commit - GitHub branch check runs - Playground - App - Docs -

- -

- CCC - CKBers' Codebase is a one-stop solution for your CKB JS/TS ecosystem development. -
- Empower yourself with CCC to discover the unlimited potential of CKB. -
- Interoperate with wallets from different chain ecosystems. -
- Fully enabling CKB's Turing completeness and cryptographic freedom power. -

- -For non-developers, you can also [try CCC's app now here](https://app.ckbccc.com/). It showcases how to use CCC for some basic scenarios in CKB. - -

- Read more about CCC on our website or GitHub Repo. -

+# Fiber SDK + +Fiber SDK is a JavaScript/TypeScript library for interacting with Fiber nodes. It provides easy-to-use APIs for managing channels, payments, invoices, and node information. + +## Features + +- Channel management (open, close, query channels) +- Payment processing (send and query payments) +- Invoice management (create, parse, query invoices) +- Node information query +- Node connection management + +## Installation + +```bash +npm install @ckb-ccc/fiber +``` + +## Usage + +### Initialize SDK + +```javascript +import { FiberSDK } from "@ckb-ccc/fiber"; + +const sdk = new FiberSDK({ + endpoint: "http://127.0.0.1:8227", // Fiber node RPC address + timeout: 5000, // Request timeout in milliseconds +}); +``` + +## API Reference + +### Channel Management + +#### listChannels +List all channel information. + +```javascript +const channels = await sdk.channel.listChannels(); +``` + +Return Parameters: +- `channels`: Array of channels, each containing: + - `channel_id`: Channel ID + - `peer_id`: Peer node ID + - `state_name`: Channel state name + - `state_flags`: Channel state flags + - `local_balance`: Local balance (hexadecimal) + - `remote_balance`: Remote balance (hexadecimal) + - `created_at`: Creation time (hexadecimal timestamp) + - `is_public`: Whether the channel is public + - `is_enabled`: Whether the channel is enabled + - `tlc_expiry_delta`: TLC expiry delta + - `tlc_min_value`: TLC minimum amount + - `tlc_fee_proportional_millionths`: TLC fee proportion + +#### openChannel +Open a new channel. + +```javascript +const result = await sdk.channel.openChannel({ + peer_id: "QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89", // Peer node ID + funding_amount: "0xba43b7400", // Channel funding amount (hexadecimal) + public: true, // Whether the channel is public +}); +``` + +Parameters: +- `peer_id`: Peer node ID +- `funding_amount`: Channel funding amount (hexadecimal) +- `public`: Whether the channel is public + +#### shutdownChannel +Close a channel. + +```javascript +await sdk.channel.shutdownChannel({ + channel_id: "channel_id", // Channel ID + close_script: { + code_hash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: "0xea076cd91e879a3c189d94068e1584c3fbcc1876", + }, + fee_rate: "0x3FC", // Fee rate (hexadecimal) + force: false, // Whether to force close +}); +``` + +Parameters: +- `channel_id`: Channel ID +- `close_script`: Close script + - `code_hash`: Code hash + - `hash_type`: Hash type + - `args`: Arguments +- `fee_rate`: Fee rate (hexadecimal) +- `force`: Whether to force close + +### Payment Processing + +#### sendPayment +Send a payment. + +```javascript +await sdk.payment.sendPayment({ + invoice: "fibt1000000001pcsaug0p0exgfw0pnm6vk0rnt4xefskmrz0k2vqxr4lnrms60qasvc54jagg2hk8v40k88exmp04pn5cpcnrcsw5lk9w0w6l0m3k84e2ax4v6gq9ne2n77u4p8h3npx6tuufqftq8eyqxw9t4upaw4f89xukcee79rm0p0jv92d5ckq7pmvm09ma3psheu3rfyy9atlrdr4el6ys8yqurl2m74msuykljp35j0s47vpw8h3crfp5ldp8kp4xlusqk6rad3ssgwn2a429qlpgfgjrtj3gzy26w50cy7gypgjm6mjgaz2ff5q4am0avf6paxja2gh2wppjagqlg466yzty0r0pfz8qpuzqgq43mkgx", // Invoice string +}); +``` + +Parameters: +- `invoice`: Invoice string + +#### getPayment +Query payment status. + +```javascript +const payment = await sdk.payment.getPayment("payment_hash"); +``` + +Parameters: +- `payment_hash`: Payment hash + +Return Parameters: +- `status`: Payment status +- `payment_hash`: Payment hash +- `created_at`: Creation time (hexadecimal timestamp) +- `last_updated_at`: Last update time (hexadecimal timestamp) +- `failed_error`: Failure reason (if any) +- `fee`: Fee amount (hexadecimal) + +### Invoice Management + +#### newInvoice +Create a new invoice. + +```javascript +const invoice = await sdk.invoice.newInvoice({ + amount: "0x5f5e100", // Amount (hexadecimal) + currency: "Fibt", // Currency type + description: "test invoice", // Description + expiry: "0xe10", // Expiry time (hexadecimal) + final_cltv: "0x28", // Final CLTV value (hexadecimal) + payment_preimage: "0x...", // Payment preimage + hash_algorithm: "sha256", // Hash algorithm +}); +``` + +Parameters: +- `amount`: Amount (hexadecimal) +- `currency`: Currency type +- `description`: Description +- `expiry`: Expiry time (hexadecimal) +- `final_cltv`: Final CLTV value (hexadecimal) +- `payment_preimage`: Payment preimage +- `hash_algorithm`: Hash algorithm + +Return Parameters: +- `payment_hash`: Payment hash +- `amount`: Amount +- `description`: Description +- `expiry`: Expiry time +- `created_at`: Creation time + +#### parseInvoice +Parse an invoice. + +```javascript +const parsedInvoice = await sdk.invoice.parseInvoice("invoice_string"); +``` + +Parameters: +- `invoice_string`: Invoice string + +Return Parameters: +- Parsed invoice information object + +#### getInvoice +Query invoice status. + +```javascript +const invoiceInfo = await sdk.invoice.getInvoice("payment_hash"); +``` + +Parameters: +- `payment_hash`: Payment hash + +Return Parameters: +- `status`: Invoice status +- `invoice_address`: Invoice address +- `invoice`: Invoice details + - `payment_hash`: Payment hash + - `amount`: Amount + - `description`: Description + - `expiry`: Expiry time + - `created_at`: Creation time + +### Node Management + +#### nodeInfo +Get node information. + +```javascript +const nodeInfo = await sdk.nodeInfo(); +``` + +Return Parameters: +- `node_name`: Node name +- `node_id`: Node ID +- `addresses`: Node addresses list +- `chain_hash`: Chain hash +- `auto_accept_min_ckb_funding_amount`: Minimum CKB funding amount for auto-accept +- `udt_cfg_infos`: UDT configuration information +- `timestamp`: Timestamp + +#### connectPeer +Connect to a peer node. + +```javascript +await sdk.peer.connectPeer({ + peer_id: "QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89", // Node ID + address: "/ip4/127.0.0.1/tcp/8119", // Node address +}); +``` + +Parameters: +- `peer_id`: Node ID +- `address`: Node address + +#### disconnectPeer +Disconnect from a peer node. + +```javascript +await sdk.peer.disconnectPeer("peer_id"); +``` + +Parameters: +- `peer_id`: Node ID + +## Error Handling + +The SDK provides a unified error handling mechanism. When an error occurs, it returns an object containing error information: + +```javascript +try { + await sdk.channel.listChannels(); +} catch (error) { + if (error.error) { + // RPC error + console.error("RPC Error:", error.error); + } else { + // Other errors + console.error("Error:", error.message); + } +} +``` + +## Testing + +The project includes multiple test files for testing various functional modules: + +- `test/channel.cjs` - Channel management tests +- `test/payment.cjs` - Payment processing tests +- `test/invoice.cjs` - Invoice management tests +- `test/peer.cjs` - Node connection tests +- `test/info.cjs` - Node information tests + +Run tests: + +```bash +node test/channel.cjs +node test/payment.cjs +node test/invoice.cjs +node test/peer.cjs +node test/info.cjs +``` + +## Notes + +1. Ensure the Fiber node is started and running properly before use +2. Make sure the RPC address is configured correctly +3. All amount parameters must be in hexadecimal format (starting with "0x") +4. It is recommended to use appropriate error handling in production environments + +## Contributing + +Issues and Pull Requests are welcome to help improve the project. + +## License + +[MIT](LICENSE) From 6697ce394b8663e0f520a16fdd8b8074c23483c8 Mon Sep 17 00:00:00 2001 From: Jack <6464072@qq.com> Date: Wed, 16 Apr 2025 09:54:08 +0800 Subject: [PATCH 08/15] feat:fiber sdk test page --- packages/demo/next.config.mjs | 8 + packages/demo/src/app/fiber/page.tsx | 150 ++++++++++-- packages/fiber/README.md | 143 +++++------- packages/fiber/components/channel-test.html | 238 ++++++++++++++++++++ packages/fiber/examples/basic-usage.html | 141 +++++++++++- packages/fiber/src/core/types.ts | 33 ++- packages/fiber/src/index.ts | 22 +- packages/fiber/src/modules/channel.ts | 2 +- packages/fiber/src/modules/info.ts | 40 +--- packages/fiber/src/modules/peer.ts | 17 +- packages/fiber/test/channel.cjs | 34 +-- packages/fiber/test/info.cjs | 40 ++-- packages/fiber/test/peer.cjs | 19 +- 13 files changed, 669 insertions(+), 218 deletions(-) create mode 100644 packages/fiber/components/channel-test.html diff --git a/packages/demo/next.config.mjs b/packages/demo/next.config.mjs index c3f0428b..3b9bef15 100644 --- a/packages/demo/next.config.mjs +++ b/packages/demo/next.config.mjs @@ -3,6 +3,14 @@ const nextConfig = { experimental: { optimizePackageImports: ["@ckb-ccc/core", "@ckb-ccc/core/bundle"], }, + async rewrites() { + return [ + { + source: '/api/fiber/:path*', + destination: 'http://127.0.0.1:8227/:path*', + }, + ]; + }, }; export default nextConfig; diff --git a/packages/demo/src/app/fiber/page.tsx b/packages/demo/src/app/fiber/page.tsx index f3d214e1..10e21475 100644 --- a/packages/demo/src/app/fiber/page.tsx +++ b/packages/demo/src/app/fiber/page.tsx @@ -3,46 +3,152 @@ import { Button } from "@/src/components/Button"; import { useEffect, useState } from "react"; import { TextInput } from "@/src/components/Input"; -import { ccc } from "@ckb-ccc/connector-react"; import { useRouter } from "next/navigation"; import { ButtonsPanel } from "@/src/components/ButtonsPanel"; import { FiberSDK } from "@ckb-ccc/fiber"; +import { useFiber } from "./context/FiberContext"; +import { BigButton } from "@/src/components/BigButton"; + +interface OpenChannelForm { + peerAddress: string; + fundingAmount: string; + feeRate: string; + tlcExpiryDelta: string; + tlcMinValue: string; + tlcFeeProportionalMillionths: string; + isPublic: boolean; + isEnabled: boolean; +} export default function Page() { const router = useRouter(); - const { client } = ccc.useCcc(); + const { fiber, setFiber } = useFiber(); const [endpoint, setEndpoint] = useState(""); - + const [nodeInfo, setNodeInfo] = useState(null); + + const initSdk = () => { - const fiber = new FiberSDK({ - endpoint: endpoint, + const newFiber = new FiberSDK({ + endpoint: endpoint || `/api/fiber`, timeout: 5000, }); - if (fiber) { - console.log(fiber); + if (newFiber) { console.log("Fiber SDK initialized"); + setFiber(newFiber); } else { console.log("Fiber SDK initialization failed"); } }; + + const getNodeInfo = async () => { + if (!fiber) return; + try { + const info = await fiber.nodeInfo(); + console.log(info); + setNodeInfo(info); + } catch (error) { + console.error("Failed to get node info:", error); + } + }; + + + + useEffect(() => { + if (fiber) { + getNodeInfo(); + } + }, [fiber]); + return ( -
- +
+
+

Fiber

+
+ {fiber ? ( +
+ router.push('/fiber/channel')} + className={'text-yellow-500'} + > + channel + + router.push('/fiber/peer')} + className={'text-yellow-500'} + > + Peer + + router.push('/fiber/payment')} + className={'text-yellow-500'} + > + Payment + + router.push('/fiber/invoice')} + className={'text-yellow-500'} + > + Invoice + +
+ ) : ( +
+
+ +
+
+ )} + + + + {nodeInfo && ( +
+

Node Information

+
+

+ Version: {nodeInfo.version} +

+

+ Commit Hash:{" "} + {nodeInfo.commit_hash} +

+

+ Node ID: {nodeInfo.node_id} +

+

+ Node Name:{" "} + {nodeInfo.node_name || "Not set"} +

+

+ Addresses:{" "} + {nodeInfo.addresses.length > 0 + ? nodeInfo.addresses.join(", ") + : "No addresses"} +

+
+
+ )} - - + + +
); } diff --git a/packages/fiber/README.md b/packages/fiber/README.md index 5a34b11e..0ef374bb 100644 --- a/packages/fiber/README.md +++ b/packages/fiber/README.md @@ -63,6 +63,24 @@ const result = await sdk.channel.openChannel({ peer_id: "QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89", // Peer node ID funding_amount: "0xba43b7400", // Channel funding amount (hexadecimal) public: true, // Whether the channel is public + funding_udt_type_script: { // Optional UDT type script + code_hash: "0x...", + hash_type: "type", + args: "0x...", + }, + shutdown_script: { // Optional shutdown script + code_hash: "0x...", + hash_type: "type", + args: "0x...", + }, + commitment_delay_epoch: "0x...", // Optional commitment delay epoch + commitment_fee_rate: "0x...", // Optional commitment fee rate + funding_fee_rate: "0x...", // Optional funding fee rate + tlc_expiry_delta: "0x...", // Optional TLC expiry delta + tlc_min_value: "0x...", // Optional TLC minimum value + tlc_fee_proportional_millionths: "0x...", // Optional TLC fee proportion + max_tlc_value_in_flight: "0x...", // Optional maximum TLC value in flight + max_tlc_number_in_flight: "0x...", // Optional maximum TLC number in flight }); ``` @@ -70,6 +88,16 @@ Parameters: - `peer_id`: Peer node ID - `funding_amount`: Channel funding amount (hexadecimal) - `public`: Whether the channel is public +- `funding_udt_type_script`: Optional UDT type script +- `shutdown_script`: Optional shutdown script +- `commitment_delay_epoch`: Optional commitment delay epoch +- `commitment_fee_rate`: Optional commitment fee rate +- `funding_fee_rate`: Optional funding fee rate +- `tlc_expiry_delta`: Optional TLC expiry delta +- `tlc_min_value`: Optional TLC minimum value +- `tlc_fee_proportional_millionths`: Optional TLC fee proportion +- `max_tlc_value_in_flight`: Optional maximum TLC value in flight +- `max_tlc_number_in_flight`: Optional maximum TLC number in flight #### shutdownChannel Close a channel. @@ -103,12 +131,20 @@ Send a payment. ```javascript await sdk.payment.sendPayment({ - invoice: "fibt1000000001pcsaug0p0exgfw0pnm6vk0rnt4xefskmrz0k2vqxr4lnrms60qasvc54jagg2hk8v40k88exmp04pn5cpcnrcsw5lk9w0w6l0m3k84e2ax4v6gq9ne2n77u4p8h3npx6tuufqftq8eyqxw9t4upaw4f89xukcee79rm0p0jv92d5ckq7pmvm09ma3psheu3rfyy9atlrdr4el6ys8yqurl2m74msuykljp35j0s47vpw8h3crfp5ldp8kp4xlusqk6rad3ssgwn2a429qlpgfgjrtj3gzy26w50cy7gypgjm6mjgaz2ff5q4am0avf6paxja2gh2wppjagqlg466yzty0r0pfz8qpuzqgq43mkgx", // Invoice string + payment_hash: "payment_hash", // Payment hash + amount: "0x5f5e100", // Amount (hexadecimal) + fee_rate: "0x3FC", // Fee rate (hexadecimal) + custom_records: {}, // Optional custom records + route: {}, // Optional route information }); ``` Parameters: -- `invoice`: Invoice string +- `payment_hash`: Payment hash +- `amount`: Amount (hexadecimal) +- `fee_rate`: Fee rate (hexadecimal) +- `custom_records`: Optional custom records +- `route`: Optional route information #### getPayment Query payment status. @@ -136,30 +172,17 @@ Create a new invoice. ```javascript const invoice = await sdk.invoice.newInvoice({ amount: "0x5f5e100", // Amount (hexadecimal) - currency: "Fibt", // Currency type - description: "test invoice", // Description - expiry: "0xe10", // Expiry time (hexadecimal) - final_cltv: "0x28", // Final CLTV value (hexadecimal) - payment_preimage: "0x...", // Payment preimage - hash_algorithm: "sha256", // Hash algorithm + description: "test invoice", // Optional description + expiry: "0xe10", // Optional expiry time (hexadecimal) + payment_secret: "0x...", // Optional payment secret }); ``` Parameters: - `amount`: Amount (hexadecimal) -- `currency`: Currency type -- `description`: Description -- `expiry`: Expiry time (hexadecimal) -- `final_cltv`: Final CLTV value (hexadecimal) -- `payment_preimage`: Payment preimage -- `hash_algorithm`: Hash algorithm - -Return Parameters: -- `payment_hash`: Payment hash -- `amount`: Amount -- `description`: Description -- `expiry`: Expiry time -- `created_at`: Creation time +- `description`: Optional description +- `expiry`: Optional expiry time (hexadecimal) +- `payment_secret`: Optional payment secret #### parseInvoice Parse an invoice. @@ -194,6 +217,16 @@ Return Parameters: - `expiry`: Expiry time - `created_at`: Creation time +#### cancelInvoice +Cancel an invoice. + +```javascript +await sdk.invoice.cancelInvoice("payment_hash"); +``` + +Parameters: +- `payment_hash`: Payment hash + ### Node Management #### nodeInfo @@ -216,75 +249,13 @@ Return Parameters: Connect to a peer node. ```javascript -await sdk.peer.connectPeer({ - peer_id: "QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89", // Node ID - address: "/ip4/127.0.0.1/tcp/8119", // Node address -}); +await sdk.peer.connectPeer("/ip4/127.0.0.1/tcp/8119/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89"); ``` Parameters: -- `peer_id`: Node ID -- `address`: Node address +- `address`: Full peer address including peer ID (e.g. "/ip4/127.0.0.1/tcp/8119/p2p/Qm...") #### disconnectPeer Disconnect from a peer node. -```javascript -await sdk.peer.disconnectPeer("peer_id"); -``` - -Parameters: -- `peer_id`: Node ID - -## Error Handling - -The SDK provides a unified error handling mechanism. When an error occurs, it returns an object containing error information: - -```javascript -try { - await sdk.channel.listChannels(); -} catch (error) { - if (error.error) { - // RPC error - console.error("RPC Error:", error.error); - } else { - // Other errors - console.error("Error:", error.message); - } -} -``` - -## Testing - -The project includes multiple test files for testing various functional modules: - -- `test/channel.cjs` - Channel management tests -- `test/payment.cjs` - Payment processing tests -- `test/invoice.cjs` - Invoice management tests -- `test/peer.cjs` - Node connection tests -- `test/info.cjs` - Node information tests - -Run tests: - -```bash -node test/channel.cjs -node test/payment.cjs -node test/invoice.cjs -node test/peer.cjs -node test/info.cjs -``` - -## Notes - -1. Ensure the Fiber node is started and running properly before use -2. Make sure the RPC address is configured correctly -3. All amount parameters must be in hexadecimal format (starting with "0x") -4. It is recommended to use appropriate error handling in production environments - -## Contributing - -Issues and Pull Requests are welcome to help improve the project. - -## License - -[MIT](LICENSE) +``` \ No newline at end of file diff --git a/packages/fiber/components/channel-test.html b/packages/fiber/components/channel-test.html new file mode 100644 index 00000000..51e01bf1 --- /dev/null +++ b/packages/fiber/components/channel-test.html @@ -0,0 +1,238 @@ + + + + + + Fiber 通道测试 + + + +
+

Fiber 通道测试

+ +
+

节点信息

+ +

+        
+ +
+

节点连接

+
+ + +
+ +
+
+ +
+

通道操作

+
+ + +
+
+ + +
+ + + +

+        
+
+ + + + + + + + + \ No newline at end of file diff --git a/packages/fiber/examples/basic-usage.html b/packages/fiber/examples/basic-usage.html index 0519ecba..bfdf6338 100644 --- a/packages/fiber/examples/basic-usage.html +++ b/packages/fiber/examples/basic-usage.html @@ -1 +1,140 @@ - \ No newline at end of file + + + + + + Fiber 基本使用示例 + + + +
+

Fiber 基本使用示例

+ +
+

节点信息

+ +

+        
+ +
+

通道操作

+
+ + + + +
+

+        
+
+ + + + + + + + + \ No newline at end of file diff --git a/packages/fiber/src/core/types.ts b/packages/fiber/src/core/types.ts index e94bd253..71db4efe 100644 --- a/packages/fiber/src/core/types.ts +++ b/packages/fiber/src/core/types.ts @@ -1,12 +1,17 @@ +import { Script } from "../types.js"; + export type Hash256 = string; export type Pubkey = string; export interface Channel { channel_id: Hash256; is_public: boolean; - channel_outpoint?: any; + channel_outpoint?: { + tx_hash: Hash256; + index: bigint; + }; peer_id: string; - funding_udt_type_script?: any; + funding_udt_type_script?: Script; state: string; local_balance: bigint; offered_tlc_balance: bigint; @@ -20,7 +25,10 @@ export interface Channel { } export interface ChannelInfo { - channel_outpoint: any; + channel_outpoint: { + tx_hash: Hash256; + index: bigint; + }; node1: Pubkey; node2: Pubkey; created_timestamp: bigint; @@ -30,7 +38,7 @@ export interface ChannelInfo { fee_rate_of_node2?: bigint; capacity: bigint; chain_hash: Hash256; - udt_type_script?: any; + udt_type_script?: Script; } export interface NodeInfo { @@ -40,7 +48,7 @@ export interface NodeInfo { timestamp: bigint; chain_hash: Hash256; auto_accept_min_ckb_funding_amount: bigint; - udt_cfg_infos: any; + udt_cfg_infos: Record; } export interface PaymentSessionStatus { @@ -50,15 +58,18 @@ export interface PaymentSessionStatus { last_updated_at: bigint; failed_error?: string; fee: bigint; - custom_records?: any; - router: any; + custom_records?: Record; + router: { + node_id: string; + fee: bigint; + }; } export interface CkbInvoice { currency: "Fibb" | "Fibt" | "Fibd"; amount?: bigint; - signature?: any; - data: any; + signature?: string; + data: Record; } export interface CkbInvoiceStatus { @@ -67,13 +78,13 @@ export interface CkbInvoiceStatus { invoice: CkbInvoice; } -export interface RPCResponse { +export interface RPCResponse { jsonrpc: string; id: string; result?: T; error?: { code: number; message: string; - data?: any; + data?: unknown; }; } diff --git a/packages/fiber/src/index.ts b/packages/fiber/src/index.ts index 6c2e8a3c..b8f31f16 100644 --- a/packages/fiber/src/index.ts +++ b/packages/fiber/src/index.ts @@ -3,7 +3,7 @@ import { ChannelModule } from "./modules/channel.js"; import { InfoModule } from "./modules/info.js"; import { InvoiceModule } from "./modules/invoice.js"; import { PaymentModule } from "./modules/payment.js"; -import { PeerModule } from "./modules/peer.js"; +import { PeerInfo, PeerModule } from "./modules/peer.js"; import { Channel, CkbInvoice, @@ -99,6 +99,15 @@ export class FiberSDK { return this.channel.shutdownChannel(params); } + /** + * 关闭通道 + /** + * 关闭通道 + */ + async abandonChannel(channel_id: Hash256): Promise { + return this.channel.abandonChannel(channel_id); + } + /** * 发送支付 */ @@ -164,8 +173,8 @@ export class FiberSDK { /** * 连接节点 */ - async connectPeer(peer_address: string): Promise { - return this.peer.connectPeer(peer_address); + async connectPeer(address: string): Promise { + return this.peer.connectPeer(address); } /** @@ -174,6 +183,13 @@ export class FiberSDK { async disconnectPeer(peer_id: string): Promise { return this.peer.disconnectPeer(peer_id); } + + /** + * 列出所有连接的节点 + */ + async listPeers(): Promise { + return this.peer.listPeers(); + } } export default FiberSDK; diff --git a/packages/fiber/src/modules/channel.ts b/packages/fiber/src/modules/channel.ts index 841e1f63..68b9742b 100644 --- a/packages/fiber/src/modules/channel.ts +++ b/packages/fiber/src/modules/channel.ts @@ -47,7 +47,7 @@ export class ChannelModule { * @returns Promise */ async abandonChannel(channelId: Hash256): Promise { - console.log(channelId); + console.log(11111, channelId); if (!channelId) { throw new Error("Channel ID cannot be empty"); } diff --git a/packages/fiber/src/modules/info.ts b/packages/fiber/src/modules/info.ts index 1da89aa6..1fb28dc5 100644 --- a/packages/fiber/src/modules/info.ts +++ b/packages/fiber/src/modules/info.ts @@ -10,44 +10,6 @@ export class InfoModule { * @throws {Error} Throws error when unable to get node information */ async nodeInfo(): Promise { - //转换nodeInfo中的十六进制数字为十进制数字 - //包括channel_count,peers_count,pending_channel_count,tlc_expiry_delta,tlc_fee_proportional_millionths,tlc_max_value,tlc_min_value,open_channel_auto_accept_min_ckb_funding_amount,auto_accept_channel_ckb_funding_amount - const nodeInfo = await this.client.call<{ - node_name: string; - addresses: string[]; - node_id: string; - timestamp: string; - chain_hash: string; - auto_accept_min_ckb_funding_amount: string; - udt_cfg_infos: Record; - default_funding_lock_script?: { - code_hash: string; - hash_type: string; - args: string; - }; - }>("node_info", []); - if (nodeInfo) { - const nodeInfoWithDecimal: NodeInfo = { - node_name: nodeInfo.node_name || "", - addresses: nodeInfo.addresses || [], - node_id: nodeInfo.node_id || "", - timestamp: nodeInfo.timestamp ? BigInt(nodeInfo.timestamp) : BigInt(0), - chain_hash: nodeInfo.chain_hash || "", - auto_accept_min_ckb_funding_amount: - nodeInfo.auto_accept_min_ckb_funding_amount - ? BigInt(nodeInfo.auto_accept_min_ckb_funding_amount) - : BigInt(0), - udt_cfg_infos: nodeInfo.udt_cfg_infos || {}, - default_funding_lock_script: nodeInfo.default_funding_lock_script - ? { - code_hash: nodeInfo.default_funding_lock_script.code_hash || "", - hash_type: nodeInfo.default_funding_lock_script.hash_type || "", - args: nodeInfo.default_funding_lock_script.args || "", - } - : undefined, - }; - return nodeInfoWithDecimal; - } - throw new Error("无法获取节点信息"); + return this.client.call("node_info", []); } } diff --git a/packages/fiber/src/modules/peer.ts b/packages/fiber/src/modules/peer.ts index d7447fc9..6706dc69 100644 --- a/packages/fiber/src/modules/peer.ts +++ b/packages/fiber/src/modules/peer.ts @@ -1,12 +1,17 @@ import { FiberClient } from "../client.js"; -import { Peer } from "../types.js"; + +export interface PeerInfo { + pubkey: string; + peer_id: string; + addresses: string[]; +} export class PeerModule { constructor(private client: FiberClient) {} /** * Connect to a peer node - * @param address Node address + * @param address Full peer address including peer ID (e.g. "/ip4/127.0.0.1/tcp/8119/p2p/Qm...") */ async connectPeer(address: string): Promise { return this.client.call("connect_peer", [{ address }]); @@ -18,4 +23,12 @@ export class PeerModule { async disconnectPeer(peer_id: string): Promise { return this.client.call("disconnect_peer", [{ peer_id }]); } + + /** + * List all connected peers + * @returns Array of peer information + */ + async listPeers(): Promise { + return this.client.call("list_peers", []); + } } diff --git a/packages/fiber/test/channel.cjs b/packages/fiber/test/channel.cjs index 40e5047c..11962cad 100644 --- a/packages/fiber/test/channel.cjs +++ b/packages/fiber/test/channel.cjs @@ -45,7 +45,7 @@ async function testListChannels() { try { // List channels console.log("Calling listChannels method..."); - const channels = await sdk.channel.listChannels(); + const channels = await sdk.listChannels(); // Output raw data console.log("Raw data:", JSON.stringify(channels, null, 2)); @@ -105,7 +105,7 @@ async function testListChannels() { } } -async function testCloseChannels() { +async function testAbandonChannel() { try { // Initialize SDK const sdk = new FiberSDK({ @@ -131,23 +131,7 @@ async function testCloseChannels() { console.log("Peer ID:", channelToClose.peer_id); console.log("State:", channelToClose.state); - // Call shutdown channel method - console.log("\nCalling shutdownChannel method..."); - await sdk.channel.shutdownChannel({ - channel_id: channelToClose.channel_id, - close_script: { - code_hash: - "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - hash_type: "type", - args: "0xea076cd91e879a3c189d94068e1584c3fbcc1876", - }, - fee_rate: "0x3FC", - force: false, - }); - console.log("Channel closed successfully"); - - // Verify channel status - console.log("\nVerifying channel status..."); + await sdk.abandonChannel(channelToClose.channel_id); } catch (error) { if (error.error) { handleRPCError(error); @@ -165,9 +149,9 @@ async function testNewChannel() { endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - const peerId = "QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89"; + const peerId = "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; console.log("Calling open_channel method, Peer ID:", peerId); - const result = await sdk.channel.openChannel({ + const result = await sdk.openChannel({ peer_id: peerId, funding_amount: "0xba43b7400", // 100 CKB public: true, @@ -204,7 +188,7 @@ async function testUpdateAndShutdownChannel() { } console.log("Calling shutdown_channel method, Channel ID:", channelId); - const result2 = await sdk.channel.shutdownChannel({ + const result2 = await sdk.shutdownChannel({ channel_id: channelId, close_script: { code_hash: @@ -220,11 +204,11 @@ async function testUpdateAndShutdownChannel() { async function main() { try { - await testListChannels(); - // await testCloseChannels(); + // await testListChannels(); // await testNewChannel(); - // await testUpdateAndShutdownChannel(); + await testUpdateAndShutdownChannel(); // await testListChannels(); + // await testAbandonChannel(); console.log("\nAll tests completed!"); } catch (error) { console.error("Error during test:", error); diff --git a/packages/fiber/test/info.cjs b/packages/fiber/test/info.cjs index 17f7cfcf..3207038c 100644 --- a/packages/fiber/test/info.cjs +++ b/packages/fiber/test/info.cjs @@ -2,10 +2,14 @@ const { FiberSDK } = require("../dist.commonjs/index.js"); // Custom error handling function function handleRPCError(error) { if (error.error && error.error.code === -32601) { - console.error("Error: Node may not be running or RPC method does not exist"); + console.error( + "Error: Node may not be running or RPC method does not exist", + ); console.error("Please ensure:"); console.error("1. Fiber node is started"); - console.error("2. Node RPC address is correct (current: http://127.0.0.1:8227)"); + console.error( + "2. Node RPC address is correct (current: http://127.0.0.1:8227)", + ); console.error("3. Node RPC interface is available"); } else if (error.error && error.error.code === -32602) { console.error("Error: Invalid parameters"); @@ -41,22 +45,22 @@ async function testNodeInfo() { // Get node information console.log("Calling node_info method..."); const info = await sdk.nodeInfo(); - - // Output node information - console.log("\nNode Information:"); - console.log("Node Name:", info.node_name); - console.log("Node ID:", info.node_id); - console.log("Addresses:", info.addresses); - console.log("Chain Hash:", info.chain_hash); - console.log( - "Auto Accept Min CKB Funding Amount:", - info.auto_accept_min_ckb_funding_amount, - ); - console.log("UDT Config Info:", info.udt_cfg_infos); - console.log( - "Timestamp:", - new Date(Number(info.timestamp)).toLocaleString(), - ); + console.log(info); + // // Output node information + // console.log("\nNode Information:"); + // console.log("Node Name:", info.node_name); + // console.log("Node ID:", info.node_id); + // console.log("Addresses:", info.addresses); + // console.log("Chain Hash:", info.chain_hash); + // console.log( + // "Auto Accept Min CKB Funding Amount:", + // info.auto_accept_min_ckb_funding_amount, + // ); + // console.log("UDT Config Info:", info.udt_cfg_infos); + // console.log( + // "Timestamp:", + // new Date(Number(info.timestamp)).toLocaleString(), + // ); } catch (error) { if (error.error) { handleRPCError(error); diff --git a/packages/fiber/test/peer.cjs b/packages/fiber/test/peer.cjs index e42a24eb..0a0bde06 100644 --- a/packages/fiber/test/peer.cjs +++ b/packages/fiber/test/peer.cjs @@ -39,13 +39,12 @@ async function testConnectPeer() { try { // Connect to peer console.log("Calling connect_peer method..."); - const peerAddress = "/ip4/18.162.235.225/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; - const [address, peerId] = peerAddress.split("/p2p/"); - + const peerAddress = + "/ip4/18.162.235.225/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; + try { - const response = await sdk.peer.connectPeer({ - peer_id: peerId, - address: address, + const response = await sdk.connectPeer({ + address: peerAddress, }); console.log("Successfully connected to peer:", response); } catch (error) { @@ -80,7 +79,7 @@ async function testDisconnectPeer() { endpoint: "http://127.0.0.1:8227", timeout: 30000, }); - const peerId = "QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89"; + const peerId = "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; console.log("正在调用 disconnect_peer 方法,节点 ID:", peerId); const result = await sdk.peer.disconnectPeer(peerId); console.log("断开链接结果:", result); @@ -98,7 +97,7 @@ async function testListChannels() { const peerId = "QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo"; console.log("正在调用 list_channels 方法,节点 ID:", peerId); - const result = await sdk.channel.listChannels({ + const result = await sdk.listChannels({ peer_id: peerId, }); @@ -113,10 +112,10 @@ async function testListChannels() { async function main() { // 1. 首先清理处于 NEGOTIATING_FUNDING 状态的通道 - await testListChannels(); + // await testListChannels(); // 2. 然后建立网络连接 - // await testConnectPeer(); + await testConnectPeer(); // 3. 断开链接 await testDisconnectPeer(); From 0a132113a7ef1d781d45c36077fbdc00e7a63866 Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Wed, 16 Apr 2025 21:43:25 +0800 Subject: [PATCH 09/15] chore: code format and remove chinesecomments --- packages/fiber/README.md | 43 +- packages/fiber/components/channel-test.html | 458 ++++++++++---------- packages/fiber/examples/basic-usage.html | 251 ++++++----- packages/fiber/src/client.ts | 2 - packages/fiber/src/index.ts | 32 +- packages/fiber/src/modules/cch.ts | 13 +- packages/fiber/src/modules/dev.ts | 12 +- packages/fiber/src/modules/graph.ts | 6 +- packages/fiber/src/modules/payment.ts | 8 +- packages/fiber/src/rpc.ts | 86 ---- packages/fiber/test/info.cjs | 2 +- packages/fiber/test/invoice.cjs | 41 +- packages/fiber/test/payment.cjs | 82 ++-- packages/fiber/test/peer.cjs | 10 +- 14 files changed, 513 insertions(+), 533 deletions(-) delete mode 100644 packages/fiber/src/rpc.ts diff --git a/packages/fiber/README.md b/packages/fiber/README.md index 0ef374bb..32cdf37e 100644 --- a/packages/fiber/README.md +++ b/packages/fiber/README.md @@ -34,6 +34,7 @@ const sdk = new FiberSDK({ ### Channel Management #### listChannels + List all channel information. ```javascript @@ -41,6 +42,7 @@ const channels = await sdk.channel.listChannels(); ``` Return Parameters: + - `channels`: Array of channels, each containing: - `channel_id`: Channel ID - `peer_id`: Peer node ID @@ -56,6 +58,7 @@ Return Parameters: - `tlc_fee_proportional_millionths`: TLC fee proportion #### openChannel + Open a new channel. ```javascript @@ -63,12 +66,14 @@ const result = await sdk.channel.openChannel({ peer_id: "QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89", // Peer node ID funding_amount: "0xba43b7400", // Channel funding amount (hexadecimal) public: true, // Whether the channel is public - funding_udt_type_script: { // Optional UDT type script + funding_udt_type_script: { + // Optional UDT type script code_hash: "0x...", hash_type: "type", args: "0x...", }, - shutdown_script: { // Optional shutdown script + shutdown_script: { + // Optional shutdown script code_hash: "0x...", hash_type: "type", args: "0x...", @@ -85,6 +90,7 @@ const result = await sdk.channel.openChannel({ ``` Parameters: + - `peer_id`: Peer node ID - `funding_amount`: Channel funding amount (hexadecimal) - `public`: Whether the channel is public @@ -100,13 +106,15 @@ Parameters: - `max_tlc_number_in_flight`: Optional maximum TLC number in flight #### shutdownChannel + Close a channel. ```javascript await sdk.channel.shutdownChannel({ channel_id: "channel_id", // Channel ID close_script: { - code_hash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + code_hash: + "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", hash_type: "type", args: "0xea076cd91e879a3c189d94068e1584c3fbcc1876", }, @@ -116,6 +124,7 @@ await sdk.channel.shutdownChannel({ ``` Parameters: + - `channel_id`: Channel ID - `close_script`: Close script - `code_hash`: Code hash @@ -127,6 +136,7 @@ Parameters: ### Payment Processing #### sendPayment + Send a payment. ```javascript @@ -140,6 +150,7 @@ await sdk.payment.sendPayment({ ``` Parameters: + - `payment_hash`: Payment hash - `amount`: Amount (hexadecimal) - `fee_rate`: Fee rate (hexadecimal) @@ -147,6 +158,7 @@ Parameters: - `route`: Optional route information #### getPayment + Query payment status. ```javascript @@ -154,9 +166,11 @@ const payment = await sdk.payment.getPayment("payment_hash"); ``` Parameters: + - `payment_hash`: Payment hash Return Parameters: + - `status`: Payment status - `payment_hash`: Payment hash - `created_at`: Creation time (hexadecimal timestamp) @@ -167,6 +181,7 @@ Return Parameters: ### Invoice Management #### newInvoice + Create a new invoice. ```javascript @@ -179,12 +194,14 @@ const invoice = await sdk.invoice.newInvoice({ ``` Parameters: + - `amount`: Amount (hexadecimal) - `description`: Optional description - `expiry`: Optional expiry time (hexadecimal) - `payment_secret`: Optional payment secret #### parseInvoice + Parse an invoice. ```javascript @@ -192,12 +209,15 @@ const parsedInvoice = await sdk.invoice.parseInvoice("invoice_string"); ``` Parameters: + - `invoice_string`: Invoice string Return Parameters: + - Parsed invoice information object #### getInvoice + Query invoice status. ```javascript @@ -205,9 +225,11 @@ const invoiceInfo = await sdk.invoice.getInvoice("payment_hash"); ``` Parameters: + - `payment_hash`: Payment hash Return Parameters: + - `status`: Invoice status - `invoice_address`: Invoice address - `invoice`: Invoice details @@ -218,6 +240,7 @@ Return Parameters: - `created_at`: Creation time #### cancelInvoice + Cancel an invoice. ```javascript @@ -225,11 +248,13 @@ await sdk.invoice.cancelInvoice("payment_hash"); ``` Parameters: + - `payment_hash`: Payment hash ### Node Management #### nodeInfo + Get node information. ```javascript @@ -237,6 +262,7 @@ const nodeInfo = await sdk.nodeInfo(); ``` Return Parameters: + - `node_name`: Node name - `node_id`: Node ID - `addresses`: Node addresses list @@ -246,16 +272,23 @@ Return Parameters: - `timestamp`: Timestamp #### connectPeer + Connect to a peer node. ```javascript -await sdk.peer.connectPeer("/ip4/127.0.0.1/tcp/8119/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89"); +await sdk.peer.connectPeer( + "/ip4/127.0.0.1/tcp/8119/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89", +); ``` Parameters: + - `address`: Full peer address including peer ID (e.g. "/ip4/127.0.0.1/tcp/8119/p2p/Qm...") #### disconnectPeer + Disconnect from a peer node. -``` \ No newline at end of file +``` + +``` diff --git a/packages/fiber/components/channel-test.html b/packages/fiber/components/channel-test.html index 51e01bf1..7fdd27b8 100644 --- a/packages/fiber/components/channel-test.html +++ b/packages/fiber/components/channel-test.html @@ -1,238 +1,258 @@ - + - - - + + + Fiber 通道测试 - - + +
-

Fiber 通道测试

- -
-

节点信息

- -

-        
+

Fiber 通道测试

-
-

节点连接

-
- - -
- -
+
+

节点信息

+ +

+      
+ +
+

节点连接

+
+ +
+ +
+
-
-

通道操作

-
- - -
-
- - -
- - - -

+      
+

通道操作

+
+ + +
+
+ +
+ + + +

+      
- + - - \ No newline at end of file + + diff --git a/packages/fiber/examples/basic-usage.html b/packages/fiber/examples/basic-usage.html index bfdf6338..5b7987e5 100644 --- a/packages/fiber/examples/basic-usage.html +++ b/packages/fiber/examples/basic-usage.html @@ -1,140 +1,159 @@ - + - - - + + + Fiber 基本使用示例 - - + +
-

Fiber 基本使用示例

- -
-

节点信息

- -

-        
+

Fiber 基本使用示例

-
-

通道操作

-
- - - - -
-

+      
+

节点信息

+ +

+      
+ +
+

通道操作

+
+ + + +
+

+      
- + - - \ No newline at end of file + + diff --git a/packages/fiber/src/client.ts b/packages/fiber/src/client.ts index 81c73960..3de5043a 100644 --- a/packages/fiber/src/client.ts +++ b/packages/fiber/src/client.ts @@ -1,5 +1,3 @@ -import axios from "axios"; -import { RPCRequest, RPCResponse } from "./types.js"; import { RequestorJsonRpc, RequestorJsonRpcConfig } from "@ckb-ccc/core"; export interface ClientConfig extends RequestorJsonRpcConfig { diff --git a/packages/fiber/src/index.ts b/packages/fiber/src/index.ts index b8f31f16..9687a0ec 100644 --- a/packages/fiber/src/index.ts +++ b/packages/fiber/src/index.ts @@ -53,21 +53,21 @@ export class FiberSDK { } /** - * 列出所有通道 + * List all channels */ async listChannels(): Promise { return this.channel.listChannels(); } /** - * 获取节点信息 + * Get node information */ async nodeInfo(): Promise { return this.info.nodeInfo(); } /** - * 打开通道 + * Open channel */ async openChannel(params: { peer_id: string; @@ -88,7 +88,7 @@ export class FiberSDK { } /** - * 关闭通道 + * Close channel */ async shutdownChannel(params: { channel_id: Hash256; @@ -100,16 +100,14 @@ export class FiberSDK { } /** - * 关闭通道 - /** - * 关闭通道 - */ + * Close channel + */ async abandonChannel(channel_id: Hash256): Promise { return this.channel.abandonChannel(channel_id); } /** - * 发送支付 + * Send payment */ async sendPayment(params: { payment_hash: string; @@ -120,14 +118,14 @@ export class FiberSDK { } /** - * 解析发票 + * Parse invoice */ async parseInvoice(invoice: string): Promise { return this.invoice.parseInvoice(invoice); } /** - * 创建新发票 + * Create new invoice */ async newInvoice(params: { amount: bigint; @@ -139,7 +137,7 @@ export class FiberSDK { } /** - * 获取发票信息 + * Get invoice information */ async getInvoice(payment_hash: string): Promise<{ status: string; @@ -150,14 +148,14 @@ export class FiberSDK { } /** - * 取消发票 + * Cancel invoice */ async cancelInvoice(payment_hash: string): Promise { return this.invoice.cancelInvoice(payment_hash); } /** - * 获取支付信息 + * Get payment information */ async getPayment(payment_hash: string): Promise<{ status: PaymentSessionStatus; @@ -171,21 +169,21 @@ export class FiberSDK { } /** - * 连接节点 + * Connect to node */ async connectPeer(address: string): Promise { return this.peer.connectPeer(address); } /** - * 断开节点连接 + * Disconnect from node */ async disconnectPeer(peer_id: string): Promise { return this.peer.disconnectPeer(peer_id); } /** - * 列出所有连接的节点 + * List all connected nodes */ async listPeers(): Promise { return this.peer.listPeers(); diff --git a/packages/fiber/src/modules/cch.ts b/packages/fiber/src/modules/cch.ts index 6b4ff7a3..9d71cc22 100644 --- a/packages/fiber/src/modules/cch.ts +++ b/packages/fiber/src/modules/cch.ts @@ -5,12 +5,9 @@ export class CchModule { constructor(private client: FiberClient) {} /** - * 发送 BTC + * Send BTC */ - async sendBtc(params: { - btc_pay_req: string; - currency: Currency; - }): Promise<{ + async sendBtc(params: { btc_pay_req: string; currency: Currency }): Promise<{ timestamp: bigint; expiry: bigint; ckb_final_tlc_expiry_delta: bigint; @@ -27,7 +24,7 @@ export class CchModule { } /** - * 接收 BTC + * Receive BTC */ async receiveBtc(params: { payment_hash: string; @@ -50,7 +47,7 @@ export class CchModule { } /** - * 获取接收 BTC 订单 + * Get receive BTC order */ async getReceiveBtcOrder(payment_hash: string): Promise<{ timestamp: bigint; @@ -66,4 +63,4 @@ export class CchModule { }> { return this.client.call("get_receive_btc_order", [payment_hash]); } -} \ No newline at end of file +} diff --git a/packages/fiber/src/modules/dev.ts b/packages/fiber/src/modules/dev.ts index dcd2d9c7..8070a4ae 100644 --- a/packages/fiber/src/modules/dev.ts +++ b/packages/fiber/src/modules/dev.ts @@ -5,7 +5,7 @@ export class DevModule { constructor(private client: FiberClient) {} /** - * 提交承诺交易 + * Submit commitment transaction */ async commitmentSigned(params: { channel_id: Hash256; @@ -15,7 +15,7 @@ export class DevModule { } /** - * 添加时间锁定合约 + * Add time-locked contract */ async addTlc(params: { channel_id: Hash256; @@ -27,7 +27,7 @@ export class DevModule { } /** - * 移除时间锁定合约 + * Remove time-locked contract */ async removeTlc(params: { channel_id: Hash256; @@ -40,7 +40,7 @@ export class DevModule { } /** - * 提交承诺交易 + * Submit commitment transaction */ async submitCommitmentTransaction(params: { channel_id: Hash256; @@ -50,9 +50,9 @@ export class DevModule { } /** - * 移除监视通道 + * Remove watch channel */ async removeWatchChannel(channel_id: Hash256): Promise { return this.client.call("remove_watch_channel", [channel_id]); } -} \ No newline at end of file +} diff --git a/packages/fiber/src/modules/graph.ts b/packages/fiber/src/modules/graph.ts index 954b0422..be43bd4c 100644 --- a/packages/fiber/src/modules/graph.ts +++ b/packages/fiber/src/modules/graph.ts @@ -5,16 +5,16 @@ export class GraphModule { constructor(private client: FiberClient) {} /** - * 获取节点列表 + * Get node list */ async graphNodes(): Promise { return this.client.call("graph_nodes", []); } /** - * 获取通道列表 + * Get channel list */ async graphChannels(): Promise { return this.client.call("graph_channels", []); } -} \ No newline at end of file +} diff --git a/packages/fiber/src/modules/payment.ts b/packages/fiber/src/modules/payment.ts index 67722c48..72b87418 100644 --- a/packages/fiber/src/modules/payment.ts +++ b/packages/fiber/src/modules/payment.ts @@ -4,17 +4,13 @@ import { PaymentCustomRecords, PaymentSessionStatus, SessionRoute, - Payment, - PaymentStatus, - PaymentType, - PaymentResult, } from "../types.js"; export class PaymentModule { constructor(private client: FiberClient) {} /** - * 发送支付 + * Send payment */ async sendPayment(params: { payment_hash: string; @@ -27,7 +23,7 @@ export class PaymentModule { } /** - * 获取支付 + * Get payment */ async getPayment(payment_hash: string): Promise<{ status: PaymentSessionStatus; diff --git a/packages/fiber/src/rpc.ts b/packages/fiber/src/rpc.ts deleted file mode 100644 index 2e55cd39..00000000 --- a/packages/fiber/src/rpc.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { RequestorJsonRpc, RequestorJsonRpcConfig } from "@ckb-ccc/core"; - -export type JsonRpcConfig = RequestorJsonRpcConfig & { - requestor?: RequestorJsonRpc; -}; - -export interface ErrorRpcBaseLike { - message?: string; - code?: number; - data: string; -} - -export class ErrorRpcBase extends Error { - public readonly code?: number; - public readonly data: string; - - constructor(origin: ErrorRpcBaseLike) { - super(`Client request error ${origin.message}`); - this.code = origin.code; - this.data = origin.data; - } -} - -const ERROR_PARSERS: [ - string, - (error: ErrorRpcBaseLike, match: RegExpMatchArray) => ErrorRpcBase, -][] = [ - // TODO: add error parsers -]; - -/** - * An abstract class implementing JSON-RPC client functionality for a specific URL and timeout. - * Provides methods for interacting with the Fiber JSON-RPC server. - */ -export abstract class FiberJsonRpc { - public readonly requestor: RequestorJsonRpc; - - /** - * Creates an instance of FiberJsonRpc. - * - * @param url_ - The URL of the JSON-RPC server. - * @param timeout - The timeout for requests in milliseconds - */ - - constructor(url_: string, config?: JsonRpcConfig) { - this.requestor = - config?.requestor ?? - new RequestorJsonRpc(url_, config, (errAny: unknown) => { - if ( - typeof errAny !== "object" || - errAny === null || - !("data" in errAny) || - typeof errAny.data !== "string" - ) { - throw errAny; - } - const err = errAny as ErrorRpcBaseLike; - - for (const [regexp, builder] of ERROR_PARSERS) { - const match = err.data.match(regexp); - if (match) { - throw builder(err, match); - } - } - - throw new ErrorRpcBase(err); - }); - } - - // TODO: add methods - - buildSender( - rpcMethod: Parameters[0], - inTransformers?: Parameters[2], - outTransformer?: Parameters[3], - ): (...req: unknown[]) => Promise { - return async (...req: unknown[]) => { - return this.requestor.request( - rpcMethod, - req, - inTransformers, - outTransformer, - ); - }; - } -} diff --git a/packages/fiber/test/info.cjs b/packages/fiber/test/info.cjs index 3207038c..c8e5e685 100644 --- a/packages/fiber/test/info.cjs +++ b/packages/fiber/test/info.cjs @@ -25,7 +25,7 @@ function handleRPCError(error) { } } -// 将十六进制字符串转换为数字 +// Convert hexadecimal string to number function hexToNumber(hex) { if (!hex) return 0; return parseInt(hex.replace("0x", ""), 16); diff --git a/packages/fiber/test/invoice.cjs b/packages/fiber/test/invoice.cjs index fd0abbde..c643db7c 100644 --- a/packages/fiber/test/invoice.cjs +++ b/packages/fiber/test/invoice.cjs @@ -1,7 +1,7 @@ const { FiberClient, FiberSDK } = require("../dist.commonjs/index.js"); const crypto = require("crypto"); -// 生成随机的 payment_preimage +// Generate random payment_preimage function generatePaymentPreimage() { const randomBytes = crypto.randomBytes(32); return "0x" + randomBytes.toString("hex"); @@ -10,10 +10,14 @@ function generatePaymentPreimage() { // Custom error handling function function handleRPCError(error) { if (error.error && error.error.code === -32601) { - console.error("Error: Node may not be running or RPC method does not exist"); + console.error( + "Error: Node may not be running or RPC method does not exist", + ); console.error("Please ensure:"); console.error("1. Fiber node is started"); - console.error("2. Node RPC address is correct (current: http://127.0.0.1:8227)"); + console.error( + "2. Node RPC address is correct (current: http://127.0.0.1:8227)", + ); console.error("3. Node RPC interface is available"); } else if (error.error && error.error.code === -32602) { console.error("Error: Invalid parameters"); @@ -29,7 +33,7 @@ function handleRPCError(error) { } } -// 将十六进制字符串转换为数字 +// Convert hexadecimal string to number function hexToNumber(hex) { return parseInt(hex.replace("0x", ""), 16); } @@ -49,7 +53,7 @@ async function testNewInvoice() { console.log("Calling new_invoice method..."); const amount = 100000000; // 100,000,000 const response = await sdk.invoice.newInvoice({ - amount: `0x${amount.toString(16)}`, // 将金额转换为十六进制 + amount: `0x${amount.toString(16)}`, // Convert amount to hexadecimal currency: "Fibt", description: "test invoice generated by node2", expiry: "0xe10", @@ -62,7 +66,10 @@ async function testNewInvoice() { console.log("Amount:", response.amount); console.log("Description:", response.description); console.log("Expiry:", response.expiry); - console.log("Created At:", new Date(response.created_at).toLocaleString()); + console.log( + "Created At:", + new Date(response.created_at).toLocaleString(), + ); return response; } catch (error) { @@ -85,34 +92,34 @@ async function testNewInvoice() { async function testParseInvoice() { try { - // 初始化SDK + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - console.log("开始测试解析发票...\n"); + console.log("Starting test parsing invoice...\n"); try { - console.log("正在调用 parseInvoice 方法..."); + console.log("Calling parseInvoice method..."); const invoice = await sdk.invoice.parseInvoice( "fibt1000000001pcsaug0p0exgfw0pnm6vk0rnt4xefskmrz0k2vqxr4lnrms60qasvc54jagg2hk8v40k88exmp04pn5cpcnrcsw5lk9w0w6l0m3k84e2ax4v6gq9ne2n77u4p8h3npx6tuufqftq8eyqxw9t4upaw4f89xukcee79rm0p0jv92d5ckq7pmvm09ma3psheu3rfyy9atlrdr4el6ys8yqurl2m74msuykljp35j0s47vpw8h3crfp5ldp8kp4xlusqk6rad3ssgwn2a429qlpgfgjrtj3gzy26w50cy7gypgjm6mjgaz2ff5q4am0avf6paxja2gh2wppjagqlg466yzty0r0pfz8qpuzqgq43mkgx", ); - console.log("解析结果:", JSON.stringify(invoice, null, 2)); + console.log("Parsing result:", JSON.stringify(invoice, null, 2)); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("解析发票失败:", error.message); + console.error("Failed to parse invoice:", error.message); } } - console.log("\n测试完成!"); + console.log("\nTest completed!"); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("测试过程中发生错误:", error.message); + console.error("Test failed:", error.message); } } } @@ -136,9 +143,7 @@ async function testGetInvoice() { } console.log("Calling get_invoice method..."); - const invoiceInfo = await sdk.invoice.getInvoice( - invoice.payment_hash, - ); + const invoiceInfo = await sdk.invoice.getInvoice(invoice.payment_hash); // Output invoice information console.log("\nInvoice details:"); @@ -163,7 +168,7 @@ async function testGetInvoice() { if (error.error) { handleRPCError(error); } else { - console.error("Error during test:", error.message); + console.error("Test failed:", error.message); } } } @@ -210,7 +215,7 @@ async function testCancelInvoice() { if (error.error) { handleRPCError(error); } else { - console.error("Error during test:", error.message); + console.error("Test failed:", error.message); } } } diff --git a/packages/fiber/test/payment.cjs b/packages/fiber/test/payment.cjs index cb458122..148c450a 100644 --- a/packages/fiber/test/payment.cjs +++ b/packages/fiber/test/payment.cjs @@ -1,124 +1,124 @@ const { FiberSDK } = require("../dist.commonjs/index.js"); -// 自定义错误处理函数 +// Custom error handling function function handleRPCError(error) { if (error.error && error.error.code === -32601) { console.error( - "错误: 节点可能未运行或 RPC 方法不存在", + "Error: Node may not be running or RPC method does not exist", ); - console.error("请确保:"); - console.error("1. Fiber 节点已启动"); + console.error("Please ensure:"); + console.error("1. Fiber node is started"); console.error( - "2. 节点 RPC 地址正确 (当前: http://127.0.0.1:8227)", + "2. Node RPC address is correct (current: http://127.0.0.1:8227)", ); - console.error("3. 节点 RPC 接口可用"); + console.error("3. Node RPC interface is available"); } else if (error.error && error.error.code === -32602) { - console.error("错误: 参数无效"); - console.error("请检查:"); - console.error("1. 参数类型是否正确"); - console.error("2. 参数值是否在有效范围内"); - console.error("3. 必填参数是否都已提供"); + console.error("Error: Invalid parameter"); + console.error("Please check:"); + console.error("1. Whether the parameter type is correct"); + console.error("2. Whether the parameter value is within the valid range"); + console.error("3. Whether all required parameters are provided"); } else { - console.error("RPC 错误:", error.message); + console.error("RPC error:", error.message); if (error.error && error.error.data) { - console.error("错误详情:", error.error.data); + console.error("Error details:", error.error.data); } } } -// 将十六进制字符串转换为数字 +// Convert hexadecimal string to number function hexToNumber(hex) { return parseInt(hex.replace("0x", ""), 16); } async function testSendPayment() { try { - // 初始化SDK + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - console.log("开始测试发送支付...\n"); + console.log("Starting test to send payment...\n"); try { - // 发送支付 - console.log("正在调用 sendPayment 方法..."); + // Send payment + console.log("Calling sendPayment method..."); await sdk.payment.sendPayment({ invoice: "fibt1000000001pcsaug0p0exgfw0pnm6vkkya5ul6wxurhh09qf9tuwwaufqnr3uzwpplgcrjpeuhe6w4rudppfkytvm4jekf6ymmwqk2h0ajvr5uhjpwfd9aga09ahpy88hz2um4l9t0xnpk3m9wlf22m2yjcshv3k4g5x7c68fn0gs6a35dw5r56cc3uztyf96l55ayeuvnd9fl4yrt68y086xn6qgjhf4n7xkml62gz5ecypm3xz0wdd59tfhtrhwvp5qlps959vmpf4jygdkspxn8xalparwj8h9ts6v6v0rf7vvhhku40z9sa4txxmgsjzwqzme4ddazxrfrlkc9m4uysh27zgqlx7jrfgvjw7rcqpmsrlga", }); - console.log("支付发送成功"); + console.log("Payment sent successfully"); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("发送支付失败:", error.message); + console.error("Payment sending failed:", error.message); } } - console.log("\n测试完成!"); + console.log("\nTest completed!"); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("测试过程中发生错误:", error.message); + console.error("Error occurred during test:", error.message); } } } async function testGetPayment() { try { - // 初始化SDK + // Initialize SDK const sdk = new FiberSDK({ endpoint: "http://127.0.0.1:8227", timeout: 5000, }); - console.log("开始测试获取支付...\n"); + console.log("Starting test to get payment...\n"); try { - // 获取支付 - console.log("正在调用 getPayment 方法..."); - const paymentHash = "payment_hash"; // 替换为实际的 payment_hash + // Get payment + console.log("Calling getPayment method..."); + const paymentHash = "payment_hash"; // Replace with actual payment_hash const payment = await sdk.payment.getPayment(paymentHash); - console.log("支付信息:", JSON.stringify(payment, null, 2)); + console.log("Payment information:", JSON.stringify(payment, null, 2)); - // 输出详细信息 - console.log("\n支付详细信息:"); - console.log("状态:", payment.status); - console.log("支付哈希:", payment.payment_hash); + // Output detailed information + console.log("\nPayment detailed information:"); + console.log("Status:", payment.status); + console.log("Payment hash:", payment.payment_hash); console.log( - "创建时间:", + "Created time:", new Date(hexToNumber(payment.created_at)).toLocaleString(), ); console.log( - "最后更新时间:", + "Last updated time:", new Date(hexToNumber(payment.last_updated_at)).toLocaleString(), ); if (payment.failed_error) { - console.log("失败原因:", payment.failed_error); + console.log("Failure reason:", payment.failed_error); } - console.log("手续费:", hexToNumber(payment.fee)); + console.log("Fee:", hexToNumber(payment.fee)); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("获取支付失败:", error.message); + console.error("Payment getting failed:", error.message); } } - console.log("\n测试完成!"); + console.log("\nTest completed!"); } catch (error) { if (error.error) { handleRPCError(error); } else { - console.error("测试过程中发生错误:", error.message); + console.error("Error occurred during test:", error.message); } } } -// 运行所有测试 -console.log("开始运行支付相关测试...\n"); +// Run all tests +console.log("Starting to run payment related tests...\n"); testSendPayment().catch(console.error); diff --git a/packages/fiber/test/peer.cjs b/packages/fiber/test/peer.cjs index 0a0bde06..a8012938 100644 --- a/packages/fiber/test/peer.cjs +++ b/packages/fiber/test/peer.cjs @@ -111,20 +111,20 @@ async function testListChannels() { } async function main() { - // 1. 首先清理处于 NEGOTIATING_FUNDING 状态的通道 + // 1. First clean up channels in NEGOTIATING_FUNDING state // await testListChannels(); - // 2. 然后建立网络连接 + // 2. Then establish network connection await testConnectPeer(); - // 3. 断开链接 + // 3. Disconnect await testDisconnectPeer(); - // 4. 最后查询通道状态 + // 4. Finally query channel status // await testListChannels(); } -// 运行测试 +// Run tests console.log("开始运行节点连接测试...\n"); main() From 774e156930f4215c2c6349283771db0f5afecb0d Mon Sep 17 00:00:00 2001 From: Jack <6464072@qq.com> Date: Thu, 17 Apr 2025 00:38:31 +0800 Subject: [PATCH 10/15] fix:pritter error --- packages/demo/next.config.mjs | 4 +- packages/demo/package.json | 2 +- packages/demo/src/app/fiber/channel/page.tsx | 534 ++++++++++++++++++ .../src/app/fiber/context/FiberContext.tsx | 29 + packages/demo/src/app/fiber/invoice/page.tsx | 127 +++++ packages/demo/src/app/fiber/layout.tsx | 7 + packages/demo/src/app/fiber/page.tsx | 52 +- packages/demo/src/app/fiber/payment/page.tsx | 62 ++ packages/demo/src/app/fiber/peer/page.tsx | 147 +++++ 9 files changed, 932 insertions(+), 32 deletions(-) create mode 100644 packages/demo/src/app/fiber/channel/page.tsx create mode 100644 packages/demo/src/app/fiber/context/FiberContext.tsx create mode 100644 packages/demo/src/app/fiber/invoice/page.tsx create mode 100644 packages/demo/src/app/fiber/layout.tsx create mode 100644 packages/demo/src/app/fiber/payment/page.tsx create mode 100644 packages/demo/src/app/fiber/peer/page.tsx diff --git a/packages/demo/next.config.mjs b/packages/demo/next.config.mjs index 3b9bef15..ee6cd32a 100644 --- a/packages/demo/next.config.mjs +++ b/packages/demo/next.config.mjs @@ -6,8 +6,8 @@ const nextConfig = { async rewrites() { return [ { - source: '/api/fiber/:path*', - destination: 'http://127.0.0.1:8227/:path*', + source: "/api/fiber/:path*", + destination: "http://127.0.0.1:8227/:path*", }, ]; }, diff --git a/packages/demo/package.json b/packages/demo/package.json index 96993112..7bd58a75 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -27,7 +27,7 @@ "@ckb-ccc/lumos-patches": "workspace:*", "@ckb-ccc/ssri": "workspace:*", "@ckb-ccc/udt": "workspace:*", - + "@ckb-ccc/fiber": "workspace:*", "@ckb-lumos/ckb-indexer": "^0.24.0-next.1", "@ckb-lumos/common-scripts": "^0.24.0-next.1", "@ckb-lumos/config-manager": "^0.24.0-next.1", diff --git a/packages/demo/src/app/fiber/channel/page.tsx b/packages/demo/src/app/fiber/channel/page.tsx new file mode 100644 index 00000000..c7986eac --- /dev/null +++ b/packages/demo/src/app/fiber/channel/page.tsx @@ -0,0 +1,534 @@ +"use client"; +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { Button } from "@/src/components/Button"; +import { TextInput } from "@/src/components/Input"; +import { ButtonsPanel } from "@/src/components/ButtonsPanel"; +import { useFiber } from "../context/FiberContext"; + +interface ChannelState { + fundingAmount: string; + feeRate: string; + tlcExpiryDelta: string; + tlcMinValue: string; + tlcFeeProportionalMillionths: string; + isPublic: boolean; + isEnabled: boolean; + forceClose: boolean; +} + +interface OpenChannelForm { + peerAddress: string; + fundingAmount: string; + isPublic: boolean; +} + +export default function Channel() { + const { fiber } = useFiber(); + const [endpoint, setEndpoint] = useState(""); + const [nodeInfo, setNodeInfo] = useState(null); + const [channels, setChannels] = useState([]); + const [peers, setPeers] = useState([]); + const [peerAddress, setPeerAddress] = useState(""); + const [channelStates, setChannelStates] = useState< + Record + >({}); + const [isLoading, setIsLoading] = useState(false); + const [openChannelForm, setOpenChannelForm] = useState({ + peerAddress: + "/ip4/18.162.235.225/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo", + fundingAmount: "50000000000", + isPublic: true, + }); + + const listChannels = useCallback(async () => { + if (!fiber) return; + setIsLoading(true); + try { + const channelList = await fiber.listChannels(); + console.log(channelList); + setChannels(channelList); + // 初始化每个通道的状态 + const newChannelStates: Record = {}; + channelList.forEach((channel: any) => { + if (!channelStates[channel.channel_id]) { + newChannelStates[channel.channel_id] = { + fundingAmount: channel.funding_amount || "0xba43b7400", + feeRate: channel.fee_rate || "0x3FC", + tlcExpiryDelta: "0x100", + tlcMinValue: "0x0", + tlcFeeProportionalMillionths: "0x0", + isPublic: true, + isEnabled: true, + forceClose: false, + }; + } else { + newChannelStates[channel.channel_id] = + channelStates[channel.channel_id]; + } + }); + setChannelStates(newChannelStates); + } catch (error) { + console.error("Failed to list channels:", error); + } finally { + setIsLoading(false); + } + }, [fiber, channelStates]); + + const updateChannel = async (channelId: string) => { + if (!fiber || !channelId) return; + const state = channelStates[channelId]; + if (!state) return; + try { + // 首先检查通道是否存在 + const channel = channels.find((c) => c.channel_id === channelId); + if (!channel) { + console.error("Channel not found:", channelId); + alert("通道不存在或已被关闭"); + return; + } + + // 检查通道状态是否允许更新 + if (channel.state.state_name !== "Normal") { + console.error( + "Channel is not in normal state:", + channel.state.state_name, + ); + alert(`通道状态为 ${channel.state.state_name},无法更新`); + return; + } + + await fiber.channel.updateChannel({ + channel_id: channelId, + enabled: state.isEnabled, + tlc_expiry_delta: BigInt(state.tlcExpiryDelta), + tlc_minimum_value: BigInt(state.tlcMinValue), + tlc_fee_proportional_millionths: BigInt( + state.tlcFeeProportionalMillionths, + ), + }); + console.log("Channel updated successfully"); + // Refresh channel list + await listChannels(); + } catch (error) { + console.error("Failed to update channel:", error); + if (error instanceof Error) { + alert(`更新通道失败: ${error.message}`); + } else { + alert("更新通道失败,请检查通道状态"); + } + } + }; + + const abandonChannel = async (channelId: string) => { + if (!fiber || !channelId) return; + try { + await fiber.abandonChannel(channelId); + console.log("Channel abandoned successfully"); + // Refresh channel list + await listChannels(); + } catch (error) { + console.error("Failed to abandon channel:", error); + } + }; + + const shutdownChannel = async (channelId: string) => { + if (!fiber || !channelId) return; + const state = channelStates[channelId]; + if (!state) return; + try { + if (state.forceClose) { + await fiber.channel.shutdownChannel({ + channel_id: channelId, + close_script: { + code_hash: + "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: ["0xcc015401df73a3287d8b2b19f0cc23572ac8b14d"], + }, + force: true, + fee_rate: BigInt(state.feeRate), + }); + } else { + await fiber.channel.shutdownChannel({ + channel_id: channelId, + close_script: { + code_hash: + "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: ["0xcc015401df73a3287d8b2b19f0cc23572ac8b14d"], + }, + force: false, + fee_rate: BigInt(state.feeRate), + }); + } + console.log("Channel shutdown initiated successfully"); + // Refresh channel list + await listChannels(); + } catch (error) { + console.error("Failed to shutdown channel:", error); + } + }; + const connectPeer = async () => { + if (!fiber) return; + try { + await fiber.connectPeer(openChannelForm.peerAddress); + console.log("Peer connected successfully"); + // 连接成功后刷新 peers 列表 + const peerList = await fiber.listPeers(); + console.log("Current peers:", peerList); + setPeers(peerList); + } catch (error) { + console.error("Failed to connect peer:", error); + if (error instanceof Error) { + alert(`连接 peer 失败: ${error.message}`); + } else { + alert("连接 peer 失败,请检查网络连接"); + } + } + }; + + const handleOpenChannel = async () => { + if (!fiber) return; + try { + // 先连接 peer + await fiber.connectPeer(openChannelForm.peerAddress); + console.log("Peer connected successfully"); + + // 然后创建通道 + const peerId = openChannelForm.peerAddress.split("/p2p/")[1]; + await fiber.channel.openChannel({ + peer_id: peerId, + funding_amount: BigInt(openChannelForm.fundingAmount), + public: openChannelForm.isPublic, + }); + console.log("Channel opened successfully"); + // 刷新通道列表 + await listChannels(); + } catch (error) { + console.error("Failed to open channel:", error); + if (error instanceof Error) { + alert(`创建通道失败: ${error.message}`); + } else { + alert("创建通道失败,请检查网络连接"); + } + } + }; + + useEffect(() => { + if (fiber) { + listChannels(); + } + }, [fiber, listChannels]); + + return ( +
+
+

通道管理

+ +
+ +
+

创建新通道

+
+ + setOpenChannelForm((prev) => ({ + ...prev, + peerAddress: value, + })), + ]} + placeholder="输入 peer 地址" + /> + + + setOpenChannelForm((prev) => ({ + ...prev, + fundingAmount: value, + })), + ]} + placeholder="输入资金数量(单位:CKB)" + type="number" + /> +
+ +
+ +
+
+ + {channels.length > 0 && ( +
+

Channel List

+
+ {channels.map((channel, index) => ( +
+
+

+ Channel ID:{" "} + {channel.channel_id} +

+

+ Peer ID:{" "} + {channel.peer_id} +

+

+ State:{" "} + {channel.state.state_name} +

+

+ Local Balance:{" "} + {channel.local_balance} +

+

+ Remote Balance:{" "} + {channel.remote_balance} +

+
+
+
+ { + const newState = { + ...channelStates[channel.channel_id], + fundingAmount: value, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="0xba43b7400" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + feeRate: value, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="0x3FC" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + tlcExpiryDelta: value, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="0x100" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + tlcMinValue: value, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="0x0" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + tlcFeeProportionalMillionths: value, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="0x0" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + isPublic: e.target.checked, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }} + className="mr-2" + /> + +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + isEnabled: e.target.checked, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }} + className="mr-2" + /> + +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + forceClose: e.target.checked, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }} + className="mr-2" + /> + +
+
+ +
+ + + +
+
+ ))} +
+
+ )} + + {nodeInfo && ( + <> +
+ +
+ + )} + + + {fiber && ( + <> + + + )} + +
+ ); +} diff --git a/packages/demo/src/app/fiber/context/FiberContext.tsx b/packages/demo/src/app/fiber/context/FiberContext.tsx new file mode 100644 index 00000000..2c06abe4 --- /dev/null +++ b/packages/demo/src/app/fiber/context/FiberContext.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { createContext, useContext, ReactNode, useState } from "react"; +import { FiberSDK } from "@ckb-ccc/fiber"; + +interface FiberContextType { + fiber: FiberSDK | null; + setFiber: (fiber: FiberSDK | null) => void; +} + +const FiberContext = createContext(null); + +export function FiberProvider({ children }: { children: ReactNode }) { + const [fiber, setFiber] = useState(null); + + return ( + + {children} + + ); +} + +export function useFiber() { + const context = useContext(FiberContext); + if (!context) { + throw new Error("useFiber must be used within a FiberProvider"); + } + return context; +} diff --git a/packages/demo/src/app/fiber/invoice/page.tsx b/packages/demo/src/app/fiber/invoice/page.tsx new file mode 100644 index 00000000..5cfd42ec --- /dev/null +++ b/packages/demo/src/app/fiber/invoice/page.tsx @@ -0,0 +1,127 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { Button } from "@/src/components/Button"; +import { TextInput } from "@/src/components/Input"; +import { ButtonsPanel } from "@/src/components/ButtonsPanel"; +import { useFiber } from "../context/FiberContext"; +import { useRouter } from "next/navigation"; + +interface Invoice { + amount: bigint; + memo: string; + invoice: string; + status: string; + created_at: bigint; +} + +export default function InvoicePage() { + const { fiber } = useFiber(); + const router = useRouter(); + const [loading, setLoading] = useState(false); + const [amount, setAmount] = useState(""); + const [memo, setMemo] = useState(""); + const [invoices, setInvoices] = useState([]); + + const createInvoice = async () => { + if (!fiber) return; + try { + setLoading(true); + const amountBigInt = BigInt(amount); + const invoice = await fiber.invoice.newInvoice({ + amount: amountBigInt, + description: memo, + }); + alert("发票创建成功!"); + // 刷新发票列表 + await listInvoices(); + } catch (error) { + console.error("创建发票失败:", error); + alert("创建发票失败,请重试"); + } finally { + setLoading(false); + } + }; + + const listInvoices = useCallback(async () => { + if (!fiber) return; + try { + // 由于没有 listInvoices 方法,我们暂时返回空数组 + setInvoices([]); + } catch (error) { + console.error("获取发票列表失败:", error); + } + }, [fiber]); + + useEffect(() => { + if (fiber) { + listInvoices(); + } + }, [fiber, listInvoices]); + + return ( +
+
+

发票管理

+
+ +
+
+ + +
+
+ + + + + +
+

发票列表

+ {invoices.length === 0 ? ( +

暂无发票记录

+ ) : ( +
+ {invoices.map((invoice, index) => ( +
+
+
+

+ 金额: {invoice.amount.toString()} +

+

+ 备注: {invoice.memo} +

+

+ 状态: {invoice.status} +

+

+ 创建时间:{" "} + {new Date(Number(invoice.created_at)).toLocaleString()} +

+
+
+ {invoice.invoice} +
+
+
+ ))} +
+ )} + + + +
+
+ ); +} diff --git a/packages/demo/src/app/fiber/layout.tsx b/packages/demo/src/app/fiber/layout.tsx new file mode 100644 index 00000000..d13e66c8 --- /dev/null +++ b/packages/demo/src/app/fiber/layout.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { FiberProvider } from "./context/FiberContext"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/packages/demo/src/app/fiber/page.tsx b/packages/demo/src/app/fiber/page.tsx index 10e21475..cf0d3322 100644 --- a/packages/demo/src/app/fiber/page.tsx +++ b/packages/demo/src/app/fiber/page.tsx @@ -1,7 +1,7 @@ "use client"; import { Button } from "@/src/components/Button"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; import { TextInput } from "@/src/components/Input"; import { useRouter } from "next/navigation"; import { ButtonsPanel } from "@/src/components/ButtonsPanel"; @@ -26,7 +26,6 @@ export default function Page() { const [endpoint, setEndpoint] = useState(""); const [nodeInfo, setNodeInfo] = useState(null); - const initSdk = () => { const newFiber = new FiberSDK({ endpoint: endpoint || `/api/fiber`, @@ -40,7 +39,7 @@ export default function Page() { } }; - const getNodeInfo = async () => { + const getNodeInfo = useCallback(async () => { if (!fiber) return; try { const info = await fiber.nodeInfo(); @@ -49,56 +48,54 @@ export default function Page() { } catch (error) { console.error("Failed to get node info:", error); } - }; - - + }, [fiber]); useEffect(() => { if (fiber) { getNodeInfo(); } - }, [fiber]); + }, [fiber, getNodeInfo]); return (
-
+

Fiber

{fiber ? (
router.push('/fiber/channel')} - className={'text-yellow-500'} + iconName={"ArrowLeftRight"} + onClick={() => router.push("/fiber/channel")} + className={"text-yellow-500"} > channel router.push('/fiber/peer')} - className={'text-yellow-500'} + iconName={"ArrowLeftRight"} + onClick={() => router.push("/fiber/peer")} + className={"text-yellow-500"} > Peer router.push('/fiber/payment')} - className={'text-yellow-500'} + iconName={"ArrowLeftRight"} + onClick={() => router.push("/fiber/payment")} + className={"text-yellow-500"} > Payment router.push('/fiber/invoice')} - className={'text-yellow-500'} + iconName={"ArrowLeftRight"} + onClick={() => router.push("/fiber/invoice")} + className={"text-yellow-500"} > Invoice @@ -115,8 +112,6 @@ export default function Page() {
)} - - {nodeInfo && (

Node Information

@@ -145,10 +140,9 @@ export default function Page() {
)} - - + + -
); } diff --git a/packages/demo/src/app/fiber/payment/page.tsx b/packages/demo/src/app/fiber/payment/page.tsx new file mode 100644 index 00000000..4056826b --- /dev/null +++ b/packages/demo/src/app/fiber/payment/page.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Button } from "@/src/components/Button"; +import { TextInput } from "@/src/components/Input"; +import { ButtonsPanel } from "@/src/components/ButtonsPanel"; + +interface PaymentForm { + amount: string; + recipient: string; +} + +export default function PaymentPage() { + const router = useRouter(); + const [loading, setLoading] = useState(false); + const [amount, setAmount] = useState(""); + const [recipient, setRecipient] = useState(""); + + const handlePayment = async () => { + try { + setLoading(true); + // TODO: 实现支付逻辑 + alert("支付成功!"); + router.push("/fiber"); + } catch (error) { + alert("支付失败,请重试"); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

支付

+
+ +
+
+ + +
+
+ + + + + +
+ ); +} diff --git a/packages/demo/src/app/fiber/peer/page.tsx b/packages/demo/src/app/fiber/peer/page.tsx new file mode 100644 index 00000000..18f301c3 --- /dev/null +++ b/packages/demo/src/app/fiber/peer/page.tsx @@ -0,0 +1,147 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Button } from "@/src/components/Button"; +import { TextInput } from "@/src/components/Input"; +import { FiberSDK } from "@ckb-ccc/fiber"; +import { useRouter } from "next/navigation"; + +interface PeerInfo { + pubkey: string; + peer_id: string; + addresses: string[]; +} + +export default function Peer() { + const [fiber, setFiber] = useState(null); + const [peers, setPeers] = useState([]); + const [peerAddress, setPeerAddress] = useState( + "/ip4/54.215.49.61/tcp/8080/p2p/QmNXkndsz6NT6A4kuXRg4mgk5DpEP33m8vRUqe2iwouEru", + ); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + + useEffect(() => { + const initFiber = () => { + const newFiber = new FiberSDK({ + endpoint: `/api/fiber`, + timeout: 5000, + }); + setFiber(newFiber); + }; + initFiber(); + }, []); + + const listPeers = async () => { + if (!fiber) return; + setIsLoading(true); + try { + const peerList = await fiber.listPeers(); + console.log(peerList); + //@ts-expect-error + setPeers(peerList.peers); + } catch (error) { + console.error("Failed to list peers:", error); + } finally { + setIsLoading(false); + } + }; + + const connectPeer = async () => { + if (!fiber || !peerAddress) return; + setIsLoading(true); + try { + await fiber.connectPeer(peerAddress); + console.log("Peer connected successfully"); + // 连接成功后立即获取并更新 peers 列表 + await listPeers(); + } catch (error) { + console.error("Failed to connect peer:", error); + if (error instanceof Error) { + alert(`连接 peer 失败: ${error.message}`); + } else { + alert("连接 peer 失败,请检查网络连接"); + } + } finally { + setIsLoading(false); + } + }; + + const disconnectPeer = async (peerId: string) => { + if (!fiber) return; + setIsLoading(true); + try { + await fiber.disconnectPeer(peerId); + console.log("Peer disconnected successfully"); + // 断开连接后立即获取并更新 peers 列表 + await listPeers(); + } catch (error) { + console.error("Failed to disconnect peer:", error); + if (error instanceof Error) { + alert(`断开连接失败: ${error.message}`); + } else { + alert("断开连接失败,请检查网络连接"); + } + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+

Peer 管理

+
+ setPeerAddress(e.target.value)} + placeholder="输入 peer 地址" + state={[peerAddress, setPeerAddress]} + className="flex-1" + /> + + + +
+
+ +
+

已连接的 Peers

+ {peers.length === 0 ? ( +

暂无连接的 peers

+ ) : ( +
+ {peers.length > 0 && + peers.map((peer) => ( +
+
+

Peer ID: {peer.peer_id}

+

+ Pubkey: {peer.pubkey} +

+

+ Addresses: {peer.addresses.join(", ")} +

+
+ +
+ ))} +
+ )} +
+
+ ); +} From cca0f1a84f20f2578b904f8fc3c8a4fe6e84b2d1 Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Thu, 17 Apr 2025 09:44:49 +0800 Subject: [PATCH 11/15] chore: tiny change --- packages/playground/package.json | 2 +- packages/playground/tailwind.config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playground/package.json b/packages/playground/package.json index d5bf9de0..53576390 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -41,4 +41,4 @@ "raw-loader": "^4.0.2", "tailwindcss": "^3.4.1" } -} \ No newline at end of file +} diff --git a/packages/playground/tailwind.config.ts b/packages/playground/tailwind.config.ts index 1af2b12b..2d90dce8 100644 --- a/packages/playground/tailwind.config.ts +++ b/packages/playground/tailwind.config.ts @@ -9,7 +9,7 @@ const config: Config = { safelist: [ { pattern: /./, // Include all Tailwind classes - } + }, ], theme: { extend: { From fd8a76cde4cc47b814af8ed916b6a80fe770591f Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Sat, 19 Apr 2025 08:39:38 +0800 Subject: [PATCH 12/15] chore: create changeset --- .changeset/unlucky-tools-deliver.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/unlucky-tools-deliver.md diff --git a/.changeset/unlucky-tools-deliver.md b/.changeset/unlucky-tools-deliver.md new file mode 100644 index 00000000..6845fc09 --- /dev/null +++ b/.changeset/unlucky-tools-deliver.md @@ -0,0 +1,6 @@ +--- +"@ckb-ccc/ccc-playground": minor +"@ckb-ccc/fiber": patch +--- + +Wrap fiber RPCs as fiber-sdk into CCC, with playable presentation From cc6ddfe2fe8283542d3264eb02a0ad47bad4eb8e Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Sat, 19 Apr 2025 08:41:38 +0800 Subject: [PATCH 13/15] chore: update lock file --- pnpm-lock.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 353960da..8ba40e30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -334,6 +334,9 @@ importers: '@ckb-ccc/connector-react': specifier: workspace:* version: link:../connector-react + '@ckb-ccc/fiber': + specifier: workspace:* + version: link:../fiber '@ckb-ccc/lumos-patches': specifier: workspace:* version: link:../lumos-patches @@ -525,7 +528,7 @@ importers: version: 10.2.3(chokidar@3.6.0)(typescript@5.7.3) '@nestjs/testing': specifier: ^10.0.0 - version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(@nestjs/platform-express@10.4.15) + version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)) '@types/express': specifier: ^4.17.17 version: 4.17.21 @@ -7527,7 +7530,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/testing@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(@nestjs/platform-express@10.4.15)': + '@nestjs/testing@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15))': dependencies: '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -9424,7 +9427,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -9475,7 +9478,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 From f50c3a31e4300cd1d2af3aa108864a2a84b2a2d5 Mon Sep 17 00:00:00 2001 From: Jack <6464072@qq.com> Date: Wed, 21 May 2025 18:27:35 +0800 Subject: [PATCH 14/15] =?UTF-8?q?fix:=20-=20=E4=BF=AE=E6=94=B9=20Script=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E4=B8=AD=20args=20=E7=9A=84=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=BB=8E=20string[]=20=E6=94=B9=E4=B8=BA=20string=20-?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9=20shutdownChannel=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=20close=5Fscript.args=20=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=20-=20=E7=A1=AE=E4=BF=9D=E6=89=80=E6=9C=89?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E9=83=BD=E7=AC=A6=E5=90=88=20API=20=E6=9C=9F?= =?UTF-8?q?=E6=9C=9B=E7=9A=84=E5=8D=81=E5=85=AD=E8=BF=9B=E5=88=B6=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/demo/src/app/fiber/channel/page.tsx | 158 ++++--- .../src/app/fiber/context/FiberContext.tsx | 33 +- packages/demo/src/app/fiber/page.tsx | 155 +++++-- .../demo/src/app/fiber/peer/[peerid]/page.tsx | 429 ++++++++++++++++++ packages/demo/src/app/fiber/peer/page.tsx | 3 +- packages/demo/src/app/fiber/utils/numbers.ts | 11 + packages/demo/src/app/page.tsx | 1 - packages/demo/src/utils/hex.ts | 20 + 8 files changed, 693 insertions(+), 117 deletions(-) create mode 100644 packages/demo/src/app/fiber/peer/[peerid]/page.tsx create mode 100644 packages/demo/src/app/fiber/utils/numbers.ts create mode 100644 packages/demo/src/utils/hex.ts diff --git a/packages/demo/src/app/fiber/channel/page.tsx b/packages/demo/src/app/fiber/channel/page.tsx index c7986eac..9ea2bfa9 100644 --- a/packages/demo/src/app/fiber/channel/page.tsx +++ b/packages/demo/src/app/fiber/channel/page.tsx @@ -1,11 +1,12 @@ "use client"; -"use client"; -import { useEffect, useState, useCallback } from "react"; +import { useEffect, useState, useCallback, useRef } from "react"; import { Button } from "@/src/components/Button"; import { TextInput } from "@/src/components/Input"; import { ButtonsPanel } from "@/src/components/ButtonsPanel"; import { useFiber } from "../context/FiberContext"; +import { hexToDecimal, decimalToHex } from '@/src/utils/hex'; +import { shannonToCKB } from "../utils/numbers" interface ChannelState { fundingAmount: string; @@ -26,15 +27,14 @@ interface OpenChannelForm { export default function Channel() { const { fiber } = useFiber(); - const [endpoint, setEndpoint] = useState(""); const [nodeInfo, setNodeInfo] = useState(null); const [channels, setChannels] = useState([]); const [peers, setPeers] = useState([]); const [peerAddress, setPeerAddress] = useState(""); - const [channelStates, setChannelStates] = useState< - Record - >({}); + const [channelStates, setChannelStates] = useState>({}); const [isLoading, setIsLoading] = useState(false); + const channelStatesRef = useRef(channelStates); + const initialized = useRef(false); const [openChannelForm, setOpenChannelForm] = useState({ peerAddress: "/ip4/18.162.235.225/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo", @@ -42,17 +42,26 @@ export default function Channel() { isPublic: true, }); + // 更新 ref 当 channelStates 改变时 + useEffect(() => { + channelStatesRef.current = channelStates; + }, [channelStates]); + const listChannels = useCallback(async () => { if (!fiber) return; setIsLoading(true); try { const channelList = await fiber.listChannels(); - console.log(channelList); setChannels(channelList); - // 初始化每个通道的状态 + + // 只在需要时更新 channelStates const newChannelStates: Record = {}; + let hasChanges = false; + channelList.forEach((channel: any) => { - if (!channelStates[channel.channel_id]) { + const existingState = channelStatesRef.current[channel.channel_id]; + if (!existingState) { + hasChanges = true; newChannelStates[channel.channel_id] = { fundingAmount: channel.funding_amount || "0xba43b7400", feeRate: channel.fee_rate || "0x3FC", @@ -64,17 +73,19 @@ export default function Channel() { forceClose: false, }; } else { - newChannelStates[channel.channel_id] = - channelStates[channel.channel_id]; + newChannelStates[channel.channel_id] = existingState; } }); - setChannelStates(newChannelStates); + + if (hasChanges) { + setChannelStates(newChannelStates); + } } catch (error) { console.error("Failed to list channels:", error); } finally { setIsLoading(false); } - }, [fiber, channelStates]); + }, [fiber]); // 只依赖 fiber const updateChannel = async (channelId: string) => { if (!fiber || !channelId) return; @@ -138,57 +149,42 @@ export default function Channel() { const state = channelStates[channelId]; if (!state) return; try { - if (state.forceClose) { - await fiber.channel.shutdownChannel({ - channel_id: channelId, - close_script: { - code_hash: - "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - hash_type: "type", - args: ["0xcc015401df73a3287d8b2b19f0cc23572ac8b14d"], - }, - force: true, - fee_rate: BigInt(state.feeRate), - }); - } else { - await fiber.channel.shutdownChannel({ - channel_id: channelId, - close_script: { - code_hash: - "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - hash_type: "type", - args: ["0xcc015401df73a3287d8b2b19f0cc23572ac8b14d"], - }, - force: false, - fee_rate: BigInt(state.feeRate), - }); + // 确保channelId是有效的十六进制字符串 + if (!channelId.startsWith('0x')) { + channelId = '0x' + channelId; } + + const params = { + channel_id: channelId, + close_script: { + code_hash: + "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: "0xcc015401df73a3287d8b2b19f0cc23572ac8b14d" + }, + force: state.forceClose, + fee_rate: state.feeRate, + }; + + console.log("Shutdown channel params:", JSON.stringify(params, null, 2)); + console.log("Fee rate type:", typeof state.feeRate); + console.log("Fee rate value:", state.feeRate); + + await fiber.channel.shutdownChannel(params); console.log("Channel shutdown initiated successfully"); // Refresh channel list await listChannels(); } catch (error) { console.error("Failed to shutdown channel:", error); - } - }; - const connectPeer = async () => { - if (!fiber) return; - try { - await fiber.connectPeer(openChannelForm.peerAddress); - console.log("Peer connected successfully"); - // 连接成功后刷新 peers 列表 - const peerList = await fiber.listPeers(); - console.log("Current peers:", peerList); - setPeers(peerList); - } catch (error) { - console.error("Failed to connect peer:", error); if (error instanceof Error) { - alert(`连接 peer 失败: ${error.message}`); + alert(`关闭通道失败: ${error.message}`); } else { - alert("连接 peer 失败,请检查网络连接"); + alert("关闭通道失败,请检查通道状态"); } } }; + const handleOpenChannel = async () => { if (!fiber) return; try { @@ -200,7 +196,7 @@ export default function Channel() { const peerId = openChannelForm.peerAddress.split("/p2p/")[1]; await fiber.channel.openChannel({ peer_id: peerId, - funding_amount: BigInt(openChannelForm.fundingAmount), + funding_amount: openChannelForm.fundingAmount, public: openChannelForm.isPublic, }); console.log("Channel opened successfully"); @@ -217,7 +213,8 @@ export default function Channel() { }; useEffect(() => { - if (fiber) { + if (fiber && !initialized.current) { + initialized.current = true; listChannels(); } }, [fiber, listChannels]); @@ -246,7 +243,6 @@ export default function Channel() { ]} placeholder="输入 peer 地址" /> -

Local Balance:{" "} - {channel.local_balance} + {hexToDecimal(channel.local_balance).toString()}

Remote Balance:{" "} - {channel.remote_balance} + {hexToDecimal(channel.remote_balance).toString()}

{ const newState = { ...channelStates[channel.channel_id], - fundingAmount: value, + fundingAmount: decimalToHex(parseInt(value) || 0), }; setChannelStates((prev) => ({ ...prev, @@ -326,18 +321,19 @@ export default function Channel() { })); }, ]} - placeholder="0xba43b7400" + placeholder="50000000000" + type="number" />
{ const newState = { ...channelStates[channel.channel_id], - feeRate: value, + feeRate: decimalToHex(parseInt(value) || 0), }; setChannelStates((prev) => ({ ...prev, @@ -345,19 +341,19 @@ export default function Channel() { })); }, ]} - placeholder="0x3FC" + placeholder="1020" + type="number" />
{ const newState = { ...channelStates[channel.channel_id], - tlcExpiryDelta: value, + tlcExpiryDelta: decimalToHex(parseInt(value) || 0), }; setChannelStates((prev) => ({ ...prev, @@ -365,18 +361,19 @@ export default function Channel() { })); }, ]} - placeholder="0x100" + placeholder="256" + type="number" />
{ const newState = { ...channelStates[channel.channel_id], - tlcMinValue: value, + tlcMinValue: decimalToHex(parseInt(value) || 0), }; setChannelStates((prev) => ({ ...prev, @@ -384,19 +381,19 @@ export default function Channel() { })); }, ]} - placeholder="0x0" + placeholder="0" + type="number" />
{ const newState = { ...channelStates[channel.channel_id], - tlcFeeProportionalMillionths: value, + tlcFeeProportionalMillionths: decimalToHex(parseInt(value) || 0), }; setChannelStates((prev) => ({ ...prev, @@ -404,7 +401,8 @@ export default function Channel() { })); }, ]} - placeholder="0x0" + placeholder="0" + type="number" />
diff --git a/packages/demo/src/app/fiber/context/FiberContext.tsx b/packages/demo/src/app/fiber/context/FiberContext.tsx index 2c06abe4..8ef9b729 100644 --- a/packages/demo/src/app/fiber/context/FiberContext.tsx +++ b/packages/demo/src/app/fiber/context/FiberContext.tsx @@ -1,6 +1,6 @@ "use client"; -import { createContext, useContext, ReactNode, useState } from "react"; +import { createContext, useContext, ReactNode, useState, useEffect } from "react"; import { FiberSDK } from "@ckb-ccc/fiber"; interface FiberContextType { @@ -11,7 +11,36 @@ interface FiberContextType { const FiberContext = createContext(null); export function FiberProvider({ children }: { children: ReactNode }) { - const [fiber, setFiber] = useState(null); + const [fiber, setFiber] = useState(() => { + // 从 localStorage 中获取存储的 fiber 配置 + if (typeof window !== 'undefined') { + const savedConfig = localStorage.getItem('fiberConfig'); + if (savedConfig) { + try { + const config = JSON.parse(savedConfig); + return new FiberSDK(config); + } catch (error) { + console.error('Failed to restore fiber from localStorage:', error); + return null; + } + } + } + return null; + }); + + // 当 fiber 更新时,保存到 localStorage + useEffect(() => { + if (fiber) { + // 保存配置到 localStorage + const config = { + endpoint: '/api/fiber', // 使用默认的 endpoint + timeout: 5000 // 使用默认的 timeout + }; + localStorage.setItem('fiberConfig', JSON.stringify(config)); + } else { + localStorage.removeItem('fiberConfig'); + } + }, [fiber]); return ( diff --git a/packages/demo/src/app/fiber/page.tsx b/packages/demo/src/app/fiber/page.tsx index cf0d3322..f780d333 100644 --- a/packages/demo/src/app/fiber/page.tsx +++ b/packages/demo/src/app/fiber/page.tsx @@ -8,24 +8,13 @@ import { ButtonsPanel } from "@/src/components/ButtonsPanel"; import { FiberSDK } from "@ckb-ccc/fiber"; import { useFiber } from "./context/FiberContext"; import { BigButton } from "@/src/components/BigButton"; - -interface OpenChannelForm { - peerAddress: string; - fundingAmount: string; - feeRate: string; - tlcExpiryDelta: string; - tlcMinValue: string; - tlcFeeProportionalMillionths: string; - isPublic: boolean; - isEnabled: boolean; -} +import { shannonToCKB } from "./utils/numbers" export default function Page() { const router = useRouter(); const { fiber, setFiber } = useFiber(); const [endpoint, setEndpoint] = useState(""); const [nodeInfo, setNodeInfo] = useState(null); - const initSdk = () => { const newFiber = new FiberSDK({ endpoint: endpoint || `/api/fiber`, @@ -115,27 +104,127 @@ export default function Page() { {nodeInfo && (

Node Information

-
-

- Version: {nodeInfo.version} -

-

- Commit Hash:{" "} - {nodeInfo.commit_hash} -

-

- Node ID: {nodeInfo.node_id} -

-

- Node Name:{" "} - {nodeInfo.node_name || "Not set"} -

-

- Addresses:{" "} - {nodeInfo.addresses.length > 0 - ? nodeInfo.addresses.join(", ") - : "No addresses"} -

+
+ {/* 基本信息 */} +
+
+

+ Node Name:{" "} + {nodeInfo.node_name || "Not set"} +

+

+ Node ID:{" "} + {nodeInfo.node_id} +

+

+ Chain Hash:{" "} + {nodeInfo.chain_hash} +

+

+ Timestamp:{" "} + {new Date(Number(nodeInfo.timestamp)).toLocaleString()} +

+
+
+ + {/* 网络统计 */} +
+

Network Statistics

+
+

+ Channel Count:{" "} + {nodeInfo.channel_count || "0"} +

+

+ Pending Channels:{" "} + {nodeInfo.pending_channel_count || "0"} +

+

+ Connected Peers:{" "} + {nodeInfo.peers_count || "0"} +

+
+
+ + {/* 通道配置 */} +
+

Channel Configuration

+
+

+ Min CKB Funding Amount:{" "} + {shannonToCKB(nodeInfo.auto_accept_min_ckb_funding_amount) || "0"} +

+

+ + Channel CKB Funding Amount: + {" "} + {shannonToCKB(nodeInfo.auto_accept_channel_ckb_funding_amount) || "0"} +

+

+ TLC Expiry Delta:{" "} + {shannonToCKB(nodeInfo.tlc_expiry_delta) || "0"} +

+

+ TLC Min Value:{" "} + {shannonToCKB(nodeInfo.tlc_min_value) || "0"} +

+

+ + TLC Fee Proportional Millionths: + {" "} + {nodeInfo.tlc_fee_proportional_millionths + ? `${shannonToCKB(nodeInfo.tlc_fee_proportional_millionths)}` + : "0%"} +

+
+
+ + {/* 节点地址 */} +
+

Node Addresses

+
+ {nodeInfo.addresses && nodeInfo.addresses.length > 0 ? ( + nodeInfo.addresses.map((address: string, index: number) => ( +

+ {address} +

+ )) + ) : ( +

No addresses configured

+ )} +
+
+ + {/* 默认资金锁定脚本 */} + {nodeInfo.default_funding_lock_script && ( +
+

Default Funding Lock Script

+
+

+ Code Hash:{" "} + {nodeInfo.default_funding_lock_script.code_hash} +

+

+ Hash Type:{" "} + {nodeInfo.default_funding_lock_script.hash_type} +

+

+ Args:{" "} + {nodeInfo.default_funding_lock_script.args} +

+
+
+ )} + + {/* UDT配置 */} + {nodeInfo.udt_cfg_infos && Object.keys(nodeInfo.udt_cfg_infos).length > 0 && ( +
+

UDT Configuration

+
+                  {JSON.stringify(nodeInfo.udt_cfg_infos, null, 2)}
+                
+
+ )}
)} diff --git a/packages/demo/src/app/fiber/peer/[peerid]/page.tsx b/packages/demo/src/app/fiber/peer/[peerid]/page.tsx new file mode 100644 index 00000000..7f402a05 --- /dev/null +++ b/packages/demo/src/app/fiber/peer/[peerid]/page.tsx @@ -0,0 +1,429 @@ +"use client"; + +import { useEffect, useState, useCallback, useRef } from "react"; +import { useParams } from "next/navigation"; +import { Button } from "@/src/components/Button"; +import { TextInput } from "@/src/components/Input"; +import { ButtonsPanel } from "@/src/components/ButtonsPanel"; +import { useFiber } from "../../context/FiberContext"; +import { hexToDecimal, decimalToHex } from '@/src/utils/hex'; +import { shannonToCKB } from "../../utils/numbers" + +interface ChannelState { + fundingAmount: string; + feeRate: string; + tlcExpiryDelta: string; + tlcMinValue: string; + tlcFeeProportionalMillionths: string; + isPublic: boolean; + isEnabled: boolean; + forceClose: boolean; +} + +export default function peerDetail() { + const params = useParams(); + const peerId = params?.peerid as string; + const { fiber } = useFiber(); + const [channels, setChannels] = useState([]); + const [channelStates, setChannelStates] = useState>({}); + const [isLoading, setIsLoading] = useState(false); + const channelStatesRef = useRef(channelStates); + const initialized = useRef(false); + + // 更新 ref 当 channelStates 改变时 + useEffect(() => { + channelStatesRef.current = channelStates; + }, [channelStates]); + + const listChannels = useCallback(async () => { + if (!fiber) return; + setIsLoading(true); + try { + const channelList = await fiber.listChannels(); + const filteredChannelList = channelList.filter((channel: any) => channel.peer_id === peerId); + setChannels(filteredChannelList); + + // 只在需要时更新 channelStates + const newChannelStates: Record = {}; + let hasChanges = false; + + channelList.forEach((channel: any) => { + const existingState = channelStatesRef.current[channel.channel_id]; + if (!existingState) { + hasChanges = true; + newChannelStates[channel.channel_id] = { + fundingAmount: channel.funding_amount || "0xba43b7400", + feeRate: channel.fee_rate || "0x3FC", + tlcExpiryDelta: "0x100", + tlcMinValue: "0x0", + tlcFeeProportionalMillionths: "0x0", + isPublic: true, + isEnabled: true, + forceClose: false, + }; + } else { + newChannelStates[channel.channel_id] = existingState; + } + }); + + if (hasChanges) { + setChannelStates(newChannelStates); + } + } catch (error) { + console.error("Failed to list channels:", error); + } finally { + setIsLoading(false); + } + }, [fiber]); // 只依赖 fiber + + const updateChannel = async (channelId: string) => { + if (!fiber || !channelId) return; + const state = channelStates[channelId]; + if (!state) return; + try { + // 首先检查通道是否存在 + const channel = channels.find((c) => c.channel_id === channelId); + if (!channel) { + console.error("Channel not found:", channelId); + alert("通道不存在或已被关闭"); + return; + } + + // 检查通道状态是否允许更新 + if (channel.state.state_name !== "Normal") { + console.error( + "Channel is not in normal state:", + channel.state.state_name, + ); + alert(`通道状态为 ${channel.state.state_name},无法更新`); + return; + } + + await fiber.channel.updateChannel({ + channel_id: channelId, + enabled: state.isEnabled, + tlc_expiry_delta: BigInt(state.tlcExpiryDelta), + tlc_minimum_value: BigInt(state.tlcMinValue), + tlc_fee_proportional_millionths: BigInt( + state.tlcFeeProportionalMillionths, + ), + }); + console.log("Channel updated successfully"); + // Refresh channel list + await listChannels(); + } catch (error) { + console.error("Failed to update channel:", error); + if (error instanceof Error) { + alert(`更新通道失败: ${error.message}`); + } else { + alert("更新通道失败,请检查通道状态"); + } + } + }; + + const abandonChannel = async (channelId: string) => { + if (!fiber || !channelId) return; + try { + await fiber.abandonChannel(channelId); + console.log("Channel abandoned successfully"); + // Refresh channel list + await listChannels(); + } catch (error) { + console.error("Failed to abandon channel:", error); + } + }; + const shutdownChannel = async (channelId: string) => { + if (!fiber || !channelId) return; + const state = channelStates[channelId]; + if (!state) return; + try { + if (state.forceClose) { + await fiber.channel.shutdownChannel({ + channel_id: channelId, + close_script: { + code_hash: + "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: "0xcc015401df73a3287d8b2b19f0cc23572ac8b14d" + }, + force: true, + fee_rate: BigInt(state.feeRate), + }); + } else { + await fiber.channel.shutdownChannel({ + channel_id: channelId, + close_script: { + code_hash: + "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + hash_type: "type", + args: "0xcc015401df73a3287d8b2b19f0cc23572ac8b14d" + }, + force: false, + fee_rate: BigInt(state.feeRate), + }); + } + console.log("Channel shutdown initiated successfully"); + // Refresh channel list + await listChannels(); + } catch (error) { + console.error("Failed to shutdown channel:", error); + } + }; + useEffect(() => { + if (fiber && !initialized.current) { + initialized.current = true; + listChannels(); + } + }, [fiber, listChannels]); + + return ( +
+ <> +

Peer Info

+

{peerId}

+ + + {channels.length > 0 && ( +
+

Channel List

+
+ {channels.map((channel, index) => ( +
+
+

+ Channel ID:{" "} + {channel.channel_id} +

+

+ Peer ID:{" "} + {channel.peer_id} +

+

+ State:{" "} + {channel.state.state_name} +

+

+ Local Balance:{" "} + {shannonToCKB(hexToDecimal(channel.local_balance).toString())} +

+

+ Remote Balance:{" "} + {shannonToCKB(hexToDecimal(channel.remote_balance).toString())} +

+
+
+
+ { + const newState = { + ...channelStates[channel.channel_id], + fundingAmount: shannonToCKB(decimalToHex(parseInt(value) || 0)), + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="500" + type="number" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + feeRate: decimalToHex(parseInt(value) || 0), + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="1020" + type="number" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + tlcExpiryDelta: decimalToHex(parseInt(value) || 0), + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="256" + type="number" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + tlcMinValue: decimalToHex(parseInt(value) || 0), + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="0" + type="number" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + tlcFeeProportionalMillionths: decimalToHex(parseInt(value) || 0), + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }, + ]} + placeholder="0" + type="number" + /> +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + isPublic: e.target.checked, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }} + className="mr-2" + /> + +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + isEnabled: e.target.checked, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }} + className="mr-2" + /> + +
+
+ { + const newState = { + ...channelStates[channel.channel_id], + forceClose: e.target.checked, + }; + setChannelStates((prev) => ({ + ...prev, + [channel.channel_id]: newState, + })); + }} + className="mr-2" + /> + +
+
+ +
+ + + +
+
+ ))} +
+
+ )} + + + + + {fiber && ( + <> + + + )} + +
+ ); +} diff --git a/packages/demo/src/app/fiber/peer/page.tsx b/packages/demo/src/app/fiber/peer/page.tsx index 18f301c3..dc04c528 100644 --- a/packages/demo/src/app/fiber/peer/page.tsx +++ b/packages/demo/src/app/fiber/peer/page.tsx @@ -119,7 +119,8 @@ export default function Peer() { peers.map((peer) => (
router.push(`/fiber/peer/${peer.peer_id}`)} >

Peer ID: {peer.peer_id}

diff --git a/packages/demo/src/app/fiber/utils/numbers.ts b/packages/demo/src/app/fiber/utils/numbers.ts new file mode 100644 index 00000000..99d0c49e --- /dev/null +++ b/packages/demo/src/app/fiber/utils/numbers.ts @@ -0,0 +1,11 @@ +import { ccc } from "@ckb-ccc/connector-react"; + +const CKB_UNIT = BigInt(100000000); // 1 CKB = 10^8 shannon + +export const shannonToCKB = (shannon: string) => { + if (!shannon) return "0"; + // 将浮点数转换为整数(shannon) + const shannonValue = Math.floor(parseFloat(shannon) * 100000000); + const shannonBigInt = BigInt(shannonValue); + return ccc.fixedPointToString(shannonBigInt / CKB_UNIT); +}; \ No newline at end of file diff --git a/packages/demo/src/app/page.tsx b/packages/demo/src/app/page.tsx index de07b731..78309344 100644 --- a/packages/demo/src/app/page.tsx +++ b/packages/demo/src/app/page.tsx @@ -3,7 +3,6 @@ import { ccc } from "@ckb-ccc/connector-react"; import React, { useEffect } from "react"; -import { Key, Wallet } from "lucide-react"; import { BigButton } from "@/src/components/BigButton"; import { useRouter } from "next/navigation"; import { useApp } from "@/src/context"; diff --git a/packages/demo/src/utils/hex.ts b/packages/demo/src/utils/hex.ts new file mode 100644 index 00000000..8043e014 --- /dev/null +++ b/packages/demo/src/utils/hex.ts @@ -0,0 +1,20 @@ +/** + * 将十六进制字符串转换为十进制数字 + * @param hex 十六进制字符串,可以带0x前缀 + * @returns 十进制数字 + */ +export function hexToDecimal(hex: string): number { + // 移除0x前缀(如果存在) + const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex; + // 使用parseInt将十六进制转换为十进制 + return parseInt(cleanHex, 16); +} + +/** + * 将十进制数字转换为十六进制字符串 + * @param decimal 十进制数字 + * @returns 带0x前缀的十六进制字符串 + */ +export function decimalToHex(decimal: number): string { + return `0x${decimal.toString(16)}`; +} \ No newline at end of file From b0d1404829775405b21d42c75f56aa3a635e2fe9 Mon Sep 17 00:00:00 2001 From: Jack <6464072@qq.com> Date: Wed, 21 May 2025 20:04:04 +0800 Subject: [PATCH 15/15] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=80=9A?= =?UTF-8?q?=E9=81=93=E5=85=B3=E9=97=AD=E6=97=B6=E7=9A=84=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 Script 接口中 args 的类型从 string[] 改为 string - 修改 shutdownChannel 函数中的 close_script.args 参数格式 - 确保所有参数都符合 API 期望的十六进制字符串格式 - 更新相关依赖和工具函数 这个修改解决了通道关闭时出现的 'invalid type: sequence, expected a 0x-prefixed hex string' 错误。 --- packages/fiber/src/index.ts | 18 +++--- packages/fiber/src/modules/channel.ts | 93 +++++++++++++++++++++------ packages/fiber/src/modules/info.ts | 63 +++++++++++++++++- packages/fiber/src/types.ts | 28 +++++--- packages/fiber/src/utils/number.ts | 66 +++++++++++++++++++ 5 files changed, 229 insertions(+), 39 deletions(-) create mode 100644 packages/fiber/src/utils/number.ts diff --git a/packages/fiber/src/index.ts b/packages/fiber/src/index.ts index 9687a0ec..5699c75d 100644 --- a/packages/fiber/src/index.ts +++ b/packages/fiber/src/index.ts @@ -71,18 +71,18 @@ export class FiberSDK { */ async openChannel(params: { peer_id: string; - funding_amount: bigint; + funding_amount: string; public?: boolean; funding_udt_type_script?: Script; shutdown_script?: Script; - commitment_delay_epoch?: bigint; - commitment_fee_rate?: bigint; - funding_fee_rate?: bigint; - tlc_expiry_delta?: bigint; - tlc_min_value?: bigint; - tlc_fee_proportional_millionths?: bigint; - max_tlc_value_in_flight?: bigint; - max_tlc_number_in_flight?: bigint; + commitment_delay_epoch?: string; + commitment_fee_rate?: string; + funding_fee_rate?: string; + tlc_expiry_delta?: string; + tlc_min_value?: string; + tlc_fee_proportional_millionths?: string; + max_tlc_value_in_flight?: string; + max_tlc_number_in_flight?: string; }): Promise { return this.channel.openChannel(params); } diff --git a/packages/fiber/src/modules/channel.ts b/packages/fiber/src/modules/channel.ts index 68b9742b..21a30a63 100644 --- a/packages/fiber/src/modules/channel.ts +++ b/packages/fiber/src/modules/channel.ts @@ -1,5 +1,11 @@ import { FiberClient } from "../client.js"; import { Channel, Hash256, Script } from "../types.js"; +import { + decimalToU128, + decimalToU64, + u128ToDecimal, + u64ToDecimal, +} from "../utils/number.js"; export class ChannelModule { constructor(private client: FiberClient) {} @@ -9,20 +15,45 @@ export class ChannelModule { */ async openChannel(params: { peer_id: string; - funding_amount: bigint; + funding_amount: string; public?: boolean; funding_udt_type_script?: Script; shutdown_script?: Script; - commitment_delay_epoch?: bigint; - commitment_fee_rate?: bigint; - funding_fee_rate?: bigint; - tlc_expiry_delta?: bigint; - tlc_min_value?: bigint; - tlc_fee_proportional_millionths?: bigint; - max_tlc_value_in_flight?: bigint; - max_tlc_number_in_flight?: bigint; + commitment_delay_epoch?: string; + commitment_fee_rate?: string; + funding_fee_rate?: string; + tlc_expiry_delta?: string; + tlc_min_value?: string; + tlc_fee_proportional_millionths?: string; + max_tlc_value_in_flight?: string; + max_tlc_number_in_flight?: string; }): Promise { - return this.client.call("open_channel", [params]); + const u128Params = { + ...params, + funding_amount: decimalToU128(params.funding_amount), + commitment_delay_epoch: params.commitment_delay_epoch + ? decimalToU128(params.commitment_delay_epoch) + : undefined, + commitment_fee_rate: params.commitment_fee_rate + ? decimalToU128(params.commitment_fee_rate) + : undefined, + funding_fee_rate: params.funding_fee_rate + ? decimalToU64(params.funding_fee_rate) + : undefined, + tlc_expiry_delta: params.tlc_expiry_delta + ? decimalToU64(params.tlc_expiry_delta) + : undefined, + tlc_min_value: params.tlc_min_value + ? decimalToU128(params.tlc_min_value) + : undefined, + tlc_fee_proportional_millionths: params.tlc_fee_proportional_millionths + ? decimalToU128(params.tlc_fee_proportional_millionths) + : undefined, + max_tlc_value_in_flight: params.max_tlc_value_in_flight + ? decimalToU64(params.max_tlc_value_in_flight) + : undefined, + }; + return this.client.call("open_channel", [u128Params]); } /** @@ -30,14 +61,25 @@ export class ChannelModule { */ async acceptChannel(params: { temporary_channel_id: string; - funding_amount: bigint; - max_tlc_value_in_flight: bigint; - max_tlc_number_in_flight: bigint; - tlc_min_value: bigint; - tlc_fee_proportional_millionths: bigint; - tlc_expiry_delta: bigint; + funding_amount: string; + max_tlc_value_in_flight: string; + max_tlc_number_in_flight: string; + tlc_min_value: string; + tlc_fee_proportional_millionths: string; + tlc_expiry_delta: string; }): Promise { - return this.client.call("accept_channel", [params]); + const u128Params = { + ...params, + funding_amount: decimalToU128(params.funding_amount), + max_tlc_value_in_flight: decimalToU128(params.max_tlc_value_in_flight), + max_tlc_number_in_flight: decimalToU128(params.max_tlc_number_in_flight), + tlc_min_value: decimalToU128(params.tlc_min_value), + tlc_fee_proportional_millionths: decimalToU128( + params.tlc_fee_proportional_millionths, + ), + tlc_expiry_delta: decimalToU128(params.tlc_expiry_delta), + }; + return this.client.call("accept_channel", [u128Params]); } /** @@ -47,7 +89,6 @@ export class ChannelModule { * @returns Promise */ async abandonChannel(channelId: Hash256): Promise { - console.log(11111, channelId); if (!channelId) { throw new Error("Channel ID cannot be empty"); } @@ -89,7 +130,21 @@ export class ChannelModule { "list_channels", [{}], ); - return response.channels; + return response.channels.map((channel) => ({ + ...channel, + local_balance: u128ToDecimal(channel.local_balance), + remote_balance: u128ToDecimal(channel.remote_balance), + offered_tlc_balance: u128ToDecimal(channel.offered_tlc_balance), + received_tlc_balance: u128ToDecimal(channel.received_tlc_balance), + tlc_expiry_delta: u128ToDecimal(channel.tlc_expiry_delta), + tlc_fee_proportional_millionths: u128ToDecimal( + channel.tlc_fee_proportional_millionths, + ), + created_at: u64ToDecimal(channel.created_at, true), + last_updated_at: channel.last_updated_at + ? u64ToDecimal(channel.last_updated_at, true) + : "", + })); } /** diff --git a/packages/fiber/src/modules/info.ts b/packages/fiber/src/modules/info.ts index 1fb28dc5..dea7ba8c 100644 --- a/packages/fiber/src/modules/info.ts +++ b/packages/fiber/src/modules/info.ts @@ -1,5 +1,29 @@ +import { fixedPointToString } from "@ckb-ccc/core"; import { FiberClient } from "../client.js"; import { NodeInfo } from "../types.js"; +import { u64ToDecimal } from "../utils/number.js"; + +interface RawNodeInfo { + node_name: string; + addresses: string[]; + node_id: string; + timestamp: bigint; + chain_hash: string; + auto_accept_min_ckb_funding_amount: bigint; + auto_accept_channel_ckb_funding_amount: bigint; + tlc_expiry_delta: bigint; + tlc_min_value: bigint; + tlc_fee_proportional_millionths: bigint; + channel_count: string; + pending_channel_count: string; + peers_count: string; + udt_cfg_infos: Record; + default_funding_lock_script?: { + code_hash: string; + hash_type: string; + args: string; + }; +} export class InfoModule { constructor(private client: FiberClient) {} @@ -10,6 +34,43 @@ export class InfoModule { * @throws {Error} Throws error when unable to get node information */ async nodeInfo(): Promise { - return this.client.call("node_info", []); + const response = await this.client.call("node_info", []); + return { + node_name: response.node_name, + addresses: response.addresses, + node_id: response.node_id, + timestamp: response.timestamp + ? u64ToDecimal(response.timestamp, true) + : "", + chain_hash: response.chain_hash, + auto_accept_min_ckb_funding_amount: + response.auto_accept_min_ckb_funding_amount + ? fixedPointToString(response.auto_accept_min_ckb_funding_amount) + : "", + auto_accept_channel_ckb_funding_amount: + response.auto_accept_channel_ckb_funding_amount + ? fixedPointToString(response.auto_accept_channel_ckb_funding_amount) + : "", + tlc_expiry_delta: response.tlc_expiry_delta + ? fixedPointToString(response.tlc_expiry_delta) + : "", + tlc_min_value: response.tlc_min_value + ? fixedPointToString(response.tlc_min_value) + : "", + tlc_fee_proportional_millionths: response.tlc_fee_proportional_millionths + ? fixedPointToString(response.tlc_fee_proportional_millionths) + : "", + channel_count: response.channel_count + ? Number(response.channel_count).toString() + : "0", + pending_channel_count: response.pending_channel_count + ? Number(response.pending_channel_count).toString() + : "0", + peers_count: response.peers_count + ? Number(response.peers_count).toString() + : "0", + udt_cfg_infos: response.udt_cfg_infos, + default_funding_lock_script: response.default_funding_lock_script, + }; } } diff --git a/packages/fiber/src/types.ts b/packages/fiber/src/types.ts index 7f0205fb..564bd4b7 100644 --- a/packages/fiber/src/types.ts +++ b/packages/fiber/src/types.ts @@ -83,7 +83,7 @@ export enum RemoveTlcReason { export interface Script { code_hash: string; hash_type: string; - args: string[]; + args: string; } export interface Channel { @@ -91,15 +91,16 @@ export interface Channel { peer_id: Pubkey; funding_udt_type_script?: Script; state: string; - local_balance: bigint; - offered_tlc_balance: bigint; - remote_balance: bigint; - received_tlc_balance: bigint; + local_balance: string; + offered_tlc_balance: string; + remote_balance: string; + received_tlc_balance: string; latest_commitment_transaction_hash?: Hash256; - created_at: bigint; + created_at: string; + last_updated_at: string; enabled: boolean; - tlc_expiry_delta: bigint; - tlc_fee_proportional_millionths: bigint; + tlc_expiry_delta: string; + tlc_fee_proportional_millionths: string; } export interface ChannelInfo { @@ -150,9 +151,16 @@ export interface NodeInfo { node_name: string; addresses: string[]; node_id: Pubkey; - timestamp: bigint; + timestamp: string; chain_hash: Hash256; - auto_accept_min_ckb_funding_amount: bigint; + auto_accept_min_ckb_funding_amount: string; + auto_accept_channel_ckb_funding_amount: string; + tlc_expiry_delta: string; + tlc_min_value: string; + tlc_fee_proportional_millionths: string; + channel_count: string; + pending_channel_count: string; + peers_count: string; udt_cfg_infos: Record; default_funding_lock_script?: { code_hash: string; diff --git a/packages/fiber/src/utils/number.ts b/packages/fiber/src/utils/number.ts new file mode 100644 index 00000000..86fb0223 --- /dev/null +++ b/packages/fiber/src/utils/number.ts @@ -0,0 +1,66 @@ +import { fixedPointFrom, fixedPointToString } from "@ckb-ccc/core"; + +/** + * 将u128类型的数字转换为十进制字符串 + * @param value - u128类型的数字(bigint或string) + * @param decimals - 小数位数,默认为8 + * @returns 十进制字符串 + * + * @example + * ```typescript + * const decimal = u128ToDecimal(123456789n); // 输出 "1.23456789" + * const decimalWithDecimals = u128ToDecimal(123456789n, 6); // 输出 "123.456789" + * ``` + */ +export function u128ToDecimal( + value: bigint | string, + decimals: number = 8, +): string { + return fixedPointToString(value, decimals); +} + +/** + * 将十进制字符串转换为u128类型 + * @param value - 十进制字符串 + * @param decimals - 小数位数,默认为8 + * @returns u128类型的数字(bigint) + * + * @example + * ```typescript + * const u128 = decimalToU128("1.23456789"); // 输出 123456789n + * const u128WithDecimals = decimalToU128("123.456789", 6); // 输出 123456789n + * ``` + */ +export function decimalToU128(value: string, decimals: number = 8): bigint { + return fixedPointFrom(value, decimals); +} + +/** + * 将十进制字符串转换为U64类型 + * @param value - 十进制字符串 + * @returns U64类型的数字(bigint) + * + * @example + * ```typescript + * const u64 = decimalToU64("1000"); // 输出 1000n + * ``` + */ +export function decimalToU64(value: string): bigint { + return BigInt(value); +} + +/** + * 将U64类型的数字转换为十进制字符串 + * @param value - U64类型的数字(bigint或string) + * @param isTimestamp - 是否为时间戳,如果是则直接返回十进制字符串 + * @returns 十进制字符串 + */ +export function u64ToDecimal( + value: bigint | string, + isTimestamp: boolean = false, +): string { + if (isTimestamp) { + return value.toString(); + } + return u128ToDecimal(value, 0); +}