@ckb-ccc/joy-id 0.0.5-alpha.8 → 0.0.7-alpha.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 (58) hide show
  1. package/dist/btc/index.d.ts +46 -1
  2. package/dist/btc/index.d.ts.map +1 -1
  3. package/dist/btc/index.js +46 -3
  4. package/dist/ckb/index.d.ts +94 -2
  5. package/dist/ckb/index.d.ts.map +1 -1
  6. package/dist/ckb/index.js +115 -14
  7. package/dist/common/index.d.ts +13 -0
  8. package/dist/common/index.d.ts.map +1 -1
  9. package/dist/common/index.js +9 -0
  10. package/dist/connectionsStorage/index.d.ts +58 -0
  11. package/dist/connectionsStorage/index.d.ts.map +1 -1
  12. package/dist/connectionsStorage/index.js +30 -0
  13. package/dist/evm/index.d.ts +51 -1
  14. package/dist/evm/index.d.ts.map +1 -1
  15. package/dist/evm/index.js +52 -4
  16. package/dist/nostr/index.d.ts +51 -0
  17. package/dist/nostr/index.d.ts.map +1 -0
  18. package/dist/nostr/index.js +106 -0
  19. package/dist/signerFactory/index.d.ts +9 -0
  20. package/dist/signerFactory/index.d.ts.map +1 -1
  21. package/dist/signerFactory/index.js +14 -0
  22. package/dist.commonjs/advanced.d.ts +1 -1
  23. package/dist.commonjs/advanced.js +40 -19
  24. package/dist.commonjs/advancedBarrel.d.ts +1 -1
  25. package/dist.commonjs/advancedBarrel.js +30 -14
  26. package/dist.commonjs/barrel.d.ts +1 -1
  27. package/dist.commonjs/barrel.js +30 -14
  28. package/dist.commonjs/btc/index.d.ts +68 -16
  29. package/dist.commonjs/btc/index.d.ts.map +1 -1
  30. package/dist.commonjs/btc/index.js +145 -76
  31. package/dist.commonjs/ckb/index.d.ts +126 -27
  32. package/dist.commonjs/ckb/index.d.ts.map +1 -1
  33. package/dist.commonjs/ckb/index.js +312 -154
  34. package/dist.commonjs/common/index.d.ts +47 -21
  35. package/dist.commonjs/common/index.d.ts.map +1 -1
  36. package/dist.commonjs/common/index.js +59 -45
  37. package/dist.commonjs/connectionsStorage/index.d.ts +81 -14
  38. package/dist.commonjs/connectionsStorage/index.d.ts.map +1 -1
  39. package/dist.commonjs/connectionsStorage/index.js +58 -25
  40. package/dist.commonjs/evm/index.d.ts +72 -16
  41. package/dist.commonjs/evm/index.d.ts.map +1 -1
  42. package/dist.commonjs/evm/index.js +140 -70
  43. package/dist.commonjs/index.d.ts +1 -1
  44. package/dist.commonjs/index.js +40 -19
  45. package/dist.commonjs/nostr/index.d.ts +57 -0
  46. package/dist.commonjs/nostr/index.d.ts.map +1 -0
  47. package/dist.commonjs/nostr/index.js +129 -0
  48. package/dist.commonjs/signerFactory/index.d.ts +15 -2
  49. package/dist.commonjs/signerFactory/index.d.ts.map +1 -1
  50. package/dist.commonjs/signerFactory/index.js +52 -27
  51. package/package.json +5 -5
  52. package/src/btc/index.ts +47 -12
  53. package/src/ckb/index.ts +127 -25
  54. package/src/common/index.ts +13 -0
  55. package/src/connectionsStorage/index.ts +64 -2
  56. package/src/evm/index.ts +56 -15
  57. package/src/nostr/index.ts +135 -0
  58. package/src/signerFactory/index.ts +14 -0
package/src/ckb/index.ts CHANGED
@@ -8,17 +8,36 @@ import {
8
8
  ConnectionsRepoLocalStorage,
9
9
  } from "../connectionsStorage";
10
10
 
11
+ /**
12
+ * Class representing a CKB signer that extends Signer from @ckb-ccc/core.
13
+ * @class
14
+ * @extends {ccc.Signer}
15
+ */
11
16
  export class CkbSigner extends ccc.Signer {
17
+ /**
18
+ * Gets the signer type.
19
+ * @returns {ccc.SignerType} The type of the signer.
20
+ */
12
21
  get type(): ccc.SignerType {
13
22
  return ccc.SignerType.CKB;
14
23
  }
15
24
 
25
+ /**
26
+ * Gets the sign type.
27
+ * @returns {ccc.SignerSignType} The sign type.
28
+ */
16
29
  get signType(): ccc.SignerSignType {
17
30
  return ccc.SignerSignType.JoyId;
18
31
  }
19
32
 
20
33
  private connection?: Connection;
21
34
 
35
+ /**
36
+ * Ensures that the signer is connected and returns the connection.
37
+ * @private
38
+ * @throws Will throw an error if not connected.
39
+ * @returns {Promise<Connection>} A promise that resolves to the current connection.
40
+ */
22
41
  private async assertConnection(): Promise<Connection> {
23
42
  if (!(await this.isConnected()) || !this.connection) {
24
43
  throw new Error("Not connected");
@@ -27,6 +46,15 @@ export class CkbSigner extends ccc.Signer {
27
46
  return this.connection;
28
47
  }
29
48
 
49
+ /**
50
+ * Creates an instance of CkbSigner.
51
+ * @param {ccc.Client} client - The client instance.
52
+ * @param {string} name - The name of the signer.
53
+ * @param {string} icon - The icon URL of the signer.
54
+ * @param {string} [_appUri] - The application URI.
55
+ * @param {string} [_aggregatorUri] - The aggregator URI.
56
+ * @param {ConnectionsRepo} [connectionsRepo=new ConnectionsRepoLocalStorage()] - The connections repository.
57
+ */
30
58
  constructor(
31
59
  client: ccc.Client,
32
60
  private readonly name: string,
@@ -38,17 +66,11 @@ export class CkbSigner extends ccc.Signer {
38
66
  super(client);
39
67
  }
40
68
 
41
- async replaceClient(client: ccc.Client): Promise<CkbSigner> {
42
- return new CkbSigner(
43
- client,
44
- this.name,
45
- this.icon,
46
- this._appUri,
47
- this._aggregatorUri,
48
- this.connectionsRepo,
49
- );
50
- }
51
-
69
+ /**
70
+ * Gets the configuration for JoyID.
71
+ * @private
72
+ * @returns {object} The configuration object.
73
+ */
52
74
  private getConfig() {
53
75
  return {
54
76
  redirectURL: location.href,
@@ -61,6 +83,11 @@ export class CkbSigner extends ccc.Signer {
61
83
  };
62
84
  }
63
85
 
86
+ /**
87
+ * Gets the aggregator URI.
88
+ * @private
89
+ * @returns {string} The aggregator URI.
90
+ */
64
91
  private getAggregatorUri(): string {
65
92
  if (this._aggregatorUri) {
66
93
  return this._aggregatorUri;
@@ -71,6 +98,10 @@ export class CkbSigner extends ccc.Signer {
71
98
  : "https://cota.nervina.dev/aggregator";
72
99
  }
73
100
 
101
+ /**
102
+ * Connects to the provider by requesting authentication.
103
+ * @returns {Promise<void>} A promise that resolves when the connection is established.
104
+ */
74
105
  async connect(): Promise<void> {
75
106
  const config = this.getConfig();
76
107
 
@@ -87,11 +118,10 @@ export class CkbSigner extends ccc.Signer {
87
118
  await this.saveConnection();
88
119
  }
89
120
 
90
- async disconnect(): Promise<void> {
91
- this.connection = undefined;
92
- await this.saveConnection();
93
- }
94
-
121
+ /**
122
+ * Checks if the signer is connected.
123
+ * @returns {Promise<boolean>} A promise that resolves to true if connected, false otherwise.
124
+ */
95
125
  async isConnected(): Promise<boolean> {
96
126
  if (this.connection) {
97
127
  return true;
@@ -100,10 +130,18 @@ export class CkbSigner extends ccc.Signer {
100
130
  return this.connection !== undefined;
101
131
  }
102
132
 
133
+ /**
134
+ * Gets the internal address.
135
+ * @returns {Promise<string>} A promise that resolves to the internal address.
136
+ */
103
137
  async getInternalAddress(): Promise<string> {
104
138
  return (await this.assertConnection()).address;
105
139
  }
106
140
 
141
+ /**
142
+ * Gets the identity of the signer.
143
+ * @returns {Promise<string>} A promise that resolves to the identity.
144
+ */
107
145
  async getIdentity(): Promise<string> {
108
146
  const connection = await this.assertConnection();
109
147
  return JSON.stringify({
@@ -112,6 +150,10 @@ export class CkbSigner extends ccc.Signer {
112
150
  });
113
151
  }
114
152
 
153
+ /**
154
+ * Gets the address object.
155
+ * @returns {Promise<ccc.Address>} A promise that resolves to the address object.
156
+ */
115
157
  async getAddressObj(): Promise<ccc.Address> {
116
158
  return await ccc.Address.fromString(
117
159
  await this.getInternalAddress(),
@@ -119,14 +161,24 @@ export class CkbSigner extends ccc.Signer {
119
161
  );
120
162
  }
121
163
 
164
+ /**
165
+ * Gets the address objects.
166
+ * @returns {Promise<ccc.Address[]>} A promise that resolves to an array of address objects.
167
+ */
122
168
  async getAddressObjs(): Promise<ccc.Address[]> {
123
169
  return [await this.getAddressObj()];
124
170
  }
125
171
 
172
+ /**
173
+ * Prepares a transaction.
174
+ * @param {ccc.TransactionLike} txLike - The transaction-like object.
175
+ * @returns {Promise<ccc.Transaction>} A promise that resolves to the prepared transaction.
176
+ */
126
177
  async prepareTransaction(
127
178
  txLike: ccc.TransactionLike,
128
179
  ): Promise<ccc.Transaction> {
129
180
  const tx = ccc.Transaction.from(txLike);
181
+ await tx.addCellDepsOfKnownScripts(this.client, ccc.KnownScript.JoyId);
130
182
  const position = await tx.findInputIndexByLock(
131
183
  (await this.getAddressObj()).script,
132
184
  this.client,
@@ -136,11 +188,20 @@ export class CkbSigner extends ccc.Signer {
136
188
  }
137
189
 
138
190
  const witness = tx.getWitnessArgsAt(position) ?? ccc.WitnessArgs.from({});
139
- witness.lock = "0x";
191
+ witness.lock = ccc.hexFrom("00".repeat(1000));
140
192
  await this.prepareTransactionForSubKey(tx, witness);
141
- return tx.setWitnessArgsAt(position, witness);
193
+ tx.setWitnessArgsAt(position, witness);
194
+
195
+ return tx;
142
196
  }
143
197
 
198
+ /**
199
+ * Prepares a transaction for a sub key.
200
+ * @private
201
+ * @param tx - The transaction object.
202
+ * @param witness - The witness arguments.
203
+ * @throws Will throw an error if no COTA cells are found for the sub key wallet.
204
+ */
144
205
  private async prepareTransactionForSubKey(
145
206
  tx: ccc.Transaction,
146
207
  witness: ccc.WitnessArgs,
@@ -161,10 +222,10 @@ export class CkbSigner extends ccc.Signer {
161
222
  witness.outputType = ccc.hexFrom(unlockEntry);
162
223
 
163
224
  const cotaDeps: ccc.CellDep[] = [];
164
- for await (const cell of this.client.findCellsByLockAndType(lock, {
165
- ...(await this.client.getKnownScript(ccc.KnownScript.COTA)),
166
- args: "0x",
167
- })) {
225
+ for await (const cell of this.client.findCellsByLockAndType(
226
+ lock,
227
+ await ccc.Script.fromKnownScript(this.client, ccc.KnownScript.COTA, "0x"),
228
+ )) {
168
229
  cotaDeps.push(
169
230
  ccc.CellDep.from({
170
231
  depType: "code",
@@ -180,11 +241,37 @@ export class CkbSigner extends ccc.Signer {
180
241
  tx.cellDeps.unshift(...cotaDeps);
181
242
  }
182
243
 
244
+ /**
245
+ * Signs a transaction.
246
+ * @param {ccc.TransactionLike} txLike - The transaction-like object.
247
+ * @returns {Promise<ccc.Transaction>} A promise that resolves to the signed transaction.
248
+ */
183
249
  async signOnlyTransaction(
184
250
  txLike: ccc.TransactionLike,
185
251
  ): Promise<ccc.Transaction> {
186
252
  const tx = ccc.Transaction.from(txLike);
187
253
  const { script } = await this.getAddressObj();
254
+ const witnessIndexes = await ccc.reduceAsync(
255
+ tx.inputs,
256
+ async (acc, input, i) => {
257
+ await input.completeExtraInfos(this.client);
258
+ if (!input.cellOutput) {
259
+ throw new Error("Unable to complete input");
260
+ }
261
+
262
+ if (input.cellOutput.lock.eq(script)) {
263
+ acc.push(i);
264
+ }
265
+ },
266
+ [] as number[],
267
+ );
268
+
269
+ // Trim unnecessary fields to reduce tx size
270
+ await tx.prepareSighashAllWitness(script, 0, this.client);
271
+ tx.inputs.forEach((i) => {
272
+ i.cellOutput = undefined;
273
+ i.outputData = undefined;
274
+ });
188
275
 
189
276
  const config = this.getConfig();
190
277
  const res = await createPopup(
@@ -193,7 +280,7 @@ export class CkbSigner extends ccc.Signer {
193
280
  ...config,
194
281
  tx: JSON.parse(tx.stringify()),
195
282
  signerAddress: (await this.assertConnection()).address,
196
- witnessIndex: await tx.findInputIndexByLock(script, this.client),
283
+ witnessIndexes,
197
284
  },
198
285
  "popup",
199
286
  "/sign-ckb-raw-tx",
@@ -207,6 +294,11 @@ export class CkbSigner extends ccc.Signer {
207
294
  return ccc.Transaction.from(res.tx);
208
295
  }
209
296
 
297
+ /**
298
+ * Signs a raw message with the account.
299
+ * @param {string | ccc.BytesLike} message - The message to sign.
300
+ * @returns {Promise<string>} A promise that resolves to the signed message.
301
+ */
210
302
  async signMessageRaw(message: string | ccc.BytesLike): Promise<string> {
211
303
  const { address } = await this.assertConnection();
212
304
 
@@ -234,7 +326,12 @@ export class CkbSigner extends ccc.Signer {
234
326
  });
235
327
  }
236
328
 
237
- private async saveConnection() {
329
+ /**
330
+ * Saves the current connection.
331
+ * @private
332
+ * @returns {Promise<void>}
333
+ */
334
+ private async saveConnection(): Promise<void> {
238
335
  return this.connectionsRepo.set(
239
336
  {
240
337
  uri: this.getConfig().joyidAppURL,
@@ -244,7 +341,12 @@ export class CkbSigner extends ccc.Signer {
244
341
  );
245
342
  }
246
343
 
247
- private async restoreConnection() {
344
+ /**
345
+ * Restores the previous connection.
346
+ * @private
347
+ * @returns {Promise<void>}
348
+ */
349
+ private async restoreConnection(): Promise<void> {
248
350
  this.connection = await this.connectionsRepo.get({
249
351
  uri: this.getConfig().joyidAppURL,
250
352
  addressType: "ckb",
@@ -16,6 +16,10 @@ import {
16
16
  openPopup,
17
17
  } from "@joyid/common";
18
18
 
19
+ /**
20
+ * Interface representing the return type for various Dapp request types.
21
+ * @interface
22
+ */
19
23
  export interface PopupReturnType {
20
24
  [DappRequestType.Auth]: AuthResponseData;
21
25
  [DappRequestType.SignMessage]: SignMessageResponseData;
@@ -36,6 +40,15 @@ export interface PopupReturnType {
36
40
  [DappRequestType.EvmWeb2Login]: EvmWeb2LoginResponse;
37
41
  }
38
42
 
43
+ /**
44
+ * Creates a popup window for JoyID Dapp requests.
45
+ * @param {string} url - The URL to open in the popup.
46
+ * @param {PopupConfigOptions<T> & { joyidAppURL: string }} config - The popup configuration options.
47
+ * @returns {Promise<PopupReturnType[T]>} A promise that resolves to the response data of the requested type.
48
+ * @throws {PopupNotSupportedError} If popups are not supported in the current browser.
49
+ * @throws {PopupCancelledError} If the popup is closed by the user.
50
+ * @throws {PopupTimeoutError} If the popup operation times out.
51
+ */
39
52
  export async function createPopup<T extends DappRequestType>(
40
53
  url: string,
41
54
  config: PopupConfigOptions<T> & { joyidAppURL: string },
@@ -1,42 +1,104 @@
1
1
  import { ccc } from "@ckb-ccc/core";
2
2
 
3
+ /**
4
+ * Type representing an account selector with a URI and address type.
5
+ * @typedef {Object} AccountSelector
6
+ * @property {string} uri - The URI of the account.
7
+ * @property {string} addressType - The address type of the account.
8
+ */
3
9
  export type AccountSelector = {
4
10
  uri: string;
5
11
  addressType: string;
6
12
  };
7
13
 
8
- export function isSelectorEq(a: AccountSelector, b: AccountSelector) {
14
+ /**
15
+ * Checks if two AccountSelectors are equal.
16
+ * @param {AccountSelector} a - The first account selector.
17
+ * @param {AccountSelector} b - The second account selector.
18
+ * @returns {boolean} True if the selectors are equal, false otherwise.
19
+ */
20
+ export function isSelectorEq(a: AccountSelector, b: AccountSelector): boolean {
9
21
  return a.uri === b.uri && a.addressType === b.addressType;
10
22
  }
11
23
 
24
+ /**
25
+ * Type representing a connection with an address, public key, and key type.
26
+ * @typedef {Object} Connection
27
+ * @property {string} address - The address of the connection.
28
+ * @property {ccc.Hex} publicKey - The public key of the connection.
29
+ * @property {string} keyType - The key type of the connection.
30
+ */
12
31
  export type Connection = {
13
32
  readonly address: string;
14
33
  readonly publicKey: ccc.Hex;
15
34
  readonly keyType: string;
16
35
  };
17
36
 
37
+ /**
38
+ * Interface representing a repository for managing connections.
39
+ * @interface
40
+ */
18
41
  export interface ConnectionsRepo {
42
+ /**
43
+ * Gets a connection for the given selector.
44
+ * @param {AccountSelector} selector - The account selector.
45
+ * @returns {Promise<Connection | undefined>} A promise that resolves to the connection, if found.
46
+ */
19
47
  get(selector: AccountSelector): Promise<Connection | undefined>;
48
+
49
+ /**
50
+ * Sets a connection for the given selector.
51
+ * @param {AccountSelector} selector - The account selector.
52
+ * @param {Connection | undefined} connection - The connection to set.
53
+ * @returns {Promise<void>} A promise that resolves when the connection is set.
54
+ */
20
55
  set(
21
56
  selector: AccountSelector,
22
57
  connection: Connection | undefined,
23
58
  ): Promise<void>;
24
59
  }
25
60
 
61
+ /**
62
+ * Class representing a local storage-based repository for managing connections.
63
+ * @class
64
+ * @implements {ConnectionsRepo}
65
+ */
26
66
  export class ConnectionsRepoLocalStorage implements ConnectionsRepo {
67
+ /**
68
+ * Creates an instance of ConnectionsRepoLocalStorage.
69
+ * @param {string} [storageKey="ccc-joy-id-signer"] - The local storage key.
70
+ */
27
71
  constructor(private readonly storageKey = "ccc-joy-id-signer") {}
28
72
 
73
+ /**
74
+ * Reads all connections from local storage.
75
+ * @returns {Promise<[AccountSelector, Connection][]>} A promise that resolves to an array of selectors and connections.
76
+ */
29
77
  async readConnections(): Promise<[AccountSelector, Connection][]> {
30
78
  return JSON.parse(window.localStorage.getItem(this.storageKey) ?? "[]");
31
79
  }
32
80
 
81
+ /**
82
+ * Gets a connection for the given selector.
83
+ * @param {AccountSelector} selector - The account selector.
84
+ * @returns {Promise<Connection | undefined>} A promise that resolves to the connection, if found.
85
+ */
33
86
  async get(selector: AccountSelector): Promise<Connection | undefined> {
34
87
  return (await this.readConnections()).find(([s]) =>
35
88
  isSelectorEq(selector, s),
36
89
  )?.[1];
37
90
  }
38
91
 
39
- async set(selector: AccountSelector, connection: Connection | undefined) {
92
+ /**
93
+ * Sets a connection for the given selector.
94
+ * @param {AccountSelector} selector - The account selector.
95
+ * @param {Connection | undefined} connection - The connection to set.
96
+ * @returns {Promise<void>}
97
+ */
98
+ async set(
99
+ selector: AccountSelector,
100
+ connection: Connection | undefined,
101
+ ): Promise<void> {
40
102
  const connections = await this.readConnections();
41
103
 
42
104
  if (connection) {
package/src/evm/index.ts CHANGED
@@ -7,10 +7,21 @@ import {
7
7
  ConnectionsRepoLocalStorage,
8
8
  } from "../connectionsStorage";
9
9
 
10
+ /**
11
+ * Class representing an EVM signer that extends SignerEvm from @ckb-ccc/core.
12
+ * @class
13
+ * @extends {ccc.SignerEvm}
14
+ */
10
15
  export class EvmSigner extends ccc.SignerEvm {
11
16
  private connection?: Connection;
12
17
 
13
- private assertConnection() {
18
+ /**
19
+ * Ensures that the signer is connected and returns the connection.
20
+ * @private
21
+ * @throws Will throw an error if not connected.
22
+ * @returns {Connection} The current connection.
23
+ */
24
+ private assertConnection(): Connection {
14
25
  if (!this.isConnected() || !this.connection) {
15
26
  throw new Error("Not connected");
16
27
  }
@@ -18,6 +29,14 @@ export class EvmSigner extends ccc.SignerEvm {
18
29
  return this.connection;
19
30
  }
20
31
 
32
+ /**
33
+ * Creates an instance of EvmSigner.
34
+ * @param {ccc.Client} client - The client instance.
35
+ * @param {string} name - The name of the signer.
36
+ * @param {string} icon - The icon URL of the signer.
37
+ * @param {string} [appUri="https://app.joy.id"] - The application URI.
38
+ * @param {ConnectionsRepo} [connectionsRepo=new ConnectionsRepoLocalStorage()] - The connections repository.
39
+ */
21
40
  constructor(
22
41
  client: ccc.Client,
23
42
  private readonly name: string,
@@ -28,16 +47,11 @@ export class EvmSigner extends ccc.SignerEvm {
28
47
  super(client);
29
48
  }
30
49
 
31
- async replaceClient(client: ccc.Client): Promise<EvmSigner> {
32
- return new EvmSigner(
33
- client,
34
- this.name,
35
- this.icon,
36
- this.appUri,
37
- this.connectionsRepo,
38
- );
39
- }
40
-
50
+ /**
51
+ * Gets the configuration for JoyID.
52
+ * @private
53
+ * @returns {object} The configuration object.
54
+ */
41
55
  private getConfig() {
42
56
  return {
43
57
  redirectURL: location.href,
@@ -48,12 +62,20 @@ export class EvmSigner extends ccc.SignerEvm {
48
62
  };
49
63
  }
50
64
 
51
- async getEvmAccount() {
65
+ /**
66
+ * Gets the EVM account address.
67
+ * @returns {Promise<string>} A promise that resolves to the EVM account address.
68
+ */
69
+ async getEvmAccount(): Promise<string> {
52
70
  return this.assertConnection().address;
53
71
  }
54
72
 
73
+ /**
74
+ * Connects to the provider by requesting authentication.
75
+ * @returns {Promise<void>} A promise that resolves when the connection is established.
76
+ */
55
77
  async connect(): Promise<void> {
56
- const config = await this.getConfig();
78
+ const config = this.getConfig();
57
79
 
58
80
  const res = await createPopup(buildJoyIDURL(config, "popup", "/auth"), {
59
81
  ...config,
@@ -68,6 +90,10 @@ export class EvmSigner extends ccc.SignerEvm {
68
90
  await this.saveConnection();
69
91
  }
70
92
 
93
+ /**
94
+ * Checks if the signer is connected.
95
+ * @returns {Promise<boolean>} A promise that resolves to true if connected, false otherwise.
96
+ */
71
97
  async isConnected(): Promise<boolean> {
72
98
  if (this.connection) {
73
99
  return true;
@@ -76,6 +102,11 @@ export class EvmSigner extends ccc.SignerEvm {
76
102
  return this.connection !== undefined;
77
103
  }
78
104
 
105
+ /**
106
+ * Signs a raw message with the EVM account.
107
+ * @param {string | ccc.BytesLike} message - The message to sign.
108
+ * @returns {Promise<ccc.Hex>} A promise that resolves to the signed message.
109
+ */
79
110
  async signMessageRaw(message: string | ccc.BytesLike): Promise<ccc.Hex> {
80
111
  const { address } = this.assertConnection();
81
112
 
@@ -99,7 +130,12 @@ export class EvmSigner extends ccc.SignerEvm {
99
130
  return ccc.hexFrom(signature);
100
131
  }
101
132
 
102
- private async saveConnection() {
133
+ /**
134
+ * Saves the current connection.
135
+ * @private
136
+ * @returns {Promise<void>}
137
+ */
138
+ private async saveConnection(): Promise<void> {
103
139
  return this.connectionsRepo.set(
104
140
  {
105
141
  uri: this.getConfig().joyidAppURL,
@@ -109,7 +145,12 @@ export class EvmSigner extends ccc.SignerEvm {
109
145
  );
110
146
  }
111
147
 
112
- private async restoreConnection() {
148
+ /**
149
+ * Restores the previous connection.
150
+ * @private
151
+ * @returns {Promise<void>}
152
+ */
153
+ private async restoreConnection(): Promise<void> {
113
154
  this.connection = await this.connectionsRepo.get({
114
155
  uri: this.getConfig().joyidAppURL,
115
156
  addressType: "ethereum",
@@ -0,0 +1,135 @@
1
+ import { ccc } from "@ckb-ccc/core";
2
+ import { DappRequestType, buildJoyIDURL } from "@joyid/common";
3
+ import { createPopup } from "../common";
4
+ import {
5
+ Connection,
6
+ ConnectionsRepo,
7
+ ConnectionsRepoLocalStorage,
8
+ } from "../connectionsStorage";
9
+
10
+ /**
11
+ * Class representing a Nostr signer that extends SignerNostr from @ckb-ccc/core.
12
+ * @class
13
+ * @extends {ccc.SignerNostr}
14
+ */
15
+ export class NostrSigner extends ccc.SignerNostr {
16
+ private connection?: Connection;
17
+
18
+ /**
19
+ * Ensures that the signer is connected and returns the connection.
20
+ * @private
21
+ * @throws Will throw an error if not connected.
22
+ * @returns {Connection} The current connection.
23
+ */
24
+ private assertConnection(): Connection {
25
+ if (!this.isConnected() || !this.connection) {
26
+ throw new Error("Not connected");
27
+ }
28
+
29
+ return this.connection;
30
+ }
31
+
32
+ /**
33
+ * Creates an instance of NostrSigner.
34
+ * @param {ccc.Client} client - The client instance.
35
+ * @param {string} name - The name of the signer.
36
+ * @param {string} icon - The icon URL of the signer.
37
+ * @param {string} [appUri="https://app.joy.id"] - The application URI.
38
+ * @param {ConnectionsRepo} [connectionsRepo=new ConnectionsRepoLocalStorage()] - The connections repository.
39
+ */
40
+ constructor(
41
+ client: ccc.Client,
42
+ private readonly name: string,
43
+ private readonly icon: string,
44
+ private readonly appUri = "https://app.joy.id",
45
+ private readonly connectionsRepo: ConnectionsRepo = new ConnectionsRepoLocalStorage(),
46
+ ) {
47
+ super(client);
48
+ }
49
+
50
+ static isValidClient(client: ccc.Client): boolean {
51
+ return client.addressPrefix === "ckt";
52
+ }
53
+
54
+ async replaceClient(client: ccc.Client): Promise<boolean> {
55
+ if (!NostrSigner.isValidClient(client)) {
56
+ return false;
57
+ }
58
+ return super.replaceClient(client);
59
+ }
60
+
61
+ /**
62
+ * Gets the configuration for JoyID.
63
+ * @private
64
+ * @returns {object} The configuration object.
65
+ */
66
+ private getConfig() {
67
+ return {
68
+ redirectURL: location.href,
69
+ joyidAppURL: this.appUri,
70
+ requestNetwork: "nostr",
71
+ name: this.name,
72
+ logo: this.icon,
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Connects to the provider by requesting authentication.
78
+ * @returns {Promise<void>} A promise that resolves when the connection is established.
79
+ */
80
+ async connect(): Promise<void> {
81
+ const config = this.getConfig();
82
+ const res = await createPopup(buildJoyIDURL(config, "popup", "/auth"), {
83
+ ...config,
84
+ type: DappRequestType.Auth,
85
+ });
86
+
87
+ this.connection = {
88
+ address: "",
89
+ publicKey: ccc.hexFrom(res.nostrPubkey),
90
+ keyType: res.keyType,
91
+ };
92
+ await this.connectionsRepo.set(
93
+ { uri: this.appUri, addressType: "nostr" },
94
+ this.connection,
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Checks if the signer is connected.
100
+ * @returns {Promise<boolean>} A promise that resolves to true if connected, false otherwise.
101
+ */
102
+ async isConnected(): Promise<boolean> {
103
+ if (this.connection) {
104
+ return true;
105
+ }
106
+
107
+ this.connection = await this.connectionsRepo.get({
108
+ uri: this.appUri,
109
+ addressType: "nostr",
110
+ });
111
+ return this.connection !== undefined;
112
+ }
113
+
114
+ async getNostrPublicKey(): Promise<ccc.Hex> {
115
+ if (!this.connection) {
116
+ throw new Error("Not connected");
117
+ }
118
+
119
+ return this.connection.publicKey;
120
+ }
121
+
122
+ async signNostrEvent(
123
+ event: ccc.NostrEvent,
124
+ ): Promise<Required<ccc.NostrEvent>> {
125
+ const config = this.getConfig();
126
+ const res = await createPopup(
127
+ buildJoyIDURL({ ...config, event }, "popup", "/sign-nostr-event"),
128
+ {
129
+ ...config,
130
+ type: DappRequestType.SignNostrEvent,
131
+ },
132
+ );
133
+ return res.event;
134
+ }
135
+ }