@sequence0/sdk 2.0.0 → 2.1.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.
Files changed (90) hide show
  1. package/README.md +10 -10
  2. package/dist/chains/casper.d.ts +74 -0
  3. package/dist/chains/casper.d.ts.map +1 -0
  4. package/dist/chains/casper.js +512 -0
  5. package/dist/chains/casper.js.map +1 -0
  6. package/dist/chains/cosmos.d.ts +22 -0
  7. package/dist/chains/cosmos.d.ts.map +1 -1
  8. package/dist/chains/cosmos.js +113 -12
  9. package/dist/chains/cosmos.js.map +1 -1
  10. package/dist/chains/ethereum.d.ts.map +1 -1
  11. package/dist/chains/ethereum.js +14 -2
  12. package/dist/chains/ethereum.js.map +1 -1
  13. package/dist/chains/flow.d.ts +57 -0
  14. package/dist/chains/flow.d.ts.map +1 -0
  15. package/dist/chains/flow.js +435 -0
  16. package/dist/chains/flow.js.map +1 -0
  17. package/dist/chains/icp.d.ts.map +1 -1
  18. package/dist/chains/icp.js +483 -67
  19. package/dist/chains/icp.js.map +1 -1
  20. package/dist/chains/iota.d.ts +80 -0
  21. package/dist/chains/iota.d.ts.map +1 -0
  22. package/dist/chains/iota.js +502 -0
  23. package/dist/chains/iota.js.map +1 -0
  24. package/dist/chains/kadena.d.ts +81 -0
  25. package/dist/chains/kadena.d.ts.map +1 -0
  26. package/dist/chains/kadena.js +356 -0
  27. package/dist/chains/kadena.js.map +1 -0
  28. package/dist/chains/near.d.ts +4 -1
  29. package/dist/chains/near.d.ts.map +1 -1
  30. package/dist/chains/near.js +58 -15
  31. package/dist/chains/near.js.map +1 -1
  32. package/dist/chains/nervos.d.ts +148 -0
  33. package/dist/chains/nervos.d.ts.map +1 -0
  34. package/dist/chains/nervos.js +913 -0
  35. package/dist/chains/nervos.js.map +1 -0
  36. package/dist/chains/radix.d.ts +81 -0
  37. package/dist/chains/radix.d.ts.map +1 -0
  38. package/dist/chains/radix.js +289 -0
  39. package/dist/chains/radix.js.map +1 -0
  40. package/dist/chains/solana.d.ts +4 -0
  41. package/dist/chains/solana.d.ts.map +1 -1
  42. package/dist/chains/solana.js +47 -13
  43. package/dist/chains/solana.js.map +1 -1
  44. package/dist/chains/stacks.d.ts +113 -0
  45. package/dist/chains/stacks.d.ts.map +1 -0
  46. package/dist/chains/stacks.js +576 -0
  47. package/dist/chains/stacks.js.map +1 -0
  48. package/dist/chains/sui.d.ts +11 -0
  49. package/dist/chains/sui.d.ts.map +1 -1
  50. package/dist/chains/sui.js +49 -8
  51. package/dist/chains/sui.js.map +1 -1
  52. package/dist/core/atomic.d.ts +1 -1
  53. package/dist/core/atomic.js +1 -1
  54. package/dist/core/client.d.ts +3 -3
  55. package/dist/core/client.d.ts.map +1 -1
  56. package/dist/core/client.js +11 -11
  57. package/dist/core/client.js.map +1 -1
  58. package/dist/core/delegation.d.ts +2 -2
  59. package/dist/core/delegation.d.ts.map +1 -1
  60. package/dist/core/delegation.js +1 -1
  61. package/dist/core/programmable.d.ts +1 -1
  62. package/dist/core/programmable.js +1 -1
  63. package/dist/core/solvency.d.ts +3 -3
  64. package/dist/core/solvency.js +3 -3
  65. package/dist/core/types.d.ts +94 -2
  66. package/dist/core/types.d.ts.map +1 -1
  67. package/dist/core/universal-account.d.ts +3 -3
  68. package/dist/core/universal-account.js +3 -3
  69. package/dist/core/witness.d.ts +3 -3
  70. package/dist/core/witness.js +3 -3
  71. package/dist/index.js +4 -4
  72. package/dist/index.js.map +1 -1
  73. package/dist/settlement/settlement.d.ts +6 -6
  74. package/dist/settlement/settlement.d.ts.map +1 -1
  75. package/dist/settlement/settlement.js +5 -5
  76. package/dist/utils/discovery.d.ts.map +1 -1
  77. package/dist/utils/discovery.js +23 -7
  78. package/dist/utils/discovery.js.map +1 -1
  79. package/dist/utils/http.d.ts +1 -1
  80. package/dist/utils/http.d.ts.map +1 -1
  81. package/dist/utils/http.js +7 -2
  82. package/dist/utils/http.js.map +1 -1
  83. package/dist/utils/logger.d.ts +3 -3
  84. package/dist/utils/logger.d.ts.map +1 -1
  85. package/dist/utils/logger.js +3 -3
  86. package/dist/utils/logger.js.map +1 -1
  87. package/dist/wallet/wallet.d.ts.map +1 -1
  88. package/dist/wallet/wallet.js +47 -2
  89. package/dist/wallet/wallet.js.map +1 -1
  90. package/package.json +1 -1
@@ -0,0 +1,913 @@
1
+ "use strict";
2
+ /**
3
+ * Nervos CKB (Common Knowledge Base) Chain Adapter
4
+ *
5
+ * Builds CKB transactions using the Cell model, computes signing payloads
6
+ * with Blake2b-256 hashing, attaches secp256k1 ECDSA signatures from the
7
+ * FROST threshold signing network, and broadcasts via CKB JSON-RPC.
8
+ *
9
+ * Nervos CKB uses:
10
+ * - Cell model (generalized UTXO) instead of account-based state
11
+ * - Blake2b-256 for transaction hashing (with CKB-specific personalization)
12
+ * - secp256k1 ECDSA with 65-byte recoverable signatures (r + s + recovery_id)
13
+ * - Molecule binary serialization for on-chain data structures
14
+ *
15
+ * No external dependencies beyond @noble/hashes/blake2b (transitive dep).
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { NervosAdapter } from '@sequence0/sdk';
20
+ *
21
+ * const ckb = new NervosAdapter('mainnet');
22
+ *
23
+ * // Build a CKB transfer transaction
24
+ * const unsignedTx = await ckb.buildTransaction(
25
+ * { to: 'ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq...', amount: '10000000000' },
26
+ * 'ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq...'
27
+ * );
28
+ *
29
+ * // ... sign via FROST secp256k1 ...
30
+ * const signedTx = await ckb.attachSignature(unsignedTx, signatureHex);
31
+ * const txHash = await ckb.broadcast(signedTx);
32
+ * ```
33
+ */
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.NervosAdapter = void 0;
36
+ exports.createNervosAdapter = createNervosAdapter;
37
+ exports.createNervosTestnetAdapter = createNervosTestnetAdapter;
38
+ const errors_1 = require("../utils/errors");
39
+ const blake2b_1 = require("@noble/hashes/blake2b");
40
+ // ── Network Configuration ──
41
+ const DEFAULT_RPC_URLS = {
42
+ 'mainnet': 'https://mainnet.ckb.dev/rpc',
43
+ 'testnet': 'https://testnet.ckb.dev/rpc',
44
+ };
45
+ /**
46
+ * CKB personalization string for Blake2b hashing.
47
+ * CKB uses "ckb-default-hash" as the personalization parameter
48
+ * for all Blake2b-256 hashes in the protocol.
49
+ */
50
+ const CKB_HASH_PERSONALIZATION = 'ckb-default-hash';
51
+ /** Minimum cell capacity in shannons (61 CKB = 6100000000 shannons for a basic cell) */
52
+ const MIN_CELL_CAPACITY = BigInt(6100000000);
53
+ /** Shannon per CKB (1 CKB = 1e8 shannons) */
54
+ const SHANNONS_PER_CKB = BigInt(100000000);
55
+ /**
56
+ * Default secp256k1_blake160 cell dep for mainnet (genesis block dep group).
57
+ * This references the system script cell dep group in the genesis block.
58
+ */
59
+ const SECP256K1_BLAKE160_CELL_DEPS = {
60
+ 'mainnet': {
61
+ outPoint: {
62
+ txHash: '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c',
63
+ index: '0x0',
64
+ },
65
+ depType: 'dep_group',
66
+ },
67
+ 'testnet': {
68
+ outPoint: {
69
+ txHash: '0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37',
70
+ index: '0x0',
71
+ },
72
+ depType: 'dep_group',
73
+ },
74
+ };
75
+ /**
76
+ * secp256k1_blake160 lock script code hash (type ID).
77
+ * This is the same on both mainnet and testnet.
78
+ */
79
+ const SECP256K1_BLAKE160_CODE_HASH = '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8';
80
+ // ── Blake2b-256 with CKB Personalization ──
81
+ /**
82
+ * Compute Blake2b-256 with the CKB-specific personalization string.
83
+ *
84
+ * CKB uses Blake2b with:
85
+ * - 32-byte output (256 bits)
86
+ * - personalization: "ckb-default-hash" (UTF-8 encoded, zero-padded to 16 bytes)
87
+ *
88
+ * @param data - Input data as Uint8Array
89
+ * @returns 32-byte Blake2b-256 hash
90
+ */
91
+ function ckbBlake2b(data) {
92
+ return (0, blake2b_1.blake2b)(data, {
93
+ dkLen: 32,
94
+ personalization: CKB_HASH_PERSONALIZATION,
95
+ });
96
+ }
97
+ /**
98
+ * Compute Blake2b-256 hash incrementally for multiple data chunks.
99
+ *
100
+ * @param chunks - Array of Uint8Array data chunks
101
+ * @returns 32-byte Blake2b-256 hash
102
+ */
103
+ function ckbBlake2bMulti(...chunks) {
104
+ const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
105
+ const combined = new Uint8Array(totalLength);
106
+ let offset = 0;
107
+ for (const chunk of chunks) {
108
+ combined.set(chunk, offset);
109
+ offset += chunk.length;
110
+ }
111
+ return ckbBlake2b(combined);
112
+ }
113
+ // ── Address Encoding/Decoding ──
114
+ const BECH32M_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
115
+ const BECH32M_CONST = 0x2bc830a3;
116
+ function bech32mPolymod(values) {
117
+ const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
118
+ let chk = 1;
119
+ for (const v of values) {
120
+ const b = chk >> 25;
121
+ chk = ((chk & 0x1ffffff) << 5) ^ v;
122
+ for (let i = 0; i < 5; i++) {
123
+ if ((b >> i) & 1) {
124
+ chk ^= GEN[i];
125
+ }
126
+ }
127
+ }
128
+ return chk;
129
+ }
130
+ function bech32mHrpExpand(hrp) {
131
+ const ret = [];
132
+ for (const c of hrp) {
133
+ ret.push(c.charCodeAt(0) >> 5);
134
+ }
135
+ ret.push(0);
136
+ for (const c of hrp) {
137
+ ret.push(c.charCodeAt(0) & 31);
138
+ }
139
+ return ret;
140
+ }
141
+ function bech32mVerify(hrp, data5bit) {
142
+ return bech32mPolymod(bech32mHrpExpand(hrp).concat(data5bit)) === BECH32M_CONST;
143
+ }
144
+ function convertBits(data, fromBits, toBits, pad) {
145
+ let acc = 0;
146
+ let bits = 0;
147
+ const ret = [];
148
+ const maxv = (1 << toBits) - 1;
149
+ for (const value of data) {
150
+ if (value < 0 || value >> fromBits !== 0) {
151
+ throw new Error('Invalid value for bit conversion');
152
+ }
153
+ acc = (acc << fromBits) | value;
154
+ bits += fromBits;
155
+ while (bits >= toBits) {
156
+ bits -= toBits;
157
+ ret.push((acc >> bits) & maxv);
158
+ }
159
+ }
160
+ if (pad) {
161
+ if (bits > 0) {
162
+ ret.push((acc << (toBits - bits)) & maxv);
163
+ }
164
+ }
165
+ else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv)) {
166
+ throw new Error('Invalid padding in bech32m conversion');
167
+ }
168
+ return ret;
169
+ }
170
+ /**
171
+ * Decode a CKB full address (new format, CKB2021).
172
+ *
173
+ * CKB full addresses use bech32m encoding with format:
174
+ * - ckb1... (mainnet)
175
+ * - ckt1... (testnet)
176
+ *
177
+ * The payload encodes: format_type (0x00) + code_hash (32 bytes) + hash_type (1 byte) + args (20 bytes)
178
+ *
179
+ * For default secp256k1_blake160 lock:
180
+ * - code_hash: SECP256K1_BLAKE160_CODE_HASH
181
+ * - hash_type: 0x01 (type)
182
+ * - args: 20-byte blake160 hash of the public key
183
+ */
184
+ function decodeCkbAddress(address) {
185
+ const parts = address.toLowerCase().split('1');
186
+ if (parts.length < 2) {
187
+ throw new Error(`Invalid CKB address format: ${address}`);
188
+ }
189
+ const hrp = parts[0];
190
+ if (hrp !== 'ckb' && hrp !== 'ckt') {
191
+ throw new Error(`Invalid CKB address prefix: ${hrp} (expected ckb or ckt)`);
192
+ }
193
+ // Everything after the first '1' is the data part
194
+ const dataStr = address.substring(hrp.length + 1).toLowerCase();
195
+ const data5bit = [];
196
+ for (const c of dataStr) {
197
+ const idx = BECH32M_CHARSET.indexOf(c);
198
+ if (idx < 0)
199
+ throw new Error(`Invalid character in CKB address: ${c}`);
200
+ data5bit.push(idx);
201
+ }
202
+ // Verify bech32m checksum
203
+ if (!bech32mVerify(hrp, data5bit)) {
204
+ throw new Error('Invalid CKB address checksum');
205
+ }
206
+ // Remove checksum (6 characters)
207
+ const payload5bit = data5bit.slice(0, data5bit.length - 6);
208
+ // Convert from 5-bit to 8-bit
209
+ const payload8bit = convertBits(payload5bit, 5, 8, false);
210
+ // CKB2021 full address format:
211
+ // payload[0] = 0x00 (full format type)
212
+ // payload[1..33] = code_hash (32 bytes)
213
+ // payload[33] = hash_type (0x00=data, 0x01=type, 0x02=data1, 0x04=data2)
214
+ // payload[34..] = args
215
+ if (payload8bit[0] !== 0x00) {
216
+ throw new Error(`Unsupported CKB address format type: 0x${payload8bit[0].toString(16)}`);
217
+ }
218
+ const codeHash = '0x' + Buffer.from(payload8bit.slice(1, 33)).toString('hex');
219
+ const hashTypeByte = payload8bit[33];
220
+ let hashType;
221
+ switch (hashTypeByte) {
222
+ case 0x00:
223
+ hashType = 'data';
224
+ break;
225
+ case 0x01:
226
+ hashType = 'type';
227
+ break;
228
+ case 0x02:
229
+ hashType = 'data1';
230
+ break;
231
+ case 0x04:
232
+ hashType = 'data2';
233
+ break;
234
+ default: throw new Error(`Unknown CKB hash type: 0x${hashTypeByte.toString(16)}`);
235
+ }
236
+ const args = '0x' + Buffer.from(payload8bit.slice(34)).toString('hex');
237
+ return { codeHash, hashType, args };
238
+ }
239
+ // ── Molecule Serialization ──
240
+ // CKB uses Molecule for binary serialization of transaction data.
241
+ // We implement the minimum needed for transaction hashing.
242
+ /**
243
+ * Serialize a byte array in Molecule format (fixed-size vector).
244
+ * For CKB scripts, Byte32 is just raw 32 bytes without length prefix.
245
+ */
246
+ function hexToBytes(hex) {
247
+ const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
248
+ const bytes = new Uint8Array(clean.length / 2);
249
+ for (let i = 0; i < bytes.length; i++) {
250
+ bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
251
+ }
252
+ return bytes;
253
+ }
254
+ function bytesToHex(bytes) {
255
+ return '0x' + Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
256
+ }
257
+ /** Write a 32-bit little-endian unsigned integer */
258
+ function writeUint32LE(value) {
259
+ const buf = new Uint8Array(4);
260
+ buf[0] = value & 0xff;
261
+ buf[1] = (value >> 8) & 0xff;
262
+ buf[2] = (value >> 16) & 0xff;
263
+ buf[3] = (value >> 24) & 0xff;
264
+ return buf;
265
+ }
266
+ /** Write a 64-bit little-endian unsigned integer from a BigInt */
267
+ function writeUint64LE(value) {
268
+ const buf = new Uint8Array(8);
269
+ for (let i = 0; i < 8; i++) {
270
+ buf[i] = Number((value >> BigInt(i * 8)) & 0xffn);
271
+ }
272
+ return buf;
273
+ }
274
+ /**
275
+ * Serialize a Molecule fixvec (fixed-size element vector).
276
+ * Format: 4-byte LE length (item count) + concatenated items
277
+ */
278
+ function serializeFixvec(items) {
279
+ const count = writeUint32LE(items.length);
280
+ const totalLen = 4 + items.reduce((sum, item) => sum + item.length, 0);
281
+ const result = new Uint8Array(totalLen);
282
+ result.set(count, 0);
283
+ let offset = 4;
284
+ for (const item of items) {
285
+ result.set(item, offset);
286
+ offset += item.length;
287
+ }
288
+ return result;
289
+ }
290
+ /**
291
+ * Serialize a Molecule dynvec (dynamic-size element vector).
292
+ * Format: 4-byte LE total_size + 4-byte LE offsets per item + concatenated items
293
+ */
294
+ function serializeDynvec(items) {
295
+ const headerSize = 4 + items.length * 4; // total_size + offsets
296
+ const totalSize = headerSize + items.reduce((sum, item) => sum + item.length, 0);
297
+ const result = new Uint8Array(totalSize);
298
+ // Write total size
299
+ result.set(writeUint32LE(totalSize), 0);
300
+ // Write offsets
301
+ let dataOffset = headerSize;
302
+ for (let i = 0; i < items.length; i++) {
303
+ result.set(writeUint32LE(dataOffset), 4 + i * 4);
304
+ dataOffset += items[i].length;
305
+ }
306
+ // Write items
307
+ dataOffset = headerSize;
308
+ for (const item of items) {
309
+ result.set(item, dataOffset);
310
+ dataOffset += item.length;
311
+ }
312
+ return result;
313
+ }
314
+ /**
315
+ * Serialize a Molecule table (struct with dynamic fields).
316
+ * Same format as dynvec: 4-byte LE total_size + offsets + fields
317
+ */
318
+ function serializeTable(fields) {
319
+ return serializeDynvec(fields);
320
+ }
321
+ /**
322
+ * Serialize a CKB Script in Molecule format.
323
+ * Script is a table: { code_hash: Byte32, hash_type: byte, args: Bytes }
324
+ */
325
+ function serializeScript(script) {
326
+ const codeHash = hexToBytes(script.codeHash); // 32 bytes
327
+ let hashTypeByte;
328
+ switch (script.hashType) {
329
+ case 'data':
330
+ hashTypeByte = 0x00;
331
+ break;
332
+ case 'type':
333
+ hashTypeByte = 0x01;
334
+ break;
335
+ case 'data1':
336
+ hashTypeByte = 0x02;
337
+ break;
338
+ case 'data2':
339
+ hashTypeByte = 0x04;
340
+ break;
341
+ default: hashTypeByte = 0x00;
342
+ }
343
+ const hashType = new Uint8Array([hashTypeByte]);
344
+ // args is a Molecule Bytes (4-byte LE length + data)
345
+ const argsData = hexToBytes(script.args);
346
+ const argsBytes = new Uint8Array(4 + argsData.length);
347
+ argsBytes.set(writeUint32LE(argsData.length), 0);
348
+ argsBytes.set(argsData, 4);
349
+ return serializeTable([codeHash, hashType, argsBytes]);
350
+ }
351
+ /**
352
+ * Serialize a CellOutput in Molecule format.
353
+ * CellOutput is a table: { capacity: Uint64, lock: Script, type_: ScriptOpt }
354
+ */
355
+ function serializeCellOutput(output) {
356
+ const capacity = writeUint64LE(BigInt(output.capacity.startsWith('0x')
357
+ ? parseInt(output.capacity, 16)
358
+ : output.capacity));
359
+ const lock = serializeScript(output.lock);
360
+ // ScriptOpt: empty (0 bytes) if null, or serialized Script
361
+ const typeScript = output.type ? serializeScript(output.type) : new Uint8Array(0);
362
+ return serializeTable([capacity, lock, typeScript]);
363
+ }
364
+ /**
365
+ * Serialize a CellInput in Molecule format.
366
+ * CellInput is a struct: { since: Uint64, previous_output: OutPoint }
367
+ * OutPoint is a struct: { tx_hash: Byte32, index: Uint32 }
368
+ * Structs are fixed-size, just concatenated fields.
369
+ */
370
+ function serializeCellInput(input) {
371
+ const since = writeUint64LE(BigInt(input.since.startsWith('0x')
372
+ ? parseInt(input.since, 16)
373
+ : input.since));
374
+ const txHash = hexToBytes(input.previousOutput.txHash); // 32 bytes
375
+ const index = writeUint32LE(parseInt(input.previousOutput.index, 16));
376
+ // Struct: fields concatenated directly (no length prefix)
377
+ const result = new Uint8Array(8 + 32 + 4);
378
+ result.set(since, 0);
379
+ result.set(txHash, 8);
380
+ result.set(index, 40);
381
+ return result;
382
+ }
383
+ /**
384
+ * Serialize a CellDep in Molecule format.
385
+ * CellDep is a struct: { out_point: OutPoint, dep_type: byte }
386
+ */
387
+ function serializeCellDep(dep) {
388
+ const txHash = hexToBytes(dep.outPoint.txHash);
389
+ const index = writeUint32LE(parseInt(dep.outPoint.index, 16));
390
+ const depType = new Uint8Array([dep.depType === 'dep_group' ? 1 : 0]);
391
+ const result = new Uint8Array(32 + 4 + 1);
392
+ result.set(txHash, 0);
393
+ result.set(index, 32);
394
+ result.set(depType, 36);
395
+ return result;
396
+ }
397
+ /**
398
+ * Serialize a RawTransaction in Molecule format and compute its hash.
399
+ *
400
+ * RawTransaction is a table:
401
+ * { version: Uint32, cell_deps: CellDepVec, header_deps: Byte32Vec,
402
+ * inputs: CellInputVec, outputs: CellOutputVec, outputs_data: BytesVec }
403
+ */
404
+ function serializeRawTransaction(tx) {
405
+ const version = writeUint32LE(parseInt(tx.version, 16) || 0);
406
+ // CellDepVec: fixvec of CellDep structs (each 37 bytes)
407
+ const cellDeps = serializeFixvec(tx.cellDeps.map(serializeCellDep));
408
+ // Byte32Vec: fixvec of 32-byte hashes
409
+ const headerDeps = serializeFixvec(tx.headerDeps.map(h => hexToBytes(h)));
410
+ // CellInputVec: fixvec of CellInput structs (each 44 bytes)
411
+ const inputs = serializeFixvec(tx.inputs.map(serializeCellInput));
412
+ // CellOutputVec: dynvec of CellOutput tables
413
+ const outputs = serializeDynvec(tx.outputs.map(serializeCellOutput));
414
+ // BytesVec: dynvec of Bytes (each is 4-byte len + data)
415
+ const outputsData = serializeDynvec(tx.outputsData.map(d => {
416
+ const data = hexToBytes(d);
417
+ const bytes = new Uint8Array(4 + data.length);
418
+ bytes.set(writeUint32LE(data.length), 0);
419
+ bytes.set(data, 4);
420
+ return bytes;
421
+ }));
422
+ return serializeTable([version, cellDeps, headerDeps, inputs, outputs, outputsData]);
423
+ }
424
+ /**
425
+ * Compute the transaction hash (Blake2b-256 of the serialized RawTransaction).
426
+ */
427
+ function computeTxHash(rawTx) {
428
+ const serialized = serializeRawTransaction(rawTx);
429
+ const hash = ckbBlake2b(serialized);
430
+ return bytesToHex(hash);
431
+ }
432
+ // ── WitnessArgs Serialization ──
433
+ /**
434
+ * Serialize WitnessArgs in Molecule format.
435
+ *
436
+ * WitnessArgs is a table: { lock: BytesOpt, input_type: BytesOpt, output_type: BytesOpt }
437
+ * BytesOpt: empty (0 bytes) if None, or Bytes (4-byte LE len + data)
438
+ */
439
+ function serializeWitnessArgs(lock, inputType, outputType) {
440
+ const fields = [];
441
+ for (const field of [lock, inputType, outputType]) {
442
+ if (field === null) {
443
+ fields.push(new Uint8Array(0));
444
+ }
445
+ else {
446
+ const bytes = new Uint8Array(4 + field.length);
447
+ bytes.set(writeUint32LE(field.length), 0);
448
+ bytes.set(field, 4);
449
+ fields.push(bytes);
450
+ }
451
+ }
452
+ return serializeTable(fields);
453
+ }
454
+ // ── JSON-RPC Helper ──
455
+ async function rpcCall(url, method, params) {
456
+ const response = await fetch(url, {
457
+ method: 'POST',
458
+ headers: { 'Content-Type': 'application/json' },
459
+ body: JSON.stringify({
460
+ id: 1,
461
+ jsonrpc: '2.0',
462
+ method,
463
+ params,
464
+ }),
465
+ });
466
+ if (!response.ok) {
467
+ throw new Error(`CKB RPC HTTP ${response.status}: ${await response.text()}`);
468
+ }
469
+ const json = await response.json();
470
+ if (json.error) {
471
+ throw new Error(`CKB RPC error: ${json.error.message} (code ${json.error.code})`);
472
+ }
473
+ return json.result;
474
+ }
475
+ // ── Adapter ──
476
+ class NervosAdapter {
477
+ /**
478
+ * Create a Nervos CKB adapter.
479
+ *
480
+ * @param network - Network to connect to: 'mainnet' or 'testnet'
481
+ * @param rpcUrl - Custom RPC URL (overrides the default for the network)
482
+ */
483
+ constructor(network = 'mainnet', rpcUrl) {
484
+ this.network = network;
485
+ this.rpcUrl = rpcUrl || DEFAULT_RPC_URLS[network];
486
+ }
487
+ getRpcUrl() {
488
+ return this.rpcUrl;
489
+ }
490
+ // ────────────────────────────────────────────────
491
+ // Build Transaction
492
+ // ────────────────────────────────────────────────
493
+ /**
494
+ * Build an unsigned CKB transaction.
495
+ *
496
+ * Fetches live cells for the sender, selects inputs to cover the
497
+ * transfer amount plus minimum change cell capacity, constructs
498
+ * outputs (recipient cell + change cell), and includes the
499
+ * secp256k1_blake160 cell dep.
500
+ *
501
+ * The witness placeholder reserves 65 zero bytes in the lock field
502
+ * for the eventual secp256k1 ECDSA signature.
503
+ *
504
+ * @param tx - Transaction parameters (to address, amount in shannons)
505
+ * @param fromAddress - Sender CKB address (ckb1... or ckt1...)
506
+ * @returns Hex-encoded JSON payload with raw transaction and metadata
507
+ */
508
+ async buildTransaction(tx, fromAddress) {
509
+ try {
510
+ const recipientLock = decodeCkbAddress(tx.to);
511
+ const senderLock = decodeCkbAddress(fromAddress);
512
+ const amount = BigInt(tx.amount);
513
+ if (amount < MIN_CELL_CAPACITY) {
514
+ throw new Error(`Amount ${amount} shannons is below minimum cell capacity ` +
515
+ `(${MIN_CELL_CAPACITY} shannons / ${Number(MIN_CELL_CAPACITY) / 1e8} CKB)`);
516
+ }
517
+ // Fetch live cells for the sender
518
+ const liveCells = await this.fetchLiveCells(senderLock);
519
+ if (liveCells.length === 0) {
520
+ throw new Error(`No live cells found for ${fromAddress}`);
521
+ }
522
+ // Select cells to cover amount + minimum change capacity
523
+ // We need: amount for recipient + MIN_CELL_CAPACITY for change cell
524
+ const needed = amount + MIN_CELL_CAPACITY;
525
+ const selectedCells = [];
526
+ let inputCapacity = 0n;
527
+ // Sort by capacity descending for efficient selection
528
+ const sorted = [...liveCells].sort((a, b) => {
529
+ const capA = BigInt(a.output.capacity.startsWith('0x')
530
+ ? parseInt(a.output.capacity, 16)
531
+ : a.output.capacity);
532
+ const capB = BigInt(b.output.capacity.startsWith('0x')
533
+ ? parseInt(b.output.capacity, 16)
534
+ : b.output.capacity);
535
+ return Number(capB - capA);
536
+ });
537
+ for (const cell of sorted) {
538
+ selectedCells.push(cell);
539
+ const cap = BigInt(cell.output.capacity.startsWith('0x')
540
+ ? parseInt(cell.output.capacity, 16)
541
+ : cell.output.capacity);
542
+ inputCapacity += cap;
543
+ if (inputCapacity >= needed)
544
+ break;
545
+ }
546
+ if (inputCapacity < needed) {
547
+ throw new Error(`Insufficient capacity: have ${inputCapacity} shannons, ` +
548
+ `need ${needed} shannons (${amount} amount + ${MIN_CELL_CAPACITY} min change)`);
549
+ }
550
+ const changeCapacity = inputCapacity - amount;
551
+ // Build the raw transaction
552
+ const cellDep = SECP256K1_BLAKE160_CELL_DEPS[this.network];
553
+ if (!cellDep) {
554
+ throw new Error(`No secp256k1_blake160 cell dep for network: ${this.network}`);
555
+ }
556
+ const inputs = selectedCells.map(cell => ({
557
+ previousOutput: cell.outPoint,
558
+ since: '0x0',
559
+ }));
560
+ const outputs = [
561
+ // Recipient output
562
+ {
563
+ capacity: '0x' + amount.toString(16),
564
+ lock: recipientLock,
565
+ },
566
+ // Change output
567
+ {
568
+ capacity: '0x' + changeCapacity.toString(16),
569
+ lock: senderLock,
570
+ },
571
+ ];
572
+ const outputsData = ['0x', '0x']; // No data for simple transfers
573
+ const rawTransaction = {
574
+ version: '0x0',
575
+ cellDeps: [cellDep],
576
+ headerDeps: [],
577
+ inputs,
578
+ outputs,
579
+ outputsData,
580
+ };
581
+ // Create witness placeholder with 65 zero bytes for lock
582
+ // This is needed for signing payload computation
583
+ const witnessPlaceholder = serializeWitnessArgs(new Uint8Array(65), // 65 zero bytes for secp256k1 signature placeholder
584
+ null, null);
585
+ // Empty witnesses for additional inputs
586
+ const witnesses = [bytesToHex(witnessPlaceholder)];
587
+ for (let i = 1; i < inputs.length; i++) {
588
+ witnesses.push('0x');
589
+ }
590
+ const payload = {
591
+ rawTransaction,
592
+ witnesses,
593
+ txHash: computeTxHash(rawTransaction),
594
+ inputCapacity: inputCapacity.toString(),
595
+ network: this.network,
596
+ };
597
+ return Buffer.from(JSON.stringify(payload)).toString('hex');
598
+ }
599
+ catch (e) {
600
+ if (e instanceof errors_1.ChainError)
601
+ throw e;
602
+ throw new errors_1.ChainError(`Failed to build CKB transaction: ${e.message}`, 'nervos');
603
+ }
604
+ }
605
+ // ────────────────────────────────────────────────
606
+ // Signing Payload
607
+ // ────────────────────────────────────────────────
608
+ /**
609
+ * Extract the signing payload from the buildTransaction output.
610
+ *
611
+ * CKB signing payload is computed as:
612
+ * Blake2b-256(tx_hash || witness_length || first_witness || other_witnesses...)
613
+ *
614
+ * Where tx_hash is Blake2b-256 of the serialized RawTransaction (Molecule format),
615
+ * witness_length is the 8-byte LE length of the first witness with placeholder,
616
+ * and the first witness has a 65-byte zero lock field as placeholder.
617
+ *
618
+ * @param unsignedTx - Hex-encoded unsigned transaction from buildTransaction
619
+ * @returns 32-byte signing hash (hex-encoded, without 0x prefix)
620
+ */
621
+ getSigningPayload(unsignedTx) {
622
+ try {
623
+ const payload = JSON.parse(Buffer.from(unsignedTx, 'hex').toString());
624
+ const txHash = hexToBytes(payload.txHash);
625
+ // The first witness is the WitnessArgs with 65-byte zero lock placeholder
626
+ const firstWitness = hexToBytes(payload.witnesses[0]);
627
+ const firstWitnessLen = writeUint64LE(BigInt(firstWitness.length));
628
+ // Build the message to hash:
629
+ // tx_hash (32 bytes) + first_witness_length (8 bytes LE) + first_witness + other_witnesses
630
+ const chunks = [txHash, firstWitnessLen, firstWitness];
631
+ // Include remaining witnesses
632
+ for (let i = 1; i < payload.witnesses.length; i++) {
633
+ const witness = hexToBytes(payload.witnesses[i]);
634
+ const witnessLen = writeUint64LE(BigInt(witness.length));
635
+ chunks.push(witnessLen);
636
+ chunks.push(witness);
637
+ }
638
+ const signingHash = ckbBlake2bMulti(...chunks);
639
+ return Array.from(signingHash).map(b => b.toString(16).padStart(2, '0')).join('');
640
+ }
641
+ catch {
642
+ return unsignedTx;
643
+ }
644
+ }
645
+ // ────────────────────────────────────────────────
646
+ // Attach Signature
647
+ // ────────────────────────────────────────────────
648
+ /**
649
+ * Attach a secp256k1 ECDSA signature to the CKB transaction.
650
+ *
651
+ * CKB expects a 65-byte recoverable signature (r: 32 bytes + s: 32 bytes + recovery_id: 1 byte)
652
+ * placed in the lock field of the first WitnessArgs.
653
+ *
654
+ * @param unsignedTx - Hex-encoded unsigned transaction from buildTransaction
655
+ * @param signature - 65-byte secp256k1 signature (hex-encoded, r + s + recovery_id)
656
+ * @returns Hex-encoded signed transaction payload
657
+ */
658
+ async attachSignature(unsignedTx, signature) {
659
+ try {
660
+ const payload = JSON.parse(Buffer.from(unsignedTx, 'hex').toString());
661
+ const sig = signature.startsWith('0x') ? signature.slice(2) : signature;
662
+ if (sig.length !== 130) {
663
+ throw new Error(`Invalid signature length: expected 130 hex chars (65 bytes), got ${sig.length}`);
664
+ }
665
+ // Build WitnessArgs with the actual signature in the lock field
666
+ const signatureBytes = hexToBytes(sig);
667
+ const witnessArgs = serializeWitnessArgs(signatureBytes, null, null);
668
+ // Replace the first witness with the signed WitnessArgs
669
+ const witnesses = [...payload.witnesses];
670
+ witnesses[0] = bytesToHex(witnessArgs);
671
+ const signedPayload = {
672
+ ...payload,
673
+ witnesses,
674
+ signed: true,
675
+ };
676
+ return Buffer.from(JSON.stringify(signedPayload)).toString('hex');
677
+ }
678
+ catch (e) {
679
+ if (e instanceof errors_1.ChainError)
680
+ throw e;
681
+ throw new errors_1.ChainError(`Failed to attach CKB signature: ${e.message}`, 'nervos');
682
+ }
683
+ }
684
+ // ────────────────────────────────────────────────
685
+ // Broadcast
686
+ // ────────────────────────────────────────────────
687
+ /**
688
+ * Broadcast a signed CKB transaction.
689
+ *
690
+ * Calls the `send_transaction` JSON-RPC method on the CKB node.
691
+ *
692
+ * @param signedTx - Hex-encoded signed transaction from attachSignature
693
+ * @returns Transaction hash (0x-prefixed)
694
+ */
695
+ async broadcast(signedTx) {
696
+ try {
697
+ const payload = JSON.parse(Buffer.from(signedTx, 'hex').toString());
698
+ if (!payload.signed) {
699
+ throw new Error('Transaction is not signed');
700
+ }
701
+ const rawTx = payload.rawTransaction;
702
+ // Build the full transaction object for the RPC
703
+ const rpcTransaction = {
704
+ version: rawTx.version,
705
+ cell_deps: rawTx.cellDeps.map((dep) => ({
706
+ out_point: {
707
+ tx_hash: dep.outPoint.txHash,
708
+ index: dep.outPoint.index,
709
+ },
710
+ dep_type: dep.depType === 'dep_group' ? 'dep_group' : 'code',
711
+ })),
712
+ header_deps: rawTx.headerDeps,
713
+ inputs: rawTx.inputs.map((input) => ({
714
+ previous_output: {
715
+ tx_hash: input.previousOutput.txHash,
716
+ index: input.previousOutput.index,
717
+ },
718
+ since: input.since,
719
+ })),
720
+ outputs: rawTx.outputs.map((output) => ({
721
+ capacity: output.capacity,
722
+ lock: {
723
+ code_hash: output.lock.codeHash,
724
+ hash_type: output.lock.hashType,
725
+ args: output.lock.args,
726
+ },
727
+ type: output.type ? {
728
+ code_hash: output.type.codeHash,
729
+ hash_type: output.type.hashType,
730
+ args: output.type.args,
731
+ } : null,
732
+ })),
733
+ outputs_data: rawTx.outputsData,
734
+ witnesses: payload.witnesses,
735
+ };
736
+ const txHash = await rpcCall(this.rpcUrl, 'send_transaction', [rpcTransaction, 'passthrough']);
737
+ return txHash;
738
+ }
739
+ catch (e) {
740
+ if (e instanceof errors_1.ChainError)
741
+ throw e;
742
+ throw new errors_1.ChainError(`Failed to broadcast CKB tx: ${e.message}`, 'nervos');
743
+ }
744
+ }
745
+ // ────────────────────────────────────────────────
746
+ // Balance
747
+ // ────────────────────────────────────────────────
748
+ /**
749
+ * Get the CKB balance for an address.
750
+ *
751
+ * Returns balance in shannons (1 CKB = 1e8 shannons).
752
+ * Uses the `get_cells_capacity` indexer RPC method to sum
753
+ * all live cell capacities for the address lock script.
754
+ *
755
+ * @param address - CKB address (ckb1... or ckt1...)
756
+ * @returns Balance in shannons as a string
757
+ */
758
+ async getBalance(address) {
759
+ try {
760
+ const lock = decodeCkbAddress(address);
761
+ const result = await rpcCall(this.rpcUrl, 'get_cells_capacity', [{
762
+ script: {
763
+ code_hash: lock.codeHash,
764
+ hash_type: lock.hashType,
765
+ args: lock.args,
766
+ },
767
+ script_type: 'lock',
768
+ }]);
769
+ if (!result || !result.capacity) {
770
+ return '0';
771
+ }
772
+ // capacity is returned as a hex string (e.g. "0x174876e800")
773
+ const capacityBigInt = BigInt(result.capacity);
774
+ return capacityBigInt.toString();
775
+ }
776
+ catch (e) {
777
+ if (e instanceof errors_1.ChainError)
778
+ throw e;
779
+ return '0';
780
+ }
781
+ }
782
+ // ────────────────────────────────────────────────
783
+ // Utility Methods
784
+ // ────────────────────────────────────────────────
785
+ /**
786
+ * Fetch live cells for a given lock script using the CKB indexer RPC.
787
+ *
788
+ * Uses the `get_cells` JSON-RPC method to query the indexer for
789
+ * live (unspent) cells matching the given lock script.
790
+ *
791
+ * @param lock - Lock script to search for
792
+ * @returns Array of live cells
793
+ */
794
+ async fetchLiveCells(lock) {
795
+ const cells = [];
796
+ let cursor;
797
+ const limit = '0x64'; // 100 cells per page
798
+ // Paginate through results
799
+ for (let page = 0; page < 10; page++) {
800
+ const params = [
801
+ {
802
+ script: {
803
+ code_hash: lock.codeHash,
804
+ hash_type: lock.hashType,
805
+ args: lock.args,
806
+ },
807
+ script_type: 'lock',
808
+ },
809
+ 'asc',
810
+ limit,
811
+ ];
812
+ if (cursor) {
813
+ params[3] = cursor;
814
+ }
815
+ const result = await rpcCall(this.rpcUrl, 'get_cells', cursor ? [...params.slice(0, 3), cursor] : params);
816
+ if (!result || !result.objects || result.objects.length === 0) {
817
+ break;
818
+ }
819
+ for (const obj of result.objects) {
820
+ const cell = obj;
821
+ // Skip cells with type scripts (they may be special cells like UDT, NervosDAO, etc.)
822
+ if (cell.output.type)
823
+ continue;
824
+ cells.push({
825
+ outPoint: {
826
+ txHash: cell.out_point.tx_hash,
827
+ index: cell.out_point.index,
828
+ },
829
+ output: {
830
+ capacity: cell.output.capacity,
831
+ lock: {
832
+ codeHash: cell.output.lock.code_hash,
833
+ hashType: cell.output.lock.hash_type,
834
+ args: cell.output.lock.args,
835
+ },
836
+ type: null,
837
+ },
838
+ outputData: cell.output_data,
839
+ });
840
+ }
841
+ cursor = result.last_cursor;
842
+ if (!cursor || result.objects.length < parseInt(limit, 16)) {
843
+ break;
844
+ }
845
+ }
846
+ return cells;
847
+ }
848
+ /**
849
+ * Derive a CKB address from a compressed secp256k1 public key.
850
+ *
851
+ * The default CKB lock script uses blake160 (first 20 bytes of Blake2b-256)
852
+ * of the public key as the args field.
853
+ *
854
+ * @param pubkeyHex - 33-byte compressed secp256k1 public key (hex-encoded)
855
+ * @returns CKB address (ckb1... for mainnet, ckt1... for testnet)
856
+ */
857
+ deriveAddress(pubkeyHex) {
858
+ const clean = pubkeyHex.startsWith('0x') ? pubkeyHex.slice(2) : pubkeyHex;
859
+ if (clean.length !== 66) {
860
+ throw new errors_1.ChainError(`Invalid public key length: expected 33 bytes (compressed secp256k1), got ${clean.length / 2}`, 'nervos');
861
+ }
862
+ // Blake160 = first 20 bytes of Blake2b-256(public_key)
863
+ const pubkeyBytes = hexToBytes(clean);
864
+ const hash = ckbBlake2b(pubkeyBytes);
865
+ const blake160 = hash.slice(0, 20);
866
+ const args = '0x' + Array.from(blake160).map(b => b.toString(16).padStart(2, '0')).join('');
867
+ // Encode as CKB2021 full address (bech32m)
868
+ const hrp = this.network === 'mainnet' ? 'ckb' : 'ckt';
869
+ const codeHashBytes = hexToBytes(SECP256K1_BLAKE160_CODE_HASH);
870
+ const hashTypeByte = 0x01; // type
871
+ // Payload: 0x00 (format) + code_hash (32) + hash_type (1) + args (20)
872
+ const payloadBytes = new Uint8Array(1 + 32 + 1 + 20);
873
+ payloadBytes[0] = 0x00; // Full format type
874
+ payloadBytes.set(codeHashBytes, 1);
875
+ payloadBytes[33] = hashTypeByte;
876
+ payloadBytes.set(blake160, 34);
877
+ // Convert to 5-bit groups
878
+ const data5bit = convertBits(Array.from(payloadBytes), 8, 5, true);
879
+ // Compute bech32m checksum
880
+ const checksumInput = bech32mHrpExpand(hrp).concat(data5bit).concat([0, 0, 0, 0, 0, 0]);
881
+ const mod = bech32mPolymod(checksumInput) ^ BECH32M_CONST;
882
+ const checksum = [];
883
+ for (let i = 0; i < 6; i++) {
884
+ checksum.push((mod >> (5 * (5 - i))) & 31);
885
+ }
886
+ let result = hrp + '1';
887
+ for (const d of data5bit.concat(checksum)) {
888
+ result += BECH32M_CHARSET[d];
889
+ }
890
+ return result;
891
+ }
892
+ }
893
+ exports.NervosAdapter = NervosAdapter;
894
+ // ── Factory Functions ──
895
+ /**
896
+ * Create a Nervos CKB mainnet adapter.
897
+ *
898
+ * @param rpcUrl - Optional custom RPC URL
899
+ * @returns NervosAdapter configured for mainnet
900
+ */
901
+ function createNervosAdapter(rpcUrl) {
902
+ return new NervosAdapter('mainnet', rpcUrl);
903
+ }
904
+ /**
905
+ * Create a Nervos CKB testnet adapter.
906
+ *
907
+ * @param rpcUrl - Optional custom RPC URL
908
+ * @returns NervosAdapter configured for testnet
909
+ */
910
+ function createNervosTestnetAdapter(rpcUrl) {
911
+ return new NervosAdapter('testnet', rpcUrl);
912
+ }
913
+ //# sourceMappingURL=nervos.js.map