@keplr-wallet/hooks-starknet 0.12.240-rc.3 → 0.12.240-rc.5

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,4 +1,10 @@
1
- import { IFeeConfig, IGasConfig, IGasSimulator, UIProperties } from "./types";
1
+ import {
2
+ IFeeConfig,
3
+ IGasConfig,
4
+ IGasSimulator,
5
+ UIProperties,
6
+ GasEstimate,
7
+ } from "./types";
2
8
  import {
3
9
  action,
4
10
  autorun,
@@ -12,31 +18,26 @@ import { useEffect, useState } from "react";
12
18
  import { KVStore } from "@keplr-wallet/common";
13
19
  import { ChainIdHelper } from "@keplr-wallet/cosmos";
14
20
  import { TxChainSetter } from "./chain";
15
- import { ChainGetter, MakeTxResponse } from "@keplr-wallet/stores";
16
- import { Coin, StdFee } from "@keplr-wallet/types";
21
+ import { ChainGetter } from "@keplr-wallet/stores";
17
22
  import { isSimpleFetchError } from "@keplr-wallet/simple-fetch";
23
+ import { Dec } from "@keplr-wallet/unit";
18
24
 
19
- type TxSimulate = Pick<MakeTxResponse, "simulate">;
20
- export type SimulateGasFn = () => TxSimulate;
25
+ export type GasSimulate = () => Promise<GasEstimate>;
26
+ export type SimulateGasFn = () => GasSimulate;
21
27
 
22
28
  class GasSimulatorState {
23
- @observable
24
- protected _outdatedCosmosSdk: boolean = false;
25
-
26
- // If the initialGasEstimated is null, it means that there is no value stored or being loaded.
27
- @observable
28
- protected _initialGasEstimated: number | null = null;
29
-
30
29
  @observable
31
30
  protected _isInitialized: boolean = false;
32
31
 
33
- @observable
34
- protected _recentGasEstimated: number | undefined = undefined;
32
+ // If the initialGasEstimate is null, it means that there is no value stored or being loaded.
33
+ @observable.ref
34
+ protected _initialGasEstimate: GasEstimate | null = null;
35
35
 
36
36
  @observable.ref
37
- protected _tx: TxSimulate | undefined = undefined;
37
+ protected _recentGasEstimate: GasEstimate | null = null;
38
+
38
39
  @observable.ref
39
- protected _stdFee: StdFee | undefined = undefined;
40
+ protected _gasSimulate: GasSimulate | undefined = undefined;
40
41
  @observable.ref
41
42
  protected _error: Error | undefined = undefined;
42
43
 
@@ -44,10 +45,6 @@ class GasSimulatorState {
44
45
  makeObservable(this);
45
46
  }
46
47
 
47
- get outdatedCosmosSdk(): boolean {
48
- return this._outdatedCosmosSdk;
49
- }
50
-
51
48
  @action
52
49
  setIsInitialized(value: boolean) {
53
50
  this._isInitialized = value;
@@ -58,48 +55,30 @@ class GasSimulatorState {
58
55
  }
59
56
 
60
57
  @action
61
- setOutdatedCosmosSdk(value: boolean) {
62
- this._outdatedCosmosSdk = value;
58
+ setInitialGasEstimate(estimate: GasEstimate) {
59
+ this._initialGasEstimate = estimate;
63
60
  }
64
61
 
65
- get initialGasEstimated(): number | null {
66
- return this._initialGasEstimated;
62
+ get initialGasEstimate(): GasEstimate | null {
63
+ return this._initialGasEstimate;
67
64
  }
68
65
 
69
66
  @action
70
- setInitialGasEstimated(value: number) {
71
- this._initialGasEstimated = value;
67
+ setRecentGasEstimate(estimate: GasEstimate) {
68
+ this._recentGasEstimate = estimate;
72
69
  }
73
70
 
74
- get recentGasEstimated(): number | undefined {
75
- return this._recentGasEstimated;
71
+ get recentGasEstimate(): GasEstimate | null {
72
+ return this._recentGasEstimate;
76
73
  }
77
74
 
78
75
  @action
79
- setRecentGasEstimated(value: number) {
80
- this._recentGasEstimated = value;
76
+ refreshGasSimulate(value: GasSimulate) {
77
+ this._gasSimulate = value;
81
78
  }
82
79
 
83
- get tx(): TxSimulate | undefined {
84
- return this._tx;
85
- }
86
-
87
- @action
88
- refreshTx(tx: TxSimulate | undefined) {
89
- this._tx = tx;
90
- }
91
-
92
- get stdFee(): StdFee | undefined {
93
- return this._stdFee;
94
- }
95
-
96
- @action
97
- refreshStdFee(fee: StdFee | undefined) {
98
- this._stdFee = fee;
99
- }
100
-
101
- get error(): Error | undefined {
102
- return this._error;
80
+ get gasSimulate(): GasSimulate | undefined {
81
+ return this._gasSimulate;
103
82
  }
104
83
 
105
84
  @action
@@ -107,18 +86,8 @@ class GasSimulatorState {
107
86
  this._error = error;
108
87
  }
109
88
 
110
- static isZeroFee(amount: readonly Coin[] | undefined): boolean {
111
- if (!amount) {
112
- return true;
113
- }
114
-
115
- for (const coin of amount) {
116
- if (coin.amount !== "0") {
117
- return false;
118
- }
119
- }
120
-
121
- return true;
89
+ get error(): Error | undefined {
90
+ return this._error;
122
91
  }
123
92
  }
124
93
 
@@ -126,9 +95,6 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
126
95
  @observable
127
96
  protected _key: string;
128
97
 
129
- @observable
130
- protected _gasAdjustmentValue: string = "1";
131
-
132
98
  @observable
133
99
  protected _enabled: boolean = false;
134
100
 
@@ -144,6 +110,9 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
144
110
  @observable.shallow
145
111
  protected _stateMap: Map<string, GasSimulatorState> = new Map();
146
112
 
113
+ protected _debounceTimeoutId: NodeJS.Timeout | null = null;
114
+ protected readonly _debounceMs: number = 300;
115
+
147
116
  protected _disposers: IReactionDisposer[] = [];
148
117
 
149
118
  constructor(
@@ -154,7 +123,6 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
154
123
  protected readonly gasConfig: IGasConfig,
155
124
  protected readonly feeConfig: IFeeConfig,
156
125
  protected readonly initialKey: string,
157
- // TODO: Add comment about the reason why simulateGasFn field is not observable.
158
126
  protected simulateGasFn: SimulateGasFn
159
127
  ) {
160
128
  super(chainGetter, initialChainId);
@@ -231,75 +199,21 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
231
199
  }
232
200
  }
233
201
 
234
- get outdatedCosmosSdk(): boolean {
235
- const key = this.storeKey;
236
- const state = this.getState(key);
237
- return state.outdatedCosmosSdk;
238
- }
239
-
240
202
  get error(): Error | undefined {
241
203
  const key = this.storeKey;
242
204
  const state = this.getState(key);
243
205
  return state.error;
244
206
  }
245
207
 
246
- get gasEstimated(): number | undefined {
208
+ get gasEstimate(): GasEstimate | undefined {
247
209
  const key = this.storeKey;
248
210
  const state = this.getState(key);
249
- if (state.recentGasEstimated != null) {
250
- return state.recentGasEstimated;
251
- }
252
211
 
253
- if (state.initialGasEstimated != null) {
254
- return state.initialGasEstimated;
212
+ if (state.recentGasEstimate != null) {
213
+ return state.recentGasEstimate;
255
214
  }
256
215
 
257
- return undefined;
258
- }
259
-
260
- get gasAdjustment(): number {
261
- if (this._gasAdjustmentValue === "") {
262
- return 0;
263
- }
264
-
265
- const num = parseFloat(this._gasAdjustmentValue);
266
- if (Number.isNaN(num) || num < 0) {
267
- return 0;
268
- }
269
-
270
- return num;
271
- }
272
-
273
- get gasAdjustmentValue(): string {
274
- return this._gasAdjustmentValue;
275
- }
276
-
277
- @action
278
- setGasAdjustmentValue(gasAdjustment: string | number) {
279
- if (typeof gasAdjustment === "number") {
280
- if (gasAdjustment < 0 || gasAdjustment > 2) {
281
- return;
282
- }
283
-
284
- this._gasAdjustmentValue = gasAdjustment.toString();
285
- return;
286
- }
287
-
288
- if (gasAdjustment === "") {
289
- this._gasAdjustmentValue = "";
290
- return;
291
- }
292
-
293
- if (gasAdjustment.startsWith(".")) {
294
- this._gasAdjustmentValue = "0" + gasAdjustment;
295
- }
296
-
297
- const num = parseFloat(gasAdjustment);
298
- if (Number.isNaN(num) || num < 0 || num > 2) {
299
- return;
300
- }
301
-
302
- this._gasAdjustmentValue = gasAdjustment;
216
+ return state.initialGasEstimate ?? undefined;
303
217
  }
304
218
 
305
219
  protected init() {
@@ -312,9 +226,17 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
312
226
  const key = this.storeKey;
313
227
  const state = this.getState(key);
314
228
 
315
- this.kvStore.get<number>(key).then((saved) => {
229
+ this.kvStore.get<string>(key).then((saved) => {
316
230
  if (saved) {
317
- state.setInitialGasEstimated(saved);
231
+ try {
232
+ const gasEstimate: GasEstimate = JSON.parse(saved);
233
+ state.setInitialGasEstimate(gasEstimate);
234
+ } catch (e) {
235
+ // initial gas estimate is not critical,
236
+ // just log the error and delete the estimate from the store.
237
+ console.warn(e);
238
+ this.kvStore.set(key, "");
239
+ }
318
240
  }
319
241
 
320
242
  state.setIsInitialized(true);
@@ -344,19 +266,11 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
344
266
  return;
345
267
  }
346
268
 
347
- const tx = this.simulateGasFn();
348
- // TODO
349
- // const fee = this.feeConfig.toStdFee();
269
+ const gasSimulate = this.simulateGasFn();
350
270
 
351
271
  runInAction(() => {
352
- if (
353
- (state.recentGasEstimated == null || state.error != null) &&
354
- !state.outdatedCosmosSdk
355
- // || GasSimulatorState.isZeroFee(state.stdFee?.amount) !==
356
- // GasSimulatorState.isZeroFee(fee.amount)
357
- ) {
358
- state.refreshTx(tx);
359
- // state.refreshStdFee(fee);
272
+ if (state.recentGasEstimate == null || state.error != null) {
273
+ state.refreshGasSimulate(gasSimulate);
360
274
  }
361
275
  });
362
276
  } catch (e) {
@@ -368,98 +282,84 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
368
282
 
369
283
  this._disposers.push(
370
284
  autorun(() => {
371
- // TODO: Add debounce logic?
372
-
373
285
  const key = this.storeKey;
374
286
  const state = this.getState(key);
375
287
 
376
- if (!state.tx) {
288
+ if (!state.gasSimulate) {
377
289
  return;
378
290
  }
379
291
 
380
- const promise = state.tx.simulate(state.stdFee);
381
-
382
- runInAction(() => {
383
- this._isSimulating = true;
384
- });
385
-
386
- promise
387
- .then(({ gasUsed }) => {
388
- // Changing the gas in the gas config definitely will make the reaction to the fee config,
389
- // and, this reaction can potentially create a reaction in the amount config as well (Ex, when the "Max" option set).
390
- // These potential reactions can create repeated meaningless reactions.
391
- // To avoid this potential problem, change the value when there is a meaningful change in the gas estimated.
392
- if (
393
- !state.recentGasEstimated ||
394
- Math.abs(state.recentGasEstimated - gasUsed) /
395
- state.recentGasEstimated >
396
- 0.02
397
- ) {
398
- state.setRecentGasEstimated(gasUsed);
399
- }
400
-
401
- state.setOutdatedCosmosSdk(false);
402
- state.setError(undefined);
292
+ if (this._debounceTimeoutId) {
293
+ clearTimeout(this._debounceTimeoutId);
294
+ }
403
295
 
404
- this.kvStore.set(key, gasUsed).catch((e) => {
405
- console.log(e);
406
- });
407
- })
408
- .catch((e) => {
409
- console.log(e);
410
- if (isSimpleFetchError(e) && e.response) {
411
- const response = e.response;
412
- if (
413
- response.status === 400 &&
414
- response.data?.message &&
415
- typeof response.data.message === "string" &&
416
- response.data.message.includes("invalid empty tx")
417
- ) {
418
- state.setOutdatedCosmosSdk(true);
419
- return;
420
- }
296
+ const promise = state.gasSimulate();
421
297
 
422
- let message = "";
423
- const contentType: string = e.response.headers
424
- ? e.response.headers.get("content-type") || ""
425
- : "";
426
- // Try to figure out the message from the response.
427
- // If the contentType in the header is specified, try to use the message from the response.
428
- if (
429
- contentType.startsWith("text/plain") &&
430
- typeof e.response.data === "string"
431
- ) {
432
- message = e.response.data;
433
- }
434
- // If the response is an object and "message" field exists, it is used as a message.
435
- if (
436
- contentType.startsWith("application/json") &&
437
- e.response.data?.message &&
438
- typeof e.response.data?.message === "string"
439
- ) {
440
- message = e.response.data.message;
441
- }
298
+ this._debounceTimeoutId = setTimeout(() => {
299
+ runInAction(() => {
300
+ this._isSimulating = true;
301
+ });
442
302
 
443
- if (message !== "") {
444
- state.setError(new Error(message));
445
- return;
303
+ promise
304
+ .then((gasEstimate) => {
305
+ state.setRecentGasEstimate(gasEstimate);
306
+ state.setError(undefined);
307
+
308
+ this.kvStore.set(key, JSON.stringify(gasEstimate)).catch((e) => {
309
+ console.log(e);
310
+ });
311
+ })
312
+ .catch((e) => {
313
+ console.log("starknet gas simulate error", e);
314
+ if (isSimpleFetchError(e) && e.response) {
315
+ let message = "";
316
+ const contentType: string = e.response.headers
317
+ ? e.response.headers.get("content-type") || ""
318
+ : "";
319
+ // Try to figure out the message from the response.
320
+ // If the contentType in the header is specified, try to use the message from the response.
321
+ if (
322
+ contentType.startsWith("text/plain") &&
323
+ typeof e.response.data === "string"
324
+ ) {
325
+ message = e.response.data;
326
+ }
327
+ // If the response is an object and "message" field exists, it is used as a message.
328
+ if (
329
+ contentType.startsWith("application/json") &&
330
+ e.response.data?.message &&
331
+ typeof e.response.data?.message === "string"
332
+ ) {
333
+ message = e.response.data.message;
334
+ }
335
+
336
+ if (message !== "") {
337
+ state.setError(new Error(message));
338
+ return;
339
+ }
446
340
  }
447
- }
448
341
 
449
- state.setError(e);
450
- })
451
- .finally(() => {
452
- runInAction(() => {
453
- this._isSimulating = false;
342
+ state.setError(e);
343
+ })
344
+ .finally(() => {
345
+ runInAction(() => {
346
+ this._isSimulating = false;
347
+ });
454
348
  });
455
- });
349
+ }, this._debounceMs);
456
350
  })
457
351
  );
458
352
 
459
353
  this._disposers.push(
460
354
  autorun(() => {
461
- if (this.enabled && this.gasEstimated != null) {
462
- this.gasConfig.setValue(this.gasEstimated * this.gasAdjustment);
355
+ if (this.enabled && this.gasEstimate != null) {
356
+ const { l1DataGas, l1Gas, l2Gas } = this.gasEstimate;
357
+
358
+ const gas = new Dec(l1DataGas.consumed)
359
+ .add(new Dec(l1Gas.consumed))
360
+ .add(new Dec(l2Gas.consumed));
361
+
362
+ this.gasConfig.setValue(gas.truncate().toString());
463
363
  }
464
364
  })
465
365
  );
@@ -477,10 +377,6 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
477
377
 
478
378
  return {
479
379
  warning: (() => {
480
- if (this.outdatedCosmosSdk) {
481
- return new Error("Outdated Cosmos SDK");
482
- }
483
-
484
380
  if (this.forceDisableReason) {
485
381
  return this.forceDisableReason;
486
382
  }
@@ -496,9 +392,7 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
496
392
 
497
393
  if (this.isSimulating) {
498
394
  // If there is no saved result of the last simulation, user interaction is blocked.
499
- return state.initialGasEstimated == null
500
- ? "loading-block"
501
- : "loading";
395
+ return state.initialGasEstimate == null ? "loading-block" : "loading";
502
396
  }
503
397
  })(),
504
398
  };
@@ -523,7 +417,7 @@ export class GasSimulator extends TxChainSetter implements IGasSimulator {
523
417
  // .toStdFee()
524
418
  // .amount.map((coin) => coin.denom)
525
419
  // .join("/");
526
- return `${chainIdentifier.identifier}/${fees}/${this.key}}`;
420
+ return `${chainIdentifier.identifier}/${fees}/${this.key}`;
527
421
  }
528
422
  }
529
423
 
package/src/tx/gas.ts CHANGED
@@ -12,6 +12,9 @@ export class GasConfig extends TxChainSetter implements IGasConfig {
12
12
  @observable
13
13
  protected _value: string = "";
14
14
 
15
+ @observable
16
+ protected _gasAdjustmentValue: string = "1.5";
17
+
15
18
  /*
16
19
  There are services that sometimes use invalid tx to sign arbitrary data on the sign page.
17
20
  In this case, there is no obligation to deal with it, but 0 gas is favorably allowed. This option is used for this case.
@@ -39,6 +42,10 @@ export class GasConfig extends TxChainSetter implements IGasConfig {
39
42
  return this._value;
40
43
  }
41
44
 
45
+ get gasAdjustmentValue(): string {
46
+ return this._gasAdjustmentValue;
47
+ }
48
+
42
49
  @action
43
50
  setValue(value: string | number): void {
44
51
  if (typeof value === "number") {
@@ -48,6 +55,47 @@ export class GasConfig extends TxChainSetter implements IGasConfig {
48
55
  }
49
56
  }
50
57
 
58
+ @action
59
+ setGasAdjustmentValue(gasAdjustment: string | number) {
60
+ if (typeof gasAdjustment === "number") {
61
+ if (gasAdjustment < 0 || gasAdjustment > 2) {
62
+ return;
63
+ }
64
+
65
+ this._gasAdjustmentValue = gasAdjustment.toString();
66
+ return;
67
+ }
68
+
69
+ if (gasAdjustment === "") {
70
+ this._gasAdjustmentValue = "";
71
+ return;
72
+ }
73
+
74
+ if (gasAdjustment.startsWith(".")) {
75
+ this._gasAdjustmentValue = "0" + gasAdjustment;
76
+ }
77
+
78
+ const num = parseFloat(gasAdjustment);
79
+ if (Number.isNaN(num) || num < 0 || num > 2) {
80
+ return;
81
+ }
82
+
83
+ this._gasAdjustmentValue = gasAdjustment;
84
+ }
85
+
86
+ get gasAdjustment(): number {
87
+ if (this._gasAdjustmentValue === "") {
88
+ return 0;
89
+ }
90
+
91
+ const num = parseFloat(this._gasAdjustmentValue);
92
+ if (Number.isNaN(num) || num < 0) {
93
+ return 0;
94
+ }
95
+
96
+ return num;
97
+ }
98
+
51
99
  get gas(): number {
52
100
  if (this.value.trim() === "") {
53
101
  return 0;
@@ -61,6 +109,10 @@ export class GasConfig extends TxChainSetter implements IGasConfig {
61
109
  return num;
62
110
  }
63
111
 
112
+ get maxGas(): number {
113
+ return Math.ceil(this.gas * this.gasAdjustment);
114
+ }
115
+
64
116
  @computed
65
117
  get uiProperties(): UIProperties {
66
118
  if (this.value.trim() === "") {
package/src/tx/types.ts CHANGED
@@ -18,11 +18,33 @@ export interface UIProperties {
18
18
  readonly loadingState?: "loading" | "loading-block";
19
19
  }
20
20
 
21
+ export interface GasEstimate {
22
+ l1Gas: {
23
+ consumed: string;
24
+ price: string;
25
+ };
26
+ l1DataGas: {
27
+ consumed: string;
28
+ price: string;
29
+ };
30
+ l2Gas: {
31
+ consumed: string;
32
+ price: string;
33
+ };
34
+ }
35
+
36
+ export type FeeType = "STRK";
37
+
21
38
  export interface IGasConfig extends ITxChainSetter {
22
39
  value: string;
40
+ gasAdjustmentValue: string;
41
+
23
42
  setValue(value: string | number): void;
43
+ setGasAdjustmentValue(gasAdjustment: string | number): void;
24
44
 
25
45
  gas: number;
46
+ maxGas: number;
47
+ gasAdjustment: number;
26
48
 
27
49
  uiProperties: UIProperties;
28
50
  }
@@ -37,8 +59,8 @@ export interface ISenderConfig extends ITxChainSetter {
37
59
  }
38
60
 
39
61
  export interface IFeeConfig extends ITxChainSetter {
40
- type: "ETH" | "STRK";
41
- setType(type: "ETH" | "STRK"): void;
62
+ type: FeeType;
63
+ setType(type: FeeType): void;
42
64
 
43
65
  gasPrice: CoinPretty | undefined;
44
66
  maxGasPrice: CoinPretty | undefined;
@@ -106,11 +128,7 @@ export interface IGasSimulator {
106
128
 
107
129
  isSimulating: boolean;
108
130
 
109
- gasEstimated: number | undefined;
110
- gasAdjustment: number;
111
-
112
- gasAdjustmentValue: string;
113
- setGasAdjustmentValue(gasAdjustment: string | number): void;
131
+ gasEstimate: GasEstimate | undefined;
114
132
 
115
133
  uiProperties: UIProperties;
116
134
  }