@ckbfs/api 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +13 -4
- package/dist/utils/transaction.d.ts +29 -0
- package/dist/utils/transaction.js +368 -60
- package/examples/publish.ts +2 -2
- package/package.json +1 -1
- package/src/index.ts +16 -3
- package/src/utils/transaction.ts +435 -66
package/README.md
CHANGED
|
@@ -14,7 +14,6 @@ CKBFS is a file system protocol built on top of the CKB blockchain that enables
|
|
|
14
14
|
- **Protocol Versions**: Support for both V1 and V2 CKBFS protocol versions
|
|
15
15
|
- **Network Support**: Compatible with CKB mainnet and testnet
|
|
16
16
|
- **Chunked Storage**: Automatic file chunking for large files
|
|
17
|
-
- **TypeScript**: Full type safety with comprehensive type definitions
|
|
18
17
|
|
|
19
18
|
## Installation
|
|
20
19
|
|
|
@@ -567,4 +566,4 @@ Contributions are welcome. Please ensure all tests pass and follow the existing
|
|
|
567
566
|
|
|
568
567
|
## Support
|
|
569
568
|
|
|
570
|
-
For issues and questions, please use the GitHub issue tracker.
|
|
569
|
+
For issues and questions, please use the GitHub issue tracker.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Script, Signer, Transaction } from "@ckb-ccc/core";
|
|
2
2
|
import { calculateChecksum, verifyChecksum, updateChecksum, verifyWitnessChecksum } from "./utils/checksum";
|
|
3
|
-
import { createCKBFSCell, createPublishTransaction as utilCreatePublishTransaction, createAppendTransaction as utilCreateAppendTransaction, publishCKBFS as utilPublishCKBFS, appendCKBFS as utilAppendCKBFS, CKBFSCellOptions, PublishOptions, AppendOptions } from "./utils/transaction";
|
|
3
|
+
import { createCKBFSCell, createPublishTransaction as utilCreatePublishTransaction, preparePublishTransaction, createAppendTransaction as utilCreateAppendTransaction, prepareAppendTransaction, createAppendTransactionDry, publishCKBFS as utilPublishCKBFS, appendCKBFS as utilAppendCKBFS, CKBFSCellOptions, PublishOptions, AppendOptions } from "./utils/transaction";
|
|
4
4
|
import { readFile, readFileAsText, readFileAsUint8Array, writeFile, getContentType, splitFileIntoChunks, combineChunksToFile, getFileContentFromChain, saveFileFromChain, getFileContentFromChainByTypeId, saveFileFromChainByTypeId, decodeFileFromChainByTypeId, getFileContentFromChainByIdentifier, saveFileFromChainByIdentifier, decodeFileFromChainByIdentifier, parseIdentifier, IdentifierType, decodeWitnessContent, decodeMultipleWitnessContents, extractFileFromWitnesses, decodeFileFromWitnessData, saveFileFromWitnessData } from "./utils/file";
|
|
5
5
|
import { createCKBFSWitness, createTextCKBFSWitness, extractCKBFSWitnessContent, isCKBFSWitness, createChunkedCKBFSWitnesses } from "./utils/witness";
|
|
6
6
|
import { CKBFSData, BackLinkV1, BackLinkV2, CKBFSDataType, BackLinkType, CKBFS_HEADER, CKBFS_HEADER_STRING } from "./utils/molecule";
|
|
@@ -37,6 +37,7 @@ export interface CKBFSOptions {
|
|
|
37
37
|
version?: ProtocolVersionType;
|
|
38
38
|
useTypeID?: boolean;
|
|
39
39
|
network?: NetworkType;
|
|
40
|
+
rpcUrl?: string;
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Main CKBFS SDK class
|
|
@@ -47,6 +48,7 @@ export declare class CKBFS {
|
|
|
47
48
|
private network;
|
|
48
49
|
private version;
|
|
49
50
|
private useTypeID;
|
|
51
|
+
private rpcUrl;
|
|
50
52
|
/**
|
|
51
53
|
* Creates a new CKBFS SDK instance
|
|
52
54
|
* @param signerOrPrivateKey The signer instance or CKB private key to use for signing transactions
|
|
@@ -130,4 +132,4 @@ export declare class CKBFS {
|
|
|
130
132
|
*/
|
|
131
133
|
createAppendContentTransaction(content: string | Uint8Array, ckbfsCell: AppendOptions["ckbfsCell"], options?: AppendContentOptions): Promise<Transaction>;
|
|
132
134
|
}
|
|
133
|
-
export { calculateChecksum, verifyChecksum, updateChecksum, verifyWitnessChecksum, createCKBFSCell, utilCreatePublishTransaction as createPublishTransaction, utilCreateAppendTransaction as createAppendTransaction, utilPublishCKBFS as publishCKBFS, utilAppendCKBFS as appendCKBFS, readFile, readFileAsText, readFileAsUint8Array, writeFile, getContentType, splitFileIntoChunks, combineChunksToFile, getFileContentFromChain, saveFileFromChain, getFileContentFromChainByTypeId, saveFileFromChainByTypeId, decodeFileFromChainByTypeId, getFileContentFromChainByIdentifier, saveFileFromChainByIdentifier, decodeFileFromChainByIdentifier, parseIdentifier, IdentifierType, decodeWitnessContent, decodeMultipleWitnessContents, extractFileFromWitnesses, decodeFileFromWitnessData, saveFileFromWitnessData, createCKBFSWitness, createTextCKBFSWitness, extractCKBFSWitnessContent, isCKBFSWitness, createChunkedCKBFSWitnesses, CKBFSData, BackLinkV1, BackLinkV2, CKBFSDataType, BackLinkType, CKBFSCellOptions, PublishOptions, AppendOptions, CKBFS_HEADER, CKBFS_HEADER_STRING, 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, };
|
|
135
|
+
export { calculateChecksum, verifyChecksum, updateChecksum, verifyWitnessChecksum, createCKBFSCell, utilCreatePublishTransaction as createPublishTransaction, preparePublishTransaction, utilCreateAppendTransaction as createAppendTransaction, prepareAppendTransaction, utilPublishCKBFS as publishCKBFS, utilAppendCKBFS as appendCKBFS, createAppendTransactionDry, readFile, readFileAsText, readFileAsUint8Array, writeFile, getContentType, splitFileIntoChunks, combineChunksToFile, getFileContentFromChain, saveFileFromChain, getFileContentFromChainByTypeId, saveFileFromChainByTypeId, decodeFileFromChainByTypeId, getFileContentFromChainByIdentifier, saveFileFromChainByIdentifier, decodeFileFromChainByIdentifier, parseIdentifier, IdentifierType, decodeWitnessContent, decodeMultipleWitnessContents, extractFileFromWitnesses, decodeFileFromWitnessData, saveFileFromWitnessData, createCKBFSWitness, createTextCKBFSWitness, extractCKBFSWitnessContent, isCKBFSWitness, createChunkedCKBFSWitnesses, CKBFSData, BackLinkV1, BackLinkV2, CKBFSDataType, BackLinkType, CKBFSCellOptions, PublishOptions, AppendOptions, CKBFS_HEADER, CKBFS_HEADER_STRING, 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, };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.getCKBFSScriptConfig = exports.DEPLOY_TX_HASH = exports.DEP_GROUP_TX_HASH = void 0;
|
|
3
|
+
exports.CKBFS_CODE_HASH = exports.DEFAULT_VERSION = exports.DEFAULT_NETWORK = exports.ProtocolVersion = exports.NetworkType = exports.CKBFS_HEADER_STRING = exports.CKBFS_HEADER = exports.BackLinkV2 = exports.BackLinkV1 = exports.CKBFSData = exports.createChunkedCKBFSWitnesses = exports.isCKBFSWitness = exports.extractCKBFSWitnessContent = exports.createTextCKBFSWitness = exports.createCKBFSWitness = exports.saveFileFromWitnessData = exports.decodeFileFromWitnessData = exports.extractFileFromWitnesses = exports.decodeMultipleWitnessContents = exports.decodeWitnessContent = exports.IdentifierType = exports.parseIdentifier = exports.decodeFileFromChainByIdentifier = exports.saveFileFromChainByIdentifier = exports.getFileContentFromChainByIdentifier = exports.decodeFileFromChainByTypeId = exports.saveFileFromChainByTypeId = exports.getFileContentFromChainByTypeId = exports.saveFileFromChain = exports.getFileContentFromChain = exports.combineChunksToFile = exports.splitFileIntoChunks = exports.getContentType = exports.writeFile = exports.readFileAsUint8Array = exports.readFileAsText = exports.readFile = exports.createAppendTransactionDry = exports.appendCKBFS = exports.publishCKBFS = exports.prepareAppendTransaction = exports.createAppendTransaction = exports.preparePublishTransaction = exports.createPublishTransaction = exports.createCKBFSCell = exports.verifyWitnessChecksum = exports.updateChecksum = exports.verifyChecksum = exports.calculateChecksum = exports.CKBFS = void 0;
|
|
4
|
+
exports.getCKBFSScriptConfig = exports.DEPLOY_TX_HASH = exports.DEP_GROUP_TX_HASH = exports.ADLER32_TYPE_ID = exports.ADLER32_CODE_HASH = exports.CKBFS_TYPE_ID = void 0;
|
|
5
5
|
const core_1 = require("@ckb-ccc/core");
|
|
6
6
|
const checksum_1 = require("./utils/checksum");
|
|
7
7
|
Object.defineProperty(exports, "calculateChecksum", { enumerable: true, get: function () { return checksum_1.calculateChecksum; } });
|
|
@@ -11,7 +11,10 @@ Object.defineProperty(exports, "verifyWitnessChecksum", { enumerable: true, get:
|
|
|
11
11
|
const transaction_1 = require("./utils/transaction");
|
|
12
12
|
Object.defineProperty(exports, "createCKBFSCell", { enumerable: true, get: function () { return transaction_1.createCKBFSCell; } });
|
|
13
13
|
Object.defineProperty(exports, "createPublishTransaction", { enumerable: true, get: function () { return transaction_1.createPublishTransaction; } });
|
|
14
|
+
Object.defineProperty(exports, "preparePublishTransaction", { enumerable: true, get: function () { return transaction_1.preparePublishTransaction; } });
|
|
14
15
|
Object.defineProperty(exports, "createAppendTransaction", { enumerable: true, get: function () { return transaction_1.createAppendTransaction; } });
|
|
16
|
+
Object.defineProperty(exports, "prepareAppendTransaction", { enumerable: true, get: function () { return transaction_1.prepareAppendTransaction; } });
|
|
17
|
+
Object.defineProperty(exports, "createAppendTransactionDry", { enumerable: true, get: function () { return transaction_1.createAppendTransactionDry; } });
|
|
15
18
|
Object.defineProperty(exports, "publishCKBFS", { enumerable: true, get: function () { return transaction_1.publishCKBFS; } });
|
|
16
19
|
Object.defineProperty(exports, "appendCKBFS", { enumerable: true, get: function () { return transaction_1.appendCKBFS; } });
|
|
17
20
|
const file_1 = require("./utils/file");
|
|
@@ -85,13 +88,18 @@ class CKBFS {
|
|
|
85
88
|
const opts = options ||
|
|
86
89
|
(typeof networkOrOptions === "object" ? networkOrOptions : {});
|
|
87
90
|
const client = network === "mainnet"
|
|
88
|
-
? new core_1.ClientPublicMainnet(
|
|
89
|
-
|
|
91
|
+
? new core_1.ClientPublicMainnet({
|
|
92
|
+
url: opts.rpcUrl,
|
|
93
|
+
})
|
|
94
|
+
: new core_1.ClientPublicTestnet({
|
|
95
|
+
url: opts.rpcUrl,
|
|
96
|
+
});
|
|
90
97
|
this.signer = new core_1.SignerCkbPrivateKey(client, privateKey);
|
|
91
98
|
this.network = network;
|
|
92
99
|
this.chunkSize = opts.chunkSize || 30 * 1024;
|
|
93
100
|
this.version = opts.version || constants_1.DEFAULT_VERSION;
|
|
94
101
|
this.useTypeID = opts.useTypeID || false;
|
|
102
|
+
this.rpcUrl = opts.rpcUrl || client.url;
|
|
95
103
|
}
|
|
96
104
|
else {
|
|
97
105
|
// Initialize with signer
|
|
@@ -101,6 +109,7 @@ class CKBFS {
|
|
|
101
109
|
this.chunkSize = opts.chunkSize || 30 * 1024;
|
|
102
110
|
this.version = opts.version || constants_1.DEFAULT_VERSION;
|
|
103
111
|
this.useTypeID = opts.useTypeID || false;
|
|
112
|
+
this.rpcUrl = opts.rpcUrl || this.signer.client.url;
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
115
|
/**
|
|
@@ -22,6 +22,7 @@ export interface CKBFSCellOptions {
|
|
|
22
22
|
export interface PublishOptions extends CKBFSCellOptions {
|
|
23
23
|
contentChunks: Uint8Array[];
|
|
24
24
|
feeRate?: number;
|
|
25
|
+
from?: Transaction;
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* Options for appending content to a CKBFS file
|
|
@@ -41,6 +42,7 @@ export interface AppendOptions {
|
|
|
41
42
|
feeRate?: number;
|
|
42
43
|
network?: NetworkType;
|
|
43
44
|
version?: ProtocolVersionType;
|
|
45
|
+
from?: Transaction;
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
48
|
* Ensures a string is prefixed with '0x'
|
|
@@ -58,6 +60,17 @@ export declare function createCKBFSCell(options: CKBFSCellOptions): {
|
|
|
58
60
|
type: ccc.Script;
|
|
59
61
|
capacity: bigint;
|
|
60
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Prepares a transaction for publishing a file to CKBFS without fee and change handling
|
|
65
|
+
* You will need to manually set the typeID if you did not provide inputs, or just check is return value emptyTypeID is true
|
|
66
|
+
* @param options Options for publishing the file
|
|
67
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
68
|
+
*/
|
|
69
|
+
export declare function preparePublishTransaction(options: PublishOptions): Promise<{
|
|
70
|
+
tx: Transaction;
|
|
71
|
+
outputIndex: number;
|
|
72
|
+
emptyTypeID: boolean;
|
|
73
|
+
}>;
|
|
61
74
|
/**
|
|
62
75
|
* Creates a transaction for publishing a file to CKBFS
|
|
63
76
|
* @param signer The signer to use for the transaction
|
|
@@ -65,6 +78,15 @@ export declare function createCKBFSCell(options: CKBFSCellOptions): {
|
|
|
65
78
|
* @returns Promise resolving to the created transaction
|
|
66
79
|
*/
|
|
67
80
|
export declare function createPublishTransaction(signer: Signer, options: PublishOptions): Promise<Transaction>;
|
|
81
|
+
/**
|
|
82
|
+
* Prepares a transaction for appending content to a CKBFS file without fee and change handling
|
|
83
|
+
* @param options Options for appending content
|
|
84
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
85
|
+
*/
|
|
86
|
+
export declare function prepareAppendTransaction(options: AppendOptions): Promise<{
|
|
87
|
+
tx: Transaction;
|
|
88
|
+
outputIndex: number;
|
|
89
|
+
}>;
|
|
68
90
|
/**
|
|
69
91
|
* Creates a transaction for appending content to a CKBFS file
|
|
70
92
|
* @param signer The signer to use for the transaction
|
|
@@ -72,6 +94,13 @@ export declare function createPublishTransaction(signer: Signer, options: Publis
|
|
|
72
94
|
* @returns Promise resolving to the created transaction
|
|
73
95
|
*/
|
|
74
96
|
export declare function createAppendTransaction(signer: Signer, options: AppendOptions): Promise<Transaction>;
|
|
97
|
+
/**
|
|
98
|
+
* Creates a transaction for appending content to a CKBFS file
|
|
99
|
+
* @param signer The signer to use for the transaction
|
|
100
|
+
* @param options Options for appending content
|
|
101
|
+
* @returns Promise resolving to the created transaction
|
|
102
|
+
*/
|
|
103
|
+
export declare function createAppendTransactionDry(signer: Signer, options: AppendOptions): Promise<Transaction>;
|
|
75
104
|
/**
|
|
76
105
|
* Creates a complete transaction for publishing a file to CKBFS
|
|
77
106
|
* @param signer The signer to use for the transaction
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensureHexPrefix = ensureHexPrefix;
|
|
4
4
|
exports.createCKBFSCell = createCKBFSCell;
|
|
5
|
+
exports.preparePublishTransaction = preparePublishTransaction;
|
|
5
6
|
exports.createPublishTransaction = createPublishTransaction;
|
|
7
|
+
exports.prepareAppendTransaction = prepareAppendTransaction;
|
|
6
8
|
exports.createAppendTransaction = createAppendTransaction;
|
|
9
|
+
exports.createAppendTransactionDry = createAppendTransactionDry;
|
|
7
10
|
exports.publishCKBFS = publishCKBFS;
|
|
8
11
|
exports.appendCKBFS = appendCKBFS;
|
|
9
12
|
const core_1 = require("@ckb-ccc/core");
|
|
@@ -41,15 +44,14 @@ function createCKBFSCell(options) {
|
|
|
41
44
|
};
|
|
42
45
|
}
|
|
43
46
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
47
|
+
* Prepares a transaction for publishing a file to CKBFS without fee and change handling
|
|
48
|
+
* You will need to manually set the typeID if you did not provide inputs, or just check is return value emptyTypeID is true
|
|
46
49
|
* @param options Options for publishing the file
|
|
47
|
-
* @returns Promise resolving to the
|
|
50
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
48
51
|
*/
|
|
49
|
-
async function
|
|
50
|
-
const { contentChunks, contentType, filename, lock, capacity,
|
|
52
|
+
async function preparePublishTransaction(options) {
|
|
53
|
+
const { from, contentChunks, contentType, filename, lock, capacity, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, useTypeID = false, } = options;
|
|
51
54
|
// Calculate checksum for the combined content
|
|
52
|
-
const textEncoder = new TextEncoder();
|
|
53
55
|
const combinedContent = Buffer.concat(contentChunks);
|
|
54
56
|
const checksum = await (0, checksum_1.calculateChecksum)(combinedContent);
|
|
55
57
|
// Create CKBFS witnesses - each chunk already includes the CKBFS header
|
|
@@ -57,9 +59,7 @@ async function createPublishTransaction(signer, options) {
|
|
|
57
59
|
// not to be confused with the Protocol Version (V1 vs V2)
|
|
58
60
|
const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
|
|
59
61
|
// Calculate the actual witness indices where our content is placed
|
|
60
|
-
|
|
61
|
-
// So our CKBFS data starts at index 1
|
|
62
|
-
const contentStartIndex = 1;
|
|
62
|
+
const contentStartIndex = from?.witnesses.length || 1;
|
|
63
63
|
const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
|
|
64
64
|
// Create CKBFS cell output data based on version
|
|
65
65
|
let outputData;
|
|
@@ -93,24 +93,72 @@ async function createPublishTransaction(signer, options) {
|
|
|
93
93
|
lock.occupiedSize +
|
|
94
94
|
8) * 100000000n;
|
|
95
95
|
// Create pre transaction without cell deps initially
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
96
|
+
let preTx;
|
|
97
|
+
if (from) {
|
|
98
|
+
// If from is not empty, inject/merge the fields
|
|
99
|
+
preTx = core_1.Transaction.from({
|
|
100
|
+
...from,
|
|
101
|
+
outputs: from.outputs.length === 0
|
|
102
|
+
? [
|
|
103
|
+
createCKBFSCell({
|
|
104
|
+
contentType,
|
|
105
|
+
filename,
|
|
106
|
+
lock,
|
|
107
|
+
network,
|
|
108
|
+
version,
|
|
109
|
+
useTypeID,
|
|
110
|
+
capacity: ckbfsCellSize || capacity,
|
|
111
|
+
}),
|
|
112
|
+
]
|
|
113
|
+
: [
|
|
114
|
+
...from.outputs,
|
|
115
|
+
createCKBFSCell({
|
|
116
|
+
contentType,
|
|
117
|
+
filename,
|
|
118
|
+
lock,
|
|
119
|
+
network,
|
|
120
|
+
version,
|
|
121
|
+
useTypeID,
|
|
122
|
+
capacity: ckbfsCellSize || capacity,
|
|
123
|
+
}),
|
|
124
|
+
],
|
|
125
|
+
witnesses: from.witnesses.length === 0
|
|
126
|
+
? [
|
|
127
|
+
[], // Empty secp witness for signing if not provided
|
|
128
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
129
|
+
]
|
|
130
|
+
: [
|
|
131
|
+
...from.witnesses,
|
|
132
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
133
|
+
],
|
|
134
|
+
outputsData: from.outputsData.length === 0
|
|
135
|
+
? [outputData]
|
|
136
|
+
: [
|
|
137
|
+
...from.outputsData,
|
|
138
|
+
outputData,
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
preTx = core_1.Transaction.from({
|
|
144
|
+
outputs: [
|
|
145
|
+
createCKBFSCell({
|
|
146
|
+
contentType,
|
|
147
|
+
filename,
|
|
148
|
+
lock,
|
|
149
|
+
network,
|
|
150
|
+
version,
|
|
151
|
+
useTypeID,
|
|
152
|
+
capacity: ckbfsCellSize || capacity,
|
|
153
|
+
}),
|
|
154
|
+
],
|
|
155
|
+
witnesses: [
|
|
156
|
+
[], // Empty secp witness for signing
|
|
157
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
158
|
+
],
|
|
159
|
+
outputsData: [outputData],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
114
162
|
// Add the CKBFS dep group cell dependency
|
|
115
163
|
preTx.addCellDeps({
|
|
116
164
|
outPoint: {
|
|
@@ -119,35 +167,261 @@ async function createPublishTransaction(signer, options) {
|
|
|
119
167
|
},
|
|
120
168
|
depType: "depGroup",
|
|
121
169
|
});
|
|
122
|
-
// Get the recommended address to ensure lock script cell deps are included
|
|
123
|
-
const address = await signer.getRecommendedAddressObj();
|
|
124
|
-
// Complete inputs by capacity
|
|
125
|
-
await preTx.completeInputsByCapacity(signer);
|
|
126
|
-
// Complete fee change to lock
|
|
127
|
-
await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
|
|
128
170
|
// Create type ID args
|
|
129
|
-
const
|
|
171
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
172
|
+
const args = preTx.inputs.length > 0 ? core_1.ccc.hashTypeId(preTx.inputs[0], outputIndex) : "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
130
173
|
// Create CKBFS type script with type ID
|
|
131
174
|
const ckbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, args);
|
|
132
175
|
// Create final transaction with same cell deps as preTx
|
|
133
176
|
const tx = core_1.Transaction.from({
|
|
134
177
|
cellDeps: preTx.cellDeps,
|
|
135
|
-
witnesses:
|
|
136
|
-
[], // Reset first witness for signing
|
|
137
|
-
...preTx.witnesses.slice(1),
|
|
138
|
-
],
|
|
178
|
+
witnesses: preTx.witnesses,
|
|
139
179
|
outputsData: preTx.outputsData,
|
|
140
180
|
inputs: preTx.inputs,
|
|
141
|
-
outputs:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
181
|
+
outputs: outputIndex === 0
|
|
182
|
+
? [
|
|
183
|
+
{
|
|
184
|
+
lock,
|
|
185
|
+
type: ckbfsTypeScript,
|
|
186
|
+
capacity: preTx.outputs[outputIndex].capacity,
|
|
187
|
+
},
|
|
188
|
+
]
|
|
189
|
+
: [
|
|
190
|
+
...preTx.outputs.slice(0, outputIndex), // Include rest of outputs (e.g., change)
|
|
191
|
+
{
|
|
192
|
+
lock,
|
|
193
|
+
type: ckbfsTypeScript,
|
|
194
|
+
capacity: preTx.outputs[outputIndex].capacity,
|
|
195
|
+
},
|
|
196
|
+
],
|
|
149
197
|
});
|
|
150
|
-
return tx;
|
|
198
|
+
return { tx, outputIndex, emptyTypeID: args === "0x0000000000000000000000000000000000000000000000000000000000000000" };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Creates a transaction for publishing a file to CKBFS
|
|
202
|
+
* @param signer The signer to use for the transaction
|
|
203
|
+
* @param options Options for publishing the file
|
|
204
|
+
* @returns Promise resolving to the created transaction
|
|
205
|
+
*/
|
|
206
|
+
async function createPublishTransaction(signer, options) {
|
|
207
|
+
const { feeRate, lock, } = options;
|
|
208
|
+
// Use preparePublishTransaction to create the base transaction
|
|
209
|
+
const { tx: preTx, outputIndex, emptyTypeID } = await preparePublishTransaction(options);
|
|
210
|
+
// Complete inputs by capacity
|
|
211
|
+
await preTx.completeInputsByCapacity(signer);
|
|
212
|
+
// Complete fee change to lock
|
|
213
|
+
await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
|
|
214
|
+
// If emptyTypeID is true, we need to create the proper type ID args
|
|
215
|
+
if (emptyTypeID) {
|
|
216
|
+
// Get CKBFS script config
|
|
217
|
+
const config = (0, constants_1.getCKBFSScriptConfig)(options.network || constants_1.DEFAULT_NETWORK, options.version || constants_1.DEFAULT_VERSION, options.useTypeID || false);
|
|
218
|
+
// Create type ID args
|
|
219
|
+
const args = core_1.ccc.hashTypeId(preTx.inputs[0], outputIndex);
|
|
220
|
+
// Create CKBFS type script with type ID
|
|
221
|
+
const ckbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, args);
|
|
222
|
+
// Create final transaction with updated type script
|
|
223
|
+
const tx = core_1.Transaction.from({
|
|
224
|
+
cellDeps: preTx.cellDeps,
|
|
225
|
+
witnesses: preTx.witnesses,
|
|
226
|
+
outputsData: preTx.outputsData,
|
|
227
|
+
inputs: preTx.inputs,
|
|
228
|
+
outputs: preTx.outputs.map((output, index) => index === outputIndex
|
|
229
|
+
? {
|
|
230
|
+
lock,
|
|
231
|
+
type: ckbfsTypeScript,
|
|
232
|
+
capacity: output.capacity,
|
|
233
|
+
}
|
|
234
|
+
: output),
|
|
235
|
+
});
|
|
236
|
+
return tx;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// If typeID was already set properly, just reset the first witness for signing
|
|
240
|
+
const tx = core_1.Transaction.from({
|
|
241
|
+
cellDeps: preTx.cellDeps,
|
|
242
|
+
witnesses: preTx.witnesses,
|
|
243
|
+
outputsData: preTx.outputsData,
|
|
244
|
+
inputs: preTx.inputs,
|
|
245
|
+
outputs: preTx.outputs,
|
|
246
|
+
});
|
|
247
|
+
return tx;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Prepares a transaction for appending content to a CKBFS file without fee and change handling
|
|
252
|
+
* @param options Options for appending content
|
|
253
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
254
|
+
*/
|
|
255
|
+
async function prepareAppendTransaction(options) {
|
|
256
|
+
const { from, ckbfsCell, contentChunks, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, } = options;
|
|
257
|
+
const { outPoint, data, type, lock, capacity } = ckbfsCell;
|
|
258
|
+
// Get CKBFS script config early to use version info
|
|
259
|
+
const config = (0, constants_1.getCKBFSScriptConfig)(network, version);
|
|
260
|
+
// Create CKBFS witnesses - each chunk already includes the CKBFS header
|
|
261
|
+
// Pass 0 as version byte - this is the protocol version byte in the witness header
|
|
262
|
+
// not to be confused with the Protocol Version (V1 vs V2)
|
|
263
|
+
const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
|
|
264
|
+
// Combine the new content chunks for checksum calculation
|
|
265
|
+
const combinedContent = Buffer.concat(contentChunks);
|
|
266
|
+
// Update the existing checksum with the new content - this matches Adler32's
|
|
267
|
+
// cumulative nature as required by Rule 11 in the RFC
|
|
268
|
+
const contentChecksum = await (0, checksum_1.updateChecksum)(data.checksum, combinedContent);
|
|
269
|
+
// Calculate the actual witness indices where our content is placed
|
|
270
|
+
const contentStartIndex = from?.witnesses.length || 1;
|
|
271
|
+
const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
|
|
272
|
+
// Create backlink for the current state based on version
|
|
273
|
+
let newBackLink;
|
|
274
|
+
if (version === constants_1.ProtocolVersion.V1) {
|
|
275
|
+
// V1 format: Use index field (single number)
|
|
276
|
+
newBackLink = {
|
|
277
|
+
// In V1, field order is index, checksum, txHash
|
|
278
|
+
// and index is a single number value, not an array
|
|
279
|
+
index: data.index ||
|
|
280
|
+
(data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
|
|
281
|
+
checksum: data.checksum,
|
|
282
|
+
txHash: outPoint.txHash,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// V2 format: Use indexes field (array of numbers)
|
|
287
|
+
newBackLink = {
|
|
288
|
+
// In V2, field order is indexes, checksum, txHash
|
|
289
|
+
// and indexes is an array of numbers
|
|
290
|
+
indexes: data.indexes || (data.index ? [data.index] : []),
|
|
291
|
+
checksum: data.checksum,
|
|
292
|
+
txHash: outPoint.txHash,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// Update backlinks - add the new one to the existing backlinks array
|
|
296
|
+
const backLinks = [...(data.backLinks || []), newBackLink];
|
|
297
|
+
// Define output data based on version
|
|
298
|
+
let outputData;
|
|
299
|
+
if (version === constants_1.ProtocolVersion.V1) {
|
|
300
|
+
// In V1, index is a single number, not an array
|
|
301
|
+
// The first witness index is used (V1 can only reference one witness)
|
|
302
|
+
outputData = molecule_1.CKBFSData.pack({
|
|
303
|
+
index: witnessIndices[0], // Use only the first index as a number
|
|
304
|
+
checksum: contentChecksum,
|
|
305
|
+
contentType: data.contentType,
|
|
306
|
+
filename: data.filename,
|
|
307
|
+
backLinks,
|
|
308
|
+
}, constants_1.ProtocolVersion.V1); // Explicitly use V1 for packing
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
// In V2, indexes is an array of witness indices
|
|
312
|
+
outputData = molecule_1.CKBFSData.pack({
|
|
313
|
+
indexes: witnessIndices,
|
|
314
|
+
checksum: contentChecksum,
|
|
315
|
+
contentType: data.contentType,
|
|
316
|
+
filename: data.filename,
|
|
317
|
+
backLinks,
|
|
318
|
+
}, constants_1.ProtocolVersion.V2); // Explicitly use V2 for packing
|
|
319
|
+
}
|
|
320
|
+
// Calculate the required capacity for the output cell
|
|
321
|
+
// This accounts for:
|
|
322
|
+
// 1. The output data size
|
|
323
|
+
// 2. The type script's occupied size
|
|
324
|
+
// 3. The lock script's occupied size
|
|
325
|
+
// 4. A constant of 8 bytes (for header overhead)
|
|
326
|
+
const ckbfsCellSize = BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) *
|
|
327
|
+
100000000n;
|
|
328
|
+
// Use the maximum value between calculated size and original capacity
|
|
329
|
+
// to ensure we have enough capacity but don't decrease capacity unnecessarily
|
|
330
|
+
const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
|
|
331
|
+
// Create initial transaction with the CKBFS cell input
|
|
332
|
+
let preTx;
|
|
333
|
+
if (from) {
|
|
334
|
+
// If from is not empty, inject/merge the fields
|
|
335
|
+
preTx = core_1.Transaction.from({
|
|
336
|
+
...from,
|
|
337
|
+
inputs: from.inputs.length === 0
|
|
338
|
+
? [
|
|
339
|
+
{
|
|
340
|
+
previousOutput: {
|
|
341
|
+
txHash: outPoint.txHash,
|
|
342
|
+
index: outPoint.index,
|
|
343
|
+
},
|
|
344
|
+
since: "0x0",
|
|
345
|
+
},
|
|
346
|
+
]
|
|
347
|
+
: [
|
|
348
|
+
...from.inputs,
|
|
349
|
+
{
|
|
350
|
+
previousOutput: {
|
|
351
|
+
txHash: outPoint.txHash,
|
|
352
|
+
index: outPoint.index,
|
|
353
|
+
},
|
|
354
|
+
since: "0x0",
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
outputs: from.outputs.length === 0
|
|
358
|
+
? [
|
|
359
|
+
{
|
|
360
|
+
lock,
|
|
361
|
+
type,
|
|
362
|
+
capacity: outputCapacity,
|
|
363
|
+
},
|
|
364
|
+
]
|
|
365
|
+
: [
|
|
366
|
+
...from.outputs,
|
|
367
|
+
{
|
|
368
|
+
lock,
|
|
369
|
+
type,
|
|
370
|
+
capacity: outputCapacity,
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
outputsData: from.outputsData.length === 0
|
|
374
|
+
? [outputData]
|
|
375
|
+
: [
|
|
376
|
+
...from.outputsData,
|
|
377
|
+
outputData,
|
|
378
|
+
],
|
|
379
|
+
witnesses: from.witnesses.length === 0
|
|
380
|
+
? [
|
|
381
|
+
[], // Empty secp witness for signing if not provided
|
|
382
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
383
|
+
]
|
|
384
|
+
: [
|
|
385
|
+
...from.witnesses,
|
|
386
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
387
|
+
],
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
preTx = core_1.Transaction.from({
|
|
392
|
+
inputs: [
|
|
393
|
+
{
|
|
394
|
+
previousOutput: {
|
|
395
|
+
txHash: outPoint.txHash,
|
|
396
|
+
index: outPoint.index,
|
|
397
|
+
},
|
|
398
|
+
since: "0x0",
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
outputs: [
|
|
402
|
+
{
|
|
403
|
+
lock,
|
|
404
|
+
type,
|
|
405
|
+
capacity: outputCapacity,
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
outputsData: [outputData],
|
|
409
|
+
witnesses: [
|
|
410
|
+
[], // Empty secp witness for signing
|
|
411
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
412
|
+
],
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
// Add the CKBFS dep group cell dependency
|
|
416
|
+
preTx.addCellDeps({
|
|
417
|
+
outPoint: {
|
|
418
|
+
txHash: ensureHexPrefix(config.depTxHash),
|
|
419
|
+
index: config.depIndex || 0,
|
|
420
|
+
},
|
|
421
|
+
depType: "depGroup",
|
|
422
|
+
});
|
|
423
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
424
|
+
return { tx: preTx, outputIndex };
|
|
151
425
|
}
|
|
152
426
|
/**
|
|
153
427
|
* Creates a transaction for appending content to a CKBFS file
|
|
@@ -156,7 +430,44 @@ async function createPublishTransaction(signer, options) {
|
|
|
156
430
|
* @returns Promise resolving to the created transaction
|
|
157
431
|
*/
|
|
158
432
|
async function createAppendTransaction(signer, options) {
|
|
159
|
-
const { ckbfsCell,
|
|
433
|
+
const { ckbfsCell, feeRate, } = options;
|
|
434
|
+
const { lock } = ckbfsCell;
|
|
435
|
+
// Use prepareAppendTransaction to create the base transaction
|
|
436
|
+
const { tx: preTx, outputIndex } = await prepareAppendTransaction(options);
|
|
437
|
+
// Get the recommended address to ensure lock script cell deps are included
|
|
438
|
+
const address = await signer.getRecommendedAddressObj();
|
|
439
|
+
const inputsBefore = preTx.inputs.length;
|
|
440
|
+
// If we need more capacity than the original cell had, add additional inputs
|
|
441
|
+
if (preTx.outputs[outputIndex].capacity > ckbfsCell.capacity) {
|
|
442
|
+
console.log(`Need additional capacity: ${preTx.outputs[outputIndex].capacity - ckbfsCell.capacity} shannons`);
|
|
443
|
+
// Add more inputs to cover the increased capacity
|
|
444
|
+
await preTx.completeInputsByCapacity(signer);
|
|
445
|
+
}
|
|
446
|
+
const witnesses = [];
|
|
447
|
+
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
448
|
+
if (address.script.hash() === lock.hash()) {
|
|
449
|
+
witnesses.push("0x");
|
|
450
|
+
}
|
|
451
|
+
// add ckbfs witnesses (skip the first witness which is for signing)
|
|
452
|
+
witnesses.push(...preTx.witnesses.slice(1));
|
|
453
|
+
// Add empty witnesses for additional signer inputs
|
|
454
|
+
// This is to ensure that the transaction is valid and can be signed
|
|
455
|
+
for (let i = inputsBefore; i < preTx.inputs.length; i++) {
|
|
456
|
+
witnesses.push("0x");
|
|
457
|
+
}
|
|
458
|
+
preTx.witnesses = witnesses;
|
|
459
|
+
// Complete fee
|
|
460
|
+
await preTx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
461
|
+
return preTx;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Creates a transaction for appending content to a CKBFS file
|
|
465
|
+
* @param signer The signer to use for the transaction
|
|
466
|
+
* @param options Options for appending content
|
|
467
|
+
* @returns Promise resolving to the created transaction
|
|
468
|
+
*/
|
|
469
|
+
async function createAppendTransactionDry(signer, options) {
|
|
470
|
+
const { ckbfsCell, contentChunks, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, } = options;
|
|
160
471
|
const { outPoint, data, type, lock, capacity } = ckbfsCell;
|
|
161
472
|
// Get CKBFS script config early to use version info
|
|
162
473
|
const config = (0, constants_1.getCKBFSScriptConfig)(network, version);
|
|
@@ -225,11 +536,6 @@ async function createAppendTransaction(signer, options) {
|
|
|
225
536
|
backLinks,
|
|
226
537
|
}, constants_1.ProtocolVersion.V2); // Explicitly use V2 for packing
|
|
227
538
|
}
|
|
228
|
-
// Pack the original data to get its size - use the appropriate version
|
|
229
|
-
const originalData = molecule_1.CKBFSData.pack(data, version);
|
|
230
|
-
const originalDataSize = originalData.length;
|
|
231
|
-
// Get sizes and calculate capacity requirements
|
|
232
|
-
const newDataSize = outputData.length;
|
|
233
539
|
// Calculate the required capacity for the output cell
|
|
234
540
|
// This accounts for:
|
|
235
541
|
// 1. The output data size
|
|
@@ -271,12 +577,14 @@ async function createAppendTransaction(signer, options) {
|
|
|
271
577
|
depType: "depGroup",
|
|
272
578
|
});
|
|
273
579
|
const inputsBefore = tx.inputs.length;
|
|
274
|
-
// If we need more capacity than the original cell had, add additional inputs
|
|
275
|
-
if (outputCapacity > capacity) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
580
|
+
// // If we need more capacity than the original cell had, add additional inputs
|
|
581
|
+
// if (outputCapacity > capacity) {
|
|
582
|
+
// console.log(
|
|
583
|
+
// `Need additional capacity: ${outputCapacity - capacity} shannons`,
|
|
584
|
+
// );
|
|
585
|
+
// // Add more inputs to cover the increased capacity
|
|
586
|
+
// await tx.completeInputsByCapacity(signer);
|
|
587
|
+
// }
|
|
280
588
|
const witnesses = [];
|
|
281
589
|
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
282
590
|
if (address.script.hash() === lock.hash()) {
|
|
@@ -291,7 +599,7 @@ async function createAppendTransaction(signer, options) {
|
|
|
291
599
|
}
|
|
292
600
|
tx.witnesses = witnesses;
|
|
293
601
|
// Complete fee
|
|
294
|
-
await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
602
|
+
//await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
295
603
|
return tx;
|
|
296
604
|
}
|
|
297
605
|
/**
|
package/examples/publish.ts
CHANGED
|
@@ -6,11 +6,11 @@ const privateKey = process.env.CKB_PRIVATE_KEY || 'your-private-key-here';
|
|
|
6
6
|
// Initialize the SDK with network and version options
|
|
7
7
|
const ckbfs = new CKBFS(
|
|
8
8
|
privateKey,
|
|
9
|
-
NetworkType.
|
|
9
|
+
NetworkType.Testnet, // Use testnet
|
|
10
10
|
{
|
|
11
11
|
version: ProtocolVersion.V2, // Use the latest version (V2)
|
|
12
12
|
chunkSize: 30 * 1024, // 30KB chunks
|
|
13
|
-
useTypeID: false // Use code hash instead of type ID
|
|
13
|
+
useTypeID: false, // Use code hash instead of type ID
|
|
14
14
|
}
|
|
15
15
|
);
|
|
16
16
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -15,7 +15,10 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
createCKBFSCell,
|
|
17
17
|
createPublishTransaction as utilCreatePublishTransaction,
|
|
18
|
+
preparePublishTransaction,
|
|
18
19
|
createAppendTransaction as utilCreateAppendTransaction,
|
|
20
|
+
prepareAppendTransaction,
|
|
21
|
+
createAppendTransactionDry,
|
|
19
22
|
publishCKBFS as utilPublishCKBFS,
|
|
20
23
|
appendCKBFS as utilAppendCKBFS,
|
|
21
24
|
CKBFSCellOptions,
|
|
@@ -122,6 +125,7 @@ export interface CKBFSOptions {
|
|
|
122
125
|
version?: ProtocolVersionType;
|
|
123
126
|
useTypeID?: boolean;
|
|
124
127
|
network?: NetworkType;
|
|
128
|
+
rpcUrl?: string;
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
/**
|
|
@@ -133,6 +137,7 @@ export class CKBFS {
|
|
|
133
137
|
private network: NetworkType;
|
|
134
138
|
private version: ProtocolVersionType;
|
|
135
139
|
private useTypeID: boolean;
|
|
140
|
+
private rpcUrl: string;
|
|
136
141
|
|
|
137
142
|
/**
|
|
138
143
|
* Creates a new CKBFS SDK instance
|
|
@@ -159,13 +164,18 @@ export class CKBFS {
|
|
|
159
164
|
|
|
160
165
|
const client =
|
|
161
166
|
network === "mainnet"
|
|
162
|
-
? new ClientPublicMainnet(
|
|
163
|
-
|
|
167
|
+
? new ClientPublicMainnet({
|
|
168
|
+
url: opts.rpcUrl,
|
|
169
|
+
})
|
|
170
|
+
: new ClientPublicTestnet({
|
|
171
|
+
url: opts.rpcUrl,
|
|
172
|
+
});
|
|
164
173
|
this.signer = new SignerCkbPrivateKey(client, privateKey);
|
|
165
174
|
this.network = network;
|
|
166
175
|
this.chunkSize = opts.chunkSize || 30 * 1024;
|
|
167
176
|
this.version = opts.version || DEFAULT_VERSION;
|
|
168
177
|
this.useTypeID = opts.useTypeID || false;
|
|
178
|
+
this.rpcUrl = opts.rpcUrl || client.url;
|
|
169
179
|
} else {
|
|
170
180
|
// Initialize with signer
|
|
171
181
|
this.signer = signerOrPrivateKey;
|
|
@@ -175,6 +185,7 @@ export class CKBFS {
|
|
|
175
185
|
this.chunkSize = opts.chunkSize || 30 * 1024;
|
|
176
186
|
this.version = opts.version || DEFAULT_VERSION;
|
|
177
187
|
this.useTypeID = opts.useTypeID || false;
|
|
188
|
+
this.rpcUrl = opts.rpcUrl || this.signer.client.url;
|
|
178
189
|
}
|
|
179
190
|
}
|
|
180
191
|
|
|
@@ -519,10 +530,12 @@ export {
|
|
|
519
530
|
// Transaction utilities (Exporting original names from transaction.ts)
|
|
520
531
|
createCKBFSCell,
|
|
521
532
|
utilCreatePublishTransaction as createPublishTransaction,
|
|
533
|
+
preparePublishTransaction,
|
|
522
534
|
utilCreateAppendTransaction as createAppendTransaction,
|
|
535
|
+
prepareAppendTransaction,
|
|
523
536
|
utilPublishCKBFS as publishCKBFS,
|
|
524
537
|
utilAppendCKBFS as appendCKBFS,
|
|
525
|
-
|
|
538
|
+
createAppendTransactionDry,
|
|
526
539
|
// File utilities
|
|
527
540
|
readFile,
|
|
528
541
|
readFileAsText,
|
package/src/utils/transaction.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface CKBFSCellOptions {
|
|
|
34
34
|
export interface PublishOptions extends CKBFSCellOptions {
|
|
35
35
|
contentChunks: Uint8Array[];
|
|
36
36
|
feeRate?: number;
|
|
37
|
+
from?: Transaction;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -51,6 +52,7 @@ export interface AppendOptions {
|
|
|
51
52
|
feeRate?: number;
|
|
52
53
|
network?: NetworkType;
|
|
53
54
|
version?: ProtocolVersionType;
|
|
55
|
+
from?: Transaction;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
@@ -100,29 +102,27 @@ export function createCKBFSCell(options: CKBFSCellOptions) {
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
+
* Prepares a transaction for publishing a file to CKBFS without fee and change handling
|
|
106
|
+
* You will need to manually set the typeID if you did not provide inputs, or just check is return value emptyTypeID is true
|
|
105
107
|
* @param options Options for publishing the file
|
|
106
|
-
* @returns Promise resolving to the
|
|
108
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
107
109
|
*/
|
|
108
|
-
export async function
|
|
109
|
-
signer: Signer,
|
|
110
|
+
export async function preparePublishTransaction(
|
|
110
111
|
options: PublishOptions,
|
|
111
|
-
): Promise<Transaction> {
|
|
112
|
+
): Promise<{tx: Transaction, outputIndex: number, emptyTypeID: boolean}> { // if emptyTypeID is true, you shall manually set the typeID after
|
|
112
113
|
const {
|
|
114
|
+
from,
|
|
113
115
|
contentChunks,
|
|
114
116
|
contentType,
|
|
115
117
|
filename,
|
|
116
118
|
lock,
|
|
117
119
|
capacity,
|
|
118
|
-
feeRate,
|
|
119
120
|
network = DEFAULT_NETWORK,
|
|
120
121
|
version = DEFAULT_VERSION,
|
|
121
122
|
useTypeID = false,
|
|
122
123
|
} = options;
|
|
123
124
|
|
|
124
125
|
// Calculate checksum for the combined content
|
|
125
|
-
const textEncoder = new TextEncoder();
|
|
126
126
|
const combinedContent = Buffer.concat(contentChunks);
|
|
127
127
|
const checksum = await calculateChecksum(combinedContent);
|
|
128
128
|
|
|
@@ -132,9 +132,8 @@ export async function createPublishTransaction(
|
|
|
132
132
|
const ckbfsWitnesses = createChunkedCKBFSWitnesses(contentChunks);
|
|
133
133
|
|
|
134
134
|
// Calculate the actual witness indices where our content is placed
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const contentStartIndex = 1;
|
|
135
|
+
|
|
136
|
+
const contentStartIndex = from?.witnesses.length || 1;
|
|
138
137
|
const witnessIndices = Array.from(
|
|
139
138
|
{ length: contentChunks.length },
|
|
140
139
|
(_, i) => contentStartIndex + i,
|
|
@@ -187,24 +186,71 @@ export async function createPublishTransaction(
|
|
|
187
186
|
8,
|
|
188
187
|
) * 100000000n;
|
|
189
188
|
// Create pre transaction without cell deps initially
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
189
|
+
let preTx: Transaction;
|
|
190
|
+
if(from) {
|
|
191
|
+
// If from is not empty, inject/merge the fields
|
|
192
|
+
preTx = Transaction.from({
|
|
193
|
+
...from,
|
|
194
|
+
outputs: from.outputs.length === 0
|
|
195
|
+
? [
|
|
196
|
+
createCKBFSCell({
|
|
197
|
+
contentType,
|
|
198
|
+
filename,
|
|
199
|
+
lock,
|
|
200
|
+
network,
|
|
201
|
+
version,
|
|
202
|
+
useTypeID,
|
|
203
|
+
capacity: ckbfsCellSize || capacity,
|
|
204
|
+
}),
|
|
205
|
+
]
|
|
206
|
+
: [
|
|
207
|
+
...from.outputs,
|
|
208
|
+
createCKBFSCell({
|
|
209
|
+
contentType,
|
|
210
|
+
filename,
|
|
211
|
+
lock,
|
|
212
|
+
network,
|
|
213
|
+
version,
|
|
214
|
+
useTypeID,
|
|
215
|
+
capacity: ckbfsCellSize || capacity,
|
|
216
|
+
}),
|
|
217
|
+
],
|
|
218
|
+
witnesses: from.witnesses.length === 0
|
|
219
|
+
? [
|
|
220
|
+
[], // Empty secp witness for signing if not provided
|
|
221
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
222
|
+
]
|
|
223
|
+
: [
|
|
224
|
+
...from.witnesses,
|
|
225
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
226
|
+
],
|
|
227
|
+
outputsData: from.outputsData.length === 0
|
|
228
|
+
? [outputData]
|
|
229
|
+
: [
|
|
230
|
+
...from.outputsData,
|
|
231
|
+
outputData,
|
|
232
|
+
],
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
preTx = Transaction.from({
|
|
236
|
+
outputs: [
|
|
237
|
+
createCKBFSCell({
|
|
238
|
+
contentType,
|
|
239
|
+
filename,
|
|
240
|
+
lock,
|
|
241
|
+
network,
|
|
242
|
+
version,
|
|
243
|
+
useTypeID,
|
|
244
|
+
capacity: ckbfsCellSize || capacity,
|
|
245
|
+
}),
|
|
246
|
+
],
|
|
247
|
+
witnesses: [
|
|
248
|
+
[], // Empty secp witness for signing
|
|
249
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
250
|
+
],
|
|
251
|
+
outputsData: [outputData],
|
|
252
|
+
});
|
|
253
|
+
}
|
|
208
254
|
|
|
209
255
|
// Add the CKBFS dep group cell dependency
|
|
210
256
|
preTx.addCellDeps({
|
|
@@ -215,17 +261,9 @@ export async function createPublishTransaction(
|
|
|
215
261
|
depType: "depGroup",
|
|
216
262
|
});
|
|
217
263
|
|
|
218
|
-
// Get the recommended address to ensure lock script cell deps are included
|
|
219
|
-
const address = await signer.getRecommendedAddressObj();
|
|
220
|
-
|
|
221
|
-
// Complete inputs by capacity
|
|
222
|
-
await preTx.completeInputsByCapacity(signer);
|
|
223
|
-
|
|
224
|
-
// Complete fee change to lock
|
|
225
|
-
await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
|
|
226
|
-
|
|
227
264
|
// Create type ID args
|
|
228
|
-
const
|
|
265
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
266
|
+
const args = preTx.inputs.length > 0 ? ccc.hashTypeId(preTx.inputs[0], outputIndex) : "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
229
267
|
|
|
230
268
|
// Create CKBFS type script with type ID
|
|
231
269
|
const ckbfsTypeScript = new Script(
|
|
@@ -237,23 +275,308 @@ export async function createPublishTransaction(
|
|
|
237
275
|
// Create final transaction with same cell deps as preTx
|
|
238
276
|
const tx = Transaction.from({
|
|
239
277
|
cellDeps: preTx.cellDeps,
|
|
240
|
-
witnesses:
|
|
241
|
-
[], // Reset first witness for signing
|
|
242
|
-
...preTx.witnesses.slice(1),
|
|
243
|
-
],
|
|
278
|
+
witnesses: preTx.witnesses,
|
|
244
279
|
outputsData: preTx.outputsData,
|
|
245
280
|
inputs: preTx.inputs,
|
|
246
|
-
outputs:
|
|
281
|
+
outputs: outputIndex === 0
|
|
282
|
+
? [
|
|
283
|
+
{
|
|
284
|
+
lock,
|
|
285
|
+
type: ckbfsTypeScript,
|
|
286
|
+
capacity: preTx.outputs[outputIndex].capacity,
|
|
287
|
+
},
|
|
288
|
+
]
|
|
289
|
+
: [
|
|
290
|
+
...preTx.outputs.slice(0, outputIndex), // Include rest of outputs (e.g., change)
|
|
291
|
+
{
|
|
292
|
+
lock,
|
|
293
|
+
type: ckbfsTypeScript,
|
|
294
|
+
capacity: preTx.outputs[outputIndex].capacity,
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
return {tx, outputIndex, emptyTypeID: args === "0x0000000000000000000000000000000000000000000000000000000000000000"};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Creates a transaction for publishing a file to CKBFS
|
|
304
|
+
* @param signer The signer to use for the transaction
|
|
305
|
+
* @param options Options for publishing the file
|
|
306
|
+
* @returns Promise resolving to the created transaction
|
|
307
|
+
*/
|
|
308
|
+
export async function createPublishTransaction(
|
|
309
|
+
signer: Signer,
|
|
310
|
+
options: PublishOptions,
|
|
311
|
+
): Promise<Transaction> {
|
|
312
|
+
const {
|
|
313
|
+
feeRate,
|
|
314
|
+
lock,
|
|
315
|
+
} = options;
|
|
316
|
+
|
|
317
|
+
// Use preparePublishTransaction to create the base transaction
|
|
318
|
+
const { tx: preTx, outputIndex, emptyTypeID } = await preparePublishTransaction(options);
|
|
319
|
+
|
|
320
|
+
// Complete inputs by capacity
|
|
321
|
+
await preTx.completeInputsByCapacity(signer);
|
|
322
|
+
|
|
323
|
+
// Complete fee change to lock
|
|
324
|
+
await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
|
|
325
|
+
|
|
326
|
+
// If emptyTypeID is true, we need to create the proper type ID args
|
|
327
|
+
if (emptyTypeID) {
|
|
328
|
+
// Get CKBFS script config
|
|
329
|
+
const config = getCKBFSScriptConfig(options.network || DEFAULT_NETWORK, options.version || DEFAULT_VERSION, options.useTypeID || false);
|
|
330
|
+
|
|
331
|
+
// Create type ID args
|
|
332
|
+
const args = ccc.hashTypeId(preTx.inputs[0], outputIndex);
|
|
333
|
+
|
|
334
|
+
// Create CKBFS type script with type ID
|
|
335
|
+
const ckbfsTypeScript = new Script(
|
|
336
|
+
ensureHexPrefix(config.codeHash),
|
|
337
|
+
config.hashType as any,
|
|
338
|
+
args,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// Create final transaction with updated type script
|
|
342
|
+
const tx = Transaction.from({
|
|
343
|
+
cellDeps: preTx.cellDeps,
|
|
344
|
+
witnesses: preTx.witnesses,
|
|
345
|
+
outputsData: preTx.outputsData,
|
|
346
|
+
inputs: preTx.inputs,
|
|
347
|
+
outputs: preTx.outputs.map((output, index) =>
|
|
348
|
+
index === outputIndex
|
|
349
|
+
? {
|
|
350
|
+
lock,
|
|
351
|
+
type: ckbfsTypeScript,
|
|
352
|
+
capacity: output.capacity,
|
|
353
|
+
}
|
|
354
|
+
: output
|
|
355
|
+
),
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return tx;
|
|
359
|
+
} else {
|
|
360
|
+
// If typeID was already set properly, just reset the first witness for signing
|
|
361
|
+
const tx = Transaction.from({
|
|
362
|
+
cellDeps: preTx.cellDeps,
|
|
363
|
+
witnesses: preTx.witnesses,
|
|
364
|
+
outputsData: preTx.outputsData,
|
|
365
|
+
inputs: preTx.inputs,
|
|
366
|
+
outputs: preTx.outputs,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
return tx;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Prepares a transaction for appending content to a CKBFS file without fee and change handling
|
|
375
|
+
* @param options Options for appending content
|
|
376
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
377
|
+
*/
|
|
378
|
+
export async function prepareAppendTransaction(
|
|
379
|
+
options: AppendOptions,
|
|
380
|
+
): Promise<{tx: Transaction, outputIndex: number}> {
|
|
381
|
+
const {
|
|
382
|
+
from,
|
|
383
|
+
ckbfsCell,
|
|
384
|
+
contentChunks,
|
|
385
|
+
network = DEFAULT_NETWORK,
|
|
386
|
+
version = DEFAULT_VERSION,
|
|
387
|
+
} = options;
|
|
388
|
+
const { outPoint, data, type, lock, capacity } = ckbfsCell;
|
|
389
|
+
|
|
390
|
+
// Get CKBFS script config early to use version info
|
|
391
|
+
const config = getCKBFSScriptConfig(network, version);
|
|
392
|
+
|
|
393
|
+
// Create CKBFS witnesses - each chunk already includes the CKBFS header
|
|
394
|
+
// Pass 0 as version byte - this is the protocol version byte in the witness header
|
|
395
|
+
// not to be confused with the Protocol Version (V1 vs V2)
|
|
396
|
+
const ckbfsWitnesses = createChunkedCKBFSWitnesses(contentChunks);
|
|
397
|
+
|
|
398
|
+
// Combine the new content chunks for checksum calculation
|
|
399
|
+
const combinedContent = Buffer.concat(contentChunks);
|
|
400
|
+
|
|
401
|
+
// Update the existing checksum with the new content - this matches Adler32's
|
|
402
|
+
// cumulative nature as required by Rule 11 in the RFC
|
|
403
|
+
const contentChecksum = await updateChecksum(data.checksum, combinedContent);
|
|
404
|
+
|
|
405
|
+
// Calculate the actual witness indices where our content is placed
|
|
406
|
+
const contentStartIndex = from?.witnesses.length || 1;
|
|
407
|
+
const witnessIndices = Array.from(
|
|
408
|
+
{ length: contentChunks.length },
|
|
409
|
+
(_, i) => contentStartIndex + i,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
// Create backlink for the current state based on version
|
|
413
|
+
let newBackLink: any;
|
|
414
|
+
|
|
415
|
+
if (version === ProtocolVersion.V1) {
|
|
416
|
+
// V1 format: Use index field (single number)
|
|
417
|
+
newBackLink = {
|
|
418
|
+
// In V1, field order is index, checksum, txHash
|
|
419
|
+
// and index is a single number value, not an array
|
|
420
|
+
index:
|
|
421
|
+
data.index ||
|
|
422
|
+
(data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
|
|
423
|
+
checksum: data.checksum,
|
|
424
|
+
txHash: outPoint.txHash,
|
|
425
|
+
};
|
|
426
|
+
} else {
|
|
427
|
+
// V2 format: Use indexes field (array of numbers)
|
|
428
|
+
newBackLink = {
|
|
429
|
+
// In V2, field order is indexes, checksum, txHash
|
|
430
|
+
// and indexes is an array of numbers
|
|
431
|
+
indexes: data.indexes || (data.index ? [data.index] : []),
|
|
432
|
+
checksum: data.checksum,
|
|
433
|
+
txHash: outPoint.txHash,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Update backlinks - add the new one to the existing backlinks array
|
|
438
|
+
const backLinks = [...(data.backLinks || []), newBackLink];
|
|
439
|
+
|
|
440
|
+
// Define output data based on version
|
|
441
|
+
let outputData: Uint8Array;
|
|
442
|
+
|
|
443
|
+
if (version === ProtocolVersion.V1) {
|
|
444
|
+
// In V1, index is a single number, not an array
|
|
445
|
+
// The first witness index is used (V1 can only reference one witness)
|
|
446
|
+
outputData = CKBFSData.pack(
|
|
247
447
|
{
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
448
|
+
index: witnessIndices[0], // Use only the first index as a number
|
|
449
|
+
checksum: contentChecksum,
|
|
450
|
+
contentType: data.contentType,
|
|
451
|
+
filename: data.filename,
|
|
452
|
+
backLinks,
|
|
251
453
|
},
|
|
252
|
-
|
|
253
|
-
|
|
454
|
+
ProtocolVersion.V1,
|
|
455
|
+
); // Explicitly use V1 for packing
|
|
456
|
+
} else {
|
|
457
|
+
// In V2, indexes is an array of witness indices
|
|
458
|
+
outputData = CKBFSData.pack(
|
|
459
|
+
{
|
|
460
|
+
indexes: witnessIndices,
|
|
461
|
+
checksum: contentChecksum,
|
|
462
|
+
contentType: data.contentType,
|
|
463
|
+
filename: data.filename,
|
|
464
|
+
backLinks,
|
|
465
|
+
},
|
|
466
|
+
ProtocolVersion.V2,
|
|
467
|
+
); // Explicitly use V2 for packing
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Calculate the required capacity for the output cell
|
|
471
|
+
// This accounts for:
|
|
472
|
+
// 1. The output data size
|
|
473
|
+
// 2. The type script's occupied size
|
|
474
|
+
// 3. The lock script's occupied size
|
|
475
|
+
// 4. A constant of 8 bytes (for header overhead)
|
|
476
|
+
const ckbfsCellSize =
|
|
477
|
+
BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) *
|
|
478
|
+
100000000n;
|
|
479
|
+
|
|
480
|
+
// Use the maximum value between calculated size and original capacity
|
|
481
|
+
// to ensure we have enough capacity but don't decrease capacity unnecessarily
|
|
482
|
+
const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
|
|
483
|
+
|
|
484
|
+
// Create initial transaction with the CKBFS cell input
|
|
485
|
+
let preTx: Transaction;
|
|
486
|
+
if (from) {
|
|
487
|
+
// If from is not empty, inject/merge the fields
|
|
488
|
+
preTx = Transaction.from({
|
|
489
|
+
...from,
|
|
490
|
+
inputs: from.inputs.length === 0
|
|
491
|
+
? [
|
|
492
|
+
{
|
|
493
|
+
previousOutput: {
|
|
494
|
+
txHash: outPoint.txHash,
|
|
495
|
+
index: outPoint.index,
|
|
496
|
+
},
|
|
497
|
+
since: "0x0",
|
|
498
|
+
},
|
|
499
|
+
]
|
|
500
|
+
: [
|
|
501
|
+
...from.inputs,
|
|
502
|
+
{
|
|
503
|
+
previousOutput: {
|
|
504
|
+
txHash: outPoint.txHash,
|
|
505
|
+
index: outPoint.index,
|
|
506
|
+
},
|
|
507
|
+
since: "0x0",
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
outputs: from.outputs.length === 0
|
|
511
|
+
? [
|
|
512
|
+
{
|
|
513
|
+
lock,
|
|
514
|
+
type,
|
|
515
|
+
capacity: outputCapacity,
|
|
516
|
+
},
|
|
517
|
+
]
|
|
518
|
+
: [
|
|
519
|
+
...from.outputs,
|
|
520
|
+
{
|
|
521
|
+
lock,
|
|
522
|
+
type,
|
|
523
|
+
capacity: outputCapacity,
|
|
524
|
+
},
|
|
525
|
+
],
|
|
526
|
+
outputsData: from.outputsData.length === 0
|
|
527
|
+
? [outputData]
|
|
528
|
+
: [
|
|
529
|
+
...from.outputsData,
|
|
530
|
+
outputData,
|
|
531
|
+
],
|
|
532
|
+
witnesses: from.witnesses.length === 0
|
|
533
|
+
? [
|
|
534
|
+
[], // Empty secp witness for signing if not provided
|
|
535
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
536
|
+
]
|
|
537
|
+
: [
|
|
538
|
+
...from.witnesses,
|
|
539
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
540
|
+
],
|
|
541
|
+
});
|
|
542
|
+
} else {
|
|
543
|
+
preTx = Transaction.from({
|
|
544
|
+
inputs: [
|
|
545
|
+
{
|
|
546
|
+
previousOutput: {
|
|
547
|
+
txHash: outPoint.txHash,
|
|
548
|
+
index: outPoint.index,
|
|
549
|
+
},
|
|
550
|
+
since: "0x0",
|
|
551
|
+
},
|
|
552
|
+
],
|
|
553
|
+
outputs: [
|
|
554
|
+
{
|
|
555
|
+
lock,
|
|
556
|
+
type,
|
|
557
|
+
capacity: outputCapacity,
|
|
558
|
+
},
|
|
559
|
+
],
|
|
560
|
+
outputsData: [outputData],
|
|
561
|
+
witnesses: [
|
|
562
|
+
[], // Empty secp witness for signing
|
|
563
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
564
|
+
],
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Add the CKBFS dep group cell dependency
|
|
569
|
+
preTx.addCellDeps({
|
|
570
|
+
outPoint: {
|
|
571
|
+
txHash: ensureHexPrefix(config.depTxHash),
|
|
572
|
+
index: config.depIndex || 0,
|
|
573
|
+
},
|
|
574
|
+
depType: "depGroup",
|
|
254
575
|
});
|
|
255
576
|
|
|
256
|
-
|
|
577
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
578
|
+
|
|
579
|
+
return {tx: preTx, outputIndex};
|
|
257
580
|
}
|
|
258
581
|
|
|
259
582
|
/**
|
|
@@ -268,8 +591,60 @@ export async function createAppendTransaction(
|
|
|
268
591
|
): Promise<Transaction> {
|
|
269
592
|
const {
|
|
270
593
|
ckbfsCell,
|
|
271
|
-
contentChunks,
|
|
272
594
|
feeRate,
|
|
595
|
+
} = options;
|
|
596
|
+
const { lock } = ckbfsCell;
|
|
597
|
+
|
|
598
|
+
// Use prepareAppendTransaction to create the base transaction
|
|
599
|
+
const { tx: preTx, outputIndex } = await prepareAppendTransaction(options);
|
|
600
|
+
|
|
601
|
+
// Get the recommended address to ensure lock script cell deps are included
|
|
602
|
+
const address = await signer.getRecommendedAddressObj();
|
|
603
|
+
|
|
604
|
+
const inputsBefore = preTx.inputs.length;
|
|
605
|
+
// If we need more capacity than the original cell had, add additional inputs
|
|
606
|
+
if (preTx.outputs[outputIndex].capacity > ckbfsCell.capacity) {
|
|
607
|
+
console.log(
|
|
608
|
+
`Need additional capacity: ${preTx.outputs[outputIndex].capacity - ckbfsCell.capacity} shannons`,
|
|
609
|
+
);
|
|
610
|
+
// Add more inputs to cover the increased capacity
|
|
611
|
+
await preTx.completeInputsByCapacity(signer);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const witnesses: any = [];
|
|
615
|
+
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
616
|
+
if (address.script.hash() === lock.hash()) {
|
|
617
|
+
witnesses.push("0x");
|
|
618
|
+
}
|
|
619
|
+
// add ckbfs witnesses (skip the first witness which is for signing)
|
|
620
|
+
witnesses.push(...preTx.witnesses.slice(1));
|
|
621
|
+
|
|
622
|
+
// Add empty witnesses for additional signer inputs
|
|
623
|
+
// This is to ensure that the transaction is valid and can be signed
|
|
624
|
+
for (let i = inputsBefore; i < preTx.inputs.length; i++) {
|
|
625
|
+
witnesses.push("0x");
|
|
626
|
+
}
|
|
627
|
+
preTx.witnesses = witnesses;
|
|
628
|
+
|
|
629
|
+
// Complete fee
|
|
630
|
+
await preTx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
631
|
+
|
|
632
|
+
return preTx;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Creates a transaction for appending content to a CKBFS file
|
|
637
|
+
* @param signer The signer to use for the transaction
|
|
638
|
+
* @param options Options for appending content
|
|
639
|
+
* @returns Promise resolving to the created transaction
|
|
640
|
+
*/
|
|
641
|
+
export async function createAppendTransactionDry(
|
|
642
|
+
signer: Signer,
|
|
643
|
+
options: AppendOptions,
|
|
644
|
+
): Promise<Transaction> {
|
|
645
|
+
const {
|
|
646
|
+
ckbfsCell,
|
|
647
|
+
contentChunks,
|
|
273
648
|
network = DEFAULT_NETWORK,
|
|
274
649
|
version = DEFAULT_VERSION,
|
|
275
650
|
} = options;
|
|
@@ -363,12 +738,6 @@ export async function createAppendTransaction(
|
|
|
363
738
|
); // Explicitly use V2 for packing
|
|
364
739
|
}
|
|
365
740
|
|
|
366
|
-
// Pack the original data to get its size - use the appropriate version
|
|
367
|
-
const originalData = CKBFSData.pack(data, version);
|
|
368
|
-
const originalDataSize = originalData.length;
|
|
369
|
-
|
|
370
|
-
// Get sizes and calculate capacity requirements
|
|
371
|
-
const newDataSize = outputData.length;
|
|
372
741
|
|
|
373
742
|
// Calculate the required capacity for the output cell
|
|
374
743
|
// This accounts for:
|
|
@@ -419,14 +788,14 @@ export async function createAppendTransaction(
|
|
|
419
788
|
});
|
|
420
789
|
|
|
421
790
|
const inputsBefore = tx.inputs.length;
|
|
422
|
-
// If we need more capacity than the original cell had, add additional inputs
|
|
423
|
-
if (outputCapacity > capacity) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
791
|
+
// // If we need more capacity than the original cell had, add additional inputs
|
|
792
|
+
// if (outputCapacity > capacity) {
|
|
793
|
+
// console.log(
|
|
794
|
+
// `Need additional capacity: ${outputCapacity - capacity} shannons`,
|
|
795
|
+
// );
|
|
796
|
+
// // Add more inputs to cover the increased capacity
|
|
797
|
+
// await tx.completeInputsByCapacity(signer);
|
|
798
|
+
// }
|
|
430
799
|
|
|
431
800
|
const witnesses: any = [];
|
|
432
801
|
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
@@ -446,7 +815,7 @@ export async function createAppendTransaction(
|
|
|
446
815
|
tx.witnesses = witnesses;
|
|
447
816
|
|
|
448
817
|
// Complete fee
|
|
449
|
-
await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
818
|
+
//await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
450
819
|
|
|
451
820
|
return tx;
|
|
452
821
|
}
|