@keplr-wallet/hooks-starknet 0.12.194 → 0.12.195-rc.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.
@@ -1,120 +1,16 @@
1
1
  import {
2
2
  IRecipientConfig,
3
- IRecipientConfigWithStarknetID,
4
3
  UIProperties,
4
+ IRecipientConfigWithNameServices,
5
5
  } from "./types";
6
6
  import { TxChainSetter } from "./chain";
7
7
  import { ChainGetter } from "@keplr-wallet/stores";
8
- import {
9
- action,
10
- computed,
11
- makeObservable,
12
- observable,
13
- runInAction,
14
- } from "mobx";
15
- import {
16
- EmptyAddressError,
17
- InvalidHexError,
18
- StarknetIDFailedToFetchError,
19
- StarknetIDIsFetchingError,
20
- } from "./errors";
8
+ import { action, computed, makeObservable, observable } from "mobx";
9
+ import { EmptyAddressError, InvalidHexError } from "./errors";
21
10
  import { useState } from "react";
22
- import { Buffer } from "buffer/";
23
- import { simpleFetch } from "@keplr-wallet/simple-fetch";
24
- import { CallData, constants } from "starknet";
25
-
26
- interface StarknetIDFetchData {
27
- isFetching: boolean;
28
- starknetHexAddress?: string;
29
- error?: Error;
30
- }
31
-
32
- const networkToNamingContractAddress = {
33
- [constants.NetworkName.SN_MAIN]:
34
- "0x6ac597f8116f886fa1c97a23fa4e08299975ecaf6b598873ca6792b9bbfb678",
35
- [constants.NetworkName.SN_SEPOLIA]:
36
- "0x0707f09bc576bd7cfee59694846291047e965f4184fe13dac62c56759b3b6fa7",
37
- };
38
-
39
- const basicAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789-";
40
- const basicSizePlusOne = BigInt(basicAlphabet.length + 1);
41
- const bigAlphabet = "这来";
42
- const basicAlphabetSize = BigInt(basicAlphabet.length);
43
- const bigAlphabetSize = BigInt(bigAlphabet.length);
44
-
45
- // Reference: https://github.com/lfglabs-dev/starknetid.js/blob/main/packages/core/src/utils.ts
46
- function isStarkDomain(domain: string): boolean {
47
- return /^(?:[a-z0-9-]{1,48}(?:[a-z0-9-]{1,48}[a-z0-9-])?\.)*[a-z0-9-]{1,48}\.stark$/.test(
48
- domain
49
- );
50
- }
51
-
52
- function encodeDomain(domain: string | undefined | null): bigint[] {
53
- if (!domain) return [BigInt(0)];
54
-
55
- const encoded = [];
56
- for (const subdomain of domain.replace(".stark", "").split("."))
57
- encoded.push(encode(subdomain));
58
- return encoded;
59
- }
60
-
61
- function extractStars(str: string): [string, number] {
62
- let k = 0;
63
- while (str.endsWith(bigAlphabet[bigAlphabet.length - 1])) {
64
- str = str.substring(0, str.length - 1);
65
- k += 1;
66
- }
67
- return [str, k];
68
- }
69
-
70
- function encode(decoded: string | undefined): bigint {
71
- let encoded = BigInt(0);
72
- let multiplier = BigInt(1);
73
-
74
- if (!decoded) {
75
- return encoded;
76
- }
77
-
78
- if (decoded.endsWith(bigAlphabet[0] + basicAlphabet[1])) {
79
- const [str, k] = extractStars(decoded.substring(0, decoded.length - 2));
80
- decoded = str + bigAlphabet[bigAlphabet.length - 1].repeat(2 * (k + 1));
81
- } else {
82
- const [str, k] = extractStars(decoded);
83
- if (k)
84
- decoded =
85
- str + bigAlphabet[bigAlphabet.length - 1].repeat(1 + 2 * (k - 1));
86
- }
87
-
88
- for (let i = 0; i < decoded.length; i += 1) {
89
- const char = decoded[i];
90
- const index = basicAlphabet.indexOf(char);
91
- const bnIndex = BigInt(basicAlphabet.indexOf(char));
92
-
93
- if (index !== -1) {
94
- // add encoded + multiplier * index
95
- if (i === decoded.length - 1 && decoded[i] === basicAlphabet[0]) {
96
- encoded += multiplier * basicAlphabetSize;
97
- multiplier *= basicSizePlusOne;
98
- // add 0
99
- multiplier *= basicSizePlusOne;
100
- } else {
101
- encoded += multiplier * bnIndex;
102
- multiplier *= basicSizePlusOne;
103
- }
104
- } else if (bigAlphabet.indexOf(char) !== -1) {
105
- // add encoded + multiplier * (basicAlphabetSize)
106
- encoded += multiplier * basicAlphabetSize;
107
- multiplier *= basicSizePlusOne;
108
- // add encoded + multiplier * index
109
- const newid =
110
- (i === decoded.length - 1 ? 1 : 0) + bigAlphabet.indexOf(char);
111
- encoded += multiplier * BigInt(newid);
112
- multiplier *= bigAlphabetSize;
113
- }
114
- }
115
-
116
- return encoded;
117
- }
11
+ import { Buffer } from "buffer";
12
+ import { NameService } from "./name-service";
13
+ import { StarknetIdNameService } from "./name-service-starknet-id";
118
14
 
119
15
  function isStarknetHexAddress(address: string): boolean {
120
16
  if (!address.startsWith("0x")) {
@@ -135,179 +31,96 @@ function isStarknetHexAddress(address: string): boolean {
135
31
 
136
32
  export class RecipientConfig
137
33
  extends TxChainSetter
138
- implements IRecipientConfig, IRecipientConfigWithStarknetID
34
+ implements IRecipientConfig, IRecipientConfigWithNameServices
139
35
  {
140
36
  @observable
141
37
  protected _value: string = "";
142
38
 
143
- // Deep equal check is required to avoid infinite re-render.
144
- @observable.struct
145
- protected _starknetID:
146
- | {
147
- networkName: string;
148
- namingContractAddress: string;
149
- }
150
- | undefined = undefined;
39
+ @observable
40
+ protected _preferredNameService: string | undefined = undefined;
151
41
 
152
- // Key is {chain identifier}/{starknet username}
153
- @observable.shallow
154
- protected _starknetIDFetchDataMap = new Map<string, StarknetIDFetchData>();
42
+ @observable.ref
43
+ protected nameServices: NameService[] = [];
155
44
 
156
45
  constructor(chainGetter: ChainGetter, initialChainId: string) {
157
46
  super(chainGetter, initialChainId);
158
47
 
48
+ this.nameServices.push(new StarknetIdNameService(this, chainGetter));
49
+
159
50
  makeObservable(this);
160
51
  }
161
52
 
162
- @action
163
- setStarknetID(chainId: string) {
164
- const split = chainId.split(":"); // `starknet:networkName`
165
- if (split.length < 2) {
166
- return;
167
- }
168
-
169
- const networkName = split[1] as constants.NetworkName;
170
- if (!networkName) {
171
- return;
172
- }
173
-
174
- const namingContractAddress = networkToNamingContractAddress[networkName];
175
- if (!namingContractAddress) {
176
- return;
177
- }
178
-
179
- this._starknetID = {
180
- networkName,
181
- namingContractAddress,
182
- };
53
+ get preferredNameService(): string | undefined {
54
+ return this._preferredNameService;
183
55
  }
184
56
 
185
- protected getStarknetIDFetchData(username: string): StarknetIDFetchData {
186
- const modularChainInfo = this.chainGetter.getModularChain(this.chainId);
187
- if (!("starknet" in modularChainInfo)) {
188
- throw new Error(`${this.chainId} is not starknet chain`);
189
- }
190
-
191
- if (!this._starknetID) {
192
- throw new Error("Starknet ID is not set");
193
- }
194
-
195
- if (!isStarkDomain(username)) {
196
- return {
197
- isFetching: false,
198
- error: new Error("Invalid domain for Starknet ID"),
199
- };
200
- }
201
-
202
- const key = `${this.chainId}/${username}`;
203
-
204
- if (!this._starknetIDFetchDataMap.has(key)) {
205
- runInAction(() => {
206
- this._starknetIDFetchDataMap.set(key, {
207
- isFetching: true,
208
- });
209
- });
210
-
211
- const domain = encodeDomain(username).map((v) => v.toString(10));
212
-
213
- simpleFetch<{
214
- jsonrpc: "2.0";
215
- result?: string[];
216
- id: string;
217
- error?: {
218
- code?: number;
219
- message?: string;
220
- };
221
- }>(modularChainInfo.starknet.rpc, "", {
222
- method: "POST",
223
- headers: {
224
- "content-type": "application/json",
225
- },
226
- body: JSON.stringify({
227
- jsonrpc: "2.0",
228
- id: "1",
229
- method: "starknet_call",
230
- params: [
231
- {
232
- contract_address: this._starknetID.namingContractAddress,
233
- calldata: CallData.toHex({ domain, hint: [] }),
234
- entry_point_selector:
235
- "0x2e269d930f6d7ab92b15ce8ff9f5e63709391617e3465fff79ba6baf278ce60", // selector.getSelectorFromName("domain_to_address"),
236
- },
237
- "latest",
238
- ],
239
- }),
240
- signal: new AbortController().signal,
241
- })
242
- .then((resp) => {
243
- if (resp.data.error && resp.data.error.message) {
244
- throw new StarknetIDIsFetchingError(resp.data.error.message);
245
- }
246
-
247
- const data = resp.data.result;
248
- if (!data) {
249
- throw new StarknetIDIsFetchingError("no address found");
250
- }
251
-
252
- const rawHexAddr = data[0];
253
- if (rawHexAddr === "0x0") {
254
- throw new StarknetIDIsFetchingError("no address found");
255
- }
256
-
257
- const addr = "0x" + rawHexAddr.replace("0x", "").padStart(64, "0");
258
-
259
- if (!isStarknetHexAddress(addr)) {
260
- throw new StarknetIDIsFetchingError("no address found");
261
- }
262
-
263
- runInAction(() => {
264
- this._starknetIDFetchDataMap.set(key, {
265
- isFetching: false,
266
- starknetHexAddress: addr,
267
- });
268
- });
269
- })
270
- .catch((error) => {
271
- runInAction(() => {
272
- this._starknetIDFetchDataMap.set(key, {
273
- isFetching: false,
274
- error,
275
- });
276
- });
277
- });
278
- }
279
-
280
- return this._starknetIDFetchDataMap.get(key) ?? { isFetching: false };
57
+ @action
58
+ setPreferredNameService(nameService: string | undefined) {
59
+ this._preferredNameService = nameService;
281
60
  }
282
61
 
283
- get isStarknetIDEnabled(): boolean {
284
- return !!this._starknetID;
62
+ getNameService(type: string): NameService | undefined {
63
+ return this.nameServices.find((nameService) => nameService.type === type);
285
64
  }
286
65
 
287
- @computed
288
- get isStarknetID(): boolean {
289
- if (this.isStarknetIDEnabled) {
290
- const parsed = this.value.trim().split(".");
291
- return parsed.length > 1 && parsed[parsed.length - 1] === "stark";
292
- }
293
-
294
- return false;
66
+ getNameServices(): NameService[] {
67
+ return this.nameServices;
295
68
  }
296
69
 
297
70
  @computed
298
- get isStarknetIDFetching(): boolean {
299
- if (!this.isStarknetIDEnabled || !this.isStarknetID) {
300
- return false;
301
- }
71
+ get nameServiceResult(): {
72
+ type: string;
73
+ address: string;
74
+ fullName: string;
75
+ domain: string;
76
+ suffix: string;
77
+ }[] {
78
+ const result: {
79
+ type: string;
80
+ address: string;
81
+ fullName: string;
82
+ domain: string;
83
+ suffix: string;
84
+ }[] = [];
85
+ for (const nameService of this.nameServices) {
86
+ if (
87
+ this.preferredNameService &&
88
+ nameService.type !== this.preferredNameService
89
+ ) {
90
+ continue;
91
+ }
302
92
 
303
- return this.getStarknetIDFetchData(this.value.trim()).isFetching;
93
+ const r = nameService.result;
94
+ if (r) {
95
+ result.push({
96
+ ...r,
97
+ type: nameService.type,
98
+ });
99
+ }
100
+ }
101
+ return result;
304
102
  }
305
103
 
306
- get starknetExpectedDomain(): string {
307
- return "stark";
104
+ @action
105
+ setStarknetID(chainId: string) {
106
+ const found = this.nameServices.find(
107
+ (nameService) => nameService.type === "starknet-id"
108
+ );
109
+ if (found) {
110
+ (found as StarknetIdNameService).setStarknetID(chainId);
111
+ } else {
112
+ this.nameServices.push(
113
+ new StarknetIdNameService(this, this.chainGetter, chainId)
114
+ );
115
+ }
308
116
  }
309
117
 
310
118
  get recipient(): string {
119
+ if (this.nameServiceResult.length > 0) {
120
+ const r = this.nameServiceResult[0];
121
+ return r.address;
122
+ }
123
+
311
124
  const rawRecipient = this.value.trim();
312
125
 
313
126
  const modularChainInfo = this.modularChainInfo;
@@ -315,22 +128,12 @@ export class RecipientConfig
315
128
  throw new Error("Chain doesn't support the starknet");
316
129
  }
317
130
 
318
- if (this.isStarknetIDEnabled && this.isStarknetID) {
319
- try {
320
- return (
321
- this.getStarknetIDFetchData(rawRecipient).starknetHexAddress || ""
322
- );
323
- } catch {
324
- return "";
325
- }
326
- }
327
-
328
131
  return rawRecipient;
329
132
  }
330
133
 
331
134
  @computed
332
135
  get uiProperties(): UIProperties {
333
- const rawRecipient = this.value.trim();
136
+ let rawRecipient = this.value.trim();
334
137
 
335
138
  if (!rawRecipient) {
336
139
  return {
@@ -338,46 +141,9 @@ export class RecipientConfig
338
141
  };
339
142
  }
340
143
 
341
- if (this.isStarknetIDEnabled && this.isStarknetID) {
342
- try {
343
- const fetched = this.getStarknetIDFetchData(rawRecipient);
344
-
345
- if (fetched.isFetching) {
346
- return {
347
- loadingState: "loading-block",
348
- };
349
- }
350
-
351
- if (fetched.error) {
352
- if (fetched.error instanceof StarknetIDIsFetchingError) {
353
- return {
354
- error: new StarknetIDFailedToFetchError(
355
- "Failed to fetch the address from Starknet ID"
356
- ),
357
- loadingState: fetched.isFetching ? "loading-block" : undefined,
358
- };
359
- }
360
-
361
- return {
362
- error: fetched.error,
363
- };
364
- }
365
-
366
- if (!fetched.starknetHexAddress) {
367
- return {
368
- error: new StarknetIDFailedToFetchError(
369
- "Failed to fetch the address from Starknet ID"
370
- ),
371
- loadingState: fetched.isFetching ? "loading-block" : undefined,
372
- };
373
- }
374
-
375
- return {};
376
- } catch (e) {
377
- return {
378
- error: e,
379
- };
380
- }
144
+ if (this.nameServiceResult.length > 0) {
145
+ const r = this.nameServiceResult[0];
146
+ rawRecipient = r.address;
381
147
  }
382
148
 
383
149
  if (!isStarknetHexAddress(rawRecipient)) {
@@ -396,6 +162,10 @@ export class RecipientConfig
396
162
  @action
397
163
  setValue(value: string): void {
398
164
  this._value = value;
165
+
166
+ for (const nameService of this.nameServices) {
167
+ nameService.setValue(value);
168
+ }
399
169
  }
400
170
  }
401
171
 
package/src/tx/types.ts CHANGED
@@ -1,9 +1,11 @@
1
- import { ERC20Currency } from "@keplr-wallet/types";
1
+ import { ERC20Currency, ModularChainInfo } from "@keplr-wallet/types";
2
2
  import { CoinPretty } from "@keplr-wallet/unit";
3
+ import { NameService } from "./name-service";
3
4
 
4
5
  export interface ITxChainSetter {
5
6
  chainId: string;
6
7
  setChain(chainId: string): void;
8
+ modularChainInfo: ModularChainInfo;
7
9
  }
8
10
 
9
11
  export interface UIProperties {
@@ -64,11 +66,21 @@ export interface IRecipientConfig extends ITxChainSetter {
64
66
  uiProperties: UIProperties;
65
67
  }
66
68
 
67
- export interface IRecipientConfigWithStarknetID extends IRecipientConfig {
68
- readonly isStarknetIDEnabled: boolean;
69
- readonly isStarknetID: boolean;
70
- readonly starknetExpectedDomain: string;
71
- readonly isStarknetIDFetching: boolean;
69
+ export interface IRecipientConfigWithNameServices extends IRecipientConfig {
70
+ preferredNameService: string | undefined;
71
+ setPreferredNameService(nameService: string | undefined): void;
72
+ getNameService(type: string): NameService | undefined;
73
+ getNameServices(): NameService[];
74
+ // address를 반환하는 name service의 결과를 반환한다.
75
+ // preferredNameService가 설정되어 있지 않으면 모든 name service의 결과를 반환한다.
76
+ // preferredNameService가 설정되어 있으면 해당 name service의 결과를 반환한다.
77
+ nameServiceResult: {
78
+ type: string;
79
+ address: string;
80
+ fullName: string;
81
+ domain: string;
82
+ suffix: string;
83
+ }[];
72
84
  }
73
85
 
74
86
  export interface IAmountConfig extends ITxChainSetter {