@0xsquid/react-hooks 1.0.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 (108) hide show
  1. package/.babelrc +7 -0
  2. package/.eslintignore +1 -0
  3. package/.eslintrc.json +44 -0
  4. package/dist/index.js +6 -0
  5. package/jest.config.ts +28 -0
  6. package/package.json +124 -0
  7. package/scripts/copyAssets.js +27 -0
  8. package/scripts/removeBuildTestFolder.js +28 -0
  9. package/scripts/replacePaths.js +37 -0
  10. package/src/assets/images/icons/wallets/binance.svg +4 -0
  11. package/src/assets/images/icons/wallets/bitget.svg +43 -0
  12. package/src/assets/images/icons/wallets/bitkeep.svg +9 -0
  13. package/src/assets/images/icons/wallets/blockchaindotcom.svg +11 -0
  14. package/src/assets/images/icons/wallets/brave.svg +18 -0
  15. package/src/assets/images/icons/wallets/c98.svg +3 -0
  16. package/src/assets/images/icons/wallets/coinbase.svg +5 -0
  17. package/src/assets/images/icons/wallets/cosmostation.svg +9 -0
  18. package/src/assets/images/icons/wallets/defiConnect.svg +25 -0
  19. package/src/assets/images/icons/wallets/exodus.svg +33 -0
  20. package/src/assets/images/icons/wallets/fetchai.svg +1 -0
  21. package/src/assets/images/icons/wallets/index.ts +43 -0
  22. package/src/assets/images/icons/wallets/keplr.svg +9 -0
  23. package/src/assets/images/icons/wallets/leap.svg +35 -0
  24. package/src/assets/images/icons/wallets/metamask.svg +48 -0
  25. package/src/assets/images/icons/wallets/okx.svg +1 -0
  26. package/src/assets/images/icons/wallets/rabby.svg +1 -0
  27. package/src/assets/images/icons/wallets/trustwallet.svg +11 -0
  28. package/src/assets/images/icons/wallets/walletConnect.svg +11 -0
  29. package/src/assets/images/icons/wallets/wallet_icon.svg +1 -0
  30. package/src/assets/images/icons/wallets/xdefi.svg +9 -0
  31. package/src/assets/images/icons/wallets/zerion.svg +11 -0
  32. package/src/connectors/ExodusConnector.ts +6 -0
  33. package/src/connectors/LedgerLiveConnector.ts +226 -0
  34. package/src/contracts/typechain/ERC20.d.ts +441 -0
  35. package/src/contracts/typechain/factories/ERC20__factory.d.ts +56 -0
  36. package/src/contracts/typechain/factories/ERC20__factory.ts +340 -0
  37. package/src/core/constants.ts +549 -0
  38. package/src/core/externalLinks.ts +26 -0
  39. package/src/core/index.ts +1 -0
  40. package/src/core/multicall3.ts +22 -0
  41. package/src/core/numbers.ts +44 -0
  42. package/src/core/providers/CosmosProvider.tsx +55 -0
  43. package/src/core/queries/queries-keys.ts +156 -0
  44. package/src/core/queries/react-query-config.ts +13 -0
  45. package/src/core/types/components.ts +16 -0
  46. package/src/core/types/config.ts +52 -0
  47. package/src/core/types/dex.ts +31 -0
  48. package/src/core/types/error.ts +52 -0
  49. package/src/core/types/event.ts +16 -0
  50. package/src/core/types/route.ts +21 -0
  51. package/src/core/types/swap.ts +8 -0
  52. package/src/core/types/tokens.ts +30 -0
  53. package/src/core/types/transaction.ts +84 -0
  54. package/src/core/types/types.ts +37 -0
  55. package/src/core/types/wallet.ts +83 -0
  56. package/src/hooks/chains/useSquidChains.ts +66 -0
  57. package/src/hooks/cosmos/useCosmos.ts +279 -0
  58. package/src/hooks/cosmos/useCosmosForChain.ts +33 -0
  59. package/src/hooks/index.ts +32 -0
  60. package/src/hooks/navigation/useKeyboardNavigation.ts +88 -0
  61. package/src/hooks/store/useSquidStore.ts +80 -0
  62. package/src/hooks/swap/useSwap.ts +359 -0
  63. package/src/hooks/tokens/useAllTokensWithBalance.ts +157 -0
  64. package/src/hooks/tokens/useBalance.ts +180 -0
  65. package/src/hooks/tokens/useMultiChainBalance.ts +59 -0
  66. package/src/hooks/tokens/usePrices.ts +52 -0
  67. package/src/hooks/tokens/useSingleTokenPrice.ts +45 -0
  68. package/src/hooks/tokens/useSquidTokens.ts +48 -0
  69. package/src/hooks/tokens/useTokensWithBalance.ts +169 -0
  70. package/src/hooks/transaction/useEstimate.ts +397 -0
  71. package/src/hooks/transaction/useEstimatePriceImpact.ts +39 -0
  72. package/src/hooks/transaction/useExecuteTransaction.ts +440 -0
  73. package/src/hooks/transaction/useExpress.ts +46 -0
  74. package/src/hooks/transaction/useGetRoute.ts +145 -0
  75. package/src/hooks/transaction/useSingleTransaction.ts +155 -0
  76. package/src/hooks/transaction/useTransaction.ts +734 -0
  77. package/src/hooks/user/useUserParams.ts +63 -0
  78. package/src/hooks/wallet/useAddToken.ts +53 -0
  79. package/src/hooks/wallet/useAutoConnect.ts +56 -0
  80. package/src/hooks/wallet/useGnosisContext.ts +84 -0
  81. package/src/hooks/wallet/useIntegratorContext.ts +44 -0
  82. package/src/hooks/wallet/useMultiChainWallet.ts +146 -0
  83. package/src/hooks/wallet/useWallet.ts +86 -0
  84. package/src/hooks/webapp-router/useSquidRouter.ts +42 -0
  85. package/src/index.ts +5 -0
  86. package/src/provider/index.tsx +105 -0
  87. package/src/react-app-env.d.ts +2 -0
  88. package/src/services/external/analyticsService.ts +46 -0
  89. package/src/services/external/coingeckoService.ts +35 -0
  90. package/src/services/external/rpcService.ts +196 -0
  91. package/src/services/external/secretService.ts +178 -0
  92. package/src/services/index.ts +1 -0
  93. package/src/services/internal/apiService.ts +6 -0
  94. package/src/services/internal/assetsService.ts +84 -0
  95. package/src/services/internal/configService.ts +261 -0
  96. package/src/services/internal/errorService.ts +124 -0
  97. package/src/services/internal/eventService.ts +93 -0
  98. package/src/services/internal/priceService.ts +10 -0
  99. package/src/services/internal/transactionService.ts +255 -0
  100. package/src/services/internal/transactionStatusService.ts +293 -0
  101. package/src/services/internal/walletService.ts +158 -0
  102. package/src/tests/assetsService.test.ts +176 -0
  103. package/src/tests/configService.test.ts +486 -0
  104. package/src/tests/fetchSquidData.ts +43 -0
  105. package/src/tests/jest-svg-transform.ts +19 -0
  106. package/src/tests/sample.json +0 -0
  107. package/src/tests/walletService.test.ts +178 -0
  108. package/tsconfig.json +30 -0
@@ -0,0 +1,255 @@
1
+ import type {
2
+ TransactionHistoryStore,
3
+ TransactionParams,
4
+ TransactionStatus,
5
+ } from "@/core/types/transaction";
6
+ import { TransactionType } from "@/core/types/transaction";
7
+ import { usePersistStore } from "@/hooks/store/useSquidStore";
8
+ import type { ChainData } from "@0xsquid/sdk/dist/types";
9
+
10
+ /**
11
+ * Helper to find the desired transaction history in array
12
+ * @param transactionID
13
+ * @returns
14
+ */
15
+ export const findHistoryItem = (
16
+ transactionID: string | undefined
17
+ ): TransactionHistoryStore | undefined => {
18
+ // TODO: This works but could be improved, persistStore shouldnt be used directly
19
+ // Or move this inside a callback hook
20
+ return usePersistStore
21
+ .getState()
22
+ .transactionsHistory?.find((th) => th.transactionId === transactionID);
23
+ };
24
+
25
+ /**
26
+ * Will return the same object but with the desired transaction history
27
+ * Status changed
28
+ * @param transactionID
29
+ * @param status
30
+ * @param transactionsHistory
31
+ * @param statusResponse
32
+ * @returns
33
+ */
34
+ export const updateTransactionHistoryStatus = (
35
+ transactionID: string | undefined,
36
+ status: TransactionStatus,
37
+ transactionsHistory: TransactionHistoryStore[] | undefined,
38
+ statusResponse: any | undefined
39
+ ): TransactionHistoryStore[] => {
40
+ return (
41
+ transactionsHistory?.map((th) => {
42
+ // Use the status response from the query if available, if not, use the one from the store
43
+ statusResponse = statusResponse ?? th.statusResponse;
44
+
45
+ // If the transaction is already in the store and had a different status, update status
46
+ if (th.transactionId === transactionID && th.status !== status) {
47
+ return {
48
+ ...th,
49
+ status,
50
+ statusResponse,
51
+ axelarUrl: statusResponse?.axelarTransactionUrl,
52
+ sourceTxExplorerUrl: statusResponse?.fromChain?.transactionUrl,
53
+ };
54
+ }
55
+ return {
56
+ ...th,
57
+ axelarUrl: statusResponse?.axelarTransactionUrl,
58
+ sourceTxExplorerUrl: statusResponse?.fromChain?.transactionUrl,
59
+ };
60
+ }) ?? []
61
+ );
62
+ };
63
+
64
+ export const replaceTransactionAtNonce = (
65
+ nonce: number,
66
+ fromAddress: string,
67
+ transactionsHistory: TransactionHistoryStore[] | undefined,
68
+ TransactionHistoryStore: TransactionHistoryStore
69
+ ): TransactionHistoryStore[] => {
70
+ const newTransactionsHistory: TransactionHistoryStore[] =
71
+ transactionsHistory ?? [];
72
+ // Find the transaction with the same nonce and from address
73
+ const transactionIndex = transactionsHistory?.findIndex(
74
+ (th) => th.nonce === nonce && th.fromAddress === fromAddress
75
+ );
76
+ // If found, replace it with the new transaction
77
+ if (transactionIndex !== undefined && transactionIndex !== -1) {
78
+ newTransactionsHistory[transactionIndex] = TransactionHistoryStore;
79
+ }
80
+
81
+ return newTransactionsHistory;
82
+ };
83
+
84
+ export const formatTransactionHistoryDate = (
85
+ transaction: TransactionHistoryStore | undefined
86
+ ): { month: string; day: string } | undefined => {
87
+ if (!transaction?.timestamp) return undefined;
88
+
89
+ try {
90
+ const date = new Date(Number(transaction.timestamp));
91
+
92
+ // Format date to: MMM DD. Examples:
93
+ // Jan 01
94
+ // May 12
95
+ const month = new Intl.DateTimeFormat("en-US", { month: "short" }).format(
96
+ date
97
+ );
98
+ const day = date.toLocaleString("en-US", { day: "2-digit" });
99
+
100
+ return { month, day };
101
+ } catch (error) {
102
+ console.error("Error formatting date:", error);
103
+ return undefined;
104
+ }
105
+ };
106
+
107
+ export const getAxelarExplorerTxUrl = (
108
+ urlPrefix: string | undefined,
109
+ routeType: string | undefined,
110
+ txID: string
111
+ ): string | undefined => {
112
+ if (!urlPrefix) {
113
+ return undefined;
114
+ }
115
+ const txType = (routeType as TransactionType) ?? TransactionType.BRIDGE;
116
+ if (
117
+ txType === TransactionType.CALL_BRIDGE ||
118
+ txType === TransactionType.BRIDGE
119
+ ) {
120
+ return `${urlPrefix}transfer/${txID}`;
121
+ }
122
+ return `${urlPrefix}gmp/${txID}`;
123
+ };
124
+
125
+ export const getSourceExplorerTxUrl = (
126
+ chain: ChainData | undefined,
127
+ txID: string
128
+ ): string | undefined => {
129
+ if (!chain || !chain.blockExplorerUrls[0]) {
130
+ return undefined;
131
+ }
132
+
133
+ let txSuffix: string;
134
+
135
+ switch (chain.chainId) {
136
+ case "agoric-3":
137
+ txSuffix = "/transactions/";
138
+ break;
139
+ default:
140
+ txSuffix = "/tx/";
141
+ }
142
+
143
+ if (chain.blockExplorerUrls[0].endsWith("/")) {
144
+ txSuffix = txSuffix.slice(1);
145
+ }
146
+
147
+ return `${chain.blockExplorerUrls[0]}${txSuffix}${txID}`;
148
+ };
149
+
150
+ export const getMainExplorerUrl = (transaction?: TransactionParams) => {
151
+ // The most accurate one is coming from squid /status api
152
+ if (transaction?.statusResponse?.axelarTransactionUrl) {
153
+ return transaction?.statusResponse.axelarTransactionUrl;
154
+ }
155
+
156
+ // If not, we can try to get it from the source chain
157
+ if (transaction?.sourceTxExplorerUrl) {
158
+ return transaction?.sourceTxExplorerUrl;
159
+ }
160
+
161
+ // If not, we can try to guess it from the transaction type
162
+ if (transaction && transaction?.transactionId) {
163
+ return getAxelarExplorerTxUrl(
164
+ transaction.statusResponse?.axelarTransactionUrl,
165
+ transaction.routeType,
166
+ transaction.transactionId
167
+ );
168
+ }
169
+
170
+ return undefined;
171
+ };
172
+
173
+ /**
174
+ * A CCTP Transaction is one that is not using Axelar, but using Circle Mint & Burn mechanism
175
+ * Squid API is using a different status endpoint to track status of CCTP transactions
176
+ * @param transaction
177
+ * @returns
178
+ */
179
+ export const isCCTPBridge = (transaction: TransactionParams | undefined) => {
180
+ return (
181
+ transaction?.fromChain?.chainId.includes("noble") ||
182
+ transaction?.toChain?.chainId.includes("noble")
183
+ );
184
+ };
185
+
186
+ export const formatDistance = (
187
+ date: number | Date,
188
+ baseDate: number | Date,
189
+ options?: {
190
+ includeSeconds?: boolean | undefined;
191
+ addSuffix?: boolean | undefined;
192
+ locale?: Locale | undefined;
193
+ }
194
+ ): string => {
195
+ const {
196
+ includeSeconds = false,
197
+ addSuffix = false,
198
+ locale = { locale: "en-US" },
199
+ } = options || {};
200
+
201
+ const elapsedMilliseconds = Math.abs(
202
+ new Date(date).getTime() - new Date(baseDate).getTime()
203
+ );
204
+
205
+ const seconds = Math.round(elapsedMilliseconds / 1000);
206
+ const minutes = Math.round(seconds / 60);
207
+ const hours = Math.round(minutes / 60);
208
+ const days = Math.round(hours / 24);
209
+ const months = Math.round(days / 30.44);
210
+ const years = Math.round(days / 365.25);
211
+
212
+ const rtf = new Intl.RelativeTimeFormat(locale.locale, { numeric: "auto" });
213
+
214
+ let formatted = "";
215
+
216
+ if (includeSeconds && seconds < 45) {
217
+ const unit = addSuffix ? "second" : "seconds";
218
+ formatted = rtf.format(-seconds, unit);
219
+ } else if (minutes < 60) {
220
+ formatted = rtf.format(-minutes, "minutes");
221
+ } else if (hours < 24) {
222
+ formatted = rtf.format(-hours, "hours");
223
+ } else if (days < 30) {
224
+ formatted = rtf.format(-days, "days");
225
+ } else if (months < 12) {
226
+ formatted = rtf.format(-months, "months");
227
+ } else {
228
+ formatted = rtf.format(-years, "years");
229
+ }
230
+
231
+ // remove "ago" from the string
232
+ // before: "2 minutes ago"
233
+ // after: "2 minutes"
234
+ return addSuffix ? formatted : formatted.replace(/\b(?:ago)\b/, "").trim();
235
+ };
236
+
237
+ export const formatSeconds = (
238
+ seconds: number,
239
+ secondsTemplate: string = " seconds",
240
+ minutesTemplate: string = " minutes"
241
+ ) => {
242
+ let duration = "";
243
+ if (seconds < 60) {
244
+ duration = `${seconds.toString()}${secondsTemplate}`;
245
+ } else {
246
+ duration = formatDistance(0, seconds * 1000, { includeSeconds: true });
247
+ }
248
+ return duration
249
+ .replace(" minutes", minutesTemplate)
250
+ .replace(" minute", minutesTemplate);
251
+ };
252
+
253
+ interface Locale {
254
+ locale?: string;
255
+ }
@@ -0,0 +1,293 @@
1
+ import { SquidStatusErrorType } from "@/core/types/error";
2
+ import type {
3
+ StepStatusGetterProps,
4
+ TransactionParams,
5
+ } from "@/core/types/transaction";
6
+ import {
7
+ AxelarStatusResponseType,
8
+ TransactionStatus,
9
+ TransactionType,
10
+ } from "@/core/types/transaction";
11
+ import type { ChainData, StatusResponse, Token } from "@0xsquid/sdk/dist/types";
12
+ import type { UseQueryResult } from "@tanstack/react-query";
13
+ import { isStatusError } from "./errorService";
14
+ import { getMainExplorerUrl } from "./transactionService";
15
+
16
+ /**
17
+ * Get the steps for a transaction
18
+ * First step and second step are always the same
19
+ * @param transaction
20
+ * @param statusResponse
21
+ * @returns {TransactionStepStatus[]}
22
+ */
23
+ export const getStepStatuses = ({
24
+ transaction,
25
+ statusResponse,
26
+ onlyFullStatusStep,
27
+ }: StepStatusGetterProps): TransactionStatus[] => {
28
+ let firstStepStatus: TransactionStatus;
29
+
30
+ // "warning" state is a custom one indicating that
31
+ // the user is taking too much time to validate the transaction
32
+ // And can be set to loading
33
+ switch (transaction?.sourceStatus) {
34
+ case "error":
35
+ case "success":
36
+ case "ongoing":
37
+ firstStepStatus = transaction.sourceStatus;
38
+ break;
39
+ case "warning":
40
+ firstStepStatus = TransactionStatus.ONGOING;
41
+ break;
42
+ default:
43
+ firstStepStatus = TransactionStatus.PENDING;
44
+ }
45
+
46
+ let middleStepStatus: TransactionStatus =
47
+ !onlyFullStatusStep && firstStepStatus !== "success"
48
+ ? TransactionStatus.PENDING
49
+ : middleStepChecker(statusResponse);
50
+
51
+ const lastStepStatus = getLastStepStatus(firstStepStatus, middleStepStatus);
52
+
53
+ // Once we have the last step status,
54
+ // we have to override the middle step for some states
55
+ middleStepStatus =
56
+ middleStepStatus === "needs_gas" || middleStepStatus === "partial_success"
57
+ ? TransactionStatus.SUCCESS
58
+ : middleStepStatus;
59
+
60
+ return onlyFullStatusStep
61
+ ? [middleStepStatus]
62
+ : [firstStepStatus, middleStepStatus, lastStepStatus];
63
+ };
64
+
65
+ const getLastStepStatus = (
66
+ first: TransactionStatus,
67
+ middle: TransactionStatus
68
+ ): TransactionStatus => {
69
+ if (
70
+ first === TransactionStatus.PENDING ||
71
+ first === "ongoing" ||
72
+ first === "error"
73
+ ) {
74
+ return TransactionStatus.PENDING;
75
+ }
76
+
77
+ switch (middle) {
78
+ case "initialLoading":
79
+ case "ongoing":
80
+ return TransactionStatus.PENDING;
81
+ default:
82
+ return middle;
83
+ }
84
+ };
85
+
86
+ export const getHalfSuccessState = (
87
+ status?: TransactionStatus
88
+ ): TransactionStatus | undefined => {
89
+ switch (status) {
90
+ case "success":
91
+ return TransactionStatus.SUCCESS;
92
+ case "partial_success": // Received axlUSDC
93
+ return TransactionStatus.PARTIAL_SUCCESS;
94
+ case "needs_gas":
95
+ return TransactionStatus.NEEDS_GAS;
96
+ default:
97
+ return undefined;
98
+ }
99
+ };
100
+
101
+ const middleStepChecker = (
102
+ statusResponse?: UseQueryResult<any, unknown>
103
+ ): TransactionStatus => {
104
+ const squidStatus = statusResponse?.data;
105
+ const successState = getHalfSuccessState(
106
+ squidStatus?.squidTransactionStatus as TransactionStatus | undefined
107
+ );
108
+ if (successState) {
109
+ return successState;
110
+ }
111
+ if (squidStatus?.status === AxelarStatusResponseType.ERROR) {
112
+ const { error } = squidStatus;
113
+ if (isStatusError(error)) {
114
+ if (error.errorType === SquidStatusErrorType.NotFoundError) {
115
+ return TransactionStatus.ERROR;
116
+ }
117
+ }
118
+ return TransactionStatus.ERROR;
119
+ }
120
+
121
+ if (statusResponse?.isInitialLoading) {
122
+ return TransactionStatus.INITIAL_LOADING;
123
+ }
124
+
125
+ return TransactionStatus.ONGOING;
126
+ };
127
+
128
+ export const getStepsInfos = ({
129
+ fromChain,
130
+ toChain,
131
+ fromToken,
132
+ toToken,
133
+ amount,
134
+ txType,
135
+ transaction,
136
+ statusResponse,
137
+ }: {
138
+ txType: TransactionType;
139
+ amount: string;
140
+ fromChain?: ChainData;
141
+ toChain?: ChainData;
142
+ fromToken?: Token;
143
+ toToken?: Token;
144
+ transaction?: TransactionParams;
145
+ statusResponse?: UseQueryResult<StatusResponse, unknown>;
146
+ }): {
147
+ label: string;
148
+ status: TransactionStatus;
149
+ subTitle?: string;
150
+ link?: string;
151
+ }[] => {
152
+ const [firstStepStatus, middleStepStatus, lastStepStatus] = getStepStatuses({
153
+ transaction,
154
+ statusResponse,
155
+ });
156
+
157
+ const payLabel = `Pay ${amount} ${fromToken?.symbol} on ${fromChain?.networkName}`;
158
+ const swapForUSDCLabel = `Swap ${fromToken?.symbol} for axlUSDC`;
159
+ const sendUSDCLabel = `Send axlUSDC to ${toChain?.networkName}`;
160
+ const swapUSDCLabel = `Swap axlUSDC for ${toToken?.symbol}`;
161
+ const receiveLabel = `Receive ${toToken?.symbol} on ${toChain?.networkName}`;
162
+ const axelarUrl = getMainExplorerUrl(transaction);
163
+ const sourceExplorerUrl = transaction?.sourceTxExplorerUrl;
164
+ const destinationExplorerUrl = statusResponse?.data?.toChain?.transactionUrl;
165
+
166
+ switch (txType) {
167
+ case TransactionType.CALL_BRIDGE_CALL:
168
+ return [
169
+ {
170
+ label: payLabel,
171
+ status: firstStepStatus,
172
+ link: sourceExplorerUrl,
173
+ subTitle: "View on explorer",
174
+ },
175
+ {
176
+ label: swapForUSDCLabel,
177
+ status:
178
+ firstStepStatus !== "success"
179
+ ? TransactionStatus.PENDING
180
+ : firstStepStatus,
181
+ link: axelarUrl,
182
+ subTitle: "View on Axelarscan",
183
+ },
184
+ {
185
+ label: sendUSDCLabel,
186
+ status: middleStepStatus,
187
+ link: axelarUrl,
188
+ subTitle: "View on Axelarscan",
189
+ },
190
+ {
191
+ label: swapUSDCLabel,
192
+ status:
193
+ middleStepStatus !== "success"
194
+ ? TransactionStatus.PENDING
195
+ : middleStepStatus,
196
+ link: axelarUrl,
197
+ subTitle: "View on Axelarscan",
198
+ },
199
+ {
200
+ label: receiveLabel,
201
+ status: lastStepStatus,
202
+ link: destinationExplorerUrl,
203
+ subTitle: "View on explorer",
204
+ },
205
+ ];
206
+ case TransactionType.CALL_BRIDGE:
207
+ return [
208
+ {
209
+ label: payLabel,
210
+ status: firstStepStatus,
211
+ link: sourceExplorerUrl,
212
+ subTitle: "View on explorer",
213
+ },
214
+ {
215
+ label: swapForUSDCLabel,
216
+ status:
217
+ firstStepStatus !== "success"
218
+ ? TransactionStatus.PENDING
219
+ : firstStepStatus,
220
+ link: axelarUrl,
221
+ subTitle: "View on Axelarscan",
222
+ },
223
+ {
224
+ label: sendUSDCLabel,
225
+ status: middleStepStatus,
226
+ link: axelarUrl,
227
+ subTitle: "View on Axelarscan",
228
+ },
229
+ {
230
+ label: receiveLabel,
231
+ status: lastStepStatus,
232
+ link: destinationExplorerUrl,
233
+ subTitle: "View on explorer",
234
+ },
235
+ ];
236
+ case TransactionType.BRIDGE:
237
+ return [
238
+ {
239
+ label: payLabel,
240
+ status: firstStepStatus,
241
+ link: sourceExplorerUrl,
242
+ subTitle: "View on explorer",
243
+ },
244
+ {
245
+ label: sendUSDCLabel,
246
+ status:
247
+ firstStepStatus !== "success"
248
+ ? TransactionStatus.PENDING
249
+ : firstStepStatus,
250
+ link: axelarUrl,
251
+ subTitle: "View on Axelarscan",
252
+ },
253
+ {
254
+ label: receiveLabel,
255
+ status: lastStepStatus,
256
+ link: destinationExplorerUrl,
257
+ subTitle: "View on explorer",
258
+ },
259
+ ];
260
+ case TransactionType.BRIDGE_CALL:
261
+ return [
262
+ {
263
+ label: payLabel,
264
+ status: firstStepStatus,
265
+ link: sourceExplorerUrl,
266
+ subTitle: "View on explorer",
267
+ },
268
+ {
269
+ label: sendUSDCLabel,
270
+ status:
271
+ firstStepStatus !== "success"
272
+ ? TransactionStatus.PENDING
273
+ : firstStepStatus,
274
+ link: axelarUrl,
275
+ subTitle: "View on Axelarscan",
276
+ },
277
+ {
278
+ label: swapUSDCLabel,
279
+ status: middleStepStatus,
280
+ link: axelarUrl,
281
+ subTitle: "View on Axelarscan",
282
+ },
283
+ {
284
+ label: receiveLabel,
285
+ status: lastStepStatus,
286
+ link: destinationExplorerUrl,
287
+ subTitle: "View on explorer",
288
+ },
289
+ ];
290
+ default:
291
+ return [];
292
+ }
293
+ };
@@ -0,0 +1,158 @@
1
+ import { walletStoreLinks, wallets } from "@/core/constants";
2
+ import type { ConnectorID, TypedWindow, Wallet } from "@/core/types/wallet";
3
+ import type { ChainData, CosmosChain } from "@0xsquid/sdk/dist/types";
4
+ import { ChainType } from "@0xsquid/sdk/dist/types";
5
+ import { fromBech32 } from "@cosmjs/encoding";
6
+ import type { ChainInfo } from "@keplr-wallet/types";
7
+ import { ethers } from "ethers";
8
+ import getProperty from "lodash/get";
9
+ import type { Chain } from "wagmi";
10
+
11
+ export const formatWalletAddress = (
12
+ walletAddress: string | undefined,
13
+ trimLength: number = 5
14
+ ) => {
15
+ return walletAddress
16
+ ? walletAddress?.length > trimLength
17
+ ? `${walletAddress.slice(0, trimLength)}...${walletAddress.slice(
18
+ walletAddress.length - (trimLength - 2), // -2 here because the start of wallet is 0x
19
+ walletAddress.length
20
+ )}`.toLowerCase()
21
+ : walletAddress
22
+ : "";
23
+ };
24
+
25
+ export const isCosmosAddressValid = (chainPrefix: string, address: string) => {
26
+ try {
27
+ if (!address.toLowerCase().startsWith(chainPrefix)) {
28
+ throw new Error("Invalid address for this chain");
29
+ }
30
+ fromBech32(address);
31
+ return true;
32
+ } catch (error) {
33
+ return false;
34
+ }
35
+ };
36
+
37
+ export const isWalletAddressValid = (
38
+ chainData?: ChainData,
39
+ address?: string
40
+ ) => {
41
+ if (address) {
42
+ if (chainData?.chainType === ChainType.EVM) {
43
+ return ethers.utils.isAddress(address);
44
+ }
45
+ if (chainData?.chainType === ChainType.COSMOS) {
46
+ return isCosmosAddressValid(
47
+ (chainData as CosmosChain).bech32Config.bech32PrefixAccAddr,
48
+ address
49
+ );
50
+ }
51
+ }
52
+ return false;
53
+ };
54
+
55
+ export const getWalletByConnectorID = (
56
+ connectorID: ConnectorID
57
+ ): Wallet | undefined => {
58
+ return wallets.find((w) => w.connectorId === connectorID);
59
+ };
60
+
61
+ export const getCosmosChainInfosObject = (chain: ChainData): ChainInfo => {
62
+ const cosmosChain = chain as CosmosChain;
63
+ return {
64
+ ...cosmosChain,
65
+ chainName: cosmosChain.networkName,
66
+ chainId: cosmosChain.chainId.toString(),
67
+ rpc: cosmosChain.rpc.split("?chain")[0],
68
+ feeCurrencies: cosmosChain.feeCurrencies.map((c) => ({
69
+ ...c,
70
+ gasPriceStep: cosmosChain.gasPriceStep,
71
+ })),
72
+ };
73
+ };
74
+
75
+ export const isWalletExtensionInstalled = (wallet: Wallet) => {
76
+ if (
77
+ (wallet.connectorId === "metaMask" && !window.ethereum) ||
78
+ (wallet.connectorId === "keplr" && !(window as TypedWindow).keplr) ||
79
+ (wallet.connectorId === "leap" && !(window as TypedWindow).leap) ||
80
+ // Cosmostation has two different connectors for different extensions
81
+ ((wallet.connectorId === "cosmostationCosmos" ||
82
+ wallet.connectorId === "cosmostation") &&
83
+ !(window as TypedWindow).cosmostation) ||
84
+ (wallet.connectorId === "xdefi" && !(window as TypedWindow).xfi) ||
85
+ (wallet.connectorId === "bitget" && !(window as TypedWindow).bitkeep) ||
86
+ (wallet.connectorId === "exodus" && !(window as TypedWindow).exodus)
87
+ ) {
88
+ return false;
89
+ }
90
+ return true;
91
+ };
92
+
93
+ export const redirectExtensionStoreIfNotInstalled = (wallet: Wallet) => {
94
+ const { userAgent } = navigator;
95
+ let link;
96
+
97
+ if (!isWalletExtensionInstalled(wallet)) {
98
+ if (userAgent.indexOf("Firefox") > -1) {
99
+ link = walletStoreLinks[wallet.connectorId].firefox;
100
+ } else if (userAgent.indexOf("Chrome") > -1) {
101
+ link = walletStoreLinks[wallet.connectorId].chrome;
102
+ }
103
+
104
+ if (link && link !== "") {
105
+ (window as any)?.open(link, "_blank").focus();
106
+ }
107
+ }
108
+ };
109
+
110
+ /**
111
+ * Get the value of an object property using a string path
112
+ * E.G. window["cosmostation.providers.keplr"]
113
+ * @param obj
114
+ * @param path
115
+ * @returns
116
+ */
117
+ export const getDescendantProp = (obj: any, path: string) => {
118
+ return path.split(".").reduce((acc, part) => acc && acc[part], obj);
119
+ };
120
+
121
+ export const formatChainsForWagmi = (chains: ChainData[]): Chain[] => {
122
+ return chains
123
+ ?.filter((c) => c.chainType === ChainType.EVM)
124
+ .map(
125
+ (c) =>
126
+ ({
127
+ id: +c.chainId,
128
+ name: c.networkName,
129
+ network: c.networkName,
130
+ contracts: {
131
+ multicall3: {
132
+ address: "0xcA11bde05977b3631167028862bE2a173976CA11",
133
+ },
134
+ },
135
+ nativeCurrency: c.nativeCurrency,
136
+ rpcUrls: { public: { http: [c.rpc] }, default: { http: [c.rpc] } },
137
+ }) as Chain
138
+ );
139
+ };
140
+
141
+ /**
142
+ * Dynamically accesses a nested property of an object based on a dot-separated string path.
143
+ * Returns the value of the property if found, otherwise undefined.
144
+ * e.g. "xfi.keplr" will return window.xfi.keplr
145
+ */
146
+ export const accessProperty = (
147
+ object: Record<string, any>,
148
+ path: string
149
+ ): any => {
150
+ const value = getProperty(object, path);
151
+ if (value === undefined) {
152
+ console.error(`Property "${path}" not found while reading object`, object);
153
+ }
154
+ return value;
155
+ };
156
+
157
+ export const metamaskIcon = getWalletByConnectorID("metaMask")?.icon;
158
+ export const EVMnetworkNotSupportedErrorCode = 4902;