UNPKG

@ckbfs/api

Version:

SDK for CKBFS protocol on CKB

1,101 lines (981 loc) 34.8 kB
import { Script, Signer, Transaction, ClientPublicTestnet, SignerCkbPrivateKey, ClientPublicMainnet, } from "@ckb-ccc/core"; import { calculateChecksum, verifyChecksum, updateChecksum, verifyWitnessChecksum, verifyV3WitnessChecksum, verifyV3WitnessChain, } from "./utils/checksum"; import { createCKBFSCell, createPublishTransaction as utilCreatePublishTransaction, preparePublishTransaction, createAppendTransaction as utilCreateAppendTransaction, prepareAppendTransaction, createAppendTransactionDry, publishCKBFS as utilPublishCKBFS, appendCKBFS as utilAppendCKBFS, CKBFSCellOptions, PublishOptions, AppendOptions, // V3 functions PublishV3Options, AppendV3Options, TransferV3Options, publishCKBFSV3, appendCKBFSV3, transferCKBFSV3, createPublishV3Transaction, prepareAppendV3Transaction, preparePublishV3Transaction, createAppendV3Transaction, createAppendV3TransactionDry, createTransferV3Transaction, } from "./utils/transaction"; import { readFile, readFileAsText, readFileAsUint8Array, writeFile, getContentType, splitFileIntoChunks, combineChunksToFile, getFileContentFromChain, saveFileFromChain, getFileContentFromChainByTypeId, saveFileFromChainByTypeId, decodeFileFromChainByTypeId, getFileContentFromChainByIdentifier, saveFileFromChainByIdentifier, decodeFileFromChainByIdentifier, parseIdentifier, IdentifierType, decodeWitnessContent, decodeMultipleWitnessContents, extractFileFromWitnesses, decodeFileFromWitnessData, saveFileFromWitnessData, // V3 file retrieval functions getFileContentFromChainV3, getFileContentFromChainByIdentifierV3, saveFileFromChainByIdentifierV3, resolveCKBFSCell } from "./utils/file"; import { createCKBFSWitness, createTextCKBFSWitness, extractCKBFSWitnessContent, isCKBFSWitness, createChunkedCKBFSWitnesses, createCKBFSV3Witness, createChunkedCKBFSV3Witnesses, extractCKBFSV3WitnessContent, isCKBFSV3Witness, CKBFSV3WitnessOptions, } from "./utils/witness"; import { CKBFSData, BackLinkV1, BackLinkV2, CKBFSDataType, BackLinkType, CKBFS_HEADER, CKBFS_HEADER_STRING, } from "./utils/molecule"; import { NetworkType, ProtocolVersion, ProtocolVersionType, DEFAULT_NETWORK, DEFAULT_VERSION, CKBFS_CODE_HASH, CKBFS_TYPE_ID, ADLER32_CODE_HASH, ADLER32_TYPE_ID, DEP_GROUP_TX_HASH, DEPLOY_TX_HASH, getCKBFSScriptConfig, CKBFSScriptConfig, } from "./utils/constants"; import { ensureHexPrefix } from "./utils/transaction"; // Helper to encode string to Uint8Array const textEncoder = new TextEncoder(); /** * Custom options for file publishing and appending */ export interface FileOptions { contentType?: string; filename?: string; capacity?: bigint; feeRate?: number; network?: NetworkType; version?: ProtocolVersionType; useTypeID?: boolean; } /** * Options required when publishing content directly (string or Uint8Array) */ export type PublishContentOptions = Omit< FileOptions, "capacity" | "contentType" | "filename" > & Required<Pick<FileOptions, "contentType" | "filename">> & { capacity?: bigint; }; /** * Options required when appending content directly (string or Uint8Array) */ export type AppendContentOptions = Omit< FileOptions, "contentType" | "filename" | "capacity" > & { capacity?: bigint; witnessStartIndex?: number; }; /** * Configuration options for the CKBFS SDK */ export interface CKBFSOptions { chunkSize?: number; version?: ProtocolVersionType; useTypeID?: boolean; network?: NetworkType; rpcUrl?: string; } /** * Main CKBFS SDK class */ export class CKBFS { private signer: Signer; private chunkSize: number; private network: NetworkType; private version: ProtocolVersionType; private useTypeID: boolean; private rpcUrl: string; /** * Creates a new CKBFS SDK instance * @param signerOrPrivateKey The signer instance or CKB private key to use for signing transactions * @param networkOrOptions The network type or configuration options * @param options Additional configuration options when using privateKey */ constructor( signerOrPrivateKey: Signer | string, networkOrOptions: NetworkType | CKBFSOptions = DEFAULT_NETWORK, options?: CKBFSOptions, ) { // Determine if first parameter is a Signer or privateKey if (typeof signerOrPrivateKey === "string") { // Initialize with private key const privateKey = signerOrPrivateKey; const network = typeof networkOrOptions === "string" ? networkOrOptions : DEFAULT_NETWORK; const opts = options || (typeof networkOrOptions === "object" ? networkOrOptions : {}); const client = network === "mainnet" ? new ClientPublicMainnet({ url: opts.rpcUrl, }) : new ClientPublicTestnet({ url: opts.rpcUrl, }); this.signer = new SignerCkbPrivateKey(client, privateKey); this.network = network; this.chunkSize = opts.chunkSize || 30 * 1024; this.version = opts.version || DEFAULT_VERSION; this.useTypeID = opts.useTypeID || false; this.rpcUrl = opts.rpcUrl || client.url; } else { // Initialize with signer this.signer = signerOrPrivateKey; const opts = typeof networkOrOptions === "object" ? networkOrOptions : {}; this.network = opts.network || DEFAULT_NETWORK; this.chunkSize = opts.chunkSize || 30 * 1024; this.version = opts.version || DEFAULT_VERSION; this.useTypeID = opts.useTypeID || false; this.rpcUrl = opts.rpcUrl || this.signer.client.url; } } /** * Gets the recommended address object for the signer * @returns Promise resolving to the address object */ async getAddress() { return this.signer.getRecommendedAddressObj(); } /** * Gets the lock script for the signer * @returns Promise resolving to the lock script */ async getLock(): Promise<Script> { const address = await this.getAddress(); return address.script; } /** * Gets the CKBFS script configuration for the current settings * @returns The CKBFS script configuration */ getCKBFSConfig(): CKBFSScriptConfig { return getCKBFSScriptConfig(this.network, this.version, this.useTypeID); } /** * Publishes a file to CKBFS * @param filePath The path to the file to publish * @param options Options for publishing the file * @returns Promise resolving to the transaction hash */ async publishFile( filePath: string, options: FileOptions = {}, ): Promise<string> { // Read the file and split into chunks const fileContent = readFileAsUint8Array(filePath); const contentChunks = []; for (let i = 0; i < fileContent.length; i += this.chunkSize) { contentChunks.push(fileContent.slice(i, i + this.chunkSize)); } // Get the lock script const lock = await this.getLock(); // Determine content type if not provided const contentType = options.contentType || getContentType(filePath); // Use the filename from the path if not provided const pathParts = filePath.split(/[\\\/]/); const filename = options.filename || pathParts[pathParts.length - 1]; // Determine version - default to V3, allow override const version = options.version || this.version; // Use V3 by default, fallback to legacy for older versions if (version === ProtocolVersion.V3) { const tx = await publishCKBFSV3(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, version: ProtocolVersion.V3, }); console.log("Publish file tx:", tx.stringify()); // Send the transaction const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } else { // Legacy V1/V2 behavior const tx = await utilPublishCKBFS(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, version, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, }); console.log("Publish file tx:", tx.stringify()); // Send the transaction const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } } /** * Publishes content (string or Uint8Array) directly to CKBFS * @param content The content string or byte array to publish * @param options Options for publishing the content (contentType and filename are required) * @returns Promise resolving to the transaction hash */ async publishContent( content: string | Uint8Array, options: PublishContentOptions, ): Promise<string> { const contentBytes = typeof content === "string" ? textEncoder.encode(content) : content; const contentChunks = []; for (let i = 0; i < contentBytes.length; i += this.chunkSize) { contentChunks.push(contentBytes.slice(i, i + this.chunkSize)); } const lock = await this.getLock(); // Use provided contentType and filename (required) const { contentType, filename } = options; // Determine version - default to V3, allow override const version = options.version || this.version; // Use V3 by default, fallback to legacy for older versions if (version === ProtocolVersion.V3) { const tx = await publishCKBFSV3(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, version: ProtocolVersion.V3, }); const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } else { // Legacy V1/V2 behavior const tx = await utilPublishCKBFS(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, version, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, }); const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } } /** * Appends content from a file to an existing CKBFS file * @param filePath The path to the file containing the content to append * @param ckbfsCell The CKBFS cell to append to * @param options Additional options for the append operation * @returns Promise resolving to the transaction hash */ async appendFile( filePath: string, ckbfsCell: AppendOptions["ckbfsCell"], options: Omit<FileOptions, "contentType" | "filename"> & { // V3 backlink parameters (optional for backward compatibility) previousTxHash?: string; previousWitnessIndex?: number; previousChecksum?: number; witnessStartIndex?: number; } = {}, ): Promise<string> { // Read the file and split into chunks const fileContent = readFileAsUint8Array(filePath); const contentChunks = []; for (let i = 0; i < fileContent.length; i += this.chunkSize) { contentChunks.push(fileContent.slice(i, i + this.chunkSize)); } // Determine version - default to V3, allow override const version = options.version || this.version; // Use V3 when backlink parameters are provided or version is V3 if (version === ProtocolVersion.V3 && options.previousTxHash && options.previousWitnessIndex !== undefined && options.previousChecksum !== undefined) { const tx = await appendCKBFSV3(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: ProtocolVersion.V3, previousTxHash: options.previousTxHash, previousWitnessIndex: options.previousWitnessIndex, previousChecksum: options.previousChecksum, }); console.log("Append file v3 tx:", tx.stringify()); const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } else { // Legacy V1/V2 behavior or when V3 backlink params are missing const tx = await utilAppendCKBFS(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: version === ProtocolVersion.V3 ? ProtocolVersion.V2 : version, // Fallback to V2 if V3 but missing backlink params }); console.log("Append file tx:", tx.stringify()); const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } } /** * Appends content (string or Uint8Array) directly to an existing CKBFS file * @param content The content string or byte array to append * @param ckbfsCell The CKBFS cell to append to * @param options Additional options for the append operation * @returns Promise resolving to the transaction hash */ async appendContent( content: string | Uint8Array, ckbfsCell: AppendOptions["ckbfsCell"], options: AppendContentOptions = {}, ): Promise<string> { const contentBytes = typeof content === "string" ? textEncoder.encode(content) : content; const contentChunks = []; for (let i = 0; i < contentBytes.length; i += this.chunkSize) { contentChunks.push(contentBytes.slice(i, i + this.chunkSize)); } // Determine version - default to V3, allow override const version = options.version || this.version; // Use V3 when backlink parameters are provided or version is V3 if (version === ProtocolVersion.V3 && ckbfsCell.outPoint.txHash && ckbfsCell.data.index !== undefined && ckbfsCell.data.checksum !== undefined) { const tx = await appendCKBFSV3(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: ProtocolVersion.V3, previousTxHash: ckbfsCell.outPoint.txHash, previousWitnessIndex: ckbfsCell.data.index, previousChecksum: ckbfsCell.data.checksum, }); const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } else { // Legacy V1/V2 behavior or when V3 backlink params are missing const tx = await utilAppendCKBFS(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: version === ProtocolVersion.V3 ? ProtocolVersion.V2 : version, // Fallback to V2 if V3 but missing backlink params // No useTypeID option for append }); const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } } /** * Creates a new transaction for publishing a file but doesn't sign or send it * @param filePath The path to the file to publish * @param options Options for publishing the file * @returns Promise resolving to the unsigned transaction */ async createPublishTransaction( filePath: string, options: FileOptions = {}, ): Promise<Transaction> { // Read the file and split into chunks const fileContent = readFileAsUint8Array(filePath); const contentChunks = []; for (let i = 0; i < fileContent.length; i += this.chunkSize) { contentChunks.push(fileContent.slice(i, i + this.chunkSize)); } // Get the lock script const lock = await this.getLock(); // Determine content type if not provided const contentType = options.contentType || getContentType(filePath); // Use the filename from the path if not provided const pathParts = filePath.split(/[\\\/]/); const filename = options.filename || pathParts[pathParts.length - 1]; // Determine version - default to V3, allow override const version = options.version || this.version; // Use V3 by default, fallback to legacy for older versions if (version === ProtocolVersion.V3) { return createPublishV3Transaction(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, version: ProtocolVersion.V3, }); } else { // Legacy V1/V2 behavior return utilCreatePublishTransaction(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, version, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, }); } } /** * Creates a new transaction for publishing content (string or Uint8Array) directly, but doesn't sign or send it * @param content The content string or byte array to publish * @param options Options for publishing the content (contentType and filename are required) * @returns Promise resolving to the unsigned transaction */ async createPublishContentTransaction( content: string | Uint8Array, options: PublishContentOptions, ): Promise<Transaction> { const contentBytes = typeof content === "string" ? textEncoder.encode(content) : content; const contentChunks = []; for (let i = 0; i < contentBytes.length; i += this.chunkSize) { contentChunks.push(contentBytes.slice(i, i + this.chunkSize)); } const lock = await this.getLock(); // Use provided contentType and filename (required) const { contentType, filename } = options; // Determine version - default to V3, allow override const version = options.version || this.version; // Use V3 by default, fallback to legacy for older versions if (version === ProtocolVersion.V3) { return createPublishV3Transaction(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, version: ProtocolVersion.V3, }); } else { // Legacy V1/V2 behavior return utilCreatePublishTransaction(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, version, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, }); } } /** * Creates a new transaction for appending content from a file but doesn't sign or send it * @param filePath The path to the file containing the content to append * @param ckbfsCell The CKBFS cell to append to * @param options Additional options for the append operation * @returns Promise resolving to the unsigned transaction */ async createAppendTransaction( filePath: string, ckbfsCell: AppendOptions["ckbfsCell"], options: Omit<FileOptions, "contentType" | "filename"> & { // V3 backlink parameters (optional for backward compatibility) previousTxHash?: string; previousWitnessIndex?: number; previousChecksum?: number; witnessStartIndex?: number; } = {}, ): Promise<Transaction> { // Read the file and split into chunks const fileContent = readFileAsUint8Array(filePath); const contentChunks = []; for (let i = 0; i < fileContent.length; i += this.chunkSize) { contentChunks.push(fileContent.slice(i, i + this.chunkSize)); } // Determine version - default to V3, allow override const version = options.version || this.version; // Use V3 when backlink parameters are provided or version is V3 if (version === ProtocolVersion.V3 && options.previousTxHash && options.previousWitnessIndex !== undefined && options.previousChecksum !== undefined) { return createAppendV3Transaction(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: ProtocolVersion.V3, previousTxHash: options.previousTxHash, previousWitnessIndex: options.previousWitnessIndex, previousChecksum: options.previousChecksum, witnessStartIndex: options.witnessStartIndex, }); } else { // Legacy V1/V2 behavior or when V3 backlink params are missing return utilCreateAppendTransaction(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: version === ProtocolVersion.V3 ? ProtocolVersion.V2 : version, // Fallback to V2 if V3 but missing backlink params }); } } /** * Creates a new transaction for appending content (string or Uint8Array) directly, but doesn't sign or send it * @param content The content string or byte array to append * @param ckbfsCell The CKBFS cell to append to * @param options Additional options for the append operation * @returns Promise resolving to the unsigned transaction */ async createAppendContentTransaction( content: string | Uint8Array, ckbfsCell: AppendOptions["ckbfsCell"], options: AppendContentOptions = {}, ): Promise<Transaction> { const contentBytes = typeof content === "string" ? textEncoder.encode(content) : content; const contentChunks = []; for (let i = 0; i < contentBytes.length; i += this.chunkSize) { contentChunks.push(contentBytes.slice(i, i + this.chunkSize)); } // Determine version - default to V3, allow override const version = options.version || this.version; // Use V3 when backlink parameters are provided or version is V3 if (version === ProtocolVersion.V3 && ckbfsCell.outPoint.txHash && ckbfsCell.data.index !== undefined && ckbfsCell.data.checksum !== undefined) { return createAppendV3Transaction(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: ProtocolVersion.V3, previousTxHash: ckbfsCell.outPoint.txHash, previousWitnessIndex: ckbfsCell.data.index, previousChecksum: ckbfsCell.data.checksum, witnessStartIndex: options.witnessStartIndex, }); } else { // Legacy V1/V2 behavior or when V3 backlink params are missing return utilCreateAppendTransaction(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: version === ProtocolVersion.V3 ? ProtocolVersion.V2 : version, // Fallback to V2 if V3 but missing backlink params // No useTypeID option for append }); } } // V3 Methods /** * Publishes a file to CKBFS v3 * @param filePath The path to the file to publish * @param options Options for publishing the file * @returns Promise resolving to the transaction hash */ async publishFileV3( filePath: string, options: Omit<FileOptions, 'version'> = {}, ): Promise<string> { // Read the file and split into chunks const fileContent = readFileAsUint8Array(filePath); const contentChunks = []; for (let i = 0; i < fileContent.length; i += this.chunkSize) { contentChunks.push(fileContent.slice(i, i + this.chunkSize)); } // Get the lock script const lock = await this.getLock(); // Determine content type if not provided const contentType = options.contentType || getContentType(filePath); // Use the filename from the path if not provided const pathParts = filePath.split(/[\\\/]/); const filename = options.filename || pathParts[pathParts.length - 1]; // Create and sign the transaction using the v3 utility function const tx = await publishCKBFSV3(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, version: ProtocolVersion.V3, }); console.log("Publish file v3 tx:", tx.stringify()); // Send the transaction const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } /** * Publishes content (string or Uint8Array) directly to CKBFS v3 * @param content The content string or byte array to publish * @param options Options for publishing the content (contentType and filename are required) * @returns Promise resolving to the transaction hash */ async publishContentV3( content: string | Uint8Array, options: PublishContentOptions, ): Promise<string> { const contentBytes = typeof content === "string" ? textEncoder.encode(content) : content; const contentChunks = []; for (let i = 0; i < contentBytes.length; i += this.chunkSize) { contentChunks.push(contentBytes.slice(i, i + this.chunkSize)); } const lock = await this.getLock(); // Use provided contentType and filename (required) const { contentType, filename } = options; const tx = await publishCKBFSV3(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, version: ProtocolVersion.V3, }); const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } /** * Appends content from a file to an existing CKBFS v3 file * @param filePath The path to the file containing the content to append * @param ckbfsCell The CKBFS cell to append to * @param previousTxHash The previous transaction hash for backlink * @param previousWitnessIndex The previous witness index for backlink * @param previousChecksum The previous checksum for backlink * @param options Additional options for the append operation * @returns Promise resolving to the transaction hash */ async appendFileV3( filePath: string, ckbfsCell: AppendV3Options["ckbfsCell"], previousTxHash: string, previousWitnessIndex: number, previousChecksum: number, options: Omit<FileOptions, "contentType" | "filename" | "version"> = {}, ): Promise<string> { // Read the file and split into chunks const fileContent = readFileAsUint8Array(filePath); const contentChunks = []; for (let i = 0; i < fileContent.length; i += this.chunkSize) { contentChunks.push(fileContent.slice(i, i + this.chunkSize)); } // Create and sign the transaction using the v3 utility function const tx = await appendCKBFSV3(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: ProtocolVersion.V3, previousTxHash, previousWitnessIndex, previousChecksum, }); console.log("Append file v3 tx:", tx.stringify()); // Send the transaction const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } /** * Appends content (string or Uint8Array) directly to an existing CKBFS v3 file * @param content The content string or byte array to append * @param ckbfsCell The CKBFS cell to append to * @param previousTxHash The previous transaction hash for backlink * @param previousWitnessIndex The previous witness index for backlink * @param previousChecksum The previous checksum for backlink * @param options Additional options for the append operation * @returns Promise resolving to the transaction hash */ async appendContentV3( content: string | Uint8Array, ckbfsCell: AppendV3Options["ckbfsCell"], previousTxHash: string, previousWitnessIndex: number, previousChecksum: number, options: AppendContentOptions = {}, ): Promise<string> { const contentBytes = typeof content === "string" ? textEncoder.encode(content) : content; const contentChunks = []; for (let i = 0; i < contentBytes.length; i += this.chunkSize) { contentChunks.push(contentBytes.slice(i, i + this.chunkSize)); } const tx = await appendCKBFSV3(this.signer, { ckbfsCell, contentChunks, feeRate: options.feeRate, network: options.network || this.network, version: ProtocolVersion.V3, previousTxHash, previousWitnessIndex, previousChecksum, }); const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } /** * Transfers ownership of a CKBFS v3 file to a new lock script * @param ckbfsCell The CKBFS cell to transfer * @param newLock The new lock script for the transferred file * @param previousTxHash The previous transaction hash for backlink * @param previousWitnessIndex The previous witness index for backlink * @param previousChecksum The previous checksum for backlink * @param options Additional options for the transfer operation * @returns Promise resolving to the transaction hash */ async transferFileV3( ckbfsCell: TransferV3Options["ckbfsCell"], newLock: Script, previousTxHash: string, previousWitnessIndex: number, previousChecksum: number, options: Omit<FileOptions, "contentType" | "filename" | "version"> = {}, ): Promise<string> { const tx = await transferCKBFSV3(this.signer, { ckbfsCell, newLock, feeRate: options.feeRate, network: options.network || this.network, version: ProtocolVersion.V3, previousTxHash, previousWitnessIndex, previousChecksum, }); console.log("Transfer file v3 tx:", tx.stringify()); const txHash = await this.signer.sendTransaction(tx); return ensureHexPrefix(txHash); } /** * Creates a new transaction for publishing a file to CKBFS v3 but doesn't sign or send it * @param filePath The path to the file to publish * @param options Options for publishing the file * @returns Promise resolving to the unsigned transaction */ async createPublishV3Transaction( filePath: string, options: Omit<FileOptions, 'version'> = {}, ): Promise<Transaction> { // Read the file and split into chunks const fileContent = readFileAsUint8Array(filePath); const contentChunks = []; for (let i = 0; i < fileContent.length; i += this.chunkSize) { contentChunks.push(fileContent.slice(i, i + this.chunkSize)); } // Get the lock script const lock = await this.getLock(); // Determine content type if not provided const contentType = options.contentType || getContentType(filePath); // Use the filename from the path if not provided const pathParts = filePath.split(/[\\\/]/); const filename = options.filename || pathParts[pathParts.length - 1]; // Create the transaction using the utility function return createPublishV3Transaction(this.signer, { contentChunks, contentType, filename, lock, capacity: options.capacity, feeRate: options.feeRate, network: options.network || this.network, useTypeID: options.useTypeID !== undefined ? options.useTypeID : this.useTypeID, version: ProtocolVersion.V3, }); } } // Export utility functions export { // Checksum utilities calculateChecksum, verifyChecksum, updateChecksum, verifyWitnessChecksum, verifyV3WitnessChecksum, verifyV3WitnessChain, // Transaction utilities (Exporting original names from transaction.ts) createCKBFSCell, utilCreatePublishTransaction as createPublishTransaction, preparePublishTransaction, utilCreateAppendTransaction as createAppendTransaction, prepareAppendTransaction, utilPublishCKBFS as publishCKBFS, utilAppendCKBFS as appendCKBFS, createAppendTransactionDry, // V3 transaction utilities publishCKBFSV3, appendCKBFSV3, transferCKBFSV3, createPublishV3Transaction, prepareAppendV3Transaction, preparePublishV3Transaction, createAppendV3Transaction, createAppendV3TransactionDry, createTransferV3Transaction, // File utilities readFile, readFileAsText, readFileAsUint8Array, writeFile, getContentType, splitFileIntoChunks, combineChunksToFile, getFileContentFromChain, saveFileFromChain, getFileContentFromChainByTypeId, saveFileFromChainByTypeId, decodeFileFromChainByTypeId, getFileContentFromChainByIdentifier, saveFileFromChainByIdentifier, decodeFileFromChainByIdentifier, parseIdentifier, IdentifierType, decodeWitnessContent, decodeMultipleWitnessContents, extractFileFromWitnesses, decodeFileFromWitnessData, saveFileFromWitnessData, // V3 File utilities getFileContentFromChainV3, getFileContentFromChainByIdentifierV3, saveFileFromChainByIdentifierV3, // Witness utilities createCKBFSWitness, createTextCKBFSWitness, extractCKBFSWitnessContent, isCKBFSWitness, createChunkedCKBFSWitnesses, // V3 Witness utilities createCKBFSV3Witness, createChunkedCKBFSV3Witnesses, extractCKBFSV3WitnessContent, isCKBFSV3Witness, resolveCKBFSCell, CKBFSV3WitnessOptions, // Molecule definitions CKBFSData, BackLinkV1, BackLinkV2, // Types CKBFSDataType, BackLinkType, CKBFSCellOptions, PublishOptions, AppendOptions, // Constants CKBFS_HEADER, CKBFS_HEADER_STRING, // CKBFS Protocol Constants & Configuration NetworkType, ProtocolVersion, ProtocolVersionType, DEFAULT_NETWORK, DEFAULT_VERSION, CKBFS_CODE_HASH, CKBFS_TYPE_ID, ADLER32_CODE_HASH, ADLER32_TYPE_ID, DEP_GROUP_TX_HASH, DEPLOY_TX_HASH, getCKBFSScriptConfig, CKBFSScriptConfig, };