@reown/appkit-core-react-native 1.1.1 → 1.2.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 (110) hide show
  1. package/lib/commonjs/controllers/ApiController.js +1 -1
  2. package/lib/commonjs/controllers/BlockchainApiController.js +107 -21
  3. package/lib/commonjs/controllers/BlockchainApiController.js.map +1 -1
  4. package/lib/commonjs/controllers/ConnectionController.js +4 -2
  5. package/lib/commonjs/controllers/ConnectionController.js.map +1 -1
  6. package/lib/commonjs/controllers/NetworkController.js +6 -0
  7. package/lib/commonjs/controllers/NetworkController.js.map +1 -1
  8. package/lib/commonjs/controllers/RouterController.js.map +1 -1
  9. package/lib/commonjs/controllers/SnackController.js +5 -0
  10. package/lib/commonjs/controllers/SnackController.js.map +1 -1
  11. package/lib/commonjs/controllers/SwapController.js +578 -6
  12. package/lib/commonjs/controllers/SwapController.js.map +1 -1
  13. package/lib/commonjs/controllers/ThemeController.js +1 -1
  14. package/lib/commonjs/controllers/ThemeController.js.map +1 -1
  15. package/lib/commonjs/index.js +14 -0
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/utils/ConnectionUtil.js +33 -0
  18. package/lib/commonjs/utils/ConnectionUtil.js.map +1 -0
  19. package/lib/commonjs/utils/ConstantsUtil.js +29 -0
  20. package/lib/commonjs/utils/ConstantsUtil.js.map +1 -1
  21. package/lib/commonjs/utils/CoreHelperUtil.js +12 -12
  22. package/lib/commonjs/utils/CoreHelperUtil.js.map +1 -1
  23. package/lib/commonjs/utils/NetworkUtil.js +46 -0
  24. package/lib/commonjs/utils/NetworkUtil.js.map +1 -0
  25. package/lib/commonjs/utils/RouterUtil.js +1 -1
  26. package/lib/commonjs/utils/RouterUtil.js.map +1 -1
  27. package/lib/commonjs/utils/SwapApiUtil.js +58 -0
  28. package/lib/commonjs/utils/SwapApiUtil.js.map +1 -1
  29. package/lib/commonjs/utils/SwapCalculationUtil.js +73 -0
  30. package/lib/commonjs/utils/SwapCalculationUtil.js.map +1 -1
  31. package/lib/module/controllers/ApiController.js +1 -1
  32. package/lib/module/controllers/BlockchainApiController.js +107 -21
  33. package/lib/module/controllers/BlockchainApiController.js.map +1 -1
  34. package/lib/module/controllers/ConnectionController.js +4 -2
  35. package/lib/module/controllers/ConnectionController.js.map +1 -1
  36. package/lib/module/controllers/NetworkController.js +6 -0
  37. package/lib/module/controllers/NetworkController.js.map +1 -1
  38. package/lib/module/controllers/RouterController.js.map +1 -1
  39. package/lib/module/controllers/SnackController.js +5 -0
  40. package/lib/module/controllers/SnackController.js.map +1 -1
  41. package/lib/module/controllers/SwapController.js +578 -6
  42. package/lib/module/controllers/SwapController.js.map +1 -1
  43. package/lib/module/controllers/ThemeController.js +1 -1
  44. package/lib/module/controllers/ThemeController.js.map +1 -1
  45. package/lib/module/index.js +2 -0
  46. package/lib/module/index.js.map +1 -1
  47. package/lib/module/utils/ConnectionUtil.js +27 -0
  48. package/lib/module/utils/ConnectionUtil.js.map +1 -0
  49. package/lib/module/utils/ConstantsUtil.js +29 -0
  50. package/lib/module/utils/ConstantsUtil.js.map +1 -1
  51. package/lib/module/utils/CoreHelperUtil.js +12 -12
  52. package/lib/module/utils/CoreHelperUtil.js.map +1 -1
  53. package/lib/module/utils/NetworkUtil.js +40 -0
  54. package/lib/module/utils/NetworkUtil.js.map +1 -0
  55. package/lib/module/utils/RouterUtil.js +1 -1
  56. package/lib/module/utils/RouterUtil.js.map +1 -1
  57. package/lib/module/utils/SwapApiUtil.js +58 -0
  58. package/lib/module/utils/SwapApiUtil.js.map +1 -1
  59. package/lib/module/utils/SwapCalculationUtil.js +73 -1
  60. package/lib/module/utils/SwapCalculationUtil.js.map +1 -1
  61. package/lib/typescript/controllers/ApiController.d.ts +1 -1
  62. package/lib/typescript/controllers/BlockchainApiController.d.ts +6 -1
  63. package/lib/typescript/controllers/BlockchainApiController.d.ts.map +1 -1
  64. package/lib/typescript/controllers/ConnectionController.d.ts +3 -1
  65. package/lib/typescript/controllers/ConnectionController.d.ts.map +1 -1
  66. package/lib/typescript/controllers/NetworkController.d.ts +1 -0
  67. package/lib/typescript/controllers/NetworkController.d.ts.map +1 -1
  68. package/lib/typescript/controllers/RouterController.d.ts +3 -2
  69. package/lib/typescript/controllers/RouterController.d.ts.map +1 -1
  70. package/lib/typescript/controllers/SnackController.d.ts +2 -1
  71. package/lib/typescript/controllers/SnackController.d.ts.map +1 -1
  72. package/lib/typescript/controllers/SwapController.d.ts +87 -0
  73. package/lib/typescript/controllers/SwapController.d.ts.map +1 -1
  74. package/lib/typescript/controllers/ThemeController.d.ts +2 -2
  75. package/lib/typescript/controllers/ThemeController.d.ts.map +1 -1
  76. package/lib/typescript/index.d.ts +2 -0
  77. package/lib/typescript/index.d.ts.map +1 -1
  78. package/lib/typescript/utils/ConnectionUtil.d.ts +4 -0
  79. package/lib/typescript/utils/ConnectionUtil.d.ts.map +1 -0
  80. package/lib/typescript/utils/ConstantsUtil.d.ts +4 -0
  81. package/lib/typescript/utils/ConstantsUtil.d.ts.map +1 -1
  82. package/lib/typescript/utils/CoreHelperUtil.d.ts +2 -2
  83. package/lib/typescript/utils/CoreHelperUtil.d.ts.map +1 -1
  84. package/lib/typescript/utils/NetworkUtil.d.ts +8 -0
  85. package/lib/typescript/utils/NetworkUtil.d.ts.map +1 -0
  86. package/lib/typescript/utils/RouterUtil.d.ts.map +1 -1
  87. package/lib/typescript/utils/SwapApiUtil.d.ts +8 -0
  88. package/lib/typescript/utils/SwapApiUtil.d.ts.map +1 -1
  89. package/lib/typescript/utils/SwapCalculationUtil.d.ts +19 -0
  90. package/lib/typescript/utils/SwapCalculationUtil.d.ts.map +1 -1
  91. package/lib/typescript/utils/TypeUtil.d.ts +146 -6
  92. package/lib/typescript/utils/TypeUtil.d.ts.map +1 -1
  93. package/package.json +2 -2
  94. package/src/controllers/ApiController.ts +1 -1
  95. package/src/controllers/BlockchainApiController.ts +114 -17
  96. package/src/controllers/ConnectionController.ts +6 -2
  97. package/src/controllers/NetworkController.ts +8 -0
  98. package/src/controllers/RouterController.ts +6 -1
  99. package/src/controllers/SnackController.ts +7 -1
  100. package/src/controllers/SwapController.ts +759 -6
  101. package/src/controllers/ThemeController.ts +3 -3
  102. package/src/index.ts +2 -0
  103. package/src/utils/ConnectionUtil.ts +27 -0
  104. package/src/utils/ConstantsUtil.ts +120 -0
  105. package/src/utils/CoreHelperUtil.ts +20 -15
  106. package/src/utils/NetworkUtil.ts +33 -0
  107. package/src/utils/RouterUtil.ts +4 -1
  108. package/src/utils/SwapApiUtil.ts +89 -0
  109. package/src/utils/SwapCalculationUtil.ts +125 -0
  110. package/src/utils/TypeUtil.ts +169 -10
@@ -1,5 +1,6 @@
1
1
  import { subscribeKey as subKey } from 'valtio/utils';
2
2
  import { proxy, subscribe as sub } from 'valtio';
3
+ import { NumberUtil } from '@reown/appkit-common-react-native';
3
4
 
4
5
  import { ConstantsUtil } from '../utils/ConstantsUtil';
5
6
  import { SwapApiUtil } from '../utils/SwapApiUtil';
@@ -7,40 +8,139 @@ import { NetworkController } from './NetworkController';
7
8
  import { BlockchainApiController } from './BlockchainApiController';
8
9
  import { OptionsController } from './OptionsController';
9
10
  import { SwapCalculationUtil } from '../utils/SwapCalculationUtil';
11
+ import { SnackController } from './SnackController';
12
+ import { RouterController } from './RouterController';
13
+ import type { SwapInputTarget, SwapTokenWithBalance } from '../utils/TypeUtil';
14
+ import { ConnectorController } from './ConnectorController';
15
+ import { AccountController } from './AccountController';
16
+ import { CoreHelperUtil } from '../utils/CoreHelperUtil';
17
+ import { ConnectionController } from './ConnectionController';
18
+ import { TransactionsController } from './TransactionsController';
19
+ import { EventsController } from './EventsController';
10
20
 
11
21
  // -- Constants ---------------------------------------- //
12
22
  export const INITIAL_GAS_LIMIT = 150000;
13
23
  export const TO_AMOUNT_DECIMALS = 6;
14
24
 
15
25
  // -- Types --------------------------------------------- //
26
+ type TransactionParams = {
27
+ data: string;
28
+ to: string;
29
+ gas: bigint;
30
+ gasPrice: bigint;
31
+ value: bigint;
32
+ toAmount: string;
33
+ };
34
+
35
+ class TransactionError extends Error {
36
+ shortMessage?: string;
37
+
38
+ constructor(message?: string, shortMessage?: string) {
39
+ super(message);
40
+ this.name = 'TransactionError';
41
+ this.shortMessage = shortMessage;
42
+ }
43
+ }
16
44
 
17
45
  export interface SwapControllerState {
46
+ // Loading states
47
+ initializing: boolean;
48
+ initialized: boolean;
49
+ loadingPrices: boolean;
50
+ loadingQuote?: boolean;
51
+ loadingApprovalTransaction?: boolean;
52
+ loadingBuildTransaction?: boolean;
53
+ loadingTransaction?: boolean;
54
+
55
+ // Error states
56
+ fetchError: boolean;
57
+
58
+ // Approval & Swap transaction states
59
+ approvalTransaction: TransactionParams | undefined;
60
+ swapTransaction: TransactionParams | undefined;
61
+ transactionError?: string;
62
+
18
63
  // Input values
64
+ sourceToken?: SwapTokenWithBalance;
65
+ sourceTokenAmount: string;
66
+ sourceTokenPriceInUSD: number;
67
+ toToken?: SwapTokenWithBalance;
68
+ toTokenAmount: string;
69
+ toTokenPriceInUSD: number;
19
70
  networkPrice: string;
71
+ networkBalanceInUSD: string;
20
72
  networkTokenSymbol: string;
73
+ inputError: string | undefined;
74
+
75
+ // Request values
76
+ slippage: number;
21
77
 
22
78
  // Tokens
79
+ tokens?: SwapTokenWithBalance[];
80
+ suggestedTokens?: SwapTokenWithBalance[];
81
+ popularTokens?: SwapTokenWithBalance[];
82
+ foundTokens?: SwapTokenWithBalance[];
83
+ myTokensWithBalance?: SwapTokenWithBalance[];
23
84
  tokensPriceMap: Record<string, number>;
24
85
 
25
86
  // Calculations
26
87
  gasFee: string;
27
88
  gasPriceInUSD?: number;
89
+ priceImpact: number | undefined;
90
+ maxSlippage: number | undefined;
91
+ providerFee: string | undefined;
28
92
  }
29
93
 
30
94
  type StateKey = keyof SwapControllerState;
31
95
 
32
96
  // -- State --------------------------------------------- //
33
97
  const initialState: SwapControllerState = {
98
+ // Loading states
99
+ initializing: false,
100
+ initialized: false,
101
+ loadingPrices: false,
102
+ loadingQuote: false,
103
+ loadingApprovalTransaction: false,
104
+ loadingBuildTransaction: false,
105
+ loadingTransaction: false,
106
+
107
+ // Error states
108
+ fetchError: false,
109
+
110
+ // Approval & Swap transaction states
111
+ approvalTransaction: undefined,
112
+ swapTransaction: undefined,
113
+ transactionError: undefined,
114
+
34
115
  // Input values
116
+ sourceToken: undefined,
117
+ sourceTokenAmount: '',
118
+ sourceTokenPriceInUSD: 0,
119
+ toToken: undefined,
120
+ toTokenAmount: '',
121
+ toTokenPriceInUSD: 0,
35
122
  networkPrice: '0',
123
+ networkBalanceInUSD: '0',
36
124
  networkTokenSymbol: '',
125
+ inputError: undefined,
126
+
127
+ // Request values
128
+ slippage: ConstantsUtil.CONVERT_SLIPPAGE_TOLERANCE,
37
129
 
38
130
  // Tokens
131
+ tokens: undefined,
132
+ popularTokens: undefined,
133
+ suggestedTokens: undefined,
134
+ foundTokens: undefined,
135
+ myTokensWithBalance: undefined,
39
136
  tokensPriceMap: {},
40
137
 
41
138
  // Calculations
42
139
  gasFee: '0',
43
- gasPriceInUSD: 0
140
+ gasPriceInUSD: 0,
141
+ priceImpact: undefined,
142
+ maxSlippage: undefined,
143
+ providerFee: undefined
44
144
  };
45
145
 
46
146
  const state = proxy<SwapControllerState>(initialState);
@@ -58,21 +158,207 @@ export const SwapController = {
58
158
  },
59
159
 
60
160
  getParams() {
61
- const caipNetwork = NetworkController.state.caipNetwork;
62
- const networkAddress = `${caipNetwork?.id}:${ConstantsUtil.NATIVE_TOKEN_ADDRESS}`;
161
+ const caipAddress = AccountController.state.caipAddress;
162
+ const address = CoreHelperUtil.getPlainAddress(caipAddress);
163
+ const networkAddress = NetworkController.getActiveNetworkTokenAddress();
164
+ const type = ConnectorController.state.connectedConnector;
165
+
166
+ if (!address) {
167
+ throw new Error('No address found to swap the tokens from.');
168
+ }
169
+
170
+ const invalidToToken = !state.toToken?.address || !state.toToken?.decimals;
171
+ const invalidSourceToken =
172
+ !state.sourceToken?.address ||
173
+ !state.sourceToken?.decimals ||
174
+ state.sourceToken.address === state.toToken?.address ||
175
+ !NumberUtil.bigNumber(state.sourceTokenAmount).isGreaterThan(0);
176
+ const invalidSourceTokenAmount = !state.sourceTokenAmount;
63
177
 
64
178
  return {
65
- networkAddress
179
+ networkAddress,
180
+ fromAddress: address,
181
+ fromCaipAddress: caipAddress,
182
+ sourceTokenAddress: state.sourceToken?.address,
183
+ toTokenAddress: state.toToken?.address,
184
+ toTokenAmount: state.toTokenAmount,
185
+ toTokenDecimals: state.toToken?.decimals,
186
+ sourceTokenAmount: state.sourceTokenAmount,
187
+ sourceTokenDecimals: state.sourceToken?.decimals,
188
+ invalidToToken,
189
+ invalidSourceToken,
190
+ invalidSourceTokenAmount,
191
+ availableToSwap:
192
+ caipAddress && !invalidToToken && !invalidSourceToken && !invalidSourceTokenAmount,
193
+ isAuthConnector: type === 'AUTH'
66
194
  };
67
195
  },
68
196
 
197
+ switchTokens() {
198
+ if (state.initializing || !state.initialized) {
199
+ return;
200
+ }
201
+
202
+ let newSourceToken = state.toToken ? { ...state.toToken } : undefined;
203
+ const sourceTokenWithBalance = state.myTokensWithBalance?.find(
204
+ token => token.address === newSourceToken?.address
205
+ );
206
+
207
+ if (sourceTokenWithBalance) {
208
+ newSourceToken = sourceTokenWithBalance;
209
+ }
210
+
211
+ const newToToken = state.sourceToken ? { ...state.sourceToken } : undefined;
212
+ const newSourceTokenAmount =
213
+ newSourceToken && state.toTokenAmount === '' ? '1' : state.toTokenAmount;
214
+
215
+ this.setSourceToken(newSourceToken);
216
+ this.setToToken(newToToken);
217
+
218
+ this.setSourceTokenAmount(newSourceTokenAmount);
219
+ this.setToTokenAmount('');
220
+ this.swapTokens();
221
+ },
222
+
69
223
  resetState() {
224
+ state.myTokensWithBalance = initialState.myTokensWithBalance;
70
225
  state.tokensPriceMap = initialState.tokensPriceMap;
226
+ state.initialized = initialState.initialized;
227
+ state.sourceToken = initialState.sourceToken;
228
+ state.sourceTokenAmount = initialState.sourceTokenAmount;
229
+ state.sourceTokenPriceInUSD = initialState.sourceTokenPriceInUSD;
230
+ state.toToken = initialState.toToken;
231
+ state.toTokenAmount = initialState.toTokenAmount;
232
+ state.toTokenPriceInUSD = initialState.toTokenPriceInUSD;
71
233
  state.networkPrice = initialState.networkPrice;
72
234
  state.networkTokenSymbol = initialState.networkTokenSymbol;
235
+ state.networkBalanceInUSD = initialState.networkBalanceInUSD;
236
+ state.inputError = initialState.inputError;
237
+ },
238
+
239
+ async fetchTokens() {
240
+ const { networkAddress } = this.getParams();
241
+
242
+ await this.getTokenList();
243
+ await this.getNetworkTokenPrice();
244
+ await this.getMyTokensWithBalance();
245
+
246
+ const networkToken = state.tokens?.find(token => token.address === networkAddress);
247
+
248
+ if (networkToken) {
249
+ state.networkTokenSymbol = networkToken.symbol;
250
+ }
251
+
252
+ const sourceToken =
253
+ state.myTokensWithBalance?.find(token => token.address.startsWith(networkAddress)) ||
254
+ state.myTokensWithBalance?.[0];
255
+
256
+ this.setSourceToken(sourceToken);
257
+ this.setSourceTokenAmount('1');
258
+ },
259
+
260
+ async getTokenList() {
261
+ const tokens = await SwapApiUtil.getTokenList();
262
+
263
+ state.tokens = tokens;
264
+ state.popularTokens = tokens.sort((aTokenInfo, bTokenInfo) => {
265
+ if (aTokenInfo.symbol < bTokenInfo.symbol) {
266
+ return -1;
267
+ }
268
+ if (aTokenInfo.symbol > bTokenInfo.symbol) {
269
+ return 1;
270
+ }
271
+
272
+ return 0;
273
+ });
274
+ state.suggestedTokens = tokens.filter(token => {
275
+ if (ConstantsUtil.SWAP_SUGGESTED_TOKENS.includes(token.symbol)) {
276
+ return true;
277
+ }
278
+
279
+ return false;
280
+ }, {});
281
+ },
282
+
283
+ async getMyTokensWithBalance(forceUpdate?: string) {
284
+ const balances = await SwapApiUtil.getMyTokensWithBalance(forceUpdate);
285
+
286
+ if (!balances) {
287
+ return;
288
+ }
289
+
290
+ await this.getInitialGasPrice();
291
+ this.setBalances(balances);
292
+ },
293
+
294
+ getFilteredPopularTokens() {
295
+ return state.popularTokens?.filter(
296
+ token => !state.myTokensWithBalance?.some(t => t.address === token.address)
297
+ );
298
+ },
299
+
300
+ setSourceToken(sourceToken: SwapTokenWithBalance | undefined) {
301
+ if (!sourceToken) {
302
+ state.sourceToken = sourceToken;
303
+ state.sourceTokenAmount = '';
304
+ state.sourceTokenPriceInUSD = 0;
305
+
306
+ return;
307
+ }
308
+
309
+ state.sourceToken = sourceToken;
310
+ this.setTokenPrice(sourceToken.address, 'sourceToken');
311
+ },
312
+
313
+ setSourceTokenAmount(amount: string) {
314
+ state.sourceTokenAmount = amount;
315
+
316
+ if (amount === '') {
317
+ state.toTokenAmount = '';
318
+ }
319
+ },
320
+
321
+ async initializeState() {
322
+ if (state.initializing) {
323
+ return;
324
+ }
325
+
326
+ state.initializing = true;
327
+ if (!state.initialized) {
328
+ try {
329
+ await this.fetchTokens();
330
+ state.initialized = true;
331
+ } catch (error) {
332
+ state.initialized = false;
333
+ SnackController.showError('Failed to initialize swap');
334
+ RouterController.goBack();
335
+ }
336
+ }
337
+ state.initializing = false;
338
+ },
339
+
340
+ async getAddressPrice(address: string) {
341
+ const existPrice = state.tokensPriceMap[address];
342
+
343
+ if (existPrice) {
344
+ return existPrice;
345
+ }
346
+
347
+ const response = await BlockchainApiController.fetchTokenPrice({
348
+ projectId: OptionsController.state.projectId,
349
+ addresses: [address]
350
+ });
351
+ const fungibles = response?.fungibles || [];
352
+ const allTokens = [...(state.tokens || []), ...(state.myTokensWithBalance || [])];
353
+ const symbol = allTokens?.find(token => token.address === address)?.symbol;
354
+ const price = fungibles.find(p => p.symbol.toLowerCase() === symbol?.toLowerCase())?.price || 0;
355
+ const priceAsFloat = parseFloat(price.toString());
356
+
357
+ state.tokensPriceMap[address] = priceAsFloat;
358
+
359
+ return priceAsFloat;
73
360
  },
74
361
 
75
- //this
76
362
  async getNetworkTokenPrice() {
77
363
  const { networkAddress } = this.getParams();
78
364
 
@@ -80,6 +366,7 @@ export const SwapController = {
80
366
  projectId: OptionsController.state.projectId,
81
367
  addresses: [networkAddress]
82
368
  });
369
+
83
370
  const token = response?.fungibles?.[0];
84
371
  const price = token?.price.toString() || '0';
85
372
  state.tokensPriceMap[networkAddress] = parseFloat(price);
@@ -87,7 +374,6 @@ export const SwapController = {
87
374
  state.networkPrice = price;
88
375
  },
89
376
 
90
- //this
91
377
  async getInitialGasPrice() {
92
378
  const res = await SwapApiUtil.fetchGasPrice();
93
379
 
@@ -104,5 +390,472 @@ export const SwapController = {
104
390
  state.gasPriceInUSD = gasPrice;
105
391
 
106
392
  return { gasPrice: gasFee, gasPriceInUSD: state.gasPriceInUSD };
393
+ },
394
+
395
+ getProviderFeePrice() {
396
+ return SwapCalculationUtil.getProviderFeePrice(
397
+ state.sourceTokenAmount,
398
+ state.sourceTokenPriceInUSD
399
+ );
400
+ },
401
+
402
+ setBalances(balances: SwapTokenWithBalance[]) {
403
+ const { networkAddress } = this.getParams();
404
+ const caipNetwork = NetworkController.state.caipNetwork;
405
+
406
+ if (!caipNetwork) {
407
+ return;
408
+ }
409
+
410
+ const networkToken = balances.find(token => token.address === networkAddress);
411
+
412
+ balances.forEach(token => {
413
+ state.tokensPriceMap[token.address] = token.price || 0;
414
+ });
415
+
416
+ state.myTokensWithBalance = balances.filter(token => token.address?.startsWith(caipNetwork.id));
417
+
418
+ state.networkBalanceInUSD = networkToken
419
+ ? NumberUtil.multiply(networkToken.quantity.numeric, networkToken.price).toString()
420
+ : '0';
421
+ },
422
+
423
+ setToToken(toToken: SwapTokenWithBalance | undefined) {
424
+ if (!toToken) {
425
+ state.toToken = toToken;
426
+ state.toTokenAmount = '';
427
+ state.toTokenPriceInUSD = 0;
428
+
429
+ return;
430
+ }
431
+
432
+ state.toToken = toToken;
433
+ this.setTokenPrice(toToken.address, 'toToken');
434
+ },
435
+
436
+ setToTokenAmount(amount: string) {
437
+ state.toTokenAmount = amount
438
+ ? NumberUtil.formatNumberToLocalString(amount, TO_AMOUNT_DECIMALS)
439
+ : '';
440
+ },
441
+
442
+ async setTokenPrice(address: string, target: SwapInputTarget) {
443
+ let price = state.tokensPriceMap[address] || 0;
444
+
445
+ if (!price) {
446
+ state.loadingPrices = true;
447
+ price = await this.getAddressPrice(address);
448
+ }
449
+
450
+ if (target === 'sourceToken') {
451
+ state.sourceTokenPriceInUSD = price;
452
+ } else if (target === 'toToken') {
453
+ state.toTokenPriceInUSD = price;
454
+ }
455
+
456
+ if (state.loadingPrices) {
457
+ state.loadingPrices = false;
458
+ }
459
+
460
+ if (this.getParams().availableToSwap) {
461
+ this.swapTokens();
462
+ }
463
+ },
464
+
465
+ // -- Swap ---------------------------------------------- //
466
+ async swapTokens() {
467
+ const address = AccountController.state.address as `${string}:${string}:${string}`;
468
+ const sourceToken = state.sourceToken;
469
+ const toToken = state.toToken;
470
+ const haveSourceTokenAmount = NumberUtil.bigNumber(state.sourceTokenAmount).isGreaterThan(0);
471
+
472
+ if (!toToken || !sourceToken || state.loadingPrices || !haveSourceTokenAmount) {
473
+ return;
474
+ }
475
+
476
+ state.loadingQuote = true;
477
+
478
+ const amountDecimal = NumberUtil.bigNumber(state.sourceTokenAmount)
479
+ .multipliedBy(10 ** sourceToken.decimals)
480
+ .integerValue();
481
+
482
+ const quoteResponse = await BlockchainApiController.fetchSwapQuote({
483
+ userAddress: address,
484
+ projectId: OptionsController.state.projectId,
485
+ from: sourceToken.address,
486
+ to: toToken.address,
487
+ gasPrice: state.gasFee,
488
+ amount: amountDecimal.toString()
489
+ });
490
+
491
+ state.loadingQuote = false;
492
+
493
+ const quoteToAmount = quoteResponse?.quotes?.[0]?.toAmount;
494
+
495
+ if (!quoteToAmount) {
496
+ return;
497
+ }
498
+
499
+ const toTokenAmount = NumberUtil.bigNumber(quoteToAmount)
500
+ .dividedBy(10 ** toToken.decimals)
501
+ .toString();
502
+
503
+ this.setToTokenAmount(toTokenAmount);
504
+
505
+ const isInsufficientToken = this.hasInsufficientToken(
506
+ state.sourceTokenAmount,
507
+ sourceToken.address
508
+ );
509
+
510
+ if (isInsufficientToken) {
511
+ state.inputError = 'Insufficient balance';
512
+ } else {
513
+ state.inputError = undefined;
514
+ this.setTransactionDetails();
515
+ }
516
+ },
517
+
518
+ // -- Create Transactions -------------------------------------- //
519
+ async getTransaction() {
520
+ const { fromCaipAddress, availableToSwap } = this.getParams();
521
+ const sourceToken = state.sourceToken;
522
+ const toToken = state.toToken;
523
+
524
+ if (!fromCaipAddress || !availableToSwap || !sourceToken || !toToken || state.loadingQuote) {
525
+ return undefined;
526
+ }
527
+
528
+ try {
529
+ state.loadingBuildTransaction = true;
530
+ const hasAllowance = await SwapApiUtil.fetchSwapAllowance({
531
+ userAddress: fromCaipAddress,
532
+ tokenAddress: sourceToken.address,
533
+ sourceTokenAmount: state.sourceTokenAmount,
534
+ sourceTokenDecimals: sourceToken.decimals
535
+ });
536
+
537
+ let transaction: TransactionParams | undefined;
538
+
539
+ if (hasAllowance) {
540
+ transaction = await this.createSwapTransaction();
541
+ } else {
542
+ transaction = await this.createAllowanceTransaction();
543
+ }
544
+
545
+ state.loadingBuildTransaction = false;
546
+ state.fetchError = false;
547
+
548
+ return transaction;
549
+ } catch (error) {
550
+ RouterController.goBack();
551
+ SnackController.showError('Failed to check allowance');
552
+ state.loadingBuildTransaction = false;
553
+ state.approvalTransaction = undefined;
554
+ state.swapTransaction = undefined;
555
+ state.fetchError = true;
556
+
557
+ return undefined;
558
+ }
559
+ },
560
+
561
+ async createAllowanceTransaction() {
562
+ const { fromCaipAddress, fromAddress, sourceTokenAddress, toTokenAddress } = this.getParams();
563
+
564
+ if (!fromCaipAddress || !toTokenAddress) {
565
+ return undefined;
566
+ }
567
+
568
+ if (!sourceTokenAddress) {
569
+ throw new Error('createAllowanceTransaction - No source token address found.');
570
+ }
571
+
572
+ try {
573
+ const response = await BlockchainApiController.generateApproveCalldata({
574
+ projectId: OptionsController.state.projectId,
575
+ from: sourceTokenAddress,
576
+ to: toTokenAddress,
577
+ userAddress: fromCaipAddress
578
+ });
579
+
580
+ if (!response) {
581
+ throw new Error('createAllowanceTransaction - No response from generateApproveCalldata');
582
+ }
583
+ const gasLimit = await ConnectionController.estimateGas({
584
+ address: fromAddress as `0x${string}`,
585
+ to: CoreHelperUtil.getPlainAddress(response.tx.to) as `0x${string}`,
586
+ data: response.tx.data
587
+ });
588
+
589
+ const transaction = {
590
+ data: response.tx.data,
591
+ to: CoreHelperUtil.getPlainAddress(response.tx.from) as `0x${string}`,
592
+ gas: gasLimit,
593
+ gasPrice: BigInt(response.tx.eip155.gasPrice),
594
+ value: BigInt(response.tx.value),
595
+ toAmount: state.toTokenAmount
596
+ };
597
+
598
+ state.swapTransaction = undefined;
599
+ state.approvalTransaction = {
600
+ data: transaction.data,
601
+ to: transaction.to,
602
+ gas: transaction.gas ?? BigInt(0),
603
+ gasPrice: transaction.gasPrice,
604
+ value: transaction.value,
605
+ toAmount: transaction.toAmount
606
+ };
607
+
608
+ return {
609
+ data: transaction.data,
610
+ to: transaction.to,
611
+ gas: transaction.gas ?? BigInt(0),
612
+ gasPrice: transaction.gasPrice,
613
+ value: transaction.value,
614
+ toAmount: transaction.toAmount
615
+ };
616
+ } catch (error) {
617
+ RouterController.goBack();
618
+ SnackController.showError('Failed to create approval transaction');
619
+ state.approvalTransaction = undefined;
620
+ state.swapTransaction = undefined;
621
+ state.fetchError = true;
622
+
623
+ return undefined;
624
+ }
625
+ },
626
+
627
+ async createSwapTransaction() {
628
+ const { networkAddress, fromCaipAddress, sourceTokenAmount } = this.getParams();
629
+ const sourceToken = state.sourceToken;
630
+ const toToken = state.toToken;
631
+
632
+ if (!fromCaipAddress || !sourceTokenAmount || !sourceToken || !toToken) {
633
+ return undefined;
634
+ }
635
+
636
+ const amount = ConnectionController.parseUnits(
637
+ sourceTokenAmount,
638
+ sourceToken.decimals
639
+ )?.toString();
640
+
641
+ try {
642
+ const response = await BlockchainApiController.generateSwapCalldata({
643
+ projectId: OptionsController.state.projectId,
644
+ userAddress: fromCaipAddress,
645
+ from: sourceToken.address,
646
+ to: toToken.address,
647
+ amount: amount as string
648
+ });
649
+
650
+ if (!response) {
651
+ throw new Error('createSwapTransaction - No response from generateSwapCalldata');
652
+ }
653
+
654
+ const isSourceNetworkToken = sourceToken.address === networkAddress;
655
+
656
+ const gas = BigInt(response.tx.eip155.gas);
657
+ const gasPrice = BigInt(response.tx.eip155.gasPrice);
658
+
659
+ const transaction = {
660
+ data: response.tx.data,
661
+ to: CoreHelperUtil.getPlainAddress(response.tx.to) as `0x${string}`,
662
+ gas,
663
+ gasPrice,
664
+ value: isSourceNetworkToken ? BigInt(amount ?? '0') : BigInt('0'),
665
+ toAmount: state.toTokenAmount
666
+ };
667
+
668
+ state.gasPriceInUSD = SwapCalculationUtil.getGasPriceInUSD(state.networkPrice, gas, gasPrice);
669
+
670
+ state.approvalTransaction = undefined;
671
+ state.swapTransaction = transaction;
672
+
673
+ return transaction;
674
+ } catch (error) {
675
+ RouterController.goBack();
676
+ SnackController.showError('Failed to create transaction');
677
+ state.approvalTransaction = undefined;
678
+ state.swapTransaction = undefined;
679
+ state.fetchError = true;
680
+
681
+ return undefined;
682
+ }
683
+ },
684
+
685
+ async sendTransactionForApproval(data: TransactionParams) {
686
+ const { fromAddress, isAuthConnector } = this.getParams();
687
+
688
+ state.loadingApprovalTransaction = true;
689
+ const approveLimitMessage = `Approve limit increase in your wallet`;
690
+
691
+ if (isAuthConnector) {
692
+ RouterController.pushTransactionStack({
693
+ view: null,
694
+ goBack: true,
695
+ onSuccess() {
696
+ SnackController.showLoading(approveLimitMessage);
697
+ }
698
+ });
699
+ } else {
700
+ SnackController.showLoading(approveLimitMessage);
701
+ }
702
+
703
+ try {
704
+ await ConnectionController.sendTransaction({
705
+ address: fromAddress as `0x${string}`,
706
+ to: data.to as `0x${string}`,
707
+ data: data.data as `0x${string}`,
708
+ value: BigInt(data.value),
709
+ gasPrice: BigInt(data.gasPrice),
710
+ chainNamespace: 'eip155'
711
+ });
712
+
713
+ await this.swapTokens();
714
+ await this.getTransaction();
715
+ state.approvalTransaction = undefined;
716
+ state.loadingApprovalTransaction = false;
717
+ } catch (err) {
718
+ const error = err as TransactionError;
719
+ state.transactionError = error?.shortMessage as unknown as string;
720
+ state.loadingApprovalTransaction = false;
721
+ SnackController.showError(error?.shortMessage ?? 'Transaction error');
722
+ }
723
+ },
724
+
725
+ async sendTransactionForSwap(data: TransactionParams | undefined) {
726
+ if (!data) {
727
+ return undefined;
728
+ }
729
+
730
+ const { fromAddress, toTokenAmount, isAuthConnector } = this.getParams();
731
+
732
+ state.loadingTransaction = true;
733
+
734
+ const snackbarPendingMessage = `Swapping ${state.sourceToken
735
+ ?.symbol} to ${NumberUtil.formatNumberToLocalString(toTokenAmount, 3)} ${state.toToken
736
+ ?.symbol}`;
737
+ const snackbarSuccessMessage = `Swapped ${state.sourceToken
738
+ ?.symbol} to ${NumberUtil.formatNumberToLocalString(toTokenAmount, 3)} ${state.toToken
739
+ ?.symbol}`;
740
+
741
+ if (isAuthConnector) {
742
+ RouterController.pushTransactionStack({
743
+ view: 'Account',
744
+ goBack: false,
745
+ onSuccess() {
746
+ SnackController.showLoading(snackbarPendingMessage);
747
+ SwapController.resetState();
748
+ }
749
+ });
750
+ } else {
751
+ SnackController.showLoading('Confirm transaction in your wallet');
752
+ }
753
+
754
+ try {
755
+ const forceUpdateAddresses = [state.sourceToken?.address, state.toToken?.address].join(',');
756
+ const transactionHash = await ConnectionController.sendTransaction({
757
+ address: fromAddress as `0x${string}`,
758
+ to: data.to as `0x${string}`,
759
+ data: data.data as `0x${string}`,
760
+ gas: data.gas,
761
+ gasPrice: BigInt(data.gasPrice),
762
+ value: data.value,
763
+ chainNamespace: 'eip155'
764
+ });
765
+
766
+ state.loadingTransaction = false;
767
+ SnackController.showSuccess(snackbarSuccessMessage);
768
+ EventsController.sendEvent({
769
+ type: 'track',
770
+ event: 'SWAP_SUCCESS',
771
+ properties: {
772
+ network: NetworkController.state.caipNetwork?.id || '',
773
+ swapFromToken: this.state.sourceToken?.symbol || '',
774
+ swapToToken: this.state.toToken?.symbol || '',
775
+ swapFromAmount: this.state.sourceTokenAmount || '',
776
+ swapToAmount: this.state.toTokenAmount || '',
777
+ isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount'
778
+ }
779
+ });
780
+ SwapController.resetState();
781
+
782
+ if (!isAuthConnector) {
783
+ RouterController.replace('AccountDefault');
784
+ }
785
+
786
+ SwapController.getMyTokensWithBalance(forceUpdateAddresses);
787
+ AccountController.fetchTokenBalance();
788
+
789
+ setTimeout(() => {
790
+ TransactionsController.fetchTransactions(AccountController.state.address, true);
791
+ }, 5000);
792
+
793
+ return transactionHash;
794
+ } catch (err) {
795
+ const error = err as TransactionError;
796
+ state.transactionError = error?.shortMessage;
797
+ state.loadingTransaction = false;
798
+ SnackController.showError(error?.shortMessage ?? 'Transaction error');
799
+ EventsController.sendEvent({
800
+ type: 'track',
801
+ event: 'SWAP_ERROR',
802
+ properties: {
803
+ message: error?.shortMessage ?? error?.message ?? 'Unknown',
804
+ network: NetworkController.state.caipNetwork?.id || '',
805
+ swapFromToken: this.state.sourceToken?.symbol || '',
806
+ swapToToken: this.state.toToken?.symbol || '',
807
+ swapFromAmount: this.state.sourceTokenAmount || '',
808
+ swapToAmount: this.state.toTokenAmount || '',
809
+ isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount'
810
+ }
811
+ });
812
+
813
+ return undefined;
814
+ }
815
+ },
816
+
817
+ // -- Checks -------------------------------------------- //
818
+ hasInsufficientToken(sourceTokenAmount: string, sourceTokenAddress: string) {
819
+ const isInsufficientSourceTokenForSwap = SwapCalculationUtil.isInsufficientSourceTokenForSwap(
820
+ sourceTokenAmount,
821
+ sourceTokenAddress,
822
+ state.myTokensWithBalance
823
+ );
824
+
825
+ let insufficientNetworkTokenForGas = true;
826
+ if (AccountController.state.preferredAccountType === 'smartAccount') {
827
+ // Smart Accounts may pay gas in any ERC20 token
828
+ insufficientNetworkTokenForGas = false;
829
+ } else {
830
+ insufficientNetworkTokenForGas = SwapCalculationUtil.isInsufficientNetworkTokenForGas(
831
+ state.networkBalanceInUSD,
832
+ state.gasPriceInUSD
833
+ );
834
+ }
835
+
836
+ return insufficientNetworkTokenForGas || isInsufficientSourceTokenForSwap;
837
+ },
838
+
839
+ // -- Calculations -------------------------------------- //
840
+ setTransactionDetails() {
841
+ const { toTokenAddress, toTokenDecimals } = this.getParams();
842
+
843
+ if (!toTokenAddress || !toTokenDecimals) {
844
+ return;
845
+ }
846
+
847
+ state.gasPriceInUSD = SwapCalculationUtil.getGasPriceInUSD(
848
+ state.networkPrice,
849
+ BigInt(state.gasFee),
850
+ BigInt(INITIAL_GAS_LIMIT)
851
+ );
852
+ state.priceImpact = SwapCalculationUtil.getPriceImpact({
853
+ sourceTokenAmount: state.sourceTokenAmount,
854
+ sourceTokenPriceInUSD: state.sourceTokenPriceInUSD,
855
+ toTokenPriceInUSD: state.toTokenPriceInUSD,
856
+ toTokenAmount: state.toTokenAmount
857
+ });
858
+ state.maxSlippage = SwapCalculationUtil.getMaxSlippage(state.slippage, state.toTokenAmount);
859
+ state.providerFee = SwapCalculationUtil.getProviderFee(state.sourceTokenAmount);
107
860
  }
108
861
  };