@ckbfs/api
Version:
SDK for CKBFS protocol on CKB
230 lines (192 loc) • 8.24 kB
text/typescript
import { CKBFS, NetworkType, ProtocolVersion, CKBFSDataType, extractCKBFSWitnessContent, isCKBFSWitness, CKBFSData, AppendContentOptions } from '../src/index';
import { Script, ClientPublicTestnet, Transaction, ccc } from "@ckb-ccc/core";
// Replace with your actual private key
const privateKey = process.env.CKB_PRIVATE_KEY || 'your-private-key-here';
// Parse command line arguments for transaction hash
const txHashArg = process.argv.find(arg => arg.startsWith('--txhash='));
const publishTxHash = txHashArg ? txHashArg.split('=')[1] : process.env.PUBLISH_TX_HASH || '0x0000000000000000000000000000000000000000000000000000000000000000';
/**
* Ensures a string is prefixed with '0x'
* @param value The string to ensure is hex prefixed
* @returns A hex prefixed string
*/
function ensureHexPrefix(value: string): `0x${string}` {
if (value.startsWith('0x')) {
return value as `0x${string}`;
}
return `0x${value}` as `0x${string}`;
}
// Initialize the SDK with network and version options
const ckbfs = new CKBFS(
privateKey,
NetworkType.Testnet, // Use testnet
{
version: ProtocolVersion.V2, // Use the latest version (V2)
chunkSize: 20 * 1024, // 20KB chunks
useTypeID: false // Use code hash instead of type ID
}
);
// Initialize CKB client for testnet
const client = new ClientPublicTestnet();
/**
* Get cell information from a transaction using CKB client
* @param txHash The transaction hash to get cell information from
* @returns Promise resolving to the cell information
*/
async function getCellInfoFromTransaction(txHash: string): Promise<{
outPoint: { txHash: string; index: number };
type: Script;
data: CKBFSDataType;
lock: Script;
capacity: bigint;
}> {
console.log(`Retrieving transaction data for: ${txHash}`);
try {
// Get transaction from RPC
const txWithStatus = await client.getTransaction(txHash);
if (!txWithStatus || !txWithStatus.transaction) {
throw new Error(`Transaction ${txHash} not found`);
}
const tx = Transaction.from(txWithStatus.transaction);
console.log(`Transaction found with ${tx.outputs.length} outputs`);
// Find the CKBFS cell output (first output with type script)
let ckbfsCellIndex = 0;
const output = tx.outputs[ckbfsCellIndex];
if (!output || !output.type) {
throw new Error('No CKBFS cell found in the transaction');
}
console.log(`Found CKBFS cell at index ${ckbfsCellIndex}`);
console.log(`Cell type script hash: ${output.type.hash()}`);
// Get output data
const outputData = tx.outputsData[ckbfsCellIndex];
if (!outputData) {
throw new Error('Output data not found');
}
// Parse the output data as CKBFS data
// First remove 0x prefix if present
const rawData = outputData.startsWith('0x')
? ccc.bytesFrom(outputData.slice(2), 'hex')
: Buffer.from(outputData, 'hex');
// Actually unpack the raw data using CKBFSData.unpack
// Use the same protocol version as configured in the SDK
const version = ProtocolVersion.V2; // Use V2 as configured in SDK initialization
console.log(`Using protocol version ${version} for unpacking cell data`);
let ckbfsData: CKBFSDataType;
try {
ckbfsData = CKBFSData.unpack(rawData, version);
// Log the actual cell data for transparency
console.log('Successfully unpacked CKBFS cell data:');
console.log(`- Checksum: ${ckbfsData.checksum}`);
console.log(`- File: ${ckbfsData.filename}`);
console.log(`- Content Type: ${ckbfsData.contentType}`);
console.log(`- Index: ${ckbfsData.index}`);
console.log(`- Indexes: ${ckbfsData.indexes}`);
console.log(`- Backlinks count: ${ckbfsData.backLinks?.length || 0}`);
} catch (error) {
console.error('Error unpacking CKBFS data:', error);
throw new Error(`Failed to unpack CKBFS data: ${error}`);
}
return {
outPoint: {
txHash,
index: ckbfsCellIndex
},
type: output.type,
lock: output.lock,
capacity: output.capacity,
data: ckbfsData
};
} catch (error) {
console.error('Error retrieving transaction data:', error);
// If we can't get or parse the real data, we should fail - not use mock data
throw new Error(`Failed to retrieve or parse cell data: ${error}`);
}
}
/**
* Example of appending content to an existing CKBFS file
*/
async function appendExample() {
try {
// Validate transaction hash
if (publishTxHash === '0x0000000000000000000000000000000000000000000000000000000000000000') {
throw new Error('Please provide a valid transaction hash by setting the PUBLISH_TX_HASH environment variable or using --txhash=0x... argument');
}
// Get address info
const address = await ckbfs.getAddress();
console.log(`Using address: ${address.toString()}`);
// Get lock script
const lock = await ckbfs.getLock();
// Get the cell information from the transaction
console.log(`Getting cell info from transaction: ${publishTxHash}`);
const ckbfsCell = await getCellInfoFromTransaction(publishTxHash);
// Append content from a file
const filePath = './append.txt';
console.log(`Appending file: ${filePath}`);
// Create a sample append.txt file for the demonstration
const fs = require('fs');
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, 'This is content to append to the previously published file.');
console.log(`Created sample file: ${filePath}`);
}
console.log('Note: When appending data, the capacity of the cell may need to increase.');
console.log('The SDK will automatically handle this by adding additional inputs if needed.');
console.log('Make sure your account has enough CKB to cover the increased capacity and fees.');
const txHash = await ckbfs.appendFile(filePath, ckbfsCell);
console.log(`File appended successfully!`);
console.log(`Transaction Hash: ${txHash}`);
console.log(`View at: https://pudge.explorer.nervos.org/transaction/${txHash}`);
return txHash;
} catch (error) {
console.error('Error appending file:', error);
throw error;
}
}
/**
* Example of appending content directly (string) to an existing CKBFS file
* @param previousAppendTxHash The transaction hash from the previous append operation
*/
async function appendContentExample(previousAppendTxHash: string) {
try {
console.log(`Getting cell info from previous append transaction: ${previousAppendTxHash}`);
const ckbfsCell = await getCellInfoFromTransaction(previousAppendTxHash);
const contentToAppend = "\nAnd this is more content appended directly as a string!";
const options: AppendContentOptions = {
// You can optionally specify feeRate, network, version
// feeRate: 3000
};
console.log(`Appending direct content: "${contentToAppend}"`);
// Append the string content
const txHash = await ckbfs.appendContent(contentToAppend, ckbfsCell, options);
console.log(`Direct content appended successfully!`);
console.log(`Transaction Hash: ${txHash}`);
console.log(`View at: https://pudge.explorer.nervos.org/transaction/${txHash}`);
return txHash;
} catch (error) {
console.error('Error appending direct content:', error);
throw error;
}
}
/**
* Main function to run the example
*/
async function main() {
console.log('Running CKBFS append example...');
console.log('-------------------------------');
console.log(`Using CKBFS protocol version: ${ProtocolVersion.V2}`);
try {
// Run the file append first
const firstAppendTxHash = await appendExample();
console.log('-------------------------------');
// Now run the content append, using the output from the first append
await appendContentExample(firstAppendTxHash);
console.log('Example completed successfully!');
process.exit(0);
} catch (error) {
console.error('Example failed:', error);
process.exit(1);
}
}
// Run the example if this file is executed directly
if (require.main === module) {
main().catch(console.error);
}