@keplr-wallet/hooks-evm 0.13.15-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 (72) hide show
  1. package/.eslintrc.json +14 -0
  2. package/build/index.d.ts +1 -0
  3. package/build/index.js +18 -0
  4. package/build/index.js.map +1 -0
  5. package/build/tx/amount.d.ts +27 -0
  6. package/build/tx/amount.js +238 -0
  7. package/build/tx/amount.js.map +1 -0
  8. package/build/tx/chain.d.ts +10 -0
  9. package/build/tx/chain.js +37 -0
  10. package/build/tx/chain.js.map +1 -0
  11. package/build/tx/errors.d.ts +30 -0
  12. package/build/tx/errors.js +84 -0
  13. package/build/tx/errors.js.map +1 -0
  14. package/build/tx/evm-fee-utils.d.ts +28 -0
  15. package/build/tx/evm-fee-utils.js +133 -0
  16. package/build/tx/evm-fee-utils.js.map +1 -0
  17. package/build/tx/fee.d.ts +61 -0
  18. package/build/tx/fee.js +523 -0
  19. package/build/tx/fee.js.map +1 -0
  20. package/build/tx/gas-simulator.d.ts +89 -0
  21. package/build/tx/gas-simulator.js +465 -0
  22. package/build/tx/gas-simulator.js.map +1 -0
  23. package/build/tx/gas.d.ts +12 -0
  24. package/build/tx/gas.js +84 -0
  25. package/build/tx/gas.js.map +1 -0
  26. package/build/tx/index.d.ts +13 -0
  27. package/build/tx/index.js +30 -0
  28. package/build/tx/index.js.map +1 -0
  29. package/build/tx/internal.d.ts +3 -0
  30. package/build/tx/internal.js +3 -0
  31. package/build/tx/internal.js.map +1 -0
  32. package/build/tx/name-service-ens.d.ts +40 -0
  33. package/build/tx/name-service-ens.js +189 -0
  34. package/build/tx/name-service-ens.js.map +1 -0
  35. package/build/tx/name-service.d.ts +20 -0
  36. package/build/tx/name-service.js +20 -0
  37. package/build/tx/name-service.js.map +1 -0
  38. package/build/tx/recipient.d.ts +35 -0
  39. package/build/tx/recipient.js +131 -0
  40. package/build/tx/recipient.js.map +1 -0
  41. package/build/tx/send-tx.d.ts +13 -0
  42. package/build/tx/send-tx.js +24 -0
  43. package/build/tx/send-tx.js.map +1 -0
  44. package/build/tx/sender.d.ts +12 -0
  45. package/build/tx/sender.js +73 -0
  46. package/build/tx/sender.js.map +1 -0
  47. package/build/tx/types.d.ts +102 -0
  48. package/build/tx/types.js +3 -0
  49. package/build/tx/types.js.map +1 -0
  50. package/build/tx/validate.d.ts +11 -0
  51. package/build/tx/validate.js +37 -0
  52. package/build/tx/validate.js.map +1 -0
  53. package/package.json +40 -0
  54. package/src/index.ts +1 -0
  55. package/src/tx/amount.ts +273 -0
  56. package/src/tx/chain.ts +31 -0
  57. package/src/tx/errors.ts +79 -0
  58. package/src/tx/evm-fee-utils.ts +217 -0
  59. package/src/tx/fee.ts +622 -0
  60. package/src/tx/gas-simulator.ts +567 -0
  61. package/src/tx/gas.ts +93 -0
  62. package/src/tx/index.ts +13 -0
  63. package/src/tx/internal.ts +4 -0
  64. package/src/tx/name-service-ens.ts +207 -0
  65. package/src/tx/name-service.ts +39 -0
  66. package/src/tx/recipient.ts +166 -0
  67. package/src/tx/send-tx.ts +55 -0
  68. package/src/tx/sender.ts +82 -0
  69. package/src/tx/types.ts +153 -0
  70. package/src/tx/validate.ts +55 -0
  71. package/tsconfig.check.json +90 -0
  72. package/tsconfig.json +12 -0
@@ -0,0 +1,567 @@
1
+ import { IFeeConfig, IGasConfig, IGasSimulator, UIProperties } from "./types";
2
+ import {
3
+ action,
4
+ autorun,
5
+ computed,
6
+ IReactionDisposer,
7
+ makeObservable,
8
+ observable,
9
+ runInAction,
10
+ } from "mobx";
11
+ import { useEffect, useState } from "react";
12
+ import { KVStore } from "@keplr-wallet/common";
13
+ import { ChainIdHelper } from "@keplr-wallet/cosmos";
14
+ import { TxChainSetter } from "./chain";
15
+ import { ChainGetter } from "@keplr-wallet/stores";
16
+ import { isSimpleFetchError } from "@keplr-wallet/simple-fetch";
17
+ import { EvmGasSimulationOutcome } from "@keplr-wallet/types";
18
+ import { CoinPretty } from "@keplr-wallet/unit";
19
+
20
+ export type SimulateGasFn = () => {
21
+ simulate: () => Promise<{
22
+ gasUsed: number;
23
+ evmSimulationOutcome?: EvmGasSimulationOutcome;
24
+ }>;
25
+ };
26
+
27
+ class GasSimulatorState {
28
+ @observable
29
+ protected _isInitialized: boolean = false;
30
+
31
+ @observable
32
+ protected _initialGasEstimated: number | undefined = undefined;
33
+
34
+ @observable
35
+ protected _recentGasEstimated: number | undefined = undefined;
36
+
37
+ @observable.ref
38
+ protected _recentEvmSimulationOutcome: EvmGasSimulationOutcome | undefined =
39
+ undefined;
40
+
41
+ @observable.ref
42
+ protected _simulateFn:
43
+ | (() => Promise<{
44
+ gasUsed: number;
45
+ evmSimulationOutcome?: EvmGasSimulationOutcome;
46
+ }>)
47
+ | undefined = undefined;
48
+
49
+ @observable
50
+ protected _isZeroFee: boolean = true;
51
+
52
+ @observable.ref
53
+ protected _error: Error | undefined = undefined;
54
+
55
+ constructor() {
56
+ makeObservable(this);
57
+ }
58
+
59
+ @action
60
+ setIsInitialized(value: boolean) {
61
+ this._isInitialized = value;
62
+ }
63
+
64
+ get isInitialized(): boolean {
65
+ return this._isInitialized;
66
+ }
67
+
68
+ @action
69
+ setInitialGasEstimated(gas: number) {
70
+ this._initialGasEstimated = gas;
71
+ }
72
+
73
+ get initialGasEstimated(): number | undefined {
74
+ return this._initialGasEstimated;
75
+ }
76
+
77
+ @action
78
+ setRecentGasEstimated(gas: number) {
79
+ this._recentGasEstimated = gas;
80
+ }
81
+
82
+ get recentGasEstimated(): number | undefined {
83
+ return this._recentGasEstimated;
84
+ }
85
+
86
+ @action
87
+ setRecentEvmSimulationOutcome(outcome: EvmGasSimulationOutcome | undefined) {
88
+ this._recentEvmSimulationOutcome = outcome;
89
+ }
90
+
91
+ get recentEvmSimulationOutcome(): EvmGasSimulationOutcome | undefined {
92
+ return this._recentEvmSimulationOutcome;
93
+ }
94
+
95
+ @action
96
+ refreshSimulateFn(
97
+ fn: () => Promise<{
98
+ gasUsed: number;
99
+ evmSimulationOutcome?: EvmGasSimulationOutcome;
100
+ }>
101
+ ) {
102
+ this._simulateFn = fn;
103
+ }
104
+
105
+ get simulateFn():
106
+ | (() => Promise<{
107
+ gasUsed: number;
108
+ evmSimulationOutcome?: EvmGasSimulationOutcome;
109
+ }>)
110
+ | undefined {
111
+ return this._simulateFn;
112
+ }
113
+
114
+ @action
115
+ setError(error: Error | undefined) {
116
+ this._error = error;
117
+ }
118
+
119
+ get error(): Error | undefined {
120
+ return this._error;
121
+ }
122
+
123
+ get isZeroFee(): boolean {
124
+ return this._isZeroFee;
125
+ }
126
+
127
+ @action
128
+ setIsZeroFee(value: boolean) {
129
+ this._isZeroFee = value;
130
+ }
131
+
132
+ static isZeroFee(fee: CoinPretty | undefined): boolean {
133
+ if (!fee) {
134
+ return true;
135
+ }
136
+ return fee.toCoin().amount === "0";
137
+ }
138
+ }
139
+
140
+ export class GasSimulator extends TxChainSetter implements IGasSimulator {
141
+ @observable
142
+ protected _key: string;
143
+
144
+ @observable
145
+ protected _gasAdjustmentValue: string = "1.3";
146
+
147
+ @observable
148
+ protected _enabled: boolean = false;
149
+
150
+ @observable
151
+ protected _forceDisabled: boolean = false;
152
+ @observable
153
+ protected _forceDisableReason: Error | undefined = undefined;
154
+
155
+ @observable
156
+ protected _isSimulating: boolean = false;
157
+
158
+ @observable.shallow
159
+ protected _stateMap: Map<string, GasSimulatorState> = new Map();
160
+
161
+ protected _debounceTimeoutId: NodeJS.Timeout | null = null;
162
+ protected readonly _debounceMs: number = 300;
163
+
164
+ protected _disposers: IReactionDisposer[] = [];
165
+
166
+ constructor(
167
+ protected kvStore: KVStore,
168
+ chainGetter: ChainGetter,
169
+ initialChainId: string,
170
+ protected readonly gasConfig: IGasConfig,
171
+ protected readonly feeConfig: IFeeConfig,
172
+ protected readonly initialKey: string,
173
+ protected simulateGasFn: SimulateGasFn
174
+ ) {
175
+ super(chainGetter, initialChainId);
176
+
177
+ this._key = initialKey;
178
+
179
+ makeObservable(this);
180
+
181
+ this.init();
182
+ }
183
+
184
+ setKVStore(kvStore: KVStore) {
185
+ this.kvStore = kvStore;
186
+ }
187
+
188
+ get key(): string {
189
+ return this._key;
190
+ }
191
+
192
+ @action
193
+ setKey(value: string) {
194
+ this._key = value;
195
+ }
196
+
197
+ get isSimulating(): boolean {
198
+ return this._isSimulating;
199
+ }
200
+
201
+ setSimulateGasFn(simulateGasFn: SimulateGasFn) {
202
+ this.simulateGasFn = simulateGasFn;
203
+ }
204
+
205
+ get enabled(): boolean {
206
+ if (this._forceDisabled) {
207
+ return false;
208
+ }
209
+
210
+ return this._enabled;
211
+ }
212
+
213
+ @action
214
+ setEnabled(value: boolean) {
215
+ if (this._forceDisabled && value) {
216
+ console.log(
217
+ "Gas simulator is disabled by force. You can not enable the gas simulator"
218
+ );
219
+ return;
220
+ }
221
+
222
+ this._enabled = value;
223
+ }
224
+
225
+ get forceDisabled(): boolean {
226
+ return this._forceDisabled;
227
+ }
228
+
229
+ get forceDisableReason(): Error | undefined {
230
+ return this._forceDisableReason;
231
+ }
232
+
233
+ @action
234
+ forceDisable(valueOrReason: boolean | Error) {
235
+ if (!valueOrReason) {
236
+ this._forceDisabled = false;
237
+ this._forceDisableReason = undefined;
238
+ } else {
239
+ if (this.enabled) {
240
+ this.setEnabled(false);
241
+ }
242
+ this._forceDisabled = true;
243
+ if (typeof valueOrReason !== "boolean") {
244
+ this._forceDisableReason = valueOrReason;
245
+ }
246
+ }
247
+ }
248
+
249
+ get error(): Error | undefined {
250
+ const key = this.storeKey;
251
+ const state = this.getState(key);
252
+ return state.error;
253
+ }
254
+
255
+ get gasEstimated(): number | undefined {
256
+ const key = this.storeKey;
257
+ const state = this.getState(key);
258
+
259
+ if (state.recentGasEstimated != null) {
260
+ return state.recentGasEstimated;
261
+ }
262
+
263
+ return state.initialGasEstimated;
264
+ }
265
+
266
+ get gasAdjustment(): number {
267
+ if (this._gasAdjustmentValue === "") {
268
+ return 0;
269
+ }
270
+
271
+ const num = parseFloat(this._gasAdjustmentValue);
272
+ if (Number.isNaN(num) || num < 0) {
273
+ return 0;
274
+ }
275
+
276
+ return num;
277
+ }
278
+
279
+ get gasAdjustmentValue(): string {
280
+ return this._gasAdjustmentValue;
281
+ }
282
+
283
+ @action
284
+ setGasAdjustmentValue(gasAdjustment: string | number) {
285
+ if (typeof gasAdjustment === "number") {
286
+ if (gasAdjustment < 0 || gasAdjustment > 3) {
287
+ return;
288
+ }
289
+
290
+ this._gasAdjustmentValue = gasAdjustment.toString();
291
+ return;
292
+ }
293
+
294
+ if (gasAdjustment === "") {
295
+ this._gasAdjustmentValue = "";
296
+ return;
297
+ }
298
+
299
+ if (gasAdjustment.startsWith(".")) {
300
+ this._gasAdjustmentValue = "0" + gasAdjustment;
301
+ }
302
+
303
+ const num = parseFloat(gasAdjustment);
304
+ if (Number.isNaN(num) || num < 0 || num > 3) {
305
+ return;
306
+ }
307
+
308
+ this._gasAdjustmentValue = gasAdjustment;
309
+ }
310
+
311
+ get evmSimulationOutcome(): EvmGasSimulationOutcome | undefined {
312
+ const key = this.storeKey;
313
+ const state = this.getState(key);
314
+ return state.recentEvmSimulationOutcome;
315
+ }
316
+
317
+ protected init() {
318
+ this._disposers.push(
319
+ autorun(() => {
320
+ if (!this.enabled) {
321
+ return;
322
+ }
323
+
324
+ const key = this.storeKey;
325
+ const state = this.getState(key);
326
+
327
+ this.kvStore.get<string>(key).then((saved) => {
328
+ if (saved) {
329
+ try {
330
+ const gas = JSON.parse(saved);
331
+ if (typeof gas === "number") {
332
+ state.setInitialGasEstimated(gas);
333
+ }
334
+ } catch (e) {
335
+ console.warn(e);
336
+ this.kvStore.set(key, "");
337
+ }
338
+ }
339
+
340
+ state.setIsInitialized(true);
341
+ });
342
+ })
343
+ );
344
+
345
+ // Every time the observable used in simulateGasFn is updated, the simulation is refreshed.
346
+ // The simulation is also refreshed when changing from zero fee to paying fee or vice versa.
347
+ this._disposers.push(
348
+ autorun(() => {
349
+ if (!this.enabled) {
350
+ return;
351
+ }
352
+
353
+ try {
354
+ const key = this.storeKey;
355
+ const state = this.getState(key);
356
+
357
+ if (!state.isInitialized) {
358
+ return;
359
+ }
360
+
361
+ const { simulate } = this.simulateGasFn();
362
+ const isZeroFee = GasSimulatorState.isZeroFee(this.feeConfig.fee);
363
+
364
+ runInAction(() => {
365
+ if (
366
+ state.recentGasEstimated == null ||
367
+ state.error != null ||
368
+ state.isZeroFee !== isZeroFee
369
+ ) {
370
+ state.refreshSimulateFn(simulate);
371
+ state.setIsZeroFee(isZeroFee);
372
+ }
373
+ });
374
+ } catch (e) {
375
+ console.log(e);
376
+ return;
377
+ }
378
+ })
379
+ );
380
+
381
+ this._disposers.push(
382
+ autorun(() => {
383
+ const key = this.storeKey;
384
+ const state = this.getState(key);
385
+
386
+ if (!state.simulateFn) {
387
+ return;
388
+ }
389
+
390
+ if (this._debounceTimeoutId) {
391
+ clearTimeout(this._debounceTimeoutId);
392
+ }
393
+
394
+ const promise = state.simulateFn();
395
+
396
+ this._debounceTimeoutId = setTimeout(() => {
397
+ runInAction(() => {
398
+ this._isSimulating = true;
399
+ });
400
+
401
+ promise
402
+ .then((res) => {
403
+ const { gasUsed, evmSimulationOutcome } = res;
404
+
405
+ const shouldUpdate =
406
+ !state.recentGasEstimated ||
407
+ Math.abs(state.recentGasEstimated - gasUsed) /
408
+ state.recentGasEstimated >
409
+ 0.02 ||
410
+ evmSimulationOutcome !== state.recentEvmSimulationOutcome;
411
+
412
+ if (shouldUpdate) {
413
+ state.setRecentGasEstimated(gasUsed);
414
+ state.setRecentEvmSimulationOutcome(evmSimulationOutcome);
415
+ }
416
+
417
+ state.setError(undefined);
418
+
419
+ this.kvStore.set(key, JSON.stringify(gasUsed)).catch((e) => {
420
+ console.log(e);
421
+ });
422
+ })
423
+ .catch((e) => {
424
+ console.log("evm gas simulate error", e);
425
+ if (isSimpleFetchError(e) && e.response) {
426
+ let message = "";
427
+ const contentType: string = e.response.headers
428
+ ? e.response.headers.get("content-type") || ""
429
+ : "";
430
+ if (
431
+ contentType.startsWith("text/plain") &&
432
+ typeof e.response.data === "string"
433
+ ) {
434
+ message = e.response.data;
435
+ }
436
+ if (
437
+ contentType.startsWith("application/json") &&
438
+ e.response.data?.message &&
439
+ typeof e.response.data?.message === "string"
440
+ ) {
441
+ message = e.response.data.message;
442
+ }
443
+
444
+ if (message !== "") {
445
+ state.setError(new Error(message));
446
+ return;
447
+ }
448
+ }
449
+
450
+ state.setError(e);
451
+ })
452
+ .finally(() => {
453
+ runInAction(() => {
454
+ this._isSimulating = false;
455
+ });
456
+ });
457
+ }, this._debounceMs);
458
+ })
459
+ );
460
+
461
+ this._disposers.push(
462
+ autorun(() => {
463
+ if (
464
+ this.enabled &&
465
+ this.gasEstimated != null &&
466
+ !Number.isNaN(this.gasEstimated)
467
+ ) {
468
+ this.gasConfig.setValue(this.gasEstimated * this.gasAdjustment);
469
+ }
470
+ })
471
+ );
472
+ }
473
+
474
+ dispose() {
475
+ for (const disposer of this._disposers) {
476
+ disposer();
477
+ }
478
+ }
479
+
480
+ get uiProperties(): UIProperties {
481
+ const key = this.storeKey;
482
+ const state = this.getState(key);
483
+
484
+ return {
485
+ warning: (() => {
486
+ if (this.forceDisableReason) {
487
+ return this.forceDisableReason;
488
+ }
489
+
490
+ if (this.error) {
491
+ return this.error;
492
+ }
493
+ })(),
494
+ loadingState: (() => {
495
+ if (!this.enabled) {
496
+ return;
497
+ }
498
+
499
+ if (this.isSimulating) {
500
+ return state.initialGasEstimated == null
501
+ ? "loading-block"
502
+ : "loading";
503
+ }
504
+ })(),
505
+ };
506
+ }
507
+
508
+ protected getState(key: string): GasSimulatorState {
509
+ if (!this._stateMap.has(key)) {
510
+ runInAction(() => {
511
+ this._stateMap.set(key, new GasSimulatorState());
512
+ });
513
+ }
514
+
515
+ return this._stateMap.get(key)!;
516
+ }
517
+
518
+ @computed
519
+ protected get storeKey(): string {
520
+ const chainIdentifier = ChainIdHelper.parse(this.chainId);
521
+ const feeDenom = this.feeConfig.fee?.currency.coinMinimalDenom ?? "";
522
+ return `${chainIdentifier.identifier}/${feeDenom}/${this.key}`;
523
+ }
524
+ }
525
+
526
+ // CONTRACT: Use with `observer`
527
+ export const useGasSimulator = (
528
+ kvStore: KVStore,
529
+ chainGetter: ChainGetter,
530
+ chainId: string,
531
+ gasConfig: IGasConfig,
532
+ feeConfig: IFeeConfig,
533
+ key: string,
534
+ simulateGasFn: SimulateGasFn,
535
+ initialDisabled?: boolean
536
+ ) => {
537
+ const [gasSimulator] = useState(() => {
538
+ const gasSimulator = new GasSimulator(
539
+ kvStore,
540
+ chainGetter,
541
+ chainId,
542
+ gasConfig,
543
+ feeConfig,
544
+ key,
545
+ simulateGasFn
546
+ );
547
+ if (initialDisabled) {
548
+ gasSimulator.setEnabled(false);
549
+ } else {
550
+ gasSimulator.setEnabled(true);
551
+ }
552
+
553
+ return gasSimulator;
554
+ });
555
+ gasSimulator.setKVStore(kvStore);
556
+ gasSimulator.setChain(chainId);
557
+ gasSimulator.setKey(key);
558
+ gasSimulator.setSimulateGasFn(simulateGasFn);
559
+
560
+ useEffect(() => {
561
+ return () => {
562
+ gasSimulator.dispose();
563
+ };
564
+ }, [gasSimulator]);
565
+
566
+ return gasSimulator;
567
+ };
package/src/tx/gas.ts ADDED
@@ -0,0 +1,93 @@
1
+ import { IGasConfig, UIProperties } from "./types";
2
+ import { TxChainSetter } from "./chain";
3
+ import { ChainGetter } from "@keplr-wallet/stores";
4
+ import { action, computed, makeObservable, observable } from "mobx";
5
+ import { useState } from "react";
6
+
7
+ export class GasConfig extends TxChainSetter implements IGasConfig {
8
+ @observable
9
+ protected _value: string = "";
10
+
11
+ constructor(
12
+ chainGetter: ChainGetter,
13
+ initialChainId: string,
14
+ initialGas?: number
15
+ ) {
16
+ super(chainGetter, initialChainId);
17
+
18
+ if (initialGas) {
19
+ this._value = initialGas.toString();
20
+ }
21
+
22
+ makeObservable(this);
23
+ }
24
+
25
+ get value(): string {
26
+ return this._value;
27
+ }
28
+
29
+ @action
30
+ setValue(value: string | number): void {
31
+ if (typeof value === "number") {
32
+ this._value = Math.ceil(value).toString();
33
+ } else {
34
+ this._value = value;
35
+ }
36
+ }
37
+
38
+ get gas(): number {
39
+ if (this.value.trim() === "") {
40
+ return 0;
41
+ }
42
+
43
+ const num = Number.parseInt(this.value);
44
+ if (Number.isNaN(num)) {
45
+ return 0;
46
+ }
47
+
48
+ return num;
49
+ }
50
+
51
+ @computed
52
+ get uiProperties(): UIProperties {
53
+ if (this.value.trim() === "") {
54
+ return {
55
+ error: new Error("Please enter a gas amount"),
56
+ };
57
+ }
58
+
59
+ const parsed = Number.parseFloat(this.value);
60
+ if (Number.isNaN(parsed)) {
61
+ return {
62
+ error: new Error("Enter a valid number for gas"),
63
+ };
64
+ }
65
+
66
+ if (this.value.includes(".") || !Number.isInteger(parsed)) {
67
+ return {
68
+ error: new Error("Gas must be a whole number"),
69
+ };
70
+ }
71
+
72
+ if (this.gas <= 0) {
73
+ return {
74
+ error: new Error("Gas must be greater than 0"),
75
+ };
76
+ }
77
+
78
+ return {};
79
+ }
80
+ }
81
+
82
+ export const useGasConfig = (
83
+ chainGetter: ChainGetter,
84
+ chainId: string,
85
+ initialGas?: number
86
+ ) => {
87
+ const [txConfig] = useState(
88
+ () => new GasConfig(chainGetter, chainId, initialGas)
89
+ );
90
+ txConfig.setChain(chainId);
91
+
92
+ return txConfig;
93
+ };
@@ -0,0 +1,13 @@
1
+ export * from "./errors";
2
+ export * from "./types";
3
+ export * from "./evm-fee-utils";
4
+ export * from "./fee";
5
+ export * from "./gas";
6
+ export * from "./recipient";
7
+ export * from "./amount";
8
+ export * from "./sender";
9
+ export * from "./send-tx";
10
+ export * from "./chain";
11
+ export * from "./gas-simulator";
12
+ export * from "./validate";
13
+ export * from "./name-service";
@@ -0,0 +1,4 @@
1
+ import { IQueriesStore } from "@keplr-wallet/stores";
2
+ import { EthereumQueries } from "@keplr-wallet/stores-eth";
3
+
4
+ export type QueriesStore = IQueriesStore<EthereumQueries>;