@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.
- package/.eslintrc.json +14 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +18 -0
- package/build/index.js.map +1 -0
- package/build/tx/amount.d.ts +27 -0
- package/build/tx/amount.js +238 -0
- package/build/tx/amount.js.map +1 -0
- package/build/tx/chain.d.ts +10 -0
- package/build/tx/chain.js +37 -0
- package/build/tx/chain.js.map +1 -0
- package/build/tx/errors.d.ts +30 -0
- package/build/tx/errors.js +84 -0
- package/build/tx/errors.js.map +1 -0
- package/build/tx/evm-fee-utils.d.ts +28 -0
- package/build/tx/evm-fee-utils.js +133 -0
- package/build/tx/evm-fee-utils.js.map +1 -0
- package/build/tx/fee.d.ts +61 -0
- package/build/tx/fee.js +523 -0
- package/build/tx/fee.js.map +1 -0
- package/build/tx/gas-simulator.d.ts +89 -0
- package/build/tx/gas-simulator.js +465 -0
- package/build/tx/gas-simulator.js.map +1 -0
- package/build/tx/gas.d.ts +12 -0
- package/build/tx/gas.js +84 -0
- package/build/tx/gas.js.map +1 -0
- package/build/tx/index.d.ts +13 -0
- package/build/tx/index.js +30 -0
- package/build/tx/index.js.map +1 -0
- package/build/tx/internal.d.ts +3 -0
- package/build/tx/internal.js +3 -0
- package/build/tx/internal.js.map +1 -0
- package/build/tx/name-service-ens.d.ts +40 -0
- package/build/tx/name-service-ens.js +189 -0
- package/build/tx/name-service-ens.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 +35 -0
- package/build/tx/recipient.js +131 -0
- package/build/tx/recipient.js.map +1 -0
- package/build/tx/send-tx.d.ts +13 -0
- package/build/tx/send-tx.js +24 -0
- package/build/tx/send-tx.js.map +1 -0
- package/build/tx/sender.d.ts +12 -0
- package/build/tx/sender.js +73 -0
- package/build/tx/sender.js.map +1 -0
- package/build/tx/types.d.ts +102 -0
- package/build/tx/types.js +3 -0
- package/build/tx/types.js.map +1 -0
- package/build/tx/validate.d.ts +11 -0
- package/build/tx/validate.js +37 -0
- package/build/tx/validate.js.map +1 -0
- package/package.json +40 -0
- package/src/index.ts +1 -0
- package/src/tx/amount.ts +273 -0
- package/src/tx/chain.ts +31 -0
- package/src/tx/errors.ts +79 -0
- package/src/tx/evm-fee-utils.ts +217 -0
- package/src/tx/fee.ts +622 -0
- package/src/tx/gas-simulator.ts +567 -0
- package/src/tx/gas.ts +93 -0
- package/src/tx/index.ts +13 -0
- package/src/tx/internal.ts +4 -0
- package/src/tx/name-service-ens.ts +207 -0
- package/src/tx/name-service.ts +39 -0
- package/src/tx/recipient.ts +166 -0
- package/src/tx/send-tx.ts +55 -0
- package/src/tx/sender.ts +82 -0
- package/src/tx/types.ts +153 -0
- package/src/tx/validate.ts +55 -0
- package/tsconfig.check.json +90 -0
- 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
|
+
};
|
package/src/tx/index.ts
ADDED
|
@@ -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";
|