@openocean.finance/widget 1.0.28 → 1.0.30
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 +3 -6
- 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 +49 -33
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- 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 +11 -12
- 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 +63 -44
- package/src/hooks/useSettingMonitor.ts +0 -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
|
|
@@ -181,9 +189,7 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
181
189
|
try {
|
|
182
190
|
useServerErrorStore.getState().setError(null)
|
|
183
191
|
const fromAmount = parseUnits(fromTokenAmount, fromToken!.decimals)
|
|
184
|
-
const formattedSlippage = slippage
|
|
185
|
-
? (Number.parseFloat(slippage) / 100).toString()
|
|
186
|
-
: '0.01' // Default slippage 1%
|
|
192
|
+
const formattedSlippage = slippage ? slippage : '1' // Default slippage 1%
|
|
187
193
|
|
|
188
194
|
let quoteResult: any // Initialize quoteResult
|
|
189
195
|
|
|
@@ -209,17 +215,29 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
209
215
|
chainId: toChainId,
|
|
210
216
|
}
|
|
211
217
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
218
|
+
const walletClient = await getWalletClient(wagmiConfig)
|
|
219
|
+
|
|
220
|
+
quoteResult = await getCrossChainQuote({
|
|
221
|
+
feeBps: 10,
|
|
222
|
+
fromMsg,
|
|
223
|
+
toMsg,
|
|
215
224
|
inAmount: fromAmount.toString(),
|
|
216
|
-
slippage_tolerance: formattedSlippage,
|
|
225
|
+
slippage_tolerance: formattedSlippage,
|
|
217
226
|
account: account?.address || '',
|
|
218
|
-
|
|
227
|
+
walletClient,
|
|
219
228
|
})
|
|
229
|
+
|
|
230
|
+
// quoteResult = await DebridgeService.swapUThenCross({
|
|
231
|
+
// fromMsg: fromMsg,
|
|
232
|
+
// toMsg: toMsg,
|
|
233
|
+
// inAmount: fromAmount.toString(),
|
|
234
|
+
// slippage_tolerance: formattedSlippage, // Debridge might use a different format/unit
|
|
235
|
+
// account: account?.address || '',
|
|
236
|
+
// receiver: toAddress, // Assuming receiver is the same as account for now
|
|
237
|
+
// })
|
|
220
238
|
// Add a flag or modify structure to indicate it's a Debridge route
|
|
221
239
|
if (quoteResult) {
|
|
222
|
-
quoteResult.
|
|
240
|
+
quoteResult.isBridge = true
|
|
223
241
|
}
|
|
224
242
|
} else {
|
|
225
243
|
console.warn(
|
|
@@ -260,27 +278,29 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
260
278
|
}
|
|
261
279
|
// Ensure the structure is consistent or add a flag
|
|
262
280
|
if (quoteResult) {
|
|
263
|
-
quoteResult.
|
|
281
|
+
quoteResult.isBridge = false
|
|
264
282
|
}
|
|
265
283
|
}
|
|
266
|
-
const data = quoteResult && quoteResult.data || {}
|
|
284
|
+
const data = (quoteResult && quoteResult.data) || {}
|
|
267
285
|
// minOutAmount calculation is now handled within DebridgeService or OpenOceanService
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
let toAmountMin = '0';
|
|
286
|
+
const isBridge = quoteResult.isBridge
|
|
287
|
+
let toAmountMin = '0'
|
|
271
288
|
if (data?.minOutAmount) {
|
|
272
|
-
toAmountMin = data.minOutAmount
|
|
273
|
-
} else if (
|
|
274
|
-
toAmountMin = '0'
|
|
289
|
+
toAmountMin = data.minOutAmount
|
|
290
|
+
} else if (isBridge) {
|
|
291
|
+
toAmountMin = '0'
|
|
275
292
|
} 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
|
-
//
|
|
293
|
+
const amount = Number(data?.outAmount || 0)
|
|
294
|
+
const slippageValue = Number.parseFloat(slippage)
|
|
295
|
+
const minAmount = (amount * (100 - slippageValue)) / 100
|
|
296
|
+
toAmountMin = minAmount.toFixed(20).replace(/\.?0+$/, '')
|
|
297
|
+
// If still in scientific notation, force convert to string
|
|
281
298
|
if (toAmountMin.includes('e') || toAmountMin.includes('E')) {
|
|
282
|
-
toAmountMin = minAmount.toLocaleString('fullwide', {
|
|
283
|
-
|
|
299
|
+
toAmountMin = minAmount.toLocaleString('fullwide', {
|
|
300
|
+
useGrouping: false,
|
|
301
|
+
maximumSignificantDigits: 21,
|
|
302
|
+
})
|
|
303
|
+
toAmountMin = toAmountMin.replace(/\.?0+$/, '')
|
|
284
304
|
}
|
|
285
305
|
}
|
|
286
306
|
const route: Route = {
|
|
@@ -303,8 +323,8 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
303
323
|
steps: [
|
|
304
324
|
{
|
|
305
325
|
id: '1',
|
|
306
|
-
type: '
|
|
307
|
-
tool:
|
|
326
|
+
type: isBridge ? 'bridge' : 'swap',
|
|
327
|
+
tool: isBridge ? 'bridge' : 'openocean',
|
|
308
328
|
transactionRequest: {
|
|
309
329
|
chainId: data?.chainId || fromChainId,
|
|
310
330
|
from: data?.from,
|
|
@@ -312,12 +332,12 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
312
332
|
to: data?.to,
|
|
313
333
|
value: data?.value || '0x0',
|
|
314
334
|
gasPrice: data?.gasPrice,
|
|
315
|
-
type:
|
|
335
|
+
type: isBridge ? data?.quoteAdapterKey : data?.dexId || '0x0',
|
|
316
336
|
},
|
|
317
337
|
toolDetails: {
|
|
318
|
-
key:
|
|
319
|
-
name:
|
|
320
|
-
logoURI:
|
|
338
|
+
key: isBridge ? data?.quoteAdapterKey : 'openocean',
|
|
339
|
+
name: isBridge ? data?.quoteAdapterName : 'OpenOcean',
|
|
340
|
+
logoURI: isBridge
|
|
321
341
|
? 'https://s3.openocean.finance/static/debridge.svg'
|
|
322
342
|
: 'https://assets.coingecko.com/coins/images/17014/small/ooe_log.png',
|
|
323
343
|
},
|
|
@@ -335,13 +355,11 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
335
355
|
fromAmount: fromAmount.toString(),
|
|
336
356
|
toAmount: data?.outAmount || '0',
|
|
337
357
|
toAmountMin,
|
|
338
|
-
approvalAddress:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
executionDuration: data?.executionDuration || Math.floor(Math.random() * 20) + 40,
|
|
344
|
-
tool: isDebridge ? 'debridge' : 'openocean',
|
|
358
|
+
approvalAddress: data?.approveContract || '0x0',
|
|
359
|
+
executionDuration:
|
|
360
|
+
data?.executionDuration ||
|
|
361
|
+
Math.floor(Math.random() * 20) + 40,
|
|
362
|
+
tool: isBridge ? 'bridge' : 'openocean',
|
|
345
363
|
feeCosts: data?.feeCosts || [
|
|
346
364
|
{
|
|
347
365
|
name: 'Gas Fee',
|
|
@@ -355,6 +373,7 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
355
373
|
],
|
|
356
374
|
},
|
|
357
375
|
includedSteps: [],
|
|
376
|
+
quoteData: isBridge ? data : null,
|
|
358
377
|
},
|
|
359
378
|
],
|
|
360
379
|
...(data as any),
|
|
@@ -27,7 +27,6 @@ export const useSettingMonitor = () => {
|
|
|
27
27
|
const { tools } = useTools()
|
|
28
28
|
const config = useWidgetConfig()
|
|
29
29
|
const { setDefaultSettings, resetSettings } = useSettingsActions()
|
|
30
|
-
|
|
31
30
|
const isSlippageChanged = config.slippage
|
|
32
31
|
? Number(slippage) !== config.slippage * 100
|
|
33
32
|
: 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,
|