@reown/appkit-core-react-native 0.0.0-feat-smart-account-20241017180406 → 0.0.0-feat-swaps-20241203153101

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