@keplr-wallet/hooks-starknet 0.12.133-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 (62) hide show
  1. package/.eslintignore +2 -0
  2. package/.prettierignore +2 -0
  3. package/LICENSE +209 -0
  4. package/build/index.d.ts +1 -0
  5. package/build/index.js +18 -0
  6. package/build/index.js.map +1 -0
  7. package/build/tx/amount.d.ts +27 -0
  8. package/build/tx/amount.js +230 -0
  9. package/build/tx/amount.js.map +1 -0
  10. package/build/tx/chain.d.ts +11 -0
  11. package/build/tx/chain.js +37 -0
  12. package/build/tx/chain.js.map +1 -0
  13. package/build/tx/errors.d.ts +48 -0
  14. package/build/tx/errors.js +132 -0
  15. package/build/tx/errors.js.map +1 -0
  16. package/build/tx/fee.d.ts +30 -0
  17. package/build/tx/fee.js +167 -0
  18. package/build/tx/fee.js.map +1 -0
  19. package/build/tx/gas-simulator.d.ts +72 -0
  20. package/build/tx/gas-simulator.js +491 -0
  21. package/build/tx/gas-simulator.js.map +1 -0
  22. package/build/tx/gas.d.ts +14 -0
  23. package/build/tx/gas.js +116 -0
  24. package/build/tx/gas.js.map +1 -0
  25. package/build/tx/index.d.ts +12 -0
  26. package/build/tx/index.js +29 -0
  27. package/build/tx/index.js.map +1 -0
  28. package/build/tx/noop-amount.d.ts +20 -0
  29. package/build/tx/noop-amount.js +90 -0
  30. package/build/tx/noop-amount.js.map +1 -0
  31. package/build/tx/recipient.d.ts +12 -0
  32. package/build/tx/recipient.js +80 -0
  33. package/build/tx/recipient.js.map +1 -0
  34. package/build/tx/send-tx.d.ts +9 -0
  35. package/build/tx/send-tx.js +22 -0
  36. package/build/tx/send-tx.js.map +1 -0
  37. package/build/tx/sender.d.ts +14 -0
  38. package/build/tx/sender.js +88 -0
  39. package/build/tx/sender.js.map +1 -0
  40. package/build/tx/types.d.ts +63 -0
  41. package/build/tx/types.js +3 -0
  42. package/build/tx/types.js.map +1 -0
  43. package/build/tx/validate.d.ts +11 -0
  44. package/build/tx/validate.js +52 -0
  45. package/build/tx/validate.js.map +1 -0
  46. package/jest.config.js +5 -0
  47. package/package.json +43 -0
  48. package/src/index.ts +1 -0
  49. package/src/tx/amount.ts +275 -0
  50. package/src/tx/chain.ts +32 -0
  51. package/src/tx/errors.ts +127 -0
  52. package/src/tx/fee.ts +209 -0
  53. package/src/tx/gas-simulator.ts +571 -0
  54. package/src/tx/gas.ts +131 -0
  55. package/src/tx/index.ts +12 -0
  56. package/src/tx/noop-amount.ts +92 -0
  57. package/src/tx/recipient.ts +82 -0
  58. package/src/tx/send-tx.ts +53 -0
  59. package/src/tx/sender.ts +104 -0
  60. package/src/tx/types.ts +97 -0
  61. package/src/tx/validate.ts +70 -0
  62. package/tsconfig.json +13 -0
@@ -0,0 +1,275 @@
1
+ import {
2
+ IAmountConfig,
3
+ IFeeConfig,
4
+ ISenderConfig,
5
+ UIProperties,
6
+ } from "./types";
7
+ import { TxChainSetter } from "./chain";
8
+ import { ChainGetter } from "@keplr-wallet/stores";
9
+ import { action, computed, makeObservable, observable } from "mobx";
10
+ import { ERC20Currency } from "@keplr-wallet/types";
11
+ import {
12
+ EmptyAmountError,
13
+ InsufficientAmountError,
14
+ InvalidNumberAmountError,
15
+ NegativeAmountError,
16
+ NotSupportedCurrencyError,
17
+ ZeroAmountError,
18
+ } from "./errors";
19
+ import { CoinPretty, Dec, DecUtils } from "@keplr-wallet/unit";
20
+ import { useState } from "react";
21
+ import { StarknetQueriesStore } from "@keplr-wallet/stores-starknet";
22
+
23
+ export class AmountConfig extends TxChainSetter implements IAmountConfig {
24
+ @observable.ref
25
+ protected _currency?: ERC20Currency = undefined;
26
+
27
+ @observable
28
+ protected _value: string = "";
29
+
30
+ @observable
31
+ protected _fraction: number = 0;
32
+
33
+ @observable.ref
34
+ protected _feeConfig: IFeeConfig | undefined = undefined;
35
+
36
+ constructor(
37
+ chainGetter: ChainGetter,
38
+ protected readonly starknetQueriesStore: StarknetQueriesStore,
39
+ initialChainId: string,
40
+ protected readonly senderConfig: ISenderConfig
41
+ ) {
42
+ super(chainGetter, initialChainId);
43
+
44
+ makeObservable(this);
45
+ }
46
+
47
+ get feeConfig(): IFeeConfig | undefined {
48
+ return this._feeConfig;
49
+ }
50
+
51
+ @action
52
+ setFeeConfig(feeConfig: IFeeConfig | undefined) {
53
+ this._feeConfig = feeConfig;
54
+ }
55
+
56
+ @computed
57
+ get value(): string {
58
+ if (this.fraction > 0) {
59
+ let result = this.starknetQueriesStore
60
+ .get(this.chainId)
61
+ .queryStarknetERC20Balance.getBalance(
62
+ this.chainId,
63
+ this.chainGetter,
64
+ this.senderConfig.sender,
65
+ this.currency.coinMinimalDenom
66
+ )?.balance;
67
+ if (!result) {
68
+ return "0";
69
+ }
70
+ if (this.feeConfig) {
71
+ if (this.feeConfig.fee) {
72
+ result = result.sub(this.feeConfig.fee);
73
+ }
74
+ }
75
+ if (result.toDec().lte(new Dec(0))) {
76
+ return "0";
77
+ }
78
+
79
+ return result
80
+ .mul(new Dec(this.fraction))
81
+ .trim(true)
82
+ .locale(false)
83
+ .hideDenom(true)
84
+ .toString();
85
+ }
86
+
87
+ return this._value;
88
+ }
89
+
90
+ @action
91
+ setValue(value: string): void {
92
+ if (value.startsWith(".")) {
93
+ value = "0" + value;
94
+ }
95
+
96
+ this._value = value;
97
+
98
+ this.setFraction(0);
99
+ }
100
+
101
+ @computed
102
+ get amount(): CoinPretty[] {
103
+ let amount: Dec;
104
+ try {
105
+ if (this.value.trim() === "") {
106
+ amount = new Dec(0);
107
+ } else {
108
+ amount = new Dec(this.value);
109
+ }
110
+ } catch {
111
+ amount = new Dec(0);
112
+ }
113
+
114
+ return [
115
+ new CoinPretty(
116
+ this.currency,
117
+ amount
118
+ .mul(DecUtils.getTenExponentN(this.currency.coinDecimals))
119
+ .truncate()
120
+ ),
121
+ ];
122
+ }
123
+
124
+ @computed
125
+ get currency(): ERC20Currency {
126
+ const modularChainInfo = this.modularChainInfo;
127
+ if (!("starknet" in modularChainInfo)) {
128
+ throw new Error("Chain doesn't support the starknet");
129
+ }
130
+
131
+ if (this._currency) {
132
+ const find = modularChainInfo.starknet.currencies.find(
133
+ (cur) => cur.coinMinimalDenom === this._currency!.coinMinimalDenom
134
+ );
135
+ if (find) {
136
+ return find;
137
+ }
138
+ }
139
+
140
+ return modularChainInfo.starknet.currencies[0];
141
+ }
142
+
143
+ @action
144
+ setCurrency(currency: ERC20Currency | undefined) {
145
+ if (currency?.coinMinimalDenom !== this._currency?.coinMinimalDenom) {
146
+ this._value = "";
147
+ this.setFraction(0);
148
+ }
149
+
150
+ this._currency = currency;
151
+ }
152
+
153
+ get fraction(): number {
154
+ return this._fraction;
155
+ }
156
+
157
+ @action
158
+ setFraction(fraction: number): void {
159
+ this._fraction = fraction;
160
+ }
161
+
162
+ canUseCurrency(currency: ERC20Currency): boolean {
163
+ const modularChainInfo = this.modularChainInfo;
164
+ if (!("starknet" in modularChainInfo)) {
165
+ throw new Error("Chain doesn't support the starknet");
166
+ }
167
+
168
+ return (
169
+ modularChainInfo.starknet.currencies.find(
170
+ (cur) => cur.coinMinimalDenom === currency.coinMinimalDenom
171
+ ) != null
172
+ );
173
+ }
174
+
175
+ @computed
176
+ get uiProperties(): UIProperties {
177
+ if (!this.currency) {
178
+ return {
179
+ error: new Error("Currency to send not set"),
180
+ };
181
+ }
182
+
183
+ if (this.value.trim() === "") {
184
+ return {
185
+ error: new EmptyAmountError("Amount is empty"),
186
+ };
187
+ }
188
+
189
+ try {
190
+ const dec = new Dec(this.value);
191
+ if (dec.equals(new Dec(0))) {
192
+ return {
193
+ error: new ZeroAmountError("Amount is zero"),
194
+ };
195
+ }
196
+ if (dec.lt(new Dec(0))) {
197
+ return {
198
+ error: new NegativeAmountError("Amount is negative"),
199
+ };
200
+ }
201
+ } catch {
202
+ return {
203
+ error: new InvalidNumberAmountError("Invalid form of number"),
204
+ };
205
+ }
206
+
207
+ for (const amount of this.amount) {
208
+ const currency = amount.currency;
209
+
210
+ if (!("type" in currency) || currency.type !== "erc20") {
211
+ return {
212
+ error: new NotSupportedCurrencyError("Not supported currency"),
213
+ };
214
+ }
215
+
216
+ if (!this.canUseCurrency(currency)) {
217
+ return {
218
+ error: new NotSupportedCurrencyError("Not supported currency"),
219
+ };
220
+ }
221
+
222
+ const bal = this.starknetQueriesStore
223
+ .get(this.chainId)
224
+ .queryStarknetERC20Balance.getBalance(
225
+ this.chainId,
226
+ this.chainGetter,
227
+ this.senderConfig.sender,
228
+ currency.coinMinimalDenom
229
+ );
230
+
231
+ if (!bal) {
232
+ return {
233
+ warning: new Error(
234
+ `Can't parse the balance for ${currency.coinMinimalDenom}`
235
+ ),
236
+ };
237
+ }
238
+
239
+ if (bal.error) {
240
+ return {
241
+ warning: new Error("Failed to fetch balance"),
242
+ };
243
+ }
244
+
245
+ if (!bal.response) {
246
+ return {
247
+ loadingState: "loading-block",
248
+ };
249
+ }
250
+
251
+ if (bal.balance.toDec().lt(amount.toDec())) {
252
+ return {
253
+ error: new InsufficientAmountError("Insufficient amount"),
254
+ loadingState: bal.isFetching ? "loading" : undefined,
255
+ };
256
+ }
257
+ }
258
+
259
+ return {};
260
+ }
261
+ }
262
+
263
+ export const useAmountConfig = (
264
+ chainGetter: ChainGetter,
265
+ queriesStore: StarknetQueriesStore,
266
+ chainId: string,
267
+ senderConfig: ISenderConfig
268
+ ) => {
269
+ const [txConfig] = useState(
270
+ () => new AmountConfig(chainGetter, queriesStore, chainId, senderConfig)
271
+ );
272
+ txConfig.setChain(chainId);
273
+
274
+ return txConfig;
275
+ };
@@ -0,0 +1,32 @@
1
+ import { action, computed, makeObservable, observable } from "mobx";
2
+ import { ChainGetter } from "@keplr-wallet/stores";
3
+ import { ITxChainSetter } from "./types";
4
+ import { ModularChainInfo } from "@keplr-wallet/types";
5
+
6
+ export class TxChainSetter implements ITxChainSetter {
7
+ @observable
8
+ protected _chainId: string;
9
+
10
+ constructor(
11
+ protected readonly chainGetter: ChainGetter,
12
+ initialChainId: string
13
+ ) {
14
+ this._chainId = initialChainId;
15
+
16
+ makeObservable(this);
17
+ }
18
+
19
+ @computed
20
+ get modularChainInfo(): ModularChainInfo {
21
+ return this.chainGetter.getModularChain(this.chainId);
22
+ }
23
+
24
+ get chainId(): string {
25
+ return this._chainId;
26
+ }
27
+
28
+ @action
29
+ setChain(chainId: string) {
30
+ this._chainId = chainId;
31
+ }
32
+ }
@@ -0,0 +1,127 @@
1
+ export class AccountNotDeployed extends Error {
2
+ constructor(m: string) {
3
+ super(m);
4
+ // Set the prototype explicitly.
5
+ Object.setPrototypeOf(this, AccountNotDeployed.prototype);
6
+ }
7
+ }
8
+
9
+ export class EmptyAddressError extends Error {
10
+ constructor(m: string) {
11
+ super(m);
12
+ // Set the prototype explicitly.
13
+ Object.setPrototypeOf(this, EmptyAddressError.prototype);
14
+ }
15
+ }
16
+
17
+ export class InvalidBech32Error extends Error {
18
+ constructor(m: string) {
19
+ super(m);
20
+ // Set the prototype explicitly.
21
+ Object.setPrototypeOf(this, InvalidBech32Error.prototype);
22
+ }
23
+ }
24
+
25
+ export class ICNSIsFetchingError extends Error {
26
+ constructor(m: string) {
27
+ super(m);
28
+ // Set the prototype explicitly.
29
+ Object.setPrototypeOf(this, ICNSIsFetchingError.prototype);
30
+ }
31
+ }
32
+
33
+ export class ICNSFailedToFetchError extends Error {
34
+ constructor(m: string) {
35
+ super(m);
36
+ // Set the prototype explicitly.
37
+ Object.setPrototypeOf(this, ICNSFailedToFetchError.prototype);
38
+ }
39
+ }
40
+
41
+ export class EmptyAmountError extends Error {
42
+ constructor(m: string) {
43
+ super(m);
44
+ // Set the prototype explicitly.
45
+ Object.setPrototypeOf(this, EmptyAmountError.prototype);
46
+ }
47
+ }
48
+
49
+ export class InvalidNumberAmountError extends Error {
50
+ constructor(m: string) {
51
+ super(m);
52
+ // Set the prototype explicitly.
53
+ Object.setPrototypeOf(this, InvalidNumberAmountError.prototype);
54
+ }
55
+ }
56
+
57
+ export class ZeroAmountError extends Error {
58
+ constructor(m: string) {
59
+ super(m);
60
+ // Set the prototype explicitly.
61
+ Object.setPrototypeOf(this, ZeroAmountError.prototype);
62
+ }
63
+ }
64
+
65
+ export class NegativeAmountError extends Error {
66
+ constructor(m: string) {
67
+ super(m);
68
+ // Set the prototype explicitly.
69
+ Object.setPrototypeOf(this, NegativeAmountError.prototype);
70
+ }
71
+ }
72
+
73
+ export class InsufficientAmountError extends Error {
74
+ constructor(m: string) {
75
+ super(m);
76
+ // Set the prototype explicitly.
77
+ Object.setPrototypeOf(this, InsufficientAmountError.prototype);
78
+ }
79
+ }
80
+
81
+ export class NotLoadedFeeError extends Error {
82
+ constructor(m: string) {
83
+ super(m);
84
+ // Set the prototype explicitly.
85
+ Object.setPrototypeOf(this, NotLoadedFeeError.prototype);
86
+ }
87
+ }
88
+
89
+ export class InsufficientFeeError extends Error {
90
+ constructor(m: string) {
91
+ super(m);
92
+ // Set the prototype explicitly.
93
+ Object.setPrototypeOf(this, InsufficientFeeError.prototype);
94
+ }
95
+ }
96
+
97
+ export class UnknownCurrencyError extends Error {
98
+ constructor(m: string) {
99
+ super(m);
100
+ // Set the prototype explicitly.
101
+ Object.setPrototypeOf(this, UnknownCurrencyError.prototype);
102
+ }
103
+ }
104
+
105
+ export class NotSupportedCurrencyError extends Error {
106
+ constructor(m: string) {
107
+ super(m);
108
+ // Set the prototype explicitly.
109
+ Object.setPrototypeOf(this, UnknownCurrencyError.prototype);
110
+ }
111
+ }
112
+
113
+ export class InvalidHexError extends Error {
114
+ constructor(m: string) {
115
+ super(m);
116
+ // Set the prototype explicitly.
117
+ Object.setPrototypeOf(this, InvalidHexError.prototype);
118
+ }
119
+ }
120
+
121
+ export class MemoSuspectMnemonicInclusion extends Error {
122
+ constructor(m: string) {
123
+ super(m);
124
+ // Set the prototype explicitly.
125
+ Object.setPrototypeOf(this, MemoSuspectMnemonicInclusion.prototype);
126
+ }
127
+ }
package/src/tx/fee.ts ADDED
@@ -0,0 +1,209 @@
1
+ import {
2
+ IAmountConfig,
3
+ IFeeConfig,
4
+ IGasConfig,
5
+ ISenderConfig,
6
+ UIProperties,
7
+ } from "./types";
8
+ import { TxChainSetter } from "./chain";
9
+ import { ChainGetter } from "@keplr-wallet/stores";
10
+ import { action, computed, makeObservable, observable } from "mobx";
11
+ import { useState } from "react";
12
+ import { StarknetQueriesStore } from "@keplr-wallet/stores-starknet";
13
+ import { CoinPretty, Dec, Int } from "@keplr-wallet/unit";
14
+ import { InsufficientFeeError } from "./errors";
15
+
16
+ export class FeeConfig extends TxChainSetter implements IFeeConfig {
17
+ @observable.ref
18
+ protected _gasPrice: CoinPretty | undefined = undefined;
19
+ @observable.ref
20
+ protected _maxGasPrice: CoinPretty | undefined = undefined;
21
+ @observable
22
+ protected _type: "ETH" | "STRK" = "STRK";
23
+
24
+ @observable
25
+ protected _disableBalanceCheck: boolean = false;
26
+
27
+ constructor(
28
+ chainGetter: ChainGetter,
29
+ protected readonly starknetQueriesStore: StarknetQueriesStore,
30
+ initialChainId: string,
31
+ protected readonly senderConfig: ISenderConfig,
32
+ protected readonly amountConfig: IAmountConfig,
33
+ protected readonly gasConfig: IGasConfig
34
+ ) {
35
+ super(chainGetter, initialChainId);
36
+
37
+ makeObservable(this);
38
+ }
39
+
40
+ @action
41
+ setDisableBalanceCheck(bool: boolean) {
42
+ this._disableBalanceCheck = bool;
43
+ }
44
+
45
+ get disableBalanceCheck(): boolean {
46
+ return this._disableBalanceCheck;
47
+ }
48
+
49
+ @computed
50
+ get uiProperties(): UIProperties {
51
+ if (this.disableBalanceCheck) {
52
+ return {};
53
+ }
54
+
55
+ if (!this._gasPrice) {
56
+ return {
57
+ error: new Error("Fee is not set"),
58
+ loadingState: "loading-block",
59
+ };
60
+ }
61
+
62
+ if (!this._maxGasPrice) {
63
+ return {
64
+ error: new Error("Fee is not set"),
65
+ loadingState: "loading-block",
66
+ };
67
+ }
68
+
69
+ if (!this.fee) {
70
+ return {
71
+ error: new Error("Fee is not set"),
72
+ loadingState: "loading-block",
73
+ };
74
+ }
75
+
76
+ if (!this.maxFee) {
77
+ return {
78
+ error: new Error("Fee is not set"),
79
+ loadingState: "loading-block",
80
+ };
81
+ }
82
+
83
+ const maxFee = this.maxFee;
84
+
85
+ const bal = this.starknetQueriesStore
86
+ .get(this.chainId)
87
+ .queryStarknetERC20Balance.getBalance(
88
+ this.chainId,
89
+ this.chainGetter,
90
+ this.senderConfig.value,
91
+ maxFee.currency.coinMinimalDenom
92
+ );
93
+
94
+ if (!bal) {
95
+ return {
96
+ warning: new Error(
97
+ `Can't parse the balance for ${maxFee.currency.coinMinimalDenom}`
98
+ ),
99
+ };
100
+ }
101
+
102
+ if (bal.error) {
103
+ return {
104
+ warning: new Error("Failed to fetch balance"),
105
+ };
106
+ }
107
+
108
+ if (!bal.response) {
109
+ return {
110
+ loadingState: "loading-block",
111
+ };
112
+ }
113
+
114
+ if (
115
+ new Int(bal.balance.toCoin().amount).lt(new Int(maxFee.toCoin().amount))
116
+ ) {
117
+ return {
118
+ error: new InsufficientFeeError("Insufficient fee"),
119
+ loadingState: bal.isFetching ? "loading" : undefined,
120
+ };
121
+ }
122
+
123
+ return {};
124
+ }
125
+
126
+ get gasPrice(): CoinPretty | undefined {
127
+ return this._gasPrice;
128
+ }
129
+
130
+ get maxGasPrice(): CoinPretty | undefined {
131
+ return this._maxGasPrice;
132
+ }
133
+
134
+ get type(): "ETH" | "STRK" {
135
+ return this._type;
136
+ }
137
+
138
+ @action
139
+ setGasPrice(
140
+ gasPrice:
141
+ | {
142
+ gasPrice: CoinPretty;
143
+ maxGasPrice: CoinPretty;
144
+ }
145
+ | undefined
146
+ ): void {
147
+ this._gasPrice = gasPrice?.gasPrice;
148
+ this._maxGasPrice = gasPrice?.maxGasPrice;
149
+ }
150
+
151
+ @action
152
+ setType(type: "ETH" | "STRK"): void {
153
+ if (this._type !== type) {
154
+ this._type = type;
155
+ this._gasPrice = undefined;
156
+ this._maxGasPrice = undefined;
157
+ }
158
+ }
159
+
160
+ @computed
161
+ get fee(): CoinPretty | undefined {
162
+ if (!this._gasPrice) {
163
+ return;
164
+ }
165
+
166
+ const gasDec = new Dec(this.gasConfig.gas);
167
+ return this._gasPrice.mul(gasDec);
168
+ }
169
+
170
+ @computed
171
+ get maxFee(): CoinPretty | undefined {
172
+ if (!this._maxGasPrice) {
173
+ return;
174
+ }
175
+
176
+ const gasDec = new Dec(this.gasConfig.gas);
177
+ return this._maxGasPrice.mul(gasDec);
178
+ }
179
+ }
180
+
181
+ export const useFeeConfig = (
182
+ chainGetter: ChainGetter,
183
+ queriesStore: StarknetQueriesStore,
184
+ chainId: string,
185
+ senderConfig: ISenderConfig,
186
+ amountConfig: IAmountConfig,
187
+ gasConfig: IGasConfig,
188
+ initialFn?: (config: FeeConfig) => void
189
+ ) => {
190
+ const [config] = useState(() => {
191
+ const config = new FeeConfig(
192
+ chainGetter,
193
+ queriesStore,
194
+ chainId,
195
+ senderConfig,
196
+ amountConfig,
197
+ gasConfig
198
+ );
199
+
200
+ if (initialFn) {
201
+ initialFn(config);
202
+ }
203
+
204
+ return config;
205
+ });
206
+ config.setChain(chainId);
207
+
208
+ return config;
209
+ };