@ckb-ccc/core 1.19.1 → 1.20.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/advanced.d.mts +2 -2
  3. package/dist/advanced.mjs +1 -1
  4. package/{dist.commonjs/advancedBarrel-CSQ2SmeJ.d.ts → dist/advancedBarrel-Di0YBlWc.d.mts} +2029 -1639
  5. package/dist/advancedBarrel-Di0YBlWc.d.mts.map +1 -0
  6. package/dist/advancedBarrel.d.mts +2 -2
  7. package/dist/advancedBarrel.mjs +1 -1
  8. package/dist/advancedBarrel.mjs.map +1 -1
  9. package/dist/barrel-G6e8gZrB.mjs +7 -0
  10. package/dist/barrel-G6e8gZrB.mjs.map +1 -0
  11. package/dist/barrel.d.mts +2 -2
  12. package/dist/barrel.mjs +1 -1
  13. package/dist/index.d.mts +2 -2
  14. package/dist/index.mjs +1 -1
  15. package/dist/transformers-DxPrDQko.mjs +2 -0
  16. package/dist/transformers-DxPrDQko.mjs.map +1 -0
  17. package/dist.commonjs/advanced.d.ts +2 -2
  18. package/dist.commonjs/advanced.js +1 -1
  19. package/{dist/advancedBarrel-BwJIDnvL.d.mts → dist.commonjs/advancedBarrel-B57gAzdK.d.ts} +2030 -1638
  20. package/dist.commonjs/advancedBarrel-B57gAzdK.d.ts.map +1 -0
  21. package/dist.commonjs/advancedBarrel.d.ts +2 -2
  22. package/dist.commonjs/advancedBarrel.js +1 -1
  23. package/dist.commonjs/advancedBarrel.js.map +1 -1
  24. package/dist.commonjs/barrel-BOCFtFjq.js +23 -0
  25. package/dist.commonjs/barrel-BOCFtFjq.js.map +1 -0
  26. package/dist.commonjs/barrel.d.ts +2 -2
  27. package/dist.commonjs/barrel.js +1 -1
  28. package/dist.commonjs/index.d.ts +2 -2
  29. package/dist.commonjs/index.js +1 -1
  30. package/dist.commonjs/{rolldown-runtime-DWNI8pZO.js → rolldown-runtime-Chgba0Kb.js} +1 -1
  31. package/dist.commonjs/transformers-0FqvcPy2.js +2 -0
  32. package/dist.commonjs/transformers-0FqvcPy2.js.map +1 -0
  33. package/package.json +8 -7
  34. package/src/advancedBarrel.ts +1 -0
  35. package/src/bytes/advanced.ts +1 -6
  36. package/src/bytes/index.ts +37 -10
  37. package/src/ckb/transaction.ts +7 -5
  38. package/src/client/client.ts +75 -19
  39. package/src/client/clientPublicMainnet.ts +75 -36
  40. package/src/client/clientPublicTestnet.ts +75 -36
  41. package/src/client/jsonRpc/client.ts +77 -30
  42. package/src/client/jsonRpc/transformers.ts +4 -4
  43. package/src/jsonRpc/index.ts +1 -0
  44. package/src/jsonRpc/requestor.ts +74 -21
  45. package/src/jsonRpc/transports/advanced.ts +0 -4
  46. package/src/jsonRpc/transports/factory.ts +12 -5
  47. package/src/jsonRpc/transports/fallback.ts +9 -5
  48. package/src/jsonRpc/transports/http.ts +21 -17
  49. package/src/jsonRpc/transports/index.ts +4 -0
  50. package/src/jsonRpc/transports/transport.ts +32 -6
  51. package/src/jsonRpc/transports/webSocket.ts +67 -20
  52. package/src/keystore/index.ts +3 -3
  53. package/src/molecule/codec.ts +20 -17
  54. package/src/signer/advanced.ts +1 -0
  55. package/src/signer/btc/verify.ts +2 -2
  56. package/src/signer/ckb/secp256k1Signing.ts +3 -3
  57. package/src/signer/doge/signerDoge.ts +1 -1
  58. package/src/signer/doge/signerDogePrivateKey.ts +1 -1
  59. package/src/signer/index.ts +1 -0
  60. package/src/signer/jsonRpc/handler.ts +217 -0
  61. package/src/signer/jsonRpc/index.ts +2 -0
  62. package/src/signer/jsonRpc/network.ts +15 -0
  63. package/src/signer/jsonRpc/signer.ts +234 -0
  64. package/src/signer/jsonRpc/transformers.ts +67 -0
  65. package/src/utils/abortSignal.ts +42 -0
  66. package/src/utils/index.ts +2 -0
  67. package/src/utils/owner/aggregated.ts +56 -0
  68. package/src/utils/owner/index.ts +4 -0
  69. package/src/utils/owner/owner.ts +141 -0
  70. package/src/utils/owner/refCount.ts +78 -0
  71. package/src/utils/owner/unique.ts +30 -0
  72. package/tsdown.config.mts +1 -0
  73. package/dist/address.advanced-Cka_9tcR.mjs +0 -2
  74. package/dist/address.advanced-Cka_9tcR.mjs.map +0 -1
  75. package/dist/advancedBarrel-BwJIDnvL.d.mts.map +0 -1
  76. package/dist/barrel-DsK2p-Rh.mjs +0 -7
  77. package/dist/barrel-DsK2p-Rh.mjs.map +0 -1
  78. package/dist.commonjs/address.advanced-BTrmYDic.js +0 -2
  79. package/dist.commonjs/address.advanced-BTrmYDic.js.map +0 -1
  80. package/dist.commonjs/advancedBarrel-CSQ2SmeJ.d.ts.map +0 -1
  81. package/dist.commonjs/barrel-D8_wJLfr.js +0 -23
  82. package/dist.commonjs/barrel-D8_wJLfr.js.map +0 -1
@@ -85,7 +85,7 @@ export function fixedItemVec<Encodable, Decoded>(
85
85
  `fixedItemVec: too short buffer, expected at least 4 bytes, but got ${value.byteLength}`,
86
86
  );
87
87
  }
88
- const itemCount = uint32From(value.slice(0, 4));
88
+ const itemCount = uint32From(value.subarray(0, 4));
89
89
  const byteLength = 4 + itemCount * itemByteLength;
90
90
  if (value.byteLength !== byteLength) {
91
91
  throw new Error(
@@ -98,7 +98,7 @@ export function fixedItemVec<Encodable, Decoded>(
98
98
  for (let offset = 4; offset < byteLength; offset += itemByteLength) {
99
99
  decodedArray.push(
100
100
  itemCodec.decode(
101
- value.slice(offset, offset + itemByteLength),
101
+ value.subarray(offset, offset + itemByteLength),
102
102
  config,
103
103
  ),
104
104
  );
@@ -145,7 +145,7 @@ export function dynItemVec<Encodable, Decoded>(
145
145
  `dynItemVec: too short buffer, expected at least 4 bytes, but got ${value.byteLength}`,
146
146
  );
147
147
  }
148
- const byteLength = uint32From(value.slice(0, 4));
148
+ const byteLength = uint32From(value.subarray(0, 4));
149
149
  if (byteLength !== value.byteLength) {
150
150
  throw new Error(
151
151
  `dynItemVec: invalid buffer size, expected ${byteLength}, but got ${value.byteLength}`,
@@ -156,10 +156,10 @@ export function dynItemVec<Encodable, Decoded>(
156
156
  return [];
157
157
  }
158
158
 
159
- const offset = uint32From(value.slice(4, 8));
159
+ const offset = uint32From(value.subarray(4, 8));
160
160
  const itemCount = (offset - 4) / 4;
161
161
  const offsets = Array.from(new Array(itemCount), (_, index) =>
162
- uint32From(value.slice(4 + index * 4, 8 + index * 4)),
162
+ uint32From(value.subarray(4 + index * 4, 8 + index * 4)),
163
163
  );
164
164
  offsets.push(byteLength);
165
165
  try {
@@ -167,7 +167,7 @@ export function dynItemVec<Encodable, Decoded>(
167
167
  for (let index = 0; index < offsets.length - 1; index++) {
168
168
  const start = offsets[index];
169
169
  const end = offsets[index + 1];
170
- const itemBuffer = value.slice(start, end);
170
+ const itemBuffer = value.subarray(start, end);
171
171
  decodedArray.push(itemCodec.decode(itemBuffer, config));
172
172
  }
173
173
  return decodedArray;
@@ -250,14 +250,14 @@ export function byteVec<Encodable, Decoded>(
250
250
  `byteVec: too short buffer, expected at least 4 bytes, but got ${value.byteLength}`,
251
251
  );
252
252
  }
253
- const byteLength = uint32From(value.slice(0, 4));
253
+ const byteLength = uint32From(value.subarray(0, 4));
254
254
  if (byteLength !== value.byteLength - 4) {
255
255
  throw new Error(
256
256
  `byteVec: invalid buffer size, expected ${byteLength}, but got ${value.byteLength}`,
257
257
  );
258
258
  }
259
259
  try {
260
- return codec.decode(value.slice(4), config);
260
+ return codec.decode(value.subarray(4), config);
261
261
  } catch (e: unknown) {
262
262
  throw new Error(`byteVec - ${getMessage(e)}`, { cause: e });
263
263
  }
@@ -331,8 +331,8 @@ export function table<
331
331
  `table: too short buffer, expected at least 4 bytes, but got ${value.byteLength}`,
332
332
  );
333
333
  }
334
- const byteLength = uint32From(value.slice(0, 4));
335
- const headerLength = uint32From(value.slice(4, 8));
334
+ const byteLength = uint32From(value.subarray(0, 4));
335
+ const headerLength = uint32From(value.subarray(4, 8));
336
336
  const actualFieldCount = (headerLength - 4) / 4;
337
337
 
338
338
  if (byteLength !== value.byteLength) {
@@ -353,12 +353,12 @@ export function table<
353
353
  );
354
354
  }
355
355
  const offsets = keys.map((_, index) =>
356
- uint32From(value.slice(4 + index * 4, 8 + index * 4)),
356
+ uint32From(value.subarray(4 + index * 4, 8 + index * 4)),
357
357
  );
358
358
  // If there are extra fields, add the last offset to the offsets array
359
359
  if (actualFieldCount > keys.length) {
360
360
  offsets.push(
361
- uint32From(value.slice(4 + keys.length * 4, 8 + keys.length * 4)),
361
+ uint32From(value.subarray(4 + keys.length * 4, 8 + keys.length * 4)),
362
362
  );
363
363
  } else {
364
364
  // If there are no extra fields, add the byte length to the offsets array
@@ -370,7 +370,7 @@ export function table<
370
370
  const end = offsets[i + 1];
371
371
  const field = keys[i];
372
372
  const codec = codecLayout[field];
373
- const payload = value.slice(start, end);
373
+ const payload = value.subarray(start, end);
374
374
  try {
375
375
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
376
376
  Object.assign(object, { [field]: codec.decode(payload, config) });
@@ -501,7 +501,7 @@ export function union<T extends Record<string, CodecLike<any, any>>>(
501
501
  },
502
502
  decode(buffer, config) {
503
503
  const value = bytesFrom(buffer);
504
- const fieldIndex = uint32From(value.slice(0, 4));
504
+ const fieldIndex = uint32From(value.subarray(0, 4));
505
505
  const keys = Object.keys(codecLayout);
506
506
 
507
507
  const field = (() => {
@@ -529,7 +529,7 @@ export function union<T extends Record<string, CodecLike<any, any>>>(
529
529
  return {
530
530
  type: field,
531
531
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
532
- value: codecLayout[field].decode(value.slice(4), config),
532
+ value: codecLayout[field].decode(value.subarray(4), config),
533
533
  } as UnionDecoded<T>;
534
534
  },
535
535
  });
@@ -573,7 +573,7 @@ export function struct<
573
573
  const object = {};
574
574
  let offset = 0;
575
575
  Object.entries(codecLayout).forEach(([key, codec]) => {
576
- const payload = value.slice(offset, offset + codec.byteLength!);
576
+ const payload = value.subarray(offset, offset + codec.byteLength!);
577
577
  try {
578
578
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
579
579
  Object.assign(object, { [key]: codec.decode(payload, config) });
@@ -627,7 +627,10 @@ export function array<Encodable, Decoded>(
627
627
  const result: Array<Decoded> = [];
628
628
  for (let i = 0; i < value.byteLength; i += itemCodec.byteLength!) {
629
629
  result.push(
630
- itemCodec.decode(value.slice(i, i + itemCodec.byteLength!), config),
630
+ itemCodec.decode(
631
+ value.subarray(i, i + itemCodec.byteLength!),
632
+ config,
633
+ ),
631
634
  );
632
635
  }
633
636
  return result;
@@ -0,0 +1 @@
1
+ export * from "./jsonRpc/transformers.js";
@@ -78,7 +78,7 @@ export function btcP2pkhAddressFromPublicKey(
78
78
  * @public
79
79
  */
80
80
  export function btcPublicKeyFromP2pkhAddress(address: string): Hex {
81
- return hexFrom(bs58check.decode(address).slice(1));
81
+ return hexFrom(bs58check.decode(address).subarray(1));
82
82
  }
83
83
 
84
84
  /**
@@ -92,7 +92,7 @@ export function verifyMessageBtcEcdsa(
92
92
  const challenge =
93
93
  typeof message === "string" ? message : hexFrom(message).slice(2);
94
94
 
95
- const rawSign = bytesFrom(signature, "base64").slice(1);
95
+ const rawSign = bytesFrom(signature, "base64").subarray(1);
96
96
 
97
97
  return secp256k1.verify(
98
98
  bytesFrom(rawSign),
@@ -21,7 +21,7 @@ export function signMessageSecp256k1(
21
21
  format: "recovered",
22
22
  prehash: false,
23
23
  });
24
- return hexFrom(bytesConcat(signature.slice(1), signature.slice(0, 1)));
24
+ return hexFrom(bytesConcat(signature.subarray(1), signature.subarray(0, 1)));
25
25
  }
26
26
 
27
27
  /**
@@ -40,7 +40,7 @@ export function verifyMessageSecp256k1(
40
40
  ): boolean {
41
41
  const signatureBytes = bytesFrom(signature);
42
42
  return secp256k1.verify(
43
- bytesConcat(signatureBytes.slice(64), signatureBytes.slice(0, 64)),
43
+ bytesConcat(signatureBytes.subarray(64), signatureBytes.subarray(0, 64)),
44
44
  bytesFrom(message),
45
45
  bytesFrom(publicKey),
46
46
  { format: "recovered", prehash: false },
@@ -62,7 +62,7 @@ export function recoverMessageSecp256k1(
62
62
  const signatureBytes = bytesFrom(signature);
63
63
  return hexFrom(
64
64
  secp256k1.recoverPublicKey(
65
- bytesConcat(signatureBytes.slice(64), signatureBytes.slice(0, 64)),
65
+ bytesConcat(signatureBytes.subarray(64), signatureBytes.subarray(0, 64)),
66
66
  bytesFrom(message),
67
67
  { prehash: false },
68
68
  ),
@@ -53,7 +53,7 @@ export abstract class SignerDoge extends Signer {
53
53
  * @returns A promise that resolves to an array of Address objects.
54
54
  */
55
55
  async getAddressObjs(): Promise<Address[]> {
56
- const hash = bs58check.decode(await this.getDogeAddress()).slice(1);
56
+ const hash = bs58check.decode(await this.getDogeAddress()).subarray(1);
57
57
 
58
58
  return [
59
59
  await Address.fromKnownScript(
@@ -93,7 +93,7 @@ export class SignerDogePrivateKey extends SignerDoge {
93
93
  },
94
94
  );
95
95
  return bytesTo(
96
- bytesConcat([31 + Number(signature[0])], signature.slice(1)),
96
+ bytesConcat([31 + Number(signature[0])], signature.subarray(1)),
97
97
  "base64",
98
98
  );
99
99
  }
@@ -3,5 +3,6 @@ export * from "./ckb/index.js";
3
3
  export * from "./doge/index.js";
4
4
  export * from "./dummy/index.js";
5
5
  export * from "./evm/index.js";
6
+ export * from "./jsonRpc/index.js";
6
7
  export * from "./nostr/index.js";
7
8
  export * from "./signer/index.js";
@@ -0,0 +1,217 @@
1
+ import type { Transaction } from "../../ckb/index.js";
2
+ import { JsonRpcTransformers } from "../../client/jsonRpc/advanced.js";
3
+ import { JsonRpcError, type JsonRpcPayload } from "../../jsonRpc/index.js";
4
+ import type { Signer } from "../signer/index.js";
5
+ import { signerJsonRpcNetworkIdFromAddressPrefix } from "./network.js";
6
+ import {
7
+ SignerJsonRpcTransformers,
8
+ type SignerJsonRpcInfo,
9
+ type SignerJsonRpcMessageToSign,
10
+ } from "./transformers.js";
11
+
12
+ export type SignerJsonRpcConfirmation =
13
+ | { method: "connect"; networkId: string }
14
+ | { method: "sign_message"; message: SignerJsonRpcMessageToSign }
15
+ | { method: "sign_transaction"; transaction: Transaction };
16
+
17
+ export type SignerJsonRpcHandlerConfig = {
18
+ getSigner: () => Signer | undefined;
19
+ getSignerMetadata?: () => Pick<SignerJsonRpcInfo, "name" | "icon">;
20
+ /** Connects the requested network and returns its connected Signer. */
21
+ connect: (networkId: string) => Promise<Signer>;
22
+ confirmRequest: (request: SignerJsonRpcConfirmation) => Promise<boolean>;
23
+ };
24
+
25
+ export type SignerJsonRpcHandler = (payload: JsonRpcPayload) => unknown;
26
+
27
+ export function buildSignerJsonRpcHandler({
28
+ connect,
29
+ confirmRequest,
30
+ getSigner,
31
+ getSignerMetadata = () => ({}),
32
+ }: SignerJsonRpcHandlerConfig): SignerJsonRpcHandler {
33
+ const requireSigner = () => {
34
+ const signer = getSigner();
35
+ if (!signer) {
36
+ throw new JsonRpcError({
37
+ code: -32000,
38
+ message: "No signer is connected",
39
+ });
40
+ }
41
+
42
+ return signer;
43
+ };
44
+
45
+ const handlers = new Map<string, SignerJsonRpcHandler>([
46
+ [
47
+ "get_info",
48
+ (payload) => {
49
+ requireParams(payload, 0);
50
+ return buildSignerInfo(requireSigner(), getSignerMetadata());
51
+ },
52
+ ],
53
+ [
54
+ "connect",
55
+ async (payload) => {
56
+ const [networkId] = requireParams(payload, 1);
57
+ if (typeof networkId !== "string" || !networkId) {
58
+ throw new JsonRpcError({
59
+ code: -32602,
60
+ message: "Invalid network ID",
61
+ });
62
+ }
63
+
64
+ await requireConfirmation(confirmRequest, {
65
+ method: "connect",
66
+ networkId,
67
+ });
68
+
69
+ const signer = await connect(networkId);
70
+ const actualNetworkId = signerJsonRpcNetworkIdFromAddressPrefix(
71
+ signer.client.addressPrefix,
72
+ );
73
+ if (actualNetworkId !== networkId) {
74
+ throw new JsonRpcError({
75
+ code: -32002,
76
+ message: `Signer uses ${actualNetworkId}, expected ${networkId}`,
77
+ });
78
+ }
79
+
80
+ return null;
81
+ },
82
+ ],
83
+ [
84
+ "get_scripts",
85
+ async (payload) => {
86
+ requireParams(payload, 0);
87
+ return (await requireSigner().getAddressObjs()).map(({ script }) =>
88
+ JsonRpcTransformers.scriptFrom(script),
89
+ );
90
+ },
91
+ ],
92
+ [
93
+ "get_native_address",
94
+ (payload) => {
95
+ requireParams(payload, 0);
96
+ return requireSigner().getInternalAddress();
97
+ },
98
+ ],
99
+ [
100
+ "get_identity",
101
+ (payload) => {
102
+ requireParams(payload, 0);
103
+ return requireSigner().getIdentity();
104
+ },
105
+ ],
106
+ [
107
+ "sign_message",
108
+ async (payload) => {
109
+ const [message] = requireParams(payload, 1);
110
+ const parsedMessage = parseMessageParam(message);
111
+ await requireConfirmation(confirmRequest, {
112
+ method: "sign_message",
113
+ message: SignerJsonRpcTransformers.messageFrom(parsedMessage),
114
+ });
115
+ return requireSigner().signMessageRaw(parsedMessage);
116
+ },
117
+ ],
118
+ [
119
+ "prepare_transaction",
120
+ async (payload) => {
121
+ const [transaction] = requireParams(payload, 1);
122
+ return JsonRpcTransformers.transactionFrom(
123
+ await requireSigner().prepareTransaction(
124
+ parseTransactionParam(transaction),
125
+ ),
126
+ );
127
+ },
128
+ ],
129
+ [
130
+ "sign_transaction",
131
+ async (payload) => {
132
+ const [transaction] = requireParams(payload, 1);
133
+ const parsedTransaction = parseTransactionParam(transaction);
134
+ await requireConfirmation(confirmRequest, {
135
+ method: "sign_transaction",
136
+ transaction: parsedTransaction,
137
+ });
138
+ return JsonRpcTransformers.transactionFrom(
139
+ await requireSigner().signOnlyTransaction(parsedTransaction),
140
+ );
141
+ },
142
+ ],
143
+ ]);
144
+
145
+ return (payload: JsonRpcPayload) => {
146
+ const handler = handlers.get(payload.method);
147
+ if (!handler) {
148
+ throw new JsonRpcError({
149
+ code: -32601,
150
+ message: `Unsupported method: ${payload.method}`,
151
+ });
152
+ }
153
+
154
+ return handler(payload);
155
+ };
156
+ }
157
+
158
+ async function requireConfirmation(
159
+ confirmRequest: SignerJsonRpcHandlerConfig["confirmRequest"],
160
+ request: SignerJsonRpcConfirmation,
161
+ ) {
162
+ if (!(await confirmRequest(request))) {
163
+ throw new JsonRpcError({
164
+ code: -32003,
165
+ message: "User rejected request",
166
+ });
167
+ }
168
+ }
169
+
170
+ function parseMessageParam(message: unknown) {
171
+ try {
172
+ return SignerJsonRpcTransformers.messageTo(message);
173
+ } catch (cause) {
174
+ throw new JsonRpcError({
175
+ code: -32602,
176
+ message:
177
+ cause instanceof Error ? cause.message : "Invalid signer message",
178
+ });
179
+ }
180
+ }
181
+
182
+ function parseTransactionParam(transaction: unknown) {
183
+ try {
184
+ return JsonRpcTransformers.transactionTo(
185
+ transaction as Parameters<typeof JsonRpcTransformers.transactionTo>[0],
186
+ );
187
+ } catch (cause) {
188
+ throw new JsonRpcError({
189
+ code: -32602,
190
+ message:
191
+ cause instanceof Error ? cause.message : "Invalid signer transaction",
192
+ });
193
+ }
194
+ }
195
+
196
+ function requireParams(payload: JsonRpcPayload, count: number) {
197
+ if (!Array.isArray(payload.params) || payload.params.length !== count) {
198
+ throw new JsonRpcError({
199
+ code: -32602,
200
+ message: `${payload.method} expects ${count} parameter${count === 1 ? "" : "s"}`,
201
+ });
202
+ }
203
+
204
+ return payload.params;
205
+ }
206
+
207
+ function buildSignerInfo(
208
+ signer: Signer,
209
+ metadata: Pick<SignerJsonRpcInfo, "name" | "icon">,
210
+ ) {
211
+ return SignerJsonRpcTransformers.infoFrom({
212
+ type: signer.type,
213
+ signType: signer.signType,
214
+ name: metadata.name,
215
+ icon: metadata.icon,
216
+ });
217
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./handler.js";
2
+ export * from "./signer.js";
@@ -0,0 +1,15 @@
1
+ const CKB_MAINNET_NETWORK_ID = "ckb-mainnet";
2
+ const CKB_TESTNET_NETWORK_ID = "ckb-testnet";
3
+
4
+ // TODO: In the next major version, read the network ID directly from Client
5
+ // instead of inferring it from addressPrefix.
6
+ export function signerJsonRpcNetworkIdFromAddressPrefix(addressPrefix: string) {
7
+ if (addressPrefix === "ckb") {
8
+ return CKB_MAINNET_NETWORK_ID;
9
+ }
10
+ if (addressPrefix === "ckt") {
11
+ return CKB_TESTNET_NETWORK_ID;
12
+ }
13
+
14
+ throw new Error(`Unsupported address prefix: ${addressPrefix}`);
15
+ }
@@ -0,0 +1,234 @@
1
+ import { Address } from "../../address/index.js";
2
+ import { Script } from "../../ckb/index.js";
3
+ import { Client } from "../../client/index.js";
4
+ import {
5
+ JsonRpcScript,
6
+ JsonRpcTransformers,
7
+ } from "../../client/jsonRpc/advanced.js";
8
+ import { RequestorJsonRpc } from "../../jsonRpc/index.js";
9
+ import { Signer } from "../signer/index.js";
10
+ import { signerJsonRpcNetworkIdFromAddressPrefix } from "./network.js";
11
+ import {
12
+ SignerJsonRpcInfo,
13
+ SignerJsonRpcTransformers,
14
+ } from "./transformers.js";
15
+
16
+ export type SignerJsonRpcConfig = Omit<
17
+ Parameters<typeof RequestorJsonRpc.new>[0],
18
+ "onError"
19
+ > & {
20
+ /** Cleans up integration-owned resources before replacement is announced. */
21
+ disconnectHandler?: () => PromiseLike<void> | void;
22
+ };
23
+
24
+ export class SignerJsonRpc extends Signer {
25
+ private connecting?: Promise<void>;
26
+ private disconnecting?: Promise<void>;
27
+ private scriptsPromise?: Promise<Script[]>;
28
+ private internalAddressPromise?: Promise<string>;
29
+ private identityPromise?: Promise<string>;
30
+ private readonly replacedListeners = new Set<() => void>();
31
+
32
+ private constructor(
33
+ client: Client,
34
+ readonly requestor: RequestorJsonRpc,
35
+ private info: SignerJsonRpcInfo,
36
+ private readonly disconnectHandler?: () => PromiseLike<void> | void,
37
+ ) {
38
+ super(client);
39
+ }
40
+
41
+ /** Creates a Signer that borrows an existing Transport. */
42
+ static async new(client: Client, config: SignerJsonRpcConfig) {
43
+ const { disconnectHandler, ...requestorConfig } = config;
44
+ const requestor = RequestorJsonRpc.new(requestorConfig);
45
+ const info = (await requestor.request(
46
+ "get_info",
47
+ [],
48
+ [],
49
+ SignerJsonRpcTransformers.infoTo,
50
+ )) as SignerJsonRpcInfo;
51
+
52
+ return new SignerJsonRpc(client, requestor, info, disconnectHandler);
53
+ }
54
+
55
+ get type() {
56
+ return this.info.type;
57
+ }
58
+
59
+ get signType() {
60
+ return this.info.signType;
61
+ }
62
+
63
+ get url() {
64
+ return this.requestor.url;
65
+ }
66
+
67
+ get name() {
68
+ return this.info.name;
69
+ }
70
+
71
+ get icon() {
72
+ return this.info.icon;
73
+ }
74
+
75
+ connect(): Promise<void> {
76
+ if (this.disconnecting) {
77
+ return this.disconnecting.then(() => this.connect());
78
+ }
79
+ if (this.connecting) {
80
+ return this.connecting;
81
+ }
82
+
83
+ const connecting = this.requestConnect(
84
+ signerJsonRpcNetworkIdFromAddressPrefix(this.client.addressPrefix),
85
+ ).catch((cause: unknown) => {
86
+ if (this.connecting === connecting) {
87
+ this.connecting = undefined;
88
+ }
89
+ throw cause;
90
+ });
91
+ this.connecting = connecting;
92
+ return connecting;
93
+ }
94
+
95
+ disconnect(): Promise<void> {
96
+ this.clearReadCache();
97
+ if (this.disconnecting) {
98
+ return this.disconnecting;
99
+ }
100
+ if (!this.connecting) {
101
+ return Promise.resolve();
102
+ }
103
+
104
+ this.connecting = undefined;
105
+ this.disconnecting = Promise.resolve()
106
+ .then(() => this.disconnectHandler?.())
107
+ .finally(() => {
108
+ this.replace();
109
+ this.disconnecting = undefined;
110
+ });
111
+ return this.disconnecting;
112
+ }
113
+
114
+ onReplaced(listener: () => void) {
115
+ this.replacedListeners.add(listener);
116
+ return () => this.replacedListeners.delete(listener);
117
+ }
118
+
119
+ replace() {
120
+ this.connecting = undefined;
121
+ this.clearReadCache();
122
+ const listeners = [...this.replacedListeners];
123
+ this.replacedListeners.clear();
124
+ listeners.forEach((listener) => listener());
125
+ }
126
+
127
+ async isConnected() {
128
+ return (
129
+ this.connecting?.then(
130
+ () => true,
131
+ () => false,
132
+ ) ?? false
133
+ );
134
+ }
135
+
136
+ getScripts(): Promise<Script[]> {
137
+ if (this.scriptsPromise) {
138
+ return this.scriptsPromise.then((scripts) => [...scripts]);
139
+ }
140
+
141
+ const pending = this.requestor
142
+ .request("get_scripts", [], [], (scripts: JsonRpcScript[]) =>
143
+ scripts.map((script) => JsonRpcTransformers.scriptTo(script)),
144
+ )
145
+ .catch((cause: unknown) => {
146
+ if (this.scriptsPromise === pending) {
147
+ this.scriptsPromise = undefined;
148
+ }
149
+ throw cause;
150
+ }) as Promise<Script[]>;
151
+ this.scriptsPromise = pending;
152
+ return pending.then((scripts) => [...scripts]);
153
+ }
154
+
155
+ async getAddressObjs() {
156
+ return (await this.getScripts()).map((script) =>
157
+ Address.fromScript(script, this.client),
158
+ );
159
+ }
160
+
161
+ getInternalAddress(): Promise<string> {
162
+ if (this.internalAddressPromise) {
163
+ return this.internalAddressPromise;
164
+ }
165
+
166
+ const pending = this.requestor
167
+ .request("get_native_address", [], [])
168
+ .catch((cause: unknown) => {
169
+ if (this.internalAddressPromise === pending) {
170
+ this.internalAddressPromise = undefined;
171
+ }
172
+ throw cause;
173
+ }) as Promise<string>;
174
+ this.internalAddressPromise = pending;
175
+ return pending;
176
+ }
177
+
178
+ getIdentity(): Promise<string> {
179
+ if (this.identityPromise) {
180
+ return this.identityPromise;
181
+ }
182
+
183
+ const pending = this.requestor
184
+ .request("get_identity", [], [])
185
+ .catch((cause: unknown) => {
186
+ if (this.identityPromise === pending) {
187
+ this.identityPromise = undefined;
188
+ }
189
+ throw cause;
190
+ }) as Promise<string>;
191
+ this.identityPromise = pending;
192
+ return pending;
193
+ }
194
+
195
+ signMessageRaw = this.buildSender("sign_message", [
196
+ SignerJsonRpcTransformers.messageFrom,
197
+ ]) as Signer["signMessageRaw"];
198
+
199
+ prepareTransaction = this.buildSender(
200
+ "prepare_transaction",
201
+ [JsonRpcTransformers.transactionFrom],
202
+ JsonRpcTransformers.transactionTo,
203
+ ) as Signer["prepareTransaction"];
204
+
205
+ signOnlyTransaction = this.buildSender(
206
+ "sign_transaction",
207
+ [JsonRpcTransformers.transactionFrom],
208
+ JsonRpcTransformers.transactionTo,
209
+ ) as Signer["signOnlyTransaction"];
210
+
211
+ buildSender(
212
+ rpcMethod: Parameters<RequestorJsonRpc["request"]>[0],
213
+ inTransformers?: Parameters<RequestorJsonRpc["request"]>[2],
214
+ outTransformer?: Parameters<RequestorJsonRpc["request"]>[3],
215
+ ): (...request: unknown[]) => Promise<unknown> {
216
+ return async (...request: unknown[]) =>
217
+ this.requestor.request(
218
+ rpcMethod,
219
+ request,
220
+ inTransformers,
221
+ outTransformer,
222
+ );
223
+ }
224
+
225
+ private clearReadCache() {
226
+ this.scriptsPromise = undefined;
227
+ this.internalAddressPromise = undefined;
228
+ this.identityPromise = undefined;
229
+ }
230
+
231
+ private requestConnect = this.buildSender("connect", []) as (
232
+ networkId: string,
233
+ ) => Promise<void>;
234
+ }