@openocean.finance/widget 1.0.28 → 1.0.29
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.
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +46 -39
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
- package/dist/esm/components/Messages/WarningMessages.js +2 -2
- package/dist/esm/components/Messages/WarningMessages.js.map +1 -1
- package/dist/esm/components/Step/Step.js +37 -29
- package/dist/esm/components/Step/Step.js.map +1 -1
- package/dist/esm/components/TransactionDetails.js +2 -5
- package/dist/esm/components/TransactionDetails.js.map +1 -1
- package/dist/esm/config/version.d.ts +1 -1
- package/dist/esm/config/version.js +1 -1
- package/dist/esm/cross/adapters/AcrossAdapter.d.ts +15 -0
- package/dist/esm/cross/adapters/AcrossAdapter.js +166 -0
- package/dist/esm/cross/adapters/AcrossAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/BaseSwapAdapter.d.ts +107 -0
- package/dist/esm/cross/adapters/BaseSwapAdapter.js +44 -0
- package/dist/esm/cross/adapters/BaseSwapAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/DebridgeAdapter.d.ts +20 -0
- package/dist/esm/cross/adapters/DebridgeAdapter.js +264 -0
- package/dist/esm/cross/adapters/DebridgeAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/LifiAdapter.d.ts +19 -0
- package/dist/esm/cross/adapters/LifiAdapter.js +169 -0
- package/dist/esm/cross/adapters/LifiAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/MayanAdapter.d.ts +14 -0
- package/dist/esm/cross/adapters/MayanAdapter.js +119 -0
- package/dist/esm/cross/adapters/MayanAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/NearIntentsAdapter.d.ts +21 -0
- package/dist/esm/cross/adapters/NearIntentsAdapter.js +425 -0
- package/dist/esm/cross/adapters/NearIntentsAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/OptimexAdapter.d.ts +19 -0
- package/dist/esm/cross/adapters/OptimexAdapter.js +216 -0
- package/dist/esm/cross/adapters/OptimexAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/OrbiterAdapter.d.ts +20 -0
- package/dist/esm/cross/adapters/OrbiterAdapter.js +213 -0
- package/dist/esm/cross/adapters/OrbiterAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/RelayAdapter.d.ts +14 -0
- package/dist/esm/cross/adapters/RelayAdapter.js +171 -0
- package/dist/esm/cross/adapters/RelayAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/SymbiosisAdapter.d.ts +14 -0
- package/dist/esm/cross/adapters/SymbiosisAdapter.js +120 -0
- package/dist/esm/cross/adapters/SymbiosisAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/XYFinanceAdapter.d.ts +14 -0
- package/dist/esm/cross/adapters/XYFinanceAdapter.js +177 -0
- package/dist/esm/cross/adapters/XYFinanceAdapter.js.map +1 -0
- package/dist/esm/cross/adapters/index.d.ts +2 -0
- package/dist/esm/cross/adapters/index.js +10 -0
- package/dist/esm/cross/adapters/index.js.map +1 -0
- package/dist/esm/cross/constants/index.d.ts +202 -0
- package/dist/esm/cross/constants/index.js +183 -0
- package/dist/esm/cross/constants/index.js.map +1 -0
- package/dist/esm/cross/crossChainQuote.d.ts +25 -0
- package/dist/esm/cross/crossChainQuote.js +127 -0
- package/dist/esm/cross/crossChainQuote.js.map +1 -0
- package/dist/esm/cross/factory.d.ts +9 -0
- package/dist/esm/cross/factory.js +125 -0
- package/dist/esm/cross/factory.js.map +1 -0
- package/dist/esm/cross/registry.d.ts +12 -0
- package/dist/esm/cross/registry.js +52 -0
- package/dist/esm/cross/registry.js.map +1 -0
- package/dist/esm/hooks/useChain.d.ts +1 -1
- package/dist/esm/hooks/useGasRefuel.d.ts +1 -1
- package/dist/esm/hooks/useGasSufficiencyBridge.js +1 -2
- package/dist/esm/hooks/useGasSufficiencyBridge.js.map +1 -1
- package/dist/esm/hooks/useRouteExecution.js +2 -1
- package/dist/esm/hooks/useRouteExecution.js.map +1 -1
- package/dist/esm/hooks/useRoutes.js +50 -32
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useSettingMonitor.js +1 -0
- package/dist/esm/hooks/useSettingMonitor.js.map +1 -1
- package/dist/esm/hooks/useTokenAddressBalance.d.ts +1 -1
- package/dist/esm/hooks/useTokenPrice.js +4 -2
- package/dist/esm/hooks/useTokenPrice.js.map +1 -1
- package/dist/esm/hooks/useTokens.d.ts +1 -1
- package/dist/esm/services/ExecuteRoute.js +142 -124
- package/dist/esm/services/ExecuteRoute.js.map +1 -1
- package/dist/esm/stores/form/useFieldController.d.ts +1 -1
- package/dist/esm/stores/routes/createRouteExecutionStore.js +6 -3
- package/dist/esm/stores/routes/createRouteExecutionStore.js.map +1 -1
- package/dist/esm/stores/routes/useSetExecutableRoute.d.ts +1 -1
- package/dist/esm/types/widget.d.ts +3 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +14 -4
- package/src/components/AmountInput/AmountInputEndAdornment.tsx +46 -46
- package/src/components/Messages/WarningMessages.tsx +7 -2
- package/src/components/Step/Step.tsx +37 -31
- package/src/components/TransactionDetails.tsx +10 -11
- package/src/config/version.ts +1 -1
- package/src/cross/adapters/AcrossAdapter.ts +193 -0
- package/src/cross/adapters/BaseSwapAdapter.ts +173 -0
- package/src/cross/adapters/DebridgeAdapter.ts +375 -0
- package/src/cross/adapters/LifiAdapter.ts +213 -0
- package/src/cross/adapters/MayanAdapter.ts +179 -0
- package/src/cross/adapters/NearIntentsAdapter.ts +539 -0
- package/src/cross/adapters/OptimexAdapter.ts +273 -0
- package/src/cross/adapters/OrbiterAdapter.ts +270 -0
- package/src/cross/adapters/RelayAdapter.ts +248 -0
- package/src/cross/adapters/SymbiosisAdapter.ts +144 -0
- package/src/cross/adapters/XYFinanceAdapter.ts +213 -0
- package/src/cross/adapters/index.ts +9 -0
- package/src/cross/constants/index.ts +223 -0
- package/src/cross/crossChainQuote.ts +181 -0
- package/src/cross/factory.ts +145 -0
- package/src/cross/registry.ts +65 -0
- package/src/hooks/useGasSufficiencyBridge.ts +1 -3
- package/src/hooks/useRouteExecution.ts +2 -1
- package/src/hooks/useRoutes.ts +64 -43
- package/src/hooks/useSettingMonitor.ts +1 -1
- package/src/hooks/useTokenPrice.ts +5 -3
- package/src/services/ExecuteRoute.ts +184 -171
- package/src/stores/routes/createRouteExecutionStore.ts +13 -4
- package/src/types/widget.ts +3 -0
package/src/hooks/useRoutes.ts
CHANGED
|
@@ -2,14 +2,17 @@ import { useAccount } from '@openocean.finance/wallet-management'
|
|
|
2
2
|
import type { Route } from '@openocean.finance/widget-sdk'
|
|
3
3
|
import { OpenOceanErrorCode } from '@openocean.finance/widget-sdk'
|
|
4
4
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
5
|
-
import { parseUnits
|
|
5
|
+
import { parseUnits } from 'viem'
|
|
6
|
+
import { useConfig } from 'wagmi'
|
|
7
|
+
import { getWalletClient } from 'wagmi/actions'
|
|
8
|
+
import { getCrossChainQuote } from '../cross/crossChainQuote.js'
|
|
6
9
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
7
|
-
import { DebridgeService } from '../services/DebridgeService.js'
|
|
8
10
|
import { OpenOceanService } from '../services/OpenOceanService.js'
|
|
9
11
|
import { useFieldValues } from '../stores/form/useFieldValues.js'
|
|
10
12
|
import { useSetExecutableRoute } from '../stores/routes/useSetExecutableRoute.js'
|
|
11
13
|
import { useSettings } from '../stores/settings/useSettings.js'
|
|
12
14
|
import { defaultSlippage } from '../stores/settings/useSettingsStore.js'
|
|
15
|
+
import { useServerErrorStore } from '../stores/useServerErrorStore.js'
|
|
13
16
|
import { WidgetEvent } from '../types/events.js'
|
|
14
17
|
import { getChainTypeFromAddress } from '../utils/chainType.js'
|
|
15
18
|
import { useChain } from './useChain.js'
|
|
@@ -20,7 +23,6 @@ import { useIsBatchingSupported } from './useIsBatchingSupported.js'
|
|
|
20
23
|
import { useSwapOnly } from './useSwapOnly.js'
|
|
21
24
|
import { useToken } from './useToken.js'
|
|
22
25
|
import { useWidgetEvents } from './useWidgetEvents.js'
|
|
23
|
-
import { useServerErrorStore } from '../stores/useServerErrorStore.js'
|
|
24
26
|
|
|
25
27
|
const refetchTime = 60_000
|
|
26
28
|
|
|
@@ -29,6 +31,8 @@ interface RoutesProps {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
34
|
+
const wagmiConfig = useConfig()
|
|
35
|
+
|
|
32
36
|
const {
|
|
33
37
|
subvariant,
|
|
34
38
|
sdkConfig,
|
|
@@ -38,7 +42,7 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
38
42
|
fee,
|
|
39
43
|
feeConfig,
|
|
40
44
|
useRelayerRoutes,
|
|
41
|
-
referrer
|
|
45
|
+
referrer,
|
|
42
46
|
} = useWidgetConfig()
|
|
43
47
|
const setExecutableRoute = useSetExecutableRoute()
|
|
44
48
|
const queryClient = useQueryClient()
|
|
@@ -96,14 +100,18 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
96
100
|
const contractCallQuoteEnabled: boolean =
|
|
97
101
|
subvariant === 'custom' ? Boolean(contractCalls && account.address) : true
|
|
98
102
|
|
|
99
|
-
const toAddress =
|
|
100
|
-
|
|
103
|
+
const toAddress =
|
|
104
|
+
fromChainId === toChainId ||
|
|
105
|
+
(fromChain?.chainType === 'EVM' && toChain?.chainType === 'EVM')
|
|
106
|
+
? account.address
|
|
107
|
+
: _toAddress
|
|
108
|
+
// When bridging between ecosystems, we need to ensure toAddress is set and has the same chainType as toChain
|
|
101
109
|
// If toAddress is set, it must have the same chainType as toChain
|
|
102
110
|
const hasToAddressAndChainTypeSatisfied: boolean =
|
|
103
111
|
!!toChain &&
|
|
104
112
|
!!toAddress &&
|
|
105
113
|
getChainTypeFromAddress(toAddress) === toChain.chainType
|
|
106
|
-
// We need to check
|
|
114
|
+
// We only need to check if toAddress is set
|
|
107
115
|
const isToAddressSatisfied = toAddress
|
|
108
116
|
? hasToAddressAndChainTypeSatisfied
|
|
109
117
|
: true
|
|
@@ -182,8 +190,8 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
182
190
|
useServerErrorStore.getState().setError(null)
|
|
183
191
|
const fromAmount = parseUnits(fromTokenAmount, fromToken!.decimals)
|
|
184
192
|
const formattedSlippage = slippage
|
|
185
|
-
?
|
|
186
|
-
: '
|
|
193
|
+
? slippage
|
|
194
|
+
: '1' // Default slippage 1%
|
|
187
195
|
|
|
188
196
|
let quoteResult: any // Initialize quoteResult
|
|
189
197
|
|
|
@@ -209,17 +217,29 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
209
217
|
chainId: toChainId,
|
|
210
218
|
}
|
|
211
219
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
220
|
+
const walletClient = await getWalletClient(wagmiConfig)
|
|
221
|
+
|
|
222
|
+
quoteResult = await getCrossChainQuote({
|
|
223
|
+
feeBps: 10,
|
|
224
|
+
fromMsg,
|
|
225
|
+
toMsg,
|
|
215
226
|
inAmount: fromAmount.toString(),
|
|
216
|
-
slippage_tolerance: formattedSlippage,
|
|
227
|
+
slippage_tolerance: formattedSlippage,
|
|
217
228
|
account: account?.address || '',
|
|
218
|
-
|
|
229
|
+
walletClient,
|
|
219
230
|
})
|
|
231
|
+
|
|
232
|
+
// quoteResult = await DebridgeService.swapUThenCross({
|
|
233
|
+
// fromMsg: fromMsg,
|
|
234
|
+
// toMsg: toMsg,
|
|
235
|
+
// inAmount: fromAmount.toString(),
|
|
236
|
+
// slippage_tolerance: formattedSlippage, // Debridge might use a different format/unit
|
|
237
|
+
// account: account?.address || '',
|
|
238
|
+
// receiver: toAddress, // Assuming receiver is the same as account for now
|
|
239
|
+
// })
|
|
220
240
|
// Add a flag or modify structure to indicate it's a Debridge route
|
|
221
241
|
if (quoteResult) {
|
|
222
|
-
quoteResult.
|
|
242
|
+
quoteResult.isBridge = true
|
|
223
243
|
}
|
|
224
244
|
} else {
|
|
225
245
|
console.warn(
|
|
@@ -260,27 +280,29 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
260
280
|
}
|
|
261
281
|
// Ensure the structure is consistent or add a flag
|
|
262
282
|
if (quoteResult) {
|
|
263
|
-
quoteResult.
|
|
283
|
+
quoteResult.isBridge = false
|
|
264
284
|
}
|
|
265
285
|
}
|
|
266
|
-
const data = quoteResult && quoteResult.data || {}
|
|
286
|
+
const data = (quoteResult && quoteResult.data) || {}
|
|
267
287
|
// minOutAmount calculation is now handled within DebridgeService or OpenOceanService
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
let toAmountMin = '0';
|
|
288
|
+
const isBridge = quoteResult.isBridge
|
|
289
|
+
let toAmountMin = '0'
|
|
271
290
|
if (data?.minOutAmount) {
|
|
272
|
-
toAmountMin = data.minOutAmount
|
|
273
|
-
} else if (
|
|
274
|
-
toAmountMin = '0'
|
|
291
|
+
toAmountMin = data.minOutAmount
|
|
292
|
+
} else if (isBridge) {
|
|
293
|
+
toAmountMin = '0'
|
|
275
294
|
} else {
|
|
276
|
-
const amount = Number(data?.outAmount || 0)
|
|
277
|
-
const slippageValue = Number.parseFloat(slippage)
|
|
278
|
-
const minAmount = (amount * (100 - slippageValue)) / 100
|
|
279
|
-
toAmountMin = minAmount.toFixed(20).replace(/\.?0+$/, '')
|
|
280
|
-
//
|
|
295
|
+
const amount = Number(data?.outAmount || 0)
|
|
296
|
+
const slippageValue = Number.parseFloat(slippage)
|
|
297
|
+
const minAmount = (amount * (100 - slippageValue)) / 100
|
|
298
|
+
toAmountMin = minAmount.toFixed(20).replace(/\.?0+$/, '')
|
|
299
|
+
// If still in scientific notation, force convert to string
|
|
281
300
|
if (toAmountMin.includes('e') || toAmountMin.includes('E')) {
|
|
282
|
-
toAmountMin = minAmount.toLocaleString('fullwide', {
|
|
283
|
-
|
|
301
|
+
toAmountMin = minAmount.toLocaleString('fullwide', {
|
|
302
|
+
useGrouping: false,
|
|
303
|
+
maximumSignificantDigits: 21,
|
|
304
|
+
})
|
|
305
|
+
toAmountMin = toAmountMin.replace(/\.?0+$/, '')
|
|
284
306
|
}
|
|
285
307
|
}
|
|
286
308
|
const route: Route = {
|
|
@@ -303,8 +325,8 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
303
325
|
steps: [
|
|
304
326
|
{
|
|
305
327
|
id: '1',
|
|
306
|
-
type: '
|
|
307
|
-
tool:
|
|
328
|
+
type: isBridge ? 'bridge' : 'swap',
|
|
329
|
+
tool: isBridge ? 'bridge' : 'openocean',
|
|
308
330
|
transactionRequest: {
|
|
309
331
|
chainId: data?.chainId || fromChainId,
|
|
310
332
|
from: data?.from,
|
|
@@ -312,12 +334,12 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
312
334
|
to: data?.to,
|
|
313
335
|
value: data?.value || '0x0',
|
|
314
336
|
gasPrice: data?.gasPrice,
|
|
315
|
-
type:
|
|
337
|
+
type: isBridge ? data?.quoteAdapterKey : data?.dexId || '0x0',
|
|
316
338
|
},
|
|
317
339
|
toolDetails: {
|
|
318
|
-
key:
|
|
319
|
-
name:
|
|
320
|
-
logoURI:
|
|
340
|
+
key: isBridge ? data?.quoteAdapterKey : 'openocean',
|
|
341
|
+
name: isBridge ? data?.quoteAdapterName : 'OpenOcean',
|
|
342
|
+
logoURI: isBridge
|
|
321
343
|
? 'https://s3.openocean.finance/static/debridge.svg'
|
|
322
344
|
: 'https://assets.coingecko.com/coins/images/17014/small/ooe_log.png',
|
|
323
345
|
},
|
|
@@ -335,13 +357,11 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
335
357
|
fromAmount: fromAmount.toString(),
|
|
336
358
|
toAmount: data?.outAmount || '0',
|
|
337
359
|
toAmountMin,
|
|
338
|
-
approvalAddress:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
executionDuration: data?.executionDuration || Math.floor(Math.random() * 20) + 40,
|
|
344
|
-
tool: isDebridge ? 'debridge' : 'openocean',
|
|
360
|
+
approvalAddress: data?.approveContract || '0x0',
|
|
361
|
+
executionDuration:
|
|
362
|
+
data?.executionDuration ||
|
|
363
|
+
Math.floor(Math.random() * 20) + 40,
|
|
364
|
+
tool: isBridge ? 'bridge' : 'openocean',
|
|
345
365
|
feeCosts: data?.feeCosts || [
|
|
346
366
|
{
|
|
347
367
|
name: 'Gas Fee',
|
|
@@ -355,6 +375,7 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
355
375
|
],
|
|
356
376
|
},
|
|
357
377
|
includedSteps: [],
|
|
378
|
+
quoteData: isBridge ? data : null,
|
|
358
379
|
},
|
|
359
380
|
],
|
|
360
381
|
...(data as any),
|
|
@@ -27,7 +27,7 @@ export const useSettingMonitor = () => {
|
|
|
27
27
|
const { tools } = useTools()
|
|
28
28
|
const config = useWidgetConfig()
|
|
29
29
|
const { setDefaultSettings, resetSettings } = useSettingsActions()
|
|
30
|
-
|
|
30
|
+
debugger;
|
|
31
31
|
const isSlippageChanged = config.slippage
|
|
32
32
|
? Number(slippage) !== config.slippage * 100
|
|
33
33
|
: slippage !== defaultConfigurableSettings.slippage
|
|
@@ -9,11 +9,13 @@ export const useTokenPrice = (chainId?: number, token?: TokenAmount) => {
|
|
|
9
9
|
if (!chainId || !token?.address) {
|
|
10
10
|
return undefined
|
|
11
11
|
}
|
|
12
|
-
const prices = await OpenOceanService.getTokensPrice(chainId.toString(), [
|
|
12
|
+
const prices = await OpenOceanService.getTokensPrice(chainId.toString(), [
|
|
13
|
+
token.address,
|
|
14
|
+
])
|
|
13
15
|
return prices[token.address.toLowerCase()] || 0
|
|
14
16
|
},
|
|
15
17
|
enabled: !!chainId && !!token?.address,
|
|
16
|
-
refetchInterval: 60_000, //
|
|
18
|
+
refetchInterval: 60_000, // Update price every minute
|
|
17
19
|
staleTime: 60_000,
|
|
18
20
|
})
|
|
19
21
|
|
|
@@ -21,4 +23,4 @@ export const useTokenPrice = (chainId?: number, token?: TokenAmount) => {
|
|
|
21
23
|
price,
|
|
22
24
|
isLoading,
|
|
23
25
|
}
|
|
24
|
-
}
|
|
26
|
+
}
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import { Connection, Transaction, VersionedTransaction } from '@solana/web3.js'
|
|
9
9
|
import { ethers } from 'ethers'
|
|
10
10
|
import { getPublicClient, getWalletClient } from 'wagmi/actions'
|
|
11
|
+
import { bridgeExecuteSwap } from '../cross/crossChainQuote.js'
|
|
11
12
|
import { useSettingsStore } from '../stores/settings/useSettingsStore.js'
|
|
12
13
|
import { sendAndConfirmSolanaTransaction } from './SendAndConfirmSolanaTransaction.js'
|
|
13
14
|
|
|
@@ -123,6 +124,121 @@ function hexToUint8Array(hexString: string): Uint8Array {
|
|
|
123
124
|
return new Uint8Array(pairs.map((s) => Number.parseInt(s, 16)))
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Convert amount with precision to actual amount
|
|
129
|
+
* @param amount Amount with precision
|
|
130
|
+
* @param decimals Precision
|
|
131
|
+
* @returns Actual amount
|
|
132
|
+
*/
|
|
133
|
+
function decimals2Amount(amount: string | number, decimals = 18): number {
|
|
134
|
+
return Number(amount) / 10 ** decimals
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Swap response type
|
|
139
|
+
*/
|
|
140
|
+
interface SwapResponse {
|
|
141
|
+
inAmount?: string
|
|
142
|
+
inToken?: {
|
|
143
|
+
decimals?: number
|
|
144
|
+
price?: string | number
|
|
145
|
+
priceUSD?: string | number
|
|
146
|
+
address?: string
|
|
147
|
+
}
|
|
148
|
+
data?: string
|
|
149
|
+
from?: string
|
|
150
|
+
to?: string
|
|
151
|
+
value?: string
|
|
152
|
+
minOutAmount?: string
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Adjust transaction parameters based on dynamic slippage to provide MEV protection
|
|
157
|
+
*/
|
|
158
|
+
async function swapQuoteMEV(
|
|
159
|
+
response: SwapResponse,
|
|
160
|
+
options?: { publicClient?: any }
|
|
161
|
+
): Promise<SwapResponse> {
|
|
162
|
+
try {
|
|
163
|
+
const inAmount = response?.inAmount
|
|
164
|
+
const inTokenDecimals = response?.inToken?.decimals || 18
|
|
165
|
+
const inTokenPrice = Number(response?.inToken?.priceUSD || 0)
|
|
166
|
+
const amount = decimals2Amount(inAmount, inTokenDecimals) * inTokenPrice
|
|
167
|
+
if (amount < 1) {
|
|
168
|
+
return response
|
|
169
|
+
}
|
|
170
|
+
const { publicClient } = options
|
|
171
|
+
const OPENOCEAN_CONTRACT = new ethers.Contract(
|
|
172
|
+
'0x6352a56caadC4F1E25CD6c75970Fa768A3304e64',
|
|
173
|
+
OpenOceanABI
|
|
174
|
+
)
|
|
175
|
+
if (
|
|
176
|
+
ethers.hexlify(ethers.getBytes(response?.data || '0x').slice(0, 4)) !==
|
|
177
|
+
OPENOCEAN_CONTRACT.interface.getFunction('swap').selector
|
|
178
|
+
) {
|
|
179
|
+
return response
|
|
180
|
+
}
|
|
181
|
+
const oldCallData = OPENOCEAN_CONTRACT.interface.decodeFunctionData(
|
|
182
|
+
'swap',
|
|
183
|
+
response?.data
|
|
184
|
+
)
|
|
185
|
+
const callData = [...oldCallData]
|
|
186
|
+
callData[1] = [...oldCallData[1]]
|
|
187
|
+
const minOutAmount = BigInt(callData[1][5] || 0)
|
|
188
|
+
const outAmount = BigInt(callData[1][6] || 0)
|
|
189
|
+
const slippageAmount = outAmount - minOutAmount
|
|
190
|
+
|
|
191
|
+
const minOutAmounts = await Promise.all(
|
|
192
|
+
[1, 2, 3].map(async (i) => {
|
|
193
|
+
const mockMinOutAmount =
|
|
194
|
+
minOutAmount + (slippageAmount / 4n) * BigInt(i)
|
|
195
|
+
callData[1][5] = mockMinOutAmount
|
|
196
|
+
const params = {
|
|
197
|
+
from: response?.from as `0x${string}`,
|
|
198
|
+
to: response?.to as `0x${string}`,
|
|
199
|
+
data: OPENOCEAN_CONTRACT.interface.encodeFunctionData(
|
|
200
|
+
'swap',
|
|
201
|
+
callData
|
|
202
|
+
) as `0x${string}`,
|
|
203
|
+
value: BigInt(response?.value || '0'),
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
await publicClient.estimateGas(params)
|
|
207
|
+
return mockMinOutAmount
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error('Failed to estimate gas:', error)
|
|
210
|
+
return undefined
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
let [min1, min2] = minOutAmounts
|
|
215
|
+
.filter((value) => value !== undefined)
|
|
216
|
+
.sort((a, b) => (BigInt(b || 0) > BigInt(a || 0) ? 1 : -1))
|
|
217
|
+
.slice(0, 2)
|
|
218
|
+
min1 = min1 ?? minOutAmount
|
|
219
|
+
min2 = min2 ?? minOutAmount
|
|
220
|
+
|
|
221
|
+
const randomFactor = BigInt(Math.floor(Math.random() * 10000))
|
|
222
|
+
const minOutAmountDiff = BigInt(min1 || 0) - BigInt(min2 || 0)
|
|
223
|
+
const finalMinOutAmount =
|
|
224
|
+
BigInt(min2 || 0) + (minOutAmountDiff * randomFactor) / BigInt(10000)
|
|
225
|
+
|
|
226
|
+
if (finalMinOutAmount < minOutAmount) {
|
|
227
|
+
return response
|
|
228
|
+
}
|
|
229
|
+
callData[1][5] = finalMinOutAmount
|
|
230
|
+
const finalCallData = OPENOCEAN_CONTRACT.interface.encodeFunctionData(
|
|
231
|
+
'swap',
|
|
232
|
+
callData
|
|
233
|
+
)
|
|
234
|
+
response.minOutAmount = finalMinOutAmount.toString()
|
|
235
|
+
response.data = finalCallData
|
|
236
|
+
return response
|
|
237
|
+
} catch {
|
|
238
|
+
return response
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
126
242
|
interface ExecuteRouteOptions {
|
|
127
243
|
updateRouteHook?: (route: Route) => void
|
|
128
244
|
acceptExchangeRateUpdateHook?: (params: any) => Promise<boolean>
|
|
@@ -135,6 +251,7 @@ interface ExecuteRouteOptions {
|
|
|
135
251
|
}
|
|
136
252
|
|
|
137
253
|
interface ExtendedOpenOceanStep extends OpenOceanStep {
|
|
254
|
+
quoteData?: any
|
|
138
255
|
execution?: {
|
|
139
256
|
status: ExecutionStatus
|
|
140
257
|
process: Process[]
|
|
@@ -172,7 +289,7 @@ async function executeSolanaSwap(
|
|
|
172
289
|
const txData: any = step.transactionRequest?.data || ''
|
|
173
290
|
const dexId = step.transactionRequest?.type || 0
|
|
174
291
|
if (step.action.fromChainId === step.action.toChainId) {
|
|
175
|
-
if (dexId
|
|
292
|
+
if (dexId === 6 || dexId === 7 || dexId === 9) {
|
|
176
293
|
transaction = VersionedTransaction.deserialize(hexToUint8Array(txData))
|
|
177
294
|
} else {
|
|
178
295
|
transaction = Transaction.from(hexToUint8Array(txData))
|
|
@@ -301,22 +418,23 @@ async function executeEvmSwap(
|
|
|
301
418
|
throw new Error('Public client not found')
|
|
302
419
|
}
|
|
303
420
|
|
|
304
|
-
console.log(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
)
|
|
309
|
-
console.log('Token Address:', step.action.fromToken.address)
|
|
310
|
-
console.log('Token Chain ID:', step.action.fromToken.chainId)
|
|
311
|
-
console.log('Owner Address:', walletClient.account.address)
|
|
312
|
-
console.log('Spender Address:', step.estimate.approvalAddress)
|
|
313
|
-
|
|
421
|
+
// console.log(
|
|
422
|
+
// 'Current Chain:',
|
|
423
|
+
// publicClient.chain?.id,
|
|
424
|
+
// publicClient.chain?.name
|
|
425
|
+
// )
|
|
426
|
+
// console.log('Token Address:', step.action.fromToken.address)
|
|
427
|
+
// console.log('Token Chain ID:', step.action.fromToken.chainId)
|
|
428
|
+
// console.log('Owner Address:', walletClient.account.address)
|
|
429
|
+
// console.log('Spender Address:', step.estimate.approvalAddress)
|
|
314
430
|
// Check token approval
|
|
315
431
|
if (
|
|
316
432
|
[
|
|
317
433
|
'0x0000000000000000000000000000000000000000',
|
|
318
434
|
'0x0000000000000000000000000000000000001010',
|
|
319
|
-
].indexOf(step.action.fromToken.address) === -1
|
|
435
|
+
].indexOf(step.action.fromToken.address) === -1 &&
|
|
436
|
+
step.estimate.approvalAddress !==
|
|
437
|
+
'0x0000000000000000000000000000000000000000'
|
|
320
438
|
) {
|
|
321
439
|
let allowance = 0n
|
|
322
440
|
try {
|
|
@@ -391,58 +509,68 @@ async function executeEvmSwap(
|
|
|
391
509
|
}
|
|
392
510
|
}
|
|
393
511
|
|
|
394
|
-
const { transactionRequest } = step || {}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
512
|
+
const { transactionRequest, type, quoteData } = step || {}
|
|
513
|
+
let hash: any = ''
|
|
514
|
+
if ((type as any) === 'bridge') {
|
|
515
|
+
const result = await bridgeExecuteSwap({
|
|
516
|
+
quoteData: quoteData,
|
|
517
|
+
walletClient: walletClient,
|
|
518
|
+
})
|
|
519
|
+
hash = result.sourceTxHash
|
|
520
|
+
} else {
|
|
521
|
+
const txRequest = {
|
|
522
|
+
chain: publicClient.chain,
|
|
523
|
+
to: transactionRequest?.to as `0x${string}`,
|
|
524
|
+
data: (transactionRequest?.data as `0x${string}`) || '0x',
|
|
525
|
+
value: BigInt(transactionRequest?.value || '0x0'),
|
|
526
|
+
account: walletClient.account.address,
|
|
527
|
+
}
|
|
402
528
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
529
|
+
// Check if dynamicSlippage is enabled in useSettingsStore to determine whether to call swap_quote_mev
|
|
530
|
+
const { dynamicSlippage } = useSettingsStore.getState()
|
|
531
|
+
if (dynamicSlippage) {
|
|
532
|
+
try {
|
|
533
|
+
// Build response object
|
|
534
|
+
const response = {
|
|
535
|
+
inAmount: step.action.fromAmount || '0',
|
|
536
|
+
inToken: step.action.fromToken,
|
|
537
|
+
data: transactionRequest?.data,
|
|
538
|
+
from: walletClient.account.address,
|
|
539
|
+
to: transactionRequest?.to,
|
|
540
|
+
value: transactionRequest?.value || '0',
|
|
541
|
+
}
|
|
416
542
|
|
|
417
|
-
|
|
418
|
-
|
|
543
|
+
// Call swap_quote_mev to get adjusted transaction data
|
|
544
|
+
const adjustedResponse = await swapQuoteMEV(response, {
|
|
545
|
+
publicClient,
|
|
546
|
+
})
|
|
419
547
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
548
|
+
// If swap_quote_mev returns modified data, update transaction request
|
|
549
|
+
if (adjustedResponse && adjustedResponse.data !== response.data) {
|
|
550
|
+
txRequest.data = adjustedResponse.data as `0x${string}`
|
|
551
|
+
txRequest.value = BigInt(adjustedResponse.value || '0')
|
|
552
|
+
// Applied MEV protection with dynamic slippage
|
|
553
|
+
}
|
|
554
|
+
} catch (error) {
|
|
555
|
+
console.error('Failed to apply MEV protection:', error)
|
|
556
|
+
// Continue with original transaction request on error
|
|
425
557
|
}
|
|
426
|
-
} catch (error) {
|
|
427
|
-
console.error('Failed to apply MEV protection:', error)
|
|
428
|
-
// 错误时继续使用原始交易请求
|
|
429
558
|
}
|
|
430
|
-
}
|
|
431
559
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
console.log('estimatedGas', estimatedGas)
|
|
560
|
+
// Estimate gas
|
|
561
|
+
const estimatedGas = await publicClient.estimateGas(txRequest)
|
|
435
562
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
563
|
+
// Add estimated gas to transaction request (using 2x the estimated value to ensure transaction success)
|
|
564
|
+
const finalTxRequest = {
|
|
565
|
+
...txRequest,
|
|
566
|
+
gas: estimatedGas * 2n,
|
|
567
|
+
}
|
|
441
568
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
569
|
+
hash = await walletClient.sendTransaction({
|
|
570
|
+
...finalTxRequest,
|
|
571
|
+
kzg: undefined,
|
|
572
|
+
})
|
|
573
|
+
}
|
|
446
574
|
|
|
447
575
|
process.status = 'PENDING'
|
|
448
576
|
process.txHash = hash
|
|
@@ -485,121 +613,6 @@ async function executeEvmSwap(
|
|
|
485
613
|
}
|
|
486
614
|
}
|
|
487
615
|
|
|
488
|
-
/**
|
|
489
|
-
* 将带精度的金额转换为实际金额
|
|
490
|
-
* @param amount 带精度的金额
|
|
491
|
-
* @param decimals 精度
|
|
492
|
-
* @returns 实际金额
|
|
493
|
-
*/
|
|
494
|
-
function decimals2Amount(amount: string | number, decimals = 18): number {
|
|
495
|
-
return Number(amount) / Math.pow(10, decimals)
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* 交易响应类型
|
|
500
|
-
*/
|
|
501
|
-
interface SwapResponse {
|
|
502
|
-
inAmount?: string
|
|
503
|
-
inToken?: {
|
|
504
|
-
decimals?: number
|
|
505
|
-
price?: string | number
|
|
506
|
-
priceUSD?: string | number
|
|
507
|
-
address?: string
|
|
508
|
-
}
|
|
509
|
-
data?: string
|
|
510
|
-
from?: string
|
|
511
|
-
to?: string
|
|
512
|
-
value?: string
|
|
513
|
-
minOutAmount?: string
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* 根据动态滑点调整交易参数,提供 MEV 保护
|
|
518
|
-
*/
|
|
519
|
-
async function swapQuoteMEV(
|
|
520
|
-
response: SwapResponse,
|
|
521
|
-
options?: { publicClient?: any }
|
|
522
|
-
): Promise<SwapResponse> {
|
|
523
|
-
try {
|
|
524
|
-
const inAmount = response?.inAmount
|
|
525
|
-
const inTokenDecimals = response?.inToken?.decimals || 18
|
|
526
|
-
const inTokenPrice = Number(response?.inToken?.priceUSD || 0)
|
|
527
|
-
const amount = decimals2Amount(inAmount, inTokenDecimals) * inTokenPrice
|
|
528
|
-
if (amount < 1) {
|
|
529
|
-
return response
|
|
530
|
-
}
|
|
531
|
-
const { publicClient } = options
|
|
532
|
-
const OPENOCEAN_CONTRACT = new ethers.Contract(
|
|
533
|
-
'0x6352a56caadC4F1E25CD6c75970Fa768A3304e64',
|
|
534
|
-
OpenOceanABI
|
|
535
|
-
)
|
|
536
|
-
if (
|
|
537
|
-
ethers.hexlify(ethers.getBytes(response?.data || '0x').slice(0, 4)) !==
|
|
538
|
-
OPENOCEAN_CONTRACT.interface.getFunction('swap').selector
|
|
539
|
-
) {
|
|
540
|
-
return response
|
|
541
|
-
}
|
|
542
|
-
const oldCallData = OPENOCEAN_CONTRACT.interface.decodeFunctionData(
|
|
543
|
-
'swap',
|
|
544
|
-
response?.data
|
|
545
|
-
)
|
|
546
|
-
const callData = [...oldCallData]
|
|
547
|
-
callData[1] = [...oldCallData[1]]
|
|
548
|
-
const minOutAmount = BigInt(callData[1][5] || 0)
|
|
549
|
-
const outAmount = BigInt(callData[1][6] || 0)
|
|
550
|
-
const slippageAmount = outAmount - minOutAmount
|
|
551
|
-
|
|
552
|
-
const minOutAmounts = await Promise.all(
|
|
553
|
-
[1, 2, 3].map(async (i) => {
|
|
554
|
-
const mockMinOutAmount =
|
|
555
|
-
minOutAmount + (slippageAmount / 4n) * BigInt(i)
|
|
556
|
-
callData[1][5] = mockMinOutAmount
|
|
557
|
-
const params = {
|
|
558
|
-
from: response?.from as `0x${string}`,
|
|
559
|
-
to: response?.to as `0x${string}`,
|
|
560
|
-
data: OPENOCEAN_CONTRACT.interface.encodeFunctionData(
|
|
561
|
-
'swap',
|
|
562
|
-
callData
|
|
563
|
-
) as `0x${string}`,
|
|
564
|
-
value: BigInt(response?.value || '0'),
|
|
565
|
-
}
|
|
566
|
-
try {
|
|
567
|
-
await publicClient.estimateGas(params)
|
|
568
|
-
return mockMinOutAmount
|
|
569
|
-
} catch (error) {
|
|
570
|
-
console.error('Failed to estimate gas:', error)
|
|
571
|
-
return undefined
|
|
572
|
-
}
|
|
573
|
-
})
|
|
574
|
-
)
|
|
575
|
-
let [min1, min2] = minOutAmounts
|
|
576
|
-
.filter((value) => value !== undefined)
|
|
577
|
-
.sort((a, b) => (BigInt(b || 0) > BigInt(a || 0) ? 1 : -1))
|
|
578
|
-
.slice(0, 2)
|
|
579
|
-
min1 = min1 ?? minOutAmount
|
|
580
|
-
min2 = min2 ?? minOutAmount
|
|
581
|
-
|
|
582
|
-
const randomFactor = BigInt(Math.floor(Math.random() * 10000))
|
|
583
|
-
const minOutAmountDiff = BigInt(min1 || 0) - BigInt(min2 || 0)
|
|
584
|
-
const finalMinOutAmount =
|
|
585
|
-
BigInt(min2 || 0) + (minOutAmountDiff * randomFactor) / BigInt(10000)
|
|
586
|
-
|
|
587
|
-
if (finalMinOutAmount < minOutAmount) {
|
|
588
|
-
return response
|
|
589
|
-
}
|
|
590
|
-
callData[1][5] = finalMinOutAmount
|
|
591
|
-
const finalCallData = OPENOCEAN_CONTRACT.interface.encodeFunctionData(
|
|
592
|
-
'swap',
|
|
593
|
-
callData
|
|
594
|
-
)
|
|
595
|
-
response.minOutAmount = finalMinOutAmount.toString()
|
|
596
|
-
response.data = finalCallData
|
|
597
|
-
return response
|
|
598
|
-
} catch (error) {
|
|
599
|
-
return response
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
616
|
// Execute transaction
|
|
604
617
|
async function executeSwap(
|
|
605
618
|
route: ExtendedRoute,
|