@ckbfs/api 1.3.0 → 1.5.0
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 +569 -0
- package/code.png +0 -0
- package/demo-output.txt +1 -0
- package/direct_direct_content_example.txt +1 -0
- package/dist/index.d.ts +17 -15
- package/dist/index.js +47 -22
- package/dist/utils/constants.d.ts +7 -3
- package/dist/utils/constants.js +41 -34
- package/dist/utils/file.d.ts +179 -0
- package/dist/utils/file.js +599 -31
- package/dist/utils/molecule.d.ts +3 -2
- package/dist/utils/molecule.js +22 -24
- package/dist/utils/transaction.d.ts +23 -4
- package/dist/utils/transaction.js +318 -66
- package/examples/append.ts +2 -39
- package/examples/example.txt +1 -0
- package/examples/identifier-test.ts +178 -0
- package/examples/index.ts +36 -24
- package/examples/publish.ts +5 -5
- package/examples/retrieve.ts +580 -0
- package/examples/witness-decode-demo.ts +190 -0
- package/identifier_direct_content_example.txt +1 -0
- package/package-lock.json +4978 -0
- package/package.json +3 -2
- package/src/index.ts +181 -99
- package/src/utils/constants.ts +77 -43
- package/src/utils/file.ts +864 -59
- package/src/utils/molecule.ts +41 -36
- package/src/utils/transaction.ts +585 -190
- package/traditional_direct_content_example.txt +1 -0
- package/typeid_direct_content_example.txt +1 -0
- package/example.txt +0 -1
- package/src/utils/createPublishTransaction +0 -24
package/src/utils/transaction.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { ccc, Transaction, Script, Signer } from "@ckb-ccc/core";
|
|
2
|
-
import { calculateChecksum, updateChecksum } from
|
|
3
|
-
import { CKBFSData, BackLinkType, CKBFSDataType } from
|
|
4
|
-
import { createChunkedCKBFSWitnesses } from
|
|
5
|
-
import {
|
|
6
|
-
getCKBFSScriptConfig,
|
|
7
|
-
NetworkType,
|
|
8
|
-
ProtocolVersion,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
import { calculateChecksum, updateChecksum } from "./checksum";
|
|
3
|
+
import { CKBFSData, BackLinkType, CKBFSDataType } from "./molecule";
|
|
4
|
+
import { createChunkedCKBFSWitnesses } from "./witness";
|
|
5
|
+
import {
|
|
6
|
+
getCKBFSScriptConfig,
|
|
7
|
+
NetworkType,
|
|
8
|
+
ProtocolVersion,
|
|
9
|
+
ProtocolVersionType,
|
|
10
|
+
DEFAULT_NETWORK,
|
|
11
|
+
DEFAULT_VERSION,
|
|
12
|
+
} from "./constants";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Utility functions for CKB transaction creation and handling
|
|
@@ -23,7 +24,7 @@ export interface CKBFSCellOptions {
|
|
|
23
24
|
capacity?: bigint;
|
|
24
25
|
lock: Script;
|
|
25
26
|
network?: NetworkType;
|
|
26
|
-
version?:
|
|
27
|
+
version?: ProtocolVersionType;
|
|
27
28
|
useTypeID?: boolean;
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -33,6 +34,7 @@ export interface CKBFSCellOptions {
|
|
|
33
34
|
export interface PublishOptions extends CKBFSCellOptions {
|
|
34
35
|
contentChunks: Uint8Array[];
|
|
35
36
|
feeRate?: number;
|
|
37
|
+
from?: Transaction;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -49,7 +51,8 @@ export interface AppendOptions {
|
|
|
49
51
|
contentChunks: Uint8Array[];
|
|
50
52
|
feeRate?: number;
|
|
51
53
|
network?: NetworkType;
|
|
52
|
-
version?:
|
|
54
|
+
version?: ProtocolVersionType;
|
|
55
|
+
from?: Transaction;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
/**
|
|
@@ -58,7 +61,7 @@ export interface AppendOptions {
|
|
|
58
61
|
* @returns A hex prefixed string
|
|
59
62
|
*/
|
|
60
63
|
export function ensureHexPrefix(value: string): `0x${string}` {
|
|
61
|
-
if (value.startsWith(
|
|
64
|
+
if (value.startsWith("0x")) {
|
|
62
65
|
return value as `0x${string}`;
|
|
63
66
|
}
|
|
64
67
|
return `0x${value}` as `0x${string}`;
|
|
@@ -70,26 +73,26 @@ export function ensureHexPrefix(value: string): `0x${string}` {
|
|
|
70
73
|
* @returns The created cell output
|
|
71
74
|
*/
|
|
72
75
|
export function createCKBFSCell(options: CKBFSCellOptions) {
|
|
73
|
-
const {
|
|
74
|
-
contentType,
|
|
75
|
-
filename,
|
|
76
|
-
capacity,
|
|
77
|
-
lock,
|
|
78
|
-
network = DEFAULT_NETWORK,
|
|
76
|
+
const {
|
|
77
|
+
contentType,
|
|
78
|
+
filename,
|
|
79
|
+
capacity,
|
|
80
|
+
lock,
|
|
81
|
+
network = DEFAULT_NETWORK,
|
|
79
82
|
version = DEFAULT_VERSION,
|
|
80
|
-
useTypeID = false
|
|
83
|
+
useTypeID = false,
|
|
81
84
|
} = options;
|
|
82
|
-
|
|
85
|
+
|
|
83
86
|
// Get CKBFS script config
|
|
84
87
|
const config = getCKBFSScriptConfig(network, version, useTypeID);
|
|
85
|
-
|
|
88
|
+
|
|
86
89
|
// Create pre CKBFS type script
|
|
87
90
|
const preCkbfsTypeScript = new Script(
|
|
88
91
|
ensureHexPrefix(config.codeHash),
|
|
89
92
|
config.hashType as any,
|
|
90
|
-
"0x0000000000000000000000000000000000000000000000000000000000000000"
|
|
93
|
+
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
91
94
|
);
|
|
92
|
-
|
|
95
|
+
|
|
93
96
|
// Return the cell output
|
|
94
97
|
return {
|
|
95
98
|
lock,
|
|
@@ -99,150 +102,481 @@ export function createCKBFSCell(options: CKBFSCellOptions) {
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
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
|
|
104
107
|
* @param options Options for publishing the file
|
|
105
|
-
* @returns Promise resolving to the
|
|
108
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
106
109
|
*/
|
|
107
|
-
export async function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
contentChunks,
|
|
113
|
-
contentType,
|
|
114
|
-
filename,
|
|
115
|
-
lock,
|
|
110
|
+
export async function preparePublishTransaction(
|
|
111
|
+
options: PublishOptions,
|
|
112
|
+
): Promise<{tx: Transaction, outputIndex: number, emptyTypeID: boolean}> { // if emptyTypeID is true, you shall manually set the typeID after
|
|
113
|
+
const {
|
|
114
|
+
from,
|
|
115
|
+
contentChunks,
|
|
116
|
+
contentType,
|
|
117
|
+
filename,
|
|
118
|
+
lock,
|
|
116
119
|
capacity,
|
|
117
|
-
feeRate,
|
|
118
120
|
network = DEFAULT_NETWORK,
|
|
119
121
|
version = DEFAULT_VERSION,
|
|
120
|
-
useTypeID = false
|
|
122
|
+
useTypeID = false,
|
|
121
123
|
} = options;
|
|
122
|
-
|
|
124
|
+
|
|
123
125
|
// Calculate checksum for the combined content
|
|
124
|
-
const textEncoder = new TextEncoder();
|
|
125
126
|
const combinedContent = Buffer.concat(contentChunks);
|
|
126
127
|
const checksum = await calculateChecksum(combinedContent);
|
|
127
|
-
|
|
128
|
+
|
|
128
129
|
// Create CKBFS witnesses - each chunk already includes the CKBFS header
|
|
129
130
|
// Pass 0 as version byte - this is the protocol version byte in the witness header
|
|
130
131
|
// not to be confused with the Protocol Version (V1 vs V2)
|
|
131
132
|
const ckbfsWitnesses = createChunkedCKBFSWitnesses(contentChunks);
|
|
132
|
-
|
|
133
|
+
|
|
133
134
|
// Calculate the actual witness indices where our content is placed
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const contentStartIndex = 1;
|
|
135
|
+
|
|
136
|
+
const contentStartIndex = from?.witnesses.length || 1;
|
|
137
137
|
const witnessIndices = Array.from(
|
|
138
|
-
{ length: contentChunks.length },
|
|
139
|
-
(_, i) => contentStartIndex + i
|
|
138
|
+
{ length: contentChunks.length },
|
|
139
|
+
(_, i) => contentStartIndex + i,
|
|
140
140
|
);
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
// Create CKBFS cell output data based on version
|
|
143
143
|
let outputData: Uint8Array;
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
if (version === ProtocolVersion.V1) {
|
|
146
146
|
// V1 format: Single index field (a single number, not an array)
|
|
147
147
|
// For V1, use the first index where content is placed
|
|
148
|
-
outputData = CKBFSData.pack(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
outputData = CKBFSData.pack(
|
|
149
|
+
{
|
|
150
|
+
index: contentStartIndex,
|
|
151
|
+
checksum,
|
|
152
|
+
contentType: contentType,
|
|
153
|
+
filename: filename,
|
|
154
|
+
backLinks: [],
|
|
155
|
+
},
|
|
156
|
+
version,
|
|
157
|
+
);
|
|
155
158
|
} else {
|
|
156
159
|
// V2 format: Multiple indexes (array of numbers)
|
|
157
160
|
// For V2, use all the indices where content is placed
|
|
158
|
-
outputData = CKBFSData.pack(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
outputData = CKBFSData.pack(
|
|
162
|
+
{
|
|
163
|
+
indexes: witnessIndices,
|
|
164
|
+
checksum,
|
|
165
|
+
contentType,
|
|
166
|
+
filename,
|
|
167
|
+
backLinks: [],
|
|
168
|
+
},
|
|
169
|
+
version,
|
|
170
|
+
);
|
|
165
171
|
}
|
|
166
|
-
|
|
172
|
+
|
|
167
173
|
// Get CKBFS script config
|
|
168
174
|
const config = getCKBFSScriptConfig(network, version, useTypeID);
|
|
169
|
-
|
|
175
|
+
|
|
170
176
|
const preCkbfsTypeScript = new Script(
|
|
171
177
|
ensureHexPrefix(config.codeHash),
|
|
172
178
|
config.hashType as any,
|
|
173
|
-
"0x0000000000000000000000000000000000000000000000000000000000000000"
|
|
179
|
+
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
174
180
|
);
|
|
175
|
-
const ckbfsCellSize =
|
|
181
|
+
const ckbfsCellSize =
|
|
182
|
+
BigInt(
|
|
183
|
+
outputData.length +
|
|
184
|
+
preCkbfsTypeScript.occupiedSize +
|
|
185
|
+
lock.occupiedSize +
|
|
186
|
+
8,
|
|
187
|
+
) * 100000000n;
|
|
176
188
|
// Create pre transaction without cell deps initially
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
+
}
|
|
254
|
+
|
|
198
255
|
// Add the CKBFS dep group cell dependency
|
|
199
256
|
preTx.addCellDeps({
|
|
200
257
|
outPoint: {
|
|
201
258
|
txHash: ensureHexPrefix(config.depTxHash),
|
|
202
259
|
index: config.depIndex || 0,
|
|
203
260
|
},
|
|
204
|
-
depType: "depGroup"
|
|
261
|
+
depType: "depGroup",
|
|
205
262
|
});
|
|
206
|
-
|
|
207
|
-
// Get the recommended address to ensure lock script cell deps are included
|
|
208
|
-
const address = await signer.getRecommendedAddressObj();
|
|
209
|
-
|
|
210
|
-
// Complete inputs by capacity
|
|
211
|
-
await preTx.completeInputsByCapacity(signer);
|
|
212
|
-
|
|
213
|
-
// Complete fee change to lock
|
|
214
|
-
await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
|
|
215
|
-
|
|
263
|
+
|
|
216
264
|
// Create type ID args
|
|
217
|
-
const
|
|
218
|
-
|
|
265
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
266
|
+
const args = preTx.inputs.length > 0 ? ccc.hashTypeId(preTx.inputs[0], outputIndex) : "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
267
|
+
|
|
219
268
|
// Create CKBFS type script with type ID
|
|
220
269
|
const ckbfsTypeScript = new Script(
|
|
221
270
|
ensureHexPrefix(config.codeHash),
|
|
222
271
|
config.hashType as any,
|
|
223
|
-
args
|
|
272
|
+
args,
|
|
224
273
|
);
|
|
225
|
-
|
|
274
|
+
|
|
226
275
|
// Create final transaction with same cell deps as preTx
|
|
227
276
|
const tx = Transaction.from({
|
|
228
277
|
cellDeps: preTx.cellDeps,
|
|
229
|
-
witnesses:
|
|
230
|
-
[], // Reset first witness for signing
|
|
231
|
-
...preTx.witnesses.slice(1)
|
|
232
|
-
],
|
|
278
|
+
witnesses: preTx.witnesses,
|
|
233
279
|
outputsData: preTx.outputsData,
|
|
234
280
|
inputs: preTx.inputs,
|
|
235
|
-
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(
|
|
236
447
|
{
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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,
|
|
240
453
|
},
|
|
241
|
-
|
|
242
|
-
|
|
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",
|
|
243
575
|
});
|
|
244
|
-
|
|
245
|
-
|
|
576
|
+
|
|
577
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
578
|
+
|
|
579
|
+
return {tx: preTx, outputIndex};
|
|
246
580
|
}
|
|
247
581
|
|
|
248
582
|
/**
|
|
@@ -252,34 +586,88 @@ export async function createPublishTransaction(
|
|
|
252
586
|
* @returns Promise resolving to the created transaction
|
|
253
587
|
*/
|
|
254
588
|
export async function createAppendTransaction(
|
|
255
|
-
signer: Signer,
|
|
256
|
-
options: AppendOptions
|
|
589
|
+
signer: Signer,
|
|
590
|
+
options: AppendOptions,
|
|
257
591
|
): Promise<Transaction> {
|
|
258
|
-
const {
|
|
259
|
-
ckbfsCell,
|
|
260
|
-
contentChunks,
|
|
592
|
+
const {
|
|
593
|
+
ckbfsCell,
|
|
261
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,
|
|
262
648
|
network = DEFAULT_NETWORK,
|
|
263
|
-
version = DEFAULT_VERSION
|
|
649
|
+
version = DEFAULT_VERSION,
|
|
264
650
|
} = options;
|
|
265
651
|
const { outPoint, data, type, lock, capacity } = ckbfsCell;
|
|
266
|
-
|
|
652
|
+
|
|
267
653
|
// Get CKBFS script config early to use version info
|
|
268
654
|
const config = getCKBFSScriptConfig(network, version);
|
|
269
|
-
|
|
655
|
+
|
|
270
656
|
// Create CKBFS witnesses - each chunk already includes the CKBFS header
|
|
271
657
|
// Pass 0 as version byte - this is the protocol version byte in the witness header
|
|
272
658
|
// not to be confused with the Protocol Version (V1 vs V2)
|
|
273
659
|
const ckbfsWitnesses = createChunkedCKBFSWitnesses(contentChunks);
|
|
274
|
-
|
|
660
|
+
|
|
275
661
|
// Combine the new content chunks for checksum calculation
|
|
276
662
|
const combinedContent = Buffer.concat(contentChunks);
|
|
277
|
-
|
|
663
|
+
|
|
278
664
|
// Update the existing checksum with the new content - this matches Adler32's
|
|
279
665
|
// cumulative nature as required by Rule 11 in the RFC
|
|
280
666
|
const contentChecksum = await updateChecksum(data.checksum, combinedContent);
|
|
281
|
-
console.log(
|
|
282
|
-
|
|
667
|
+
console.log(
|
|
668
|
+
`Updated checksum from ${data.checksum} to ${contentChecksum} for appended content`,
|
|
669
|
+
);
|
|
670
|
+
|
|
283
671
|
// Get the recommended address to ensure lock script cell deps are included
|
|
284
672
|
const address = await signer.getRecommendedAddressObj();
|
|
285
673
|
|
|
@@ -288,19 +676,21 @@ export async function createAppendTransaction(
|
|
|
288
676
|
// else CKBFS data starts at index 0
|
|
289
677
|
const contentStartIndex = address.script.hash() === lock.hash() ? 1 : 0;
|
|
290
678
|
const witnessIndices = Array.from(
|
|
291
|
-
{ length: contentChunks.length },
|
|
292
|
-
(_, i) => contentStartIndex + i
|
|
679
|
+
{ length: contentChunks.length },
|
|
680
|
+
(_, i) => contentStartIndex + i,
|
|
293
681
|
);
|
|
294
|
-
|
|
682
|
+
|
|
295
683
|
// Create backlink for the current state based on version
|
|
296
684
|
let newBackLink: any;
|
|
297
|
-
|
|
685
|
+
|
|
298
686
|
if (version === ProtocolVersion.V1) {
|
|
299
687
|
// V1 format: Use index field (single number)
|
|
300
688
|
newBackLink = {
|
|
301
689
|
// In V1, field order is index, checksum, txHash
|
|
302
690
|
// and index is a single number value, not an array
|
|
303
|
-
index:
|
|
691
|
+
index:
|
|
692
|
+
data.index ||
|
|
693
|
+
(data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
|
|
304
694
|
checksum: data.checksum,
|
|
305
695
|
txHash: outPoint.txHash,
|
|
306
696
|
};
|
|
@@ -314,51 +704,55 @@ export async function createAppendTransaction(
|
|
|
314
704
|
txHash: outPoint.txHash,
|
|
315
705
|
};
|
|
316
706
|
}
|
|
317
|
-
|
|
707
|
+
|
|
318
708
|
// Update backlinks - add the new one to the existing backlinks array
|
|
319
709
|
const backLinks = [...(data.backLinks || []), newBackLink];
|
|
320
|
-
|
|
710
|
+
|
|
321
711
|
// Define output data based on version
|
|
322
712
|
let outputData: Uint8Array;
|
|
323
|
-
|
|
713
|
+
|
|
324
714
|
if (version === ProtocolVersion.V1) {
|
|
325
715
|
// In V1, index is a single number, not an array
|
|
326
716
|
// The first witness index is used (V1 can only reference one witness)
|
|
327
|
-
outputData = CKBFSData.pack(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
717
|
+
outputData = CKBFSData.pack(
|
|
718
|
+
{
|
|
719
|
+
index: witnessIndices[0], // Use only the first index as a number
|
|
720
|
+
checksum: contentChecksum,
|
|
721
|
+
contentType: data.contentType,
|
|
722
|
+
filename: data.filename,
|
|
723
|
+
backLinks,
|
|
724
|
+
},
|
|
725
|
+
ProtocolVersion.V1,
|
|
726
|
+
); // Explicitly use V1 for packing
|
|
334
727
|
} else {
|
|
335
728
|
// In V2, indexes is an array of witness indices
|
|
336
|
-
outputData = CKBFSData.pack(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
729
|
+
outputData = CKBFSData.pack(
|
|
730
|
+
{
|
|
731
|
+
indexes: witnessIndices,
|
|
732
|
+
checksum: contentChecksum,
|
|
733
|
+
contentType: data.contentType,
|
|
734
|
+
filename: data.filename,
|
|
735
|
+
backLinks,
|
|
736
|
+
},
|
|
737
|
+
ProtocolVersion.V2,
|
|
738
|
+
); // Explicitly use V2 for packing
|
|
343
739
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const originalData = CKBFSData.pack(data, version);
|
|
347
|
-
const originalDataSize = originalData.length;
|
|
348
|
-
|
|
349
|
-
// Get sizes and calculate capacity requirements
|
|
350
|
-
const newDataSize = outputData.length;
|
|
351
|
-
|
|
740
|
+
|
|
741
|
+
|
|
352
742
|
// Calculate the required capacity for the output cell
|
|
353
|
-
// This accounts for:
|
|
743
|
+
// This accounts for:
|
|
354
744
|
// 1. The output data size
|
|
355
745
|
// 2. The type script's occupied size
|
|
356
746
|
// 3. The lock script's occupied size
|
|
357
747
|
// 4. A constant of 8 bytes (for header overhead)
|
|
358
|
-
const ckbfsCellSize =
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
748
|
+
const ckbfsCellSize =
|
|
749
|
+
BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) *
|
|
750
|
+
100000000n;
|
|
751
|
+
|
|
752
|
+
console.log(
|
|
753
|
+
`Original capacity: ${capacity}, Calculated size: ${ckbfsCellSize}, Data size: ${outputData.length}`,
|
|
754
|
+
);
|
|
755
|
+
|
|
362
756
|
// Use the maximum value between calculated size and original capacity
|
|
363
757
|
// to ensure we have enough capacity but don't decrease capacity unnecessarily
|
|
364
758
|
const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
|
|
@@ -372,56 +766,57 @@ export async function createAppendTransaction(
|
|
|
372
766
|
index: outPoint.index,
|
|
373
767
|
},
|
|
374
768
|
since: "0x0",
|
|
375
|
-
}
|
|
769
|
+
},
|
|
376
770
|
],
|
|
377
771
|
outputs: [
|
|
378
772
|
{
|
|
379
773
|
lock,
|
|
380
774
|
type,
|
|
381
775
|
capacity: outputCapacity,
|
|
382
|
-
}
|
|
776
|
+
},
|
|
383
777
|
],
|
|
384
|
-
outputsData: [
|
|
385
|
-
outputData,
|
|
386
|
-
]
|
|
778
|
+
outputsData: [outputData],
|
|
387
779
|
});
|
|
388
780
|
|
|
389
|
-
|
|
390
781
|
// Add the CKBFS dep group cell dependency
|
|
391
782
|
tx.addCellDeps({
|
|
392
783
|
outPoint: {
|
|
393
784
|
txHash: ensureHexPrefix(config.depTxHash),
|
|
394
785
|
index: config.depIndex || 0,
|
|
395
786
|
},
|
|
396
|
-
depType: "depGroup"
|
|
787
|
+
depType: "depGroup",
|
|
397
788
|
});
|
|
398
|
-
|
|
789
|
+
|
|
399
790
|
const inputsBefore = tx.inputs.length;
|
|
400
|
-
// If we need more capacity than the original cell had, add additional inputs
|
|
401
|
-
if (outputCapacity > capacity) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
+
// }
|
|
406
799
|
|
|
407
|
-
const witnesses: any = []
|
|
800
|
+
const witnesses: any = [];
|
|
408
801
|
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
409
|
-
if(address.script.hash() === lock.hash()) {
|
|
410
|
-
witnesses.push(
|
|
802
|
+
if (address.script.hash() === lock.hash()) {
|
|
803
|
+
witnesses.push("0x");
|
|
411
804
|
}
|
|
412
805
|
// add ckbfs witnesses
|
|
413
|
-
witnesses.push(
|
|
806
|
+
witnesses.push(
|
|
807
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
808
|
+
);
|
|
414
809
|
|
|
415
810
|
// Add empty witnesses for signer's input
|
|
416
811
|
// This is to ensure that the transaction is valid and can be signed
|
|
417
|
-
for(let i = inputsBefore; i < tx.inputs.length; i++) {
|
|
418
|
-
witnesses.push(
|
|
812
|
+
for (let i = inputsBefore; i < tx.inputs.length; i++) {
|
|
813
|
+
witnesses.push("0x");
|
|
419
814
|
}
|
|
420
|
-
tx.witnesses = witnesses
|
|
815
|
+
tx.witnesses = witnesses;
|
|
421
816
|
|
|
422
817
|
// Complete fee
|
|
423
|
-
await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
424
|
-
|
|
818
|
+
//await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
819
|
+
|
|
425
820
|
return tx;
|
|
426
821
|
}
|
|
427
822
|
|
|
@@ -432,8 +827,8 @@ export async function createAppendTransaction(
|
|
|
432
827
|
* @returns Promise resolving to the signed transaction
|
|
433
828
|
*/
|
|
434
829
|
export async function publishCKBFS(
|
|
435
|
-
signer: Signer,
|
|
436
|
-
options: PublishOptions
|
|
830
|
+
signer: Signer,
|
|
831
|
+
options: PublishOptions,
|
|
437
832
|
): Promise<Transaction> {
|
|
438
833
|
const tx = await createPublishTransaction(signer, options);
|
|
439
834
|
return signer.signTransaction(tx);
|
|
@@ -446,9 +841,9 @@ export async function publishCKBFS(
|
|
|
446
841
|
* @returns Promise resolving to the signed transaction
|
|
447
842
|
*/
|
|
448
843
|
export async function appendCKBFS(
|
|
449
|
-
signer: Signer,
|
|
450
|
-
options: AppendOptions
|
|
844
|
+
signer: Signer,
|
|
845
|
+
options: AppendOptions,
|
|
451
846
|
): Promise<Transaction> {
|
|
452
847
|
const tx = await createAppendTransaction(signer, options);
|
|
453
848
|
return signer.signTransaction(tx);
|
|
454
|
-
}
|
|
849
|
+
}
|