@reown/appkit-core-react-native 0.0.0-feat-smart-account-20241016183051 → 0.0.0-feat-swaps-20241129153709

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