@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.
- package/build/tx/index.d.ts +1 -0
- package/build/tx/index.js +1 -0
- package/build/tx/index.js.map +1 -1
- package/build/tx/name-service-starknet-id.d.ts +37 -0
- package/build/tx/name-service-starknet-id.js +287 -0
- package/build/tx/name-service-starknet-id.js.map +1 -0
- package/build/tx/name-service.d.ts +20 -0
- package/build/tx/name-service.js +20 -0
- package/build/tx/name-service.js.map +1 -0
- package/build/tx/recipient.d.ts +16 -18
- package/build/tx/recipient.js +52 -237
- package/build/tx/recipient.js.map +1 -1
- package/build/tx/types.d.ts +15 -6
- package/package.json +14 -14
- package/src/tx/index.ts +1 -0
- package/src/tx/name-service-starknet-id.ts +326 -0
- package/src/tx/name-service.ts +39 -0
- package/src/tx/recipient.ts +77 -307
- package/src/tx/types.ts +18 -6
@@ -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
|
+
}
|