import fs from "fs" import path, { parse } from 'path'; import { CKBFS, NetworkType, CKBFSDataType, ProtocolVersion, CKBFSData, getFileContentFromChain, getCKBFSScriptConfig, extractCKBFSV3WitnessContent, isCKBFSV3Witness, resolveCKBFSCell, createAppendV3Transaction } from '../src'; import { Script, Address, ClientPublicTestnet, ClientPublicMainnet, Transaction, ccc } from "@ckb-ccc/core"; import { transferSpore } from "@ckb-ccc/spore"; //import { isMainnet, network, CKB_PRIVATE_KEY } from "../config" const isMainnet = false const CKB_PRIVATE_KEY = "0x1733e6f7825f99eb41ca26f8569fa90c2cb8c5ae199bbd38a97562291f784983" const network = isMainnet ? NetworkType.Mainnet : NetworkType.Testnet const client = isMainnet ? new ClientPublicMainnet() : new ClientPublicTestnet() export const TestnetTraceLockDep = { outPoint: { // txHash: "0xb60c07715089877b238a60559ff4c15b46991ddcf1cfd2f9a40c32d6fff668ee", txHash: "0x7a3e492188438ca4b260fed0f7a00d18ec9e4671a8c3b54fc2b3b49447ab2e2f", index: 0x0, }, depType: "code", } export const MainnetTraceLockDep = { outPoint: { txHash: "0xd00a0222a12ae202d68fa4e90650ba560b325a3707c901395966a191372c47f7", index: 0x0, }, depType: "code", } export const TestnetShadowDep = { outPoint: { txHash: "0x2d8a311a55e42d7d6810610149f6e125f0d292112f8ed4a21177aec05da2b905", index: 0x0, }, depType: "code", } export const MainnetShadowDep = { outPoint: { txHash: "0x5b20ec7d7d5c6410ac4924c57d1eac1dd50129b6263be3dc93055397b5724dc2", index: 0x0, }, depType: "code", } const TraceLockDep = isMainnet ? MainnetTraceLockDep : TestnetTraceLockDep const ShadowDep = isMainnet ? MainnetShadowDep : TestnetShadowDep const TraceLogCodeHash = "0xbd7e56daaa4cc9ecf488a50d2f4db2a55a7c86eb2df2157d24a62be9be5b34ab" const ShadowSporeCodeHash = "0x6361d4b20d845953d9c9431bbba08905573005a71e2a2432e7e0e7c685666f24" const ckbfs = new CKBFS( CKB_PRIVATE_KEY, isMainnet ? NetworkType.Mainnet : NetworkType.Testnet, { version: ProtocolVersion.V3, chunkSize: 30 * 1024, // 30KB chunks useTypeID: false // Use code hash instead of type ID } ); async function getAddressScriptHash(addr: string | Address) { if (typeof addr === 'string') { addr = await Address.fromString(addr, client) console.log("addr=", addr) } return `0x03${addr.script.hash().slice(2)}` } export async function mint(physicalArtifactNo: string) { // Get address info const address = await ckbfs.getAddress(); const ownerHash = await getAddressScriptHash(address) const content = `MINT,TO:${ownerHash}\n` // You can provide additional options const options = { contentType: 'text/plain', filename: `${physicalArtifactNo}.LOGS`, }; const tx = await ckbfs.createPublishContentTransaction(content, options); const signer = ckbfs['signer'] const txHash = await signer.sendTransaction(tx) const ckbfsId = tx.outputs[0].type?.args.toString() return { txHash, log: content, ckbfsId }; } export async function claim(to: string, sporeId: string, ckbfsId: string) { const from = await ckbfs.getAddress(); const ckbfsCell = await getCkbfsCellInfo(ckbfsId); console.log("ckbfsCell=", ckbfsCell) const fromHash = await getAddressScriptHash(from) const toHash = await getAddressScriptHash(to) const content = `TRANSFER,FROM:${fromHash},TO:${toHash}\n` const tx = await ckbfs.createAppendContentTransaction(content, ckbfsCell); tx.addCellDeps(TraceLockDep) const sizeBefore = tx.outputs[0].lock.occupiedSize const traceLogLock = Script.from({ codeHash: TraceLogCodeHash, hashType: "type", args: toHash }) tx.outputs[0].lock = traceLogLock const sizeAfter = traceLogLock.occupiedSize const diffSize = BigInt(sizeAfter - sizeBefore) * BigInt(100000000) tx.outputs[0].capacity = tx.outputs[0].capacity + diffSize const sporeLockArgs = `0x01${ tx.outputs[0].type?.hash().slice(2) }` const newSporeLock = Script.from({ codeHash: ShadowSporeCodeHash, args: sporeLockArgs, hashType: "data1", }) tx.outputs = [ tx.outputs[0] ] const signer = ckbfs['signer'] const sporeTx = await transferSpore({ signer: signer as any, id: sporeId, to: newSporeLock, tx: tx, }) tx.addCellDeps(ShadowDep) const finalTx = sporeTx.tx await finalTx.completeFeeBy(signer as any) console.log("finalTx=", finalTx) const txHash = await signer.sendTransaction(finalTx) console.log(`File appended successfully! Transaction Hash: ${txHash}`); return { txHash, log: content } } export async function giveName(name: string, ckbfsId: string) { const ckbfsCell = await getCkbfsCellInfo(ckbfsId) const content = `GIVE_NAME,NEW_NAME:${name}\n` console.log("ckbfsCell=", ckbfsCell) const signer = ckbfs['signer'] let tx = await ckbfs.createAppendContentTransaction(content, ckbfsCell); tx.addCellDeps(TraceLockDep) parseCkbfsData(tx.outputsData[0]) //tx.witnesses = tx.witnesses.reverse() // tx.witnesses[0] = '0x' // tx.witnesses[0] = tx.witnesses[0] console.log("tx=", tx) tx = await signer.signTransaction(tx) console.log("signed tx=", tx) // await signer.sendTransaction(tx) const txHash = await signer.sendTransaction(tx) console.log(`File appended successfully! Transaction Hash: ${txHash}`); return { txHash, log: content } } export async function transfer(to: string, ckbfsId: string) { const ckbfsCell = await getCkbfsCellInfo(ckbfsId) const from = await ckbfs.getAddress(); const fromHash = await getAddressScriptHash(from) const toHash = await getAddressScriptHash(to) const content = `TRANSFER,FROM:${fromHash},TO:${toHash}\n` const signer = ckbfs['signer'] let tx = await ckbfs.createAppendContentTransaction(content, ckbfsCell); tx.outputs[0].lock.args = toHash as `0x{string}`; // tx.addCellDeps(TestnetTraceLockDep) const txHash = await signer.sendTransaction(tx) console.log(`File appended successfully! Transaction Hash: ${txHash}`); return { txHash, log: content }; } // export async function release(from: string, ckbfsId: string) { // const ckbfsCell = await await getCkbfsCellInfo(ckbfsId) // const fromHash = await getAddressScriptHash(from) // const content = `RELEASE,FROM:${fromHash}\n` // const signer = ckbfs['signer'] // let tx = await ckbfs.createAppendContentTransaction(content, ckbfsCell); // tx.addCellDeps(TestnetTraceLockDep) // const txHash = await signer.sendTransaction(tx) // console.log(`File appended successfully! Transaction Hash: ${txHash}`); // return { txHash, log: content }; // } // export async function retrieveLogsFromChain(ckbfsId: string) { // const { outPoint, data } = await getCkbfsCellInfo(ckbfsId) // const content = await getFileContentFromChain(client, outPoint, data) // const logs = content.toString().trim().split("\n") // return logs // } export async function getCkbfsCellInfo(ckbfsId: string) { const { codeHash, hashType } = getCKBFSScriptConfig(isMainnet ? NetworkType.Mainnet : NetworkType.Testnet, ProtocolVersion.V3) console.log("codeHash, hashType=", codeHash, hashType, ckbfsId) const cell = await client.findSingletonCellByType({ codeHash, hashType, args: ckbfsId }, true) if (!cell || !cell.cellOutput.type) { throw new Error('No CKBFS cell found'); } const rawData = cell.outputData.startsWith('0x') ? Buffer.from(cell.outputData.slice(2), 'hex') : Buffer.from(cell.outputData, 'hex'); const ckbfsData = CKBFSData.unpack(rawData, ProtocolVersion.V3) return { outPoint: { txHash: cell.outPoint.txHash as string, index: Number(cell.outPoint.index) }, type: cell.cellOutput.type, lock: cell.cellOutput.lock, capacity: cell.cellOutput.capacity, data: ckbfsData, }; } export function getCkbfsList() { const file = path.join(__dirname, `./data/${isMainnet ? 'mainnet' : 'testnet'}/ckbfs-list-x3.json`) return JSON.parse(fs.readFileSync(file).toString()) } export async function mintAll() { const ckbfsList = getCkbfsList() for(const no of ckbfsList) { const { txHash, ckbfsId } = await mint(no) console.log(`minted ckbfs no=${no}, ckbfsId=${ckbfsId}, txHash=${txHash}`); const interval = setInterval(async () => { try { const tx = await client.getTransaction(txHash) if (tx && tx.status === 'committed') { clearInterval(interval) console.info(`tx confirmed, https://explorer.nervos.org/transaction/${txHash}`); } } catch (error) { console.error(error) } }, 5 * 1000) } } async function getCellInfoFromV3Transaction(txHash: string): Promise<{ outPoint: { txHash: string; index: number }; type: Script; data: CKBFSDataType; lock: Script; capacity: bigint; previousTxHash: string; previousWitnessIndex: number; previousChecksum: number; }> { console.log(`Retrieving v3 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 v3 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 v3 data const rawData = outputData.startsWith('0x') ? ccc.bytesFrom(outputData.slice(2), 'hex') : Buffer.from(outputData, 'hex'); // Unpack the raw data using v3 format const version = ProtocolVersion.V3; console.log(`Using protocol version ${version} for unpacking v3 cell data`); let ckbfsData: CKBFSDataType; try { ckbfsData = CKBFSData.unpack(rawData, version); console.log('Successfully unpacked CKBFS v3 cell data:'); console.log(`- Checksum: ${ckbfsData.checksum}`); console.log(`- File: ${ckbfsData.filename}`); console.log(`- Content Type: ${ckbfsData.contentType}`); console.log(`- Index: ${ckbfsData.index}`); } catch (error) { console.error('Error unpacking CKBFS v3 data:', error); throw new Error(`Failed to unpack CKBFS v3 data: ${error}`); } // Extract backlink information from v3 witness let previousTxHash = '0x' + '00'.repeat(32); let previousWitnessIndex = 0; let previousChecksum = 0; if (ckbfsData.index !== undefined && ckbfsData.index < tx.witnesses.length) { const witnessHex = tx.witnesses[ckbfsData.index]; const witness = Buffer.from(witnessHex.slice(2), 'hex'); if (isCKBFSV3Witness(witness)) { try { const witnessData = extractCKBFSV3WitnessContent(witness, true); previousTxHash = witnessData.previousTxHash || previousTxHash; previousWitnessIndex = witnessData.previousWitnessIndex || 0; previousChecksum = witnessData.previousChecksum || 0; console.log('Extracted v3 backlink information:'); console.log(`- Previous TX Hash: ${previousTxHash}`); console.log(`- Previous Witness Index: ${previousWitnessIndex}`); console.log(`- Previous Checksum: ${previousChecksum}`); } catch (error) { console.warn('Could not extract backlink info from witness:', error); } } } return { outPoint: { txHash, index: ckbfsCellIndex }, type: output.type, lock: output.lock, capacity: output.capacity, data: ckbfsData, previousTxHash, previousWitnessIndex, previousChecksum }; } catch (error) { console.error('Error retrieving v3 transaction data:', error); throw new Error(`Failed to retrieve or parse v3 cell data: ${error}`); } } export async function getCkbfsTxContent(txHash: string, index = 0) { const tx = await client.getTransaction(txHash); const { transaction, status: txStatus, blockNumber, } = tx!; let timestamp: number = 0; if (txStatus === 'committed') { const header = await client.getHeaderByNumber(blockNumber!); timestamp = Number(header?.timestamp); } const toAddress = Address.fromScript( transaction.outputs[0].lock, client, ).toString(); const outputData = transaction.outputsData[index]; const rawData = outputData.startsWith('0x') ? Buffer.from(outputData.slice(2), 'hex') : Buffer.from(outputData, 'hex'); const ckbfsData = CKBFSData.unpack(rawData as any, ProtocolVersion.V3); const contentChunks = []; // const indexes = // ckbfsData.indexes || // (ckbfsData.index !== undefined ? [ckbfsData.index] : []); // if (indexes.length > 0) { // // Get content from each witness index // for (const idx of indexes) { // if (idx >= transaction.witnesses.length) { // console.warn(`Witness index ${idx} out of range`); // continue; // } // const witnessHex = transaction.witnesses[idx]; // const witness = Buffer.from(witnessHex.slice(2), 'hex'); // Remove 0x prefix // // Extract content (skip CKBFS header + version byte) // if (witness.length >= 6 && witness.slice(0, 5).toString() === 'CKBFS') { // const content = witness.slice(6); // contentChunks.push(content); // Add to beginning of array (we're going backwards) // } else { // console.warn(`Witness at index ${idx} is not a valid CKBFS witness`); // } // } // } const witnessHex = transaction.witnesses[ckbfsData.index!]; const witness = Buffer.from(witnessHex.slice(2), 'hex'); const witnessData = extractCKBFSV3WitnessContent(new Uint8Array(witness), true); // const witnessData = extractCKBFSV3WitnessContent(witness as any, true); console.log("witnessData=", witnessData) console.log("content=", Buffer.from(witnessData.content).toString()) const ckbfsId = transaction.outputs[0].type?.args.toString(); // const content = Buffer.concat(contentChunks).toString().trim(); return { data: ckbfsData, toAddress, content: "", ckbfsId, txStatus, committedAt: timestamp, }; } function parseCkbfsData(outputData: string){ // const outputData = "0x4500000014000000180000001c0000002a000000010000002614cb020a000000746578742f706c61696e170000003358303132354e4d4e5641503030303130312e4c4f4753" // const outputData = "0x4500000014000000180000001c0000002a000000010000009443df5b0a000000746578742f706c61696e170000003358303132354e4d4e5641503030303130322e4c4f4753" const rawData = outputData.startsWith('0x') ? Buffer.from(outputData.slice(2), 'hex') : Buffer.from(outputData, 'hex'); const ckbfsData = CKBFSData.unpack(rawData as any, ProtocolVersion.V3); console.log(ckbfsData) } // mintAll() // mint("3X0125NMNVAP000102").then(console.log) // claim("ckt1qyqtaymen8quq9r3sf7x20uuq4pl5t8r4x8qqunj4q", // "0x2b3cbd4c86a42e3ea1d092daab2f213e1600203906f33645f825159e1071d1e4", // sporeId // "0x33fd0f19f411eec7cc84d32577a015612afc00cd008225a2ed34a8fda475e498" // ckbfsId // ); // getCellInfoFromV3Transaction("0x54c86dbdc6bdafb4616221d54cfc0ff186a4a772eba509838684f8e1ea271b40").then(console.log) // parseCkbfsData() giveName( "aaa", "0x33fd0f19f411eec7cc84d32577a015612afc00cd008225a2ed34a8fda475e498" // ckbfsId ).then(console.log) // transfer( // "ckt1qyqtaymen8quq9r3sf7x20uuq4pl5t8r4x8qqunj4q", // "0xe58baa82c4844add60911bb890487fea3c45436b9d7b203cc1065301d0a9c503" // ).then(console.log) // getCkbfsCellInfo("0x52315569e5ff0d6d6392f047fcbea97bae1b32b688c29aca5251a157157278d3").then((data) => { // console.log("ckbfs data=", data) // }) // getCkbfsTxContent("0xecc76092e301d15a808ee91570bc8c71ea448d17e9dc186c699db716878f116a").then(console.log) // getCellInfoFromV3Transaction("0x8dc79a7630145e2a220e2363beca2781c6a4f0f79ef7a01ab73f17dc90571fb7").then(console.log) // getCkbfsCellInfo("0x0084106b9bea08f512e35b725c4a710ebf09cb65c05601ebde8d911bc6c662bf").then(console.log) // testReresolve("0x0084106b9bea08f512e35b725c4a710ebf09cb65c05601ebde8d911bc6c662bf").then(console.log)