@keplr-wallet/hooks-starknet 0.12.194 → 0.12.195-rc.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.
@@ -0,0 +1,326 @@
1
+ import { action, autorun, makeObservable, observable, runInAction } from "mobx";
2
+ import { ChainGetter } from "@keplr-wallet/stores";
3
+ import { FetchDebounce, NameService } from "./name-service";
4
+ import { ITxChainSetter } from "./types";
5
+ import { CallData, constants } from "starknet";
6
+ import { simpleFetch } from "@keplr-wallet/simple-fetch";
7
+ import { StarknetIDIsFetchingError } from "./errors";
8
+
9
+ export class StarknetIdNameService implements NameService {
10
+ readonly type = "starknet-id";
11
+
12
+ @observable
13
+ protected _isEnabled: boolean = true;
14
+
15
+ @observable
16
+ protected _isFetching: boolean = false;
17
+
18
+ @observable
19
+ protected _value: string = "";
20
+
21
+ @observable.ref
22
+ protected _result:
23
+ | {
24
+ address: string;
25
+ fullName: string;
26
+ domain: string;
27
+ suffix: string;
28
+ }
29
+ | undefined = undefined;
30
+
31
+ // Deep equal check is required to avoid infinite re-render.
32
+ @observable.struct
33
+ protected _starknetID:
34
+ | {
35
+ networkName: string;
36
+ namingContractAddress: string;
37
+ }
38
+ | undefined = undefined;
39
+
40
+ protected debounce = new FetchDebounce();
41
+
42
+ constructor(
43
+ protected readonly base: ITxChainSetter,
44
+ protected readonly chainGetter: ChainGetter,
45
+ starknetID: string | undefined = undefined
46
+ ) {
47
+ if (starknetID) {
48
+ this.setStarknetID(starknetID);
49
+ }
50
+
51
+ makeObservable(this);
52
+
53
+ autorun(() => {
54
+ noop(
55
+ this.base.modularChainInfo,
56
+ this._starknetID,
57
+ this.isEnabled,
58
+ this.value
59
+ );
60
+ // 위의 값에 변경이 있으면 새로고침
61
+ this.fetch();
62
+ });
63
+ }
64
+
65
+ @action
66
+ setStarknetID(chainId: string) {
67
+ const split = chainId.split(":"); // `starknet:networkName`
68
+ if (split.length < 2) {
69
+ return;
70
+ }
71
+
72
+ const networkName = split[1] as constants.NetworkName;
73
+ if (!networkName) {
74
+ return;
75
+ }
76
+
77
+ const namingContractAddress = networkToNamingContractAddress[networkName];
78
+ if (!namingContractAddress) {
79
+ this._starknetID = undefined;
80
+ return;
81
+ }
82
+
83
+ this._starknetID = {
84
+ networkName,
85
+ namingContractAddress,
86
+ };
87
+ }
88
+
89
+ @action
90
+ setIsEnabled(isEnabled: boolean) {
91
+ this._isEnabled = isEnabled;
92
+ }
93
+
94
+ get isEnabled(): boolean {
95
+ if (!this._starknetID || !("starknet" in this.base.modularChainInfo)) {
96
+ return false;
97
+ }
98
+
99
+ return this._isEnabled;
100
+ }
101
+
102
+ @action
103
+ setValue(value: string) {
104
+ this._value = value;
105
+ }
106
+
107
+ get value(): string {
108
+ let v = this._value;
109
+ if (this.isEnabled) {
110
+ const suffix = "stark";
111
+ if (v.endsWith("." + suffix)) {
112
+ v = v.slice(0, v.length - suffix.length - 1);
113
+ }
114
+ }
115
+
116
+ return v;
117
+ }
118
+
119
+ get result() {
120
+ if (!this.isEnabled) {
121
+ return undefined;
122
+ }
123
+
124
+ if (!this._result) {
125
+ return undefined;
126
+ }
127
+
128
+ if (this._result.domain !== this.value) {
129
+ return undefined;
130
+ }
131
+
132
+ return this._result;
133
+ }
134
+
135
+ get isFetching(): boolean {
136
+ return this._isFetching;
137
+ }
138
+
139
+ protected async fetch(): Promise<void> {
140
+ if (
141
+ !this.isEnabled ||
142
+ this.value.trim().length === 0 ||
143
+ !this._starknetID
144
+ ) {
145
+ runInAction(() => {
146
+ this._result = undefined;
147
+ this._isFetching = false;
148
+ });
149
+ return;
150
+ }
151
+
152
+ this.debounce.run(() => this.fetchInternal());
153
+ }
154
+
155
+ protected async fetchInternal(): Promise<void> {
156
+ try {
157
+ const modularChainInfo = this.base.modularChainInfo;
158
+ if (!("starknet" in modularChainInfo)) {
159
+ throw new Error(`${modularChainInfo.chainId} is not starknet chain`);
160
+ }
161
+ if (!this._starknetID) {
162
+ throw new Error("Starknet id is not set");
163
+ }
164
+
165
+ runInAction(() => {
166
+ this._isFetching = true;
167
+ });
168
+
169
+ const prevValue = this.value;
170
+
171
+ const suffix = "stark";
172
+ const domain = this.value;
173
+ const username = domain + "." + suffix;
174
+
175
+ const res = await simpleFetch<{
176
+ jsonrpc: "2.0";
177
+ result?: string[];
178
+ id: string;
179
+ error?: {
180
+ code?: number;
181
+ message?: string;
182
+ };
183
+ }>(modularChainInfo.starknet.rpc, "", {
184
+ method: "POST",
185
+ headers: {
186
+ "content-type": "application/json",
187
+ },
188
+ body: JSON.stringify({
189
+ jsonrpc: "2.0",
190
+ id: "1",
191
+ method: "starknet_call",
192
+ params: [
193
+ {
194
+ contract_address: this._starknetID.namingContractAddress,
195
+ calldata: CallData.toHex({
196
+ domain: encodeDomain(username).map((v) => v.toString(10)),
197
+ hint: [],
198
+ }),
199
+ entry_point_selector:
200
+ "0x2e269d930f6d7ab92b15ce8ff9f5e63709391617e3465fff79ba6baf278ce60", // selector.getSelectorFromName("domain_to_address"),
201
+ },
202
+ "latest",
203
+ ],
204
+ }),
205
+ });
206
+
207
+ if (this.value === prevValue) {
208
+ if (res.data.error && res.data.error.message) {
209
+ throw new StarknetIDIsFetchingError(res.data.error.message);
210
+ }
211
+
212
+ const data = res.data.result;
213
+ if (!data) {
214
+ throw new StarknetIDIsFetchingError("no address found");
215
+ }
216
+
217
+ const rawHexAddr = data[0];
218
+ if (rawHexAddr === "0x0") {
219
+ throw new StarknetIDIsFetchingError("no address found");
220
+ }
221
+
222
+ const addr = "0x" + rawHexAddr.replace("0x", "").padStart(64, "0");
223
+
224
+ runInAction(() => {
225
+ this._result = {
226
+ address: addr,
227
+ fullName: username,
228
+ domain,
229
+ suffix,
230
+ };
231
+ this._isFetching = false;
232
+ });
233
+ }
234
+ } catch (e) {
235
+ console.log(e);
236
+ runInAction(() => {
237
+ this._result = undefined;
238
+ this._isFetching = false;
239
+ });
240
+ }
241
+ }
242
+ }
243
+
244
+ const noop = (..._args: any[]) => {
245
+ // noop
246
+ };
247
+
248
+ const networkToNamingContractAddress = {
249
+ [constants.NetworkName.SN_MAIN]:
250
+ "0x6ac597f8116f886fa1c97a23fa4e08299975ecaf6b598873ca6792b9bbfb678",
251
+ [constants.NetworkName.SN_SEPOLIA]:
252
+ "0x0707f09bc576bd7cfee59694846291047e965f4184fe13dac62c56759b3b6fa7",
253
+ };
254
+
255
+ const basicAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789-";
256
+ const basicSizePlusOne = BigInt(basicAlphabet.length + 1);
257
+ const bigAlphabet = "这来";
258
+ const basicAlphabetSize = BigInt(basicAlphabet.length);
259
+ const bigAlphabetSize = BigInt(bigAlphabet.length);
260
+
261
+ function encodeDomain(domain: string | undefined | null): bigint[] {
262
+ if (!domain) return [BigInt(0)];
263
+
264
+ const encoded = [];
265
+ for (const subdomain of domain.replace(".stark", "").split("."))
266
+ encoded.push(encode(subdomain));
267
+ return encoded;
268
+ }
269
+
270
+ function extractStars(str: string): [string, number] {
271
+ let k = 0;
272
+ while (str.endsWith(bigAlphabet[bigAlphabet.length - 1])) {
273
+ str = str.substring(0, str.length - 1);
274
+ k += 1;
275
+ }
276
+ return [str, k];
277
+ }
278
+
279
+ function encode(decoded: string | undefined): bigint {
280
+ let encoded = BigInt(0);
281
+ let multiplier = BigInt(1);
282
+
283
+ if (!decoded) {
284
+ return encoded;
285
+ }
286
+
287
+ if (decoded.endsWith(bigAlphabet[0] + basicAlphabet[1])) {
288
+ const [str, k] = extractStars(decoded.substring(0, decoded.length - 2));
289
+ decoded = str + bigAlphabet[bigAlphabet.length - 1].repeat(2 * (k + 1));
290
+ } else {
291
+ const [str, k] = extractStars(decoded);
292
+ if (k)
293
+ decoded =
294
+ str + bigAlphabet[bigAlphabet.length - 1].repeat(1 + 2 * (k - 1));
295
+ }
296
+
297
+ for (let i = 0; i < decoded.length; i += 1) {
298
+ const char = decoded[i];
299
+ const index = basicAlphabet.indexOf(char);
300
+ const bnIndex = BigInt(basicAlphabet.indexOf(char));
301
+
302
+ if (index !== -1) {
303
+ // add encoded + multiplier * index
304
+ if (i === decoded.length - 1 && decoded[i] === basicAlphabet[0]) {
305
+ encoded += multiplier * basicAlphabetSize;
306
+ multiplier *= basicSizePlusOne;
307
+ // add 0
308
+ multiplier *= basicSizePlusOne;
309
+ } else {
310
+ encoded += multiplier * bnIndex;
311
+ multiplier *= basicSizePlusOne;
312
+ }
313
+ } else if (bigAlphabet.indexOf(char) !== -1) {
314
+ // add encoded + multiplier * (basicAlphabetSize)
315
+ encoded += multiplier * basicAlphabetSize;
316
+ multiplier *= basicSizePlusOne;
317
+ // add encoded + multiplier * index
318
+ const newid =
319
+ (i === decoded.length - 1 ? 1 : 0) + bigAlphabet.indexOf(char);
320
+ encoded += multiplier * BigInt(newid);
321
+ multiplier *= bigAlphabetSize;
322
+ }
323
+ }
324
+
325
+ return encoded;
326
+ }
@@ -0,0 +1,39 @@
1
+ export interface NameService {
2
+ type: string;
3
+
4
+ setIsEnabled: (isEnabled: boolean) => void;
5
+ // must be observable
6
+ isEnabled: boolean;
7
+
8
+ // must be observable
9
+ result:
10
+ | {
11
+ address: string;
12
+ fullName: string;
13
+ domain: string;
14
+ suffix: string;
15
+ }
16
+ | undefined;
17
+
18
+ // must be observable
19
+ isFetching: boolean;
20
+
21
+ setValue: (value: string) => void;
22
+ value: string;
23
+ }
24
+
25
+ export class FetchDebounce {
26
+ readonly debounceMs = 50;
27
+
28
+ protected timeout: NodeJS.Timeout | undefined = undefined;
29
+
30
+ run(fn: () => Promise<void>) {
31
+ if (this.timeout) {
32
+ clearTimeout(this.timeout);
33
+ }
34
+ this.timeout = setTimeout(() => {
35
+ this.timeout = undefined;
36
+ fn();
37
+ }, this.debounceMs);
38
+ }
39
+ }