@keplr-wallet/stores-core 0.12.32-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.
Files changed (54) hide show
  1. package/.eslintignore +2 -0
  2. package/.prettierignore +2 -0
  3. package/LICENSE +209 -0
  4. package/build/core/index.d.ts +4 -0
  5. package/build/core/index.js +21 -0
  6. package/build/core/index.js.map +1 -0
  7. package/build/core/interaction/chain-suggest.d.ts +35 -0
  8. package/build/core/interaction/chain-suggest.js +106 -0
  9. package/build/core/interaction/chain-suggest.js.map +1 -0
  10. package/build/core/interaction/eth-sign.d.ts +25 -0
  11. package/build/core/interaction/eth-sign.js +60 -0
  12. package/build/core/interaction/eth-sign.js.map +1 -0
  13. package/build/core/interaction/icns.d.ts +32 -0
  14. package/build/core/interaction/icns.js +58 -0
  15. package/build/core/interaction/icns.js.map +1 -0
  16. package/build/core/interaction/index.d.ts +6 -0
  17. package/build/core/interaction/index.js +23 -0
  18. package/build/core/interaction/index.js.map +1 -0
  19. package/build/core/interaction/interaction.d.ts +64 -0
  20. package/build/core/interaction/interaction.js +232 -0
  21. package/build/core/interaction/interaction.js.map +1 -0
  22. package/build/core/interaction/permission.d.ts +22 -0
  23. package/build/core/interaction/permission.js +108 -0
  24. package/build/core/interaction/permission.js.map +1 -0
  25. package/build/core/interaction/sign.d.ts +49 -0
  26. package/build/core/interaction/sign.js +78 -0
  27. package/build/core/interaction/sign.js.map +1 -0
  28. package/build/core/keyring.d.ts +132 -0
  29. package/build/core/keyring.js +278 -0
  30. package/build/core/keyring.js.map +1 -0
  31. package/build/core/permission-manager.d.ts +15 -0
  32. package/build/core/permission-manager.js +122 -0
  33. package/build/core/permission-manager.js.map +1 -0
  34. package/build/core/tokens.d.ts +39 -0
  35. package/build/core/tokens.js +225 -0
  36. package/build/core/tokens.js.map +1 -0
  37. package/build/index.d.ts +1 -0
  38. package/build/index.js +18 -0
  39. package/build/index.js.map +1 -0
  40. package/jest.config.js +5 -0
  41. package/package.json +31 -0
  42. package/src/core/index.ts +4 -0
  43. package/src/core/interaction/chain-suggest.ts +136 -0
  44. package/src/core/interaction/eth-sign.ts +71 -0
  45. package/src/core/interaction/icns.ts +68 -0
  46. package/src/core/interaction/index.ts +6 -0
  47. package/src/core/interaction/interaction.ts +290 -0
  48. package/src/core/interaction/permission.ts +121 -0
  49. package/src/core/interaction/sign.ts +124 -0
  50. package/src/core/keyring.ts +354 -0
  51. package/src/core/permission-manager.ts +114 -0
  52. package/src/core/tokens.ts +275 -0
  53. package/src/index.ts +1 -0
  54. package/tsconfig.json +12 -0
@@ -0,0 +1,290 @@
1
+ import {
2
+ Router,
3
+ MessageRequester,
4
+ BACKGROUND_PORT,
5
+ } from "@keplr-wallet/router";
6
+ import {
7
+ InteractionForegroundHandler,
8
+ interactionForegroundInit,
9
+ InteractionForegroundService,
10
+ InteractionWaitingData,
11
+ ApproveInteractionMsg,
12
+ ApproveInteractionV2Msg,
13
+ RejectInteractionMsg,
14
+ RejectInteractionV2Msg,
15
+ } from "@keplr-wallet/background";
16
+ import { action, observable, makeObservable, flow, toJS } from "mobx";
17
+ import { computedFn } from "mobx-utils";
18
+
19
+ export class InteractionStore implements InteractionForegroundHandler {
20
+ @observable.shallow
21
+ protected data: InteractionWaitingData[] = [];
22
+ // 원래 obsolete에 대한 정보를 data 밑의 field에 포함시켰는데
23
+ // obsolete 처리가 추가되기 전에는 data는 한번 받으면 그 이후에 변화되지 않는다는 가정으로 다른 로직이 짜여졌었다.
24
+ // ref도 변하면 안됐기 때문에 obsolete가 data 밑에 있으면 이러한 요구사항을 이루면서 처리할 수가 없다.
25
+ // (특히 서명 페이지에서 문제가 될 수 있음)
26
+ // 기존의 로직과의 호환성을 위해서 아예 분리되었음.
27
+ @observable.shallow
28
+ protected obsoleteData = new Map<string, boolean>();
29
+
30
+ constructor(
31
+ protected readonly router: Router,
32
+ protected readonly msgRequester: MessageRequester
33
+ ) {
34
+ makeObservable(this);
35
+
36
+ const service = new InteractionForegroundService(this);
37
+ interactionForegroundInit(router, service);
38
+ }
39
+
40
+ getAllData = computedFn(
41
+ <T = unknown>(type: string): InteractionWaitingData<T>[] => {
42
+ return toJS(
43
+ this.data.filter((d) => d.type === type)
44
+ ) as InteractionWaitingData<T>[];
45
+ }
46
+ );
47
+
48
+ getData = computedFn(
49
+ <T = unknown>(id: string): InteractionWaitingData<T> | undefined => {
50
+ return this.data.find((d) => d.id === id) as InteractionWaitingData<T>;
51
+ }
52
+ );
53
+
54
+ @action
55
+ onInteractionDataReceived(data: InteractionWaitingData) {
56
+ this.data.push(data);
57
+ }
58
+
59
+ @action
60
+ onEventDataReceived() {
61
+ // noop
62
+ }
63
+
64
+ /**
65
+ * 웹페이지에서 어떤 API를 요청해서 extension이 켜졌을때
66
+ * extension에서 요청을 처리하고 바로 팝업을 닫으면
67
+ * 이후에 연속적인 api 요청의 경우 다시 페이지가 열려야하는데 이게 은근히 어색한 UX를 만들기 때문에
68
+ * 이를 대충 해결하기 위해서 approve 이후에 대충 조금 기다리고 남은 interaction이 있느냐 아니냐에 따라 다른 처리를 한다.
69
+ * @param type
70
+ * @param id
71
+ * @param result
72
+ * @param afterFn
73
+ */
74
+ @flow
75
+ *approveWithProceedNext(
76
+ id: string,
77
+ result: unknown,
78
+ afterFn: (proceedNext: boolean) => void | Promise<void>
79
+ ) {
80
+ const d = this.getData(id);
81
+ if (!d || this.isObsoleteInteraction(id)) {
82
+ return;
83
+ }
84
+
85
+ this.markAsObsolete(id);
86
+ yield this.msgRequester.sendMessage(
87
+ BACKGROUND_PORT,
88
+ new ApproveInteractionMsg(id, result)
89
+ );
90
+ yield this.delay(100);
91
+ yield afterFn(this.hasOtherData(id));
92
+ this.removeData(id);
93
+ }
94
+
95
+ /**
96
+ * 웹페이지에서 어떤 API를 요청해서 extension이 켜졌을때
97
+ * extension에서 요청을 처리하고 바로 팝업을 닫으면
98
+ * 이후에 연속적인 api 요청의 경우 다시 페이지가 열려야하는데 이게 은근히 어색한 UX를 만들기 때문에
99
+ * 이를 대충 해결하기 위해서 approve 이후에 대충 조금 기다리고 남은 interaction이 있느냐 아니냐에 따라 다른 처리를 한다.
100
+ * @param type
101
+ * @param id
102
+ * @param result
103
+ * @param afterFn
104
+ */
105
+ @flow
106
+ *approveWithProceedNextV2(
107
+ ids: string | string[],
108
+ result: unknown,
109
+ afterFn: (proceedNext: boolean) => void | Promise<void>,
110
+ options: {
111
+ preDelay?: number;
112
+ postDelay?: number;
113
+ } = {}
114
+ ) {
115
+ if (typeof ids === "string") {
116
+ ids = [ids];
117
+ }
118
+
119
+ const fresh: string[] = [];
120
+
121
+ for (const id of ids) {
122
+ const d = this.getData(id);
123
+ if (!d || this.isObsoleteInteraction(id)) {
124
+ continue;
125
+ }
126
+
127
+ this.markAsObsolete(id);
128
+
129
+ fresh.push(id);
130
+ }
131
+
132
+ if (options.preDelay && options.preDelay > 0) {
133
+ yield new Promise((resolve) => setTimeout(resolve, options.preDelay));
134
+ }
135
+
136
+ const promises: Promise<unknown>[] = [];
137
+ for (const id of fresh) {
138
+ promises.push(
139
+ this.msgRequester.sendMessage(
140
+ BACKGROUND_PORT,
141
+ new ApproveInteractionV2Msg(id, result)
142
+ )
143
+ );
144
+ }
145
+
146
+ yield Promise.all(promises);
147
+
148
+ if (options.postDelay == null || options.postDelay > 0) {
149
+ yield this.delay(options.postDelay ?? 50);
150
+ }
151
+ yield afterFn(this.hasOtherData(ids));
152
+ this.removeData(ids);
153
+ }
154
+
155
+ /**
156
+ * 웹페이지에서 어떤 API를 요청해서 extension이 켜졌을때
157
+ * extension에서 요청을 처리하고 바로 팝업을 닫으면
158
+ * 이후에 연속적인 api 요청의 경우 다시 페이지가 열려야하는데 이게 은근히 어색한 UX를 만들기 때문에
159
+ * 이를 대충 해결하기 위해서 approve 이후에 대충 조금 기다리고 남은 interaction이 있느냐 아니냐에 따라 다른 처리를 한다.
160
+ * @param type
161
+ * @param id
162
+ * @param afterFn
163
+ */
164
+ @flow
165
+ *rejectWithProceedNext(
166
+ id: string,
167
+ afterFn: (proceedNext: boolean) => void | Promise<void>
168
+ ) {
169
+ const d = this.getData(id);
170
+ if (!d || this.isObsoleteInteraction(id)) {
171
+ return;
172
+ }
173
+
174
+ this.markAsObsolete(id);
175
+ yield this.msgRequester.sendMessage(
176
+ BACKGROUND_PORT,
177
+ new RejectInteractionMsg(id)
178
+ );
179
+ yield this.delay(100);
180
+ yield afterFn(this.hasOtherData(id));
181
+ this.removeData(id);
182
+ }
183
+
184
+ /**
185
+ * 웹페이지에서 어떤 API를 요청해서 extension이 켜졌을때
186
+ * extension에서 요청을 처리하고 바로 팝업을 닫으면
187
+ * 이후에 연속적인 api 요청의 경우 다시 페이지가 열려야하는데 이게 은근히 어색한 UX를 만들기 때문에
188
+ * 이를 대충 해결하기 위해서 approve 이후에 대충 조금 기다리고 남은 interaction이 있느냐 아니냐에 따라 다른 처리를 한다.
189
+ * @param type
190
+ * @param id
191
+ * @param afterFn
192
+ */
193
+ @flow
194
+ *rejectWithProceedNextV2(
195
+ ids: string | string[],
196
+ afterFn: (proceedNext: boolean) => void | Promise<void>
197
+ ) {
198
+ if (typeof ids === "string") {
199
+ ids = [ids];
200
+ }
201
+
202
+ const fresh: string[] = [];
203
+
204
+ for (const id of ids) {
205
+ const d = this.getData(id);
206
+ if (!d || this.isObsoleteInteraction(id)) {
207
+ continue;
208
+ }
209
+
210
+ this.markAsObsolete(id);
211
+
212
+ fresh.push(id);
213
+ }
214
+
215
+ const promises: Promise<unknown>[] = [];
216
+ for (const id of fresh) {
217
+ promises.push(
218
+ this.msgRequester.sendMessage(
219
+ BACKGROUND_PORT,
220
+ new RejectInteractionV2Msg(id)
221
+ )
222
+ );
223
+ }
224
+
225
+ yield Promise.all(promises);
226
+
227
+ yield this.delay(50);
228
+ yield afterFn(this.hasOtherData(ids));
229
+ this.removeData(ids);
230
+ }
231
+
232
+ protected delay(ms: number): Promise<void> {
233
+ return new Promise((resolve) => {
234
+ setTimeout(resolve, ms);
235
+ });
236
+ }
237
+
238
+ @flow
239
+ *rejectAll(type: string) {
240
+ const data = this.getAllData(type);
241
+ for (const d of data) {
242
+ if (this.isObsoleteInteraction(d.id)) {
243
+ continue;
244
+ }
245
+ yield this.msgRequester.sendMessage(
246
+ BACKGROUND_PORT,
247
+ new RejectInteractionMsg(d.id)
248
+ );
249
+ this.removeData(d.id);
250
+ }
251
+ }
252
+
253
+ // UI에서 좀 더 편하게 쓸 수 있게 하려고 undefined도 파라미터로 허용함.
254
+ isObsoleteInteraction(id: string | undefined): boolean {
255
+ if (!id) {
256
+ return false;
257
+ }
258
+ return this.obsoleteData.get(id) ?? false;
259
+ }
260
+
261
+ @action
262
+ protected removeData(ids: string | string[]) {
263
+ if (typeof ids === "string") {
264
+ ids = [ids];
265
+ }
266
+
267
+ for (const id of ids) {
268
+ this.data = this.data.filter((d) => d.id !== id);
269
+ this.obsoleteData.delete(id);
270
+ }
271
+ }
272
+
273
+ @action
274
+ protected markAsObsolete(id: string) {
275
+ if (this.getData(id)) {
276
+ this.obsoleteData.set(id, true);
277
+ }
278
+ }
279
+
280
+ protected hasOtherData(ids: string | string[]): boolean {
281
+ if (typeof ids === "string") {
282
+ ids = [ids];
283
+ }
284
+
285
+ const find = this.data.find((data) => {
286
+ return !ids.includes(data.id);
287
+ });
288
+ return !!find;
289
+ }
290
+ }
@@ -0,0 +1,121 @@
1
+ import { InteractionStore } from "./interaction";
2
+ import {
3
+ GlobalPermissionData,
4
+ INTERACTION_TYPE_GLOBAL_PERMISSION,
5
+ INTERACTION_TYPE_PERMISSION,
6
+ PermissionData,
7
+ } from "@keplr-wallet/background";
8
+ import { MessageRequester } from "@keplr-wallet/router";
9
+ import { computed, makeObservable } from "mobx";
10
+
11
+ export class PermissionStore {
12
+ constructor(
13
+ protected readonly interactionStore: InteractionStore,
14
+ protected readonly requester: MessageRequester
15
+ ) {
16
+ makeObservable(this);
17
+ }
18
+
19
+ get waitingPermissionData() {
20
+ if (this.waitingPermissionDatas.length > 0) {
21
+ return this.waitingPermissionDatas[0];
22
+ }
23
+ }
24
+
25
+ get waitingPermissionDatas() {
26
+ return this.interactionStore.getAllData<PermissionData>(
27
+ INTERACTION_TYPE_PERMISSION
28
+ );
29
+ }
30
+
31
+ @computed
32
+ get waitingPermissionMergedData():
33
+ | ({
34
+ ids: string[];
35
+ } & PermissionData)
36
+ | undefined {
37
+ const data = this.waitingPermissionDatas;
38
+ if (data.length === 0) {
39
+ return;
40
+ }
41
+
42
+ const first = data[0];
43
+ const res: {
44
+ ids: string[];
45
+ } & PermissionData = {
46
+ ids: [first.id],
47
+ chainIds: first.data.chainIds,
48
+ type: first.data.type,
49
+ origins: first.data.origins,
50
+ };
51
+ for (let i = 1; i < data.length; i++) {
52
+ const d = data[i];
53
+ if (d.data.type !== first.data.type) {
54
+ break;
55
+ }
56
+ if (d.data.origins.join(",") !== first.data.origins.join(",")) {
57
+ break;
58
+ }
59
+
60
+ res.ids.push(d.id);
61
+ res.chainIds.push(...d.data.chainIds);
62
+ }
63
+
64
+ // Remove duplicated chain ids.
65
+ res.chainIds = [...new Set(res.chainIds)];
66
+
67
+ return res;
68
+ }
69
+
70
+ get waitingGlobalPermissionData() {
71
+ if (this.waitingGlobalPermissionDatas.length > 0) {
72
+ return this.waitingGlobalPermissionDatas[0];
73
+ }
74
+ }
75
+
76
+ get waitingGlobalPermissionDatas() {
77
+ return this.interactionStore.getAllData<GlobalPermissionData>(
78
+ INTERACTION_TYPE_GLOBAL_PERMISSION
79
+ );
80
+ }
81
+
82
+ async approvePermissionWithProceedNext(
83
+ id: string | string[],
84
+ afterFn: (proceedNext: boolean) => void | Promise<void>
85
+ ) {
86
+ await this.interactionStore.approveWithProceedNextV2(id, {}, afterFn);
87
+ }
88
+
89
+ async rejectPermissionWithProceedNext(
90
+ id: string | string[],
91
+ afterFn: (proceedNext: boolean) => void | Promise<void>
92
+ ) {
93
+ await this.interactionStore.rejectWithProceedNextV2(id, afterFn);
94
+ }
95
+
96
+ async rejectPermissionAll() {
97
+ await this.interactionStore.rejectAll(INTERACTION_TYPE_PERMISSION);
98
+ }
99
+
100
+ async approveGlobalPermissionWithProceedNext(
101
+ id: string,
102
+ afterFn: (proceedNext: boolean) => void | Promise<void>
103
+ ) {
104
+ await this.interactionStore.approveWithProceedNextV2(id, {}, afterFn);
105
+ }
106
+
107
+ async rejectGlobalPermissionWithProceedNext(
108
+ id: string,
109
+ afterFn: (proceedNext: boolean) => void | Promise<void>
110
+ ) {
111
+ await this.interactionStore.rejectWithProceedNextV2(id, afterFn);
112
+ }
113
+
114
+ async rejectGlobalPermissionAll() {
115
+ await this.interactionStore.rejectAll(INTERACTION_TYPE_GLOBAL_PERMISSION);
116
+ }
117
+
118
+ isObsoleteInteraction(id: string | undefined): boolean {
119
+ return this.interactionStore.isObsoleteInteraction(id);
120
+ }
121
+ }
@@ -0,0 +1,124 @@
1
+ import { InteractionStore } from "./interaction";
2
+ import { computed, makeObservable } from "mobx";
3
+ import { SignDocWrapper } from "@keplr-wallet/cosmos";
4
+ import { KeplrSignOptions, StdSignDoc } from "@keplr-wallet/types";
5
+ import { InteractionWaitingData, PlainObject } from "@keplr-wallet/background";
6
+
7
+ export type SignInteractionData =
8
+ | {
9
+ origin: string;
10
+ chainId: string;
11
+ mode: "amino";
12
+ signer: string;
13
+ pubKey: Uint8Array;
14
+ signDoc: StdSignDoc;
15
+ signOptions: KeplrSignOptions & {
16
+ isADR36WithString?: boolean;
17
+ };
18
+ keyType: string;
19
+ keyInsensitive: PlainObject;
20
+
21
+ eip712?: {
22
+ types: Record<string, { name: string; type: string }[] | undefined>;
23
+ domain: Record<string, any>;
24
+ primaryType: string;
25
+ };
26
+ }
27
+ | {
28
+ origin: string;
29
+ chainId: string;
30
+ mode: "direct";
31
+ signer: string;
32
+ pubKey: Uint8Array;
33
+ signDocBytes: Uint8Array;
34
+ signOptions: KeplrSignOptions;
35
+ keyType: string;
36
+ keyInsensitive: PlainObject;
37
+ };
38
+
39
+ export class SignInteractionStore {
40
+ constructor(protected readonly interactionStore: InteractionStore) {
41
+ makeObservable(this);
42
+ }
43
+
44
+ get waitingDatas() {
45
+ return this.interactionStore.getAllData<SignInteractionData>(
46
+ "request-sign-cosmos"
47
+ );
48
+ }
49
+
50
+ @computed
51
+ get waitingData():
52
+ | InteractionWaitingData<
53
+ SignInteractionData & { signDocWrapper: SignDocWrapper }
54
+ >
55
+ | undefined {
56
+ const datas = this.waitingDatas;
57
+
58
+ if (datas.length === 0) {
59
+ return undefined;
60
+ }
61
+
62
+ const data = datas[0];
63
+ const wrapper =
64
+ data.data.mode === "amino"
65
+ ? SignDocWrapper.fromAminoSignDoc(data.data.signDoc)
66
+ : SignDocWrapper.fromDirectSignDocBytes(data.data.signDocBytes);
67
+
68
+ return {
69
+ id: data.id,
70
+ type: data.type,
71
+ isInternal: data.isInternal,
72
+ data: {
73
+ ...data.data,
74
+ signDocWrapper: wrapper,
75
+ },
76
+ };
77
+ }
78
+
79
+ async approveWithProceedNext(
80
+ id: string,
81
+ newSignDocWrapper: SignDocWrapper,
82
+ signature: Uint8Array | undefined,
83
+ afterFn: (proceedNext: boolean) => void | Promise<void>,
84
+ options: {
85
+ preDelay?: number;
86
+ } = {}
87
+ ) {
88
+ const res = (() => {
89
+ if (newSignDocWrapper.mode === "amino") {
90
+ return {
91
+ newSignDoc: newSignDocWrapper.aminoSignDoc,
92
+ };
93
+ }
94
+ return {
95
+ newSignDocBytes: newSignDocWrapper.protoSignDoc.toBytes(),
96
+ };
97
+ })();
98
+
99
+ await this.interactionStore.approveWithProceedNextV2(
100
+ id,
101
+ {
102
+ ...res,
103
+ signature,
104
+ },
105
+ afterFn,
106
+ options
107
+ );
108
+ }
109
+
110
+ async rejectWithProceedNext(
111
+ id: string,
112
+ afterFn: (proceedNext: boolean) => void | Promise<void>
113
+ ) {
114
+ await this.interactionStore.rejectWithProceedNext(id, afterFn);
115
+ }
116
+
117
+ async rejectAll() {
118
+ await this.interactionStore.rejectAll("request-sign-cosmos");
119
+ }
120
+
121
+ isObsoleteInteraction(id: string | undefined): boolean {
122
+ return this.interactionStore.isObsoleteInteraction(id);
123
+ }
124
+ }