@openocean.finance/widget 1.0.52 → 1.0.53
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/AppProvider.js +2 -1
- package/dist/esm/AppProvider.js.map +1 -1
- package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js +21 -0
- package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js.map +1 -1
- package/dist/esm/components/TokenList/VirtualizedTokenList.js +1 -1
- package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
- package/dist/esm/components/TransactionDetails.js +6 -0
- 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/NearIntentsAdapter.js +94 -25
- package/dist/esm/cross/adapters/NearIntentsAdapter.js.map +1 -1
- package/dist/esm/cross/crossChainQuote.d.ts +3 -2
- package/dist/esm/cross/crossChainQuote.js +2 -2
- package/dist/esm/cross/crossChainQuote.js.map +1 -1
- package/dist/esm/hooks/useGasPrice.js +2 -2
- package/dist/esm/hooks/useGasPrice.js.map +1 -1
- package/dist/esm/hooks/useGasRecommendation.d.ts +1 -1
- package/dist/esm/hooks/useGasRecommendation.js +7 -1
- package/dist/esm/hooks/useGasRecommendation.js.map +1 -1
- package/dist/esm/hooks/useRouteExecution.js +5 -0
- package/dist/esm/hooks/useRouteExecution.js.map +1 -1
- package/dist/esm/hooks/useRoutes.js +1 -1
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useToken.js.map +1 -1
- package/dist/esm/providers/WalletProvider/NearProvider.d.ts +3 -0
- package/dist/esm/providers/WalletProvider/NearProvider.js +95 -0
- package/dist/esm/providers/WalletProvider/NearProvider.js.map +1 -0
- package/dist/esm/providers/WalletProvider/SDKProviders.js +4 -1
- package/dist/esm/providers/WalletProvider/SDKProviders.js.map +1 -1
- package/dist/esm/providers/WalletProvider/useExternalWalletProvider.js +1 -1
- package/dist/esm/providers/WalletProvider/useExternalWalletProvider.js.map +1 -1
- package/dist/esm/services/ExecuteRoute.d.ts +1 -0
- package/dist/esm/services/ExecuteRoute.js +121 -0
- package/dist/esm/services/ExecuteRoute.js.map +1 -1
- package/dist/esm/services/OpenOceanService.d.ts +5 -1
- package/dist/esm/services/OpenOceanService.js +121 -6
- package/dist/esm/services/OpenOceanService.js.map +1 -1
- package/package.json +9 -5
- package/src/AppProvider.tsx +8 -5
- package/src/components/BaseTransactionButton/BaseTransactionButton.tsx +20 -0
- package/src/components/TokenList/VirtualizedTokenList.tsx +1 -1
- package/src/components/TransactionDetails.tsx +17 -11
- package/src/config/version.ts +1 -1
- package/src/cross/adapters/NearIntentsAdapter.ts +100 -28
- package/src/cross/crossChainQuote.ts +6 -2
- package/src/hooks/useGasPrice.ts +2 -2
- package/src/hooks/useGasRecommendation.ts +8 -1
- package/src/hooks/useRouteExecution.ts +5 -0
- package/src/hooks/useRoutes.ts +1 -1
- package/src/hooks/useToken.ts +0 -1
- package/src/providers/WalletProvider/NearProvider.tsx +110 -0
- package/src/providers/WalletProvider/SDKProviders.tsx +5 -0
- package/src/providers/WalletProvider/useExternalWalletProvider.ts +1 -1
- package/src/services/ExecuteRoute.ts +134 -1
- package/src/services/OpenOceanService.ts +135 -7
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
ChainId,
|
|
3
3
|
getGasRecommendation,
|
|
4
4
|
} from '@openocean.finance/widget-sdk'
|
|
5
5
|
import { useQuery } from '@tanstack/react-query'
|
|
@@ -14,9 +14,12 @@ export const useGasRecommendation = (
|
|
|
14
14
|
) => {
|
|
15
15
|
const { chains } = useAvailableChains()
|
|
16
16
|
|
|
17
|
+
const fromIsNear = fromChain === ChainId.NEAR
|
|
18
|
+
|
|
17
19
|
const checkRecommendationLiFuel =
|
|
18
20
|
Boolean(toChainId) &&
|
|
19
21
|
Boolean(fromChain) &&
|
|
22
|
+
!fromIsNear &&
|
|
20
23
|
Boolean(fromToken) &&
|
|
21
24
|
Boolean(chains?.length)
|
|
22
25
|
|
|
@@ -29,6 +32,10 @@ export const useGasRecommendation = (
|
|
|
29
32
|
queryKey: [_, toChainId, fromChain, fromToken],
|
|
30
33
|
signal,
|
|
31
34
|
}) => {
|
|
35
|
+
// from 是 Near 链时,当前不支持 Li.Fuel / gas 预估,直接跳过查询
|
|
36
|
+
if (fromChain === ChainId.NEAR) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
32
39
|
if (!chains?.some((chain) => chain.id === toChainId)) {
|
|
33
40
|
return null
|
|
34
41
|
}
|
|
@@ -11,6 +11,8 @@ import { updateRouteExecution } from '@openocean.finance/widget-sdk'
|
|
|
11
11
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
12
12
|
import { useCallback, useEffect, useRef } from 'react'
|
|
13
13
|
import { useConfig, useWalletClient } from 'wagmi'
|
|
14
|
+
// @ts-ignore - runtime implementation is provided by the host app (widget package)
|
|
15
|
+
import { useWalletSelector } from '@near-wallet-selector/react-hook'
|
|
14
16
|
import { shallow } from 'zustand/shallow'
|
|
15
17
|
import { executeRoute } from '../services/ExecuteRoute.js'
|
|
16
18
|
import {
|
|
@@ -46,6 +48,7 @@ export const useRouteExecution = ({
|
|
|
46
48
|
const disconnect = useAccountDisconnect()
|
|
47
49
|
const { openWalletMenu } = useWalletMenu()
|
|
48
50
|
const solanaWallet = useWalletClient()
|
|
51
|
+
const nearWallet = useWalletSelector() as any
|
|
49
52
|
// const { wallet: solanaWallet } = useWallet()
|
|
50
53
|
const resumedAfterMount = useRef(false)
|
|
51
54
|
const emitter = useWidgetEvents()
|
|
@@ -151,6 +154,7 @@ export const useRouteExecution = ({
|
|
|
151
154
|
onDisconnect: disconnect,
|
|
152
155
|
onOpenWalletMenu: openWalletMenu,
|
|
153
156
|
solanaWallet: solanaWallet,
|
|
157
|
+
nearWallet,
|
|
154
158
|
})
|
|
155
159
|
},
|
|
156
160
|
onMutate: () => {
|
|
@@ -187,6 +191,7 @@ export const useRouteExecution = ({
|
|
|
187
191
|
wagmiConfig,
|
|
188
192
|
onDisconnect: disconnect,
|
|
189
193
|
onOpenWalletMenu: openWalletMenu,
|
|
194
|
+
nearWallet,
|
|
190
195
|
})
|
|
191
196
|
},
|
|
192
197
|
onMutate: () => {
|
package/src/hooks/useRoutes.ts
CHANGED
|
@@ -303,7 +303,7 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
303
303
|
// minOutAmount calculation is now handled within DebridgeService or OpenOceanService
|
|
304
304
|
const isBridge = quoteResult.isBridge
|
|
305
305
|
let toAmountMin = '0'
|
|
306
|
-
if (data?.minOutAmount) {
|
|
306
|
+
if (data?.minOutAmount && Number(data.minOutAmount) > 0) {
|
|
307
307
|
toAmountMin = data.minOutAmount
|
|
308
308
|
} else if (isBridge) {
|
|
309
309
|
toAmountMin = '0'
|
package/src/hooks/useToken.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type { TokenAmount } from '../types/token.js'
|
|
|
6
6
|
|
|
7
7
|
export const useToken = (chainId?: number, tokenAddress?: string) => {
|
|
8
8
|
const { tokens, isLoading } = useTokens(chainId)
|
|
9
|
-
|
|
10
9
|
const token = useMemo(() => {
|
|
11
10
|
const token = tokens?.find(
|
|
12
11
|
(token: TokenAmount) => token.address === tokenAddress && token.chainId === chainId
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { FC, PropsWithChildren } from 'react'
|
|
2
|
+
import { useEffect, useMemo } from 'react'
|
|
3
|
+
import { WalletSelectorProvider } from '@near-wallet-selector/react-hook'
|
|
4
|
+
import { setupMeteorWallet } from '@near-wallet-selector/meteor-wallet'
|
|
5
|
+
import { setupSender } from '@near-wallet-selector/sender'
|
|
6
|
+
import "@near-wallet-selector/modal-ui/styles.css";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export const NearProvider: FC<PropsWithChildren> = ({ children }) => {
|
|
10
|
+
const config = useMemo(
|
|
11
|
+
() => ({
|
|
12
|
+
network: 'mainnet' as const,
|
|
13
|
+
modules: [setupMeteorWallet(), setupSender()],
|
|
14
|
+
}),
|
|
15
|
+
[]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// 在这里注入你自己的全局 CSS,覆盖 Near modal 的样式
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const styleId = 'near-wallet-selector-custom-style'
|
|
21
|
+
let styleEl = document.getElementById(styleId) as HTMLStyleElement | null
|
|
22
|
+
|
|
23
|
+
if (!styleEl) {
|
|
24
|
+
styleEl = document.createElement('style')
|
|
25
|
+
styleEl.id = styleId
|
|
26
|
+
document.head.appendChild(styleEl)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
styleEl.textContent = `
|
|
30
|
+
/* 覆盖 Near modal 最外层容器 */
|
|
31
|
+
#near-wallet-selector-modal {
|
|
32
|
+
z-index: 1200 !important; /* 保证在你自己的 widget 之上 */
|
|
33
|
+
font-family: inherit; /* 或者用你自己的字体 */
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* 覆盖遮罩层 */
|
|
37
|
+
#near-wallet-selector-modal .nws-modal-overlay {
|
|
38
|
+
background: rgba(0, 0, 0, 0.75) !important;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* 覆盖内容区域,可以调圆角/阴影等 */
|
|
42
|
+
#near-wallet-selector-modal .nws-modal {
|
|
43
|
+
width: 420px !important;
|
|
44
|
+
border-radius: 24px;
|
|
45
|
+
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.5);
|
|
46
|
+
background-color: #222037 !important;
|
|
47
|
+
z-index: 1300 !important;
|
|
48
|
+
position: absolute;
|
|
49
|
+
}
|
|
50
|
+
#near-wallet-selector-modal .nws-modal .modal-left {
|
|
51
|
+
border:none !important;
|
|
52
|
+
width: 100% !important;
|
|
53
|
+
}
|
|
54
|
+
#near-wallet-selector-modal .nws-modal .modal-right {
|
|
55
|
+
background-color: #222037 !important;
|
|
56
|
+
display: none;
|
|
57
|
+
}
|
|
58
|
+
.nws-modal-wrapper .nws-modal .modal-left .modal-left-title{
|
|
59
|
+
padding-top: 0 !important;
|
|
60
|
+
}
|
|
61
|
+
.nws-modal-wrapper .nws-modal .modal-left .modal-left-title h2{
|
|
62
|
+
text-align: center;
|
|
63
|
+
color: #ffffff !important;
|
|
64
|
+
}
|
|
65
|
+
.nws-modal-wrapper .nws-modal .modal-left .modal-left-title .nws-remember-wallet{
|
|
66
|
+
display: none;
|
|
67
|
+
}
|
|
68
|
+
.nws-modal-wrapper .nws-modal .modal-left .modal-left-title .nws-switch{
|
|
69
|
+
display: none;
|
|
70
|
+
}
|
|
71
|
+
.nws-modal-wrapper .nws-modal .modal-left .modal-left-title {
|
|
72
|
+
position: relative;
|
|
73
|
+
width: 100%;
|
|
74
|
+
background-color: #222037 !important;
|
|
75
|
+
}
|
|
76
|
+
.nws-modal-wrapper .nws-modal .wallet-options-wrapper{
|
|
77
|
+
margin-top: 20px;
|
|
78
|
+
}
|
|
79
|
+
.nws-modal-wrapper .nws-modal .wallet-options-wrapper .options-list {
|
|
80
|
+
display: block;
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
.nws-modal-wrapper .nws-modal .wallet-options-wrapper .options-list .single-wallet.sidebar{
|
|
84
|
+
margin:10px 0;
|
|
85
|
+
}
|
|
86
|
+
.nws-modal-wrapper .nws-modal .wallet-options-wrapper .title{
|
|
87
|
+
color: #ffffff !important;
|
|
88
|
+
}
|
|
89
|
+
.options-list-section-header{
|
|
90
|
+
display: none !important;
|
|
91
|
+
}
|
|
92
|
+
.nws-modal-wrapper .nws-modal .wallet-options-wrapper .options-list .single-wallet.sidebar.selected-wallet{
|
|
93
|
+
background-color: inherit;
|
|
94
|
+
}
|
|
95
|
+
.nws-modal-wrapper .nws-modal .wallet-options-wrapper .options-list .single-wallet.sidebar.selected-wallet:hover{
|
|
96
|
+
background-color: #2d3860;
|
|
97
|
+
}
|
|
98
|
+
`
|
|
99
|
+
// 一般可以保留样式,不需要卸载时删除
|
|
100
|
+
}, [])
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<WalletSelectorProvider config={config}>
|
|
106
|
+
{children}
|
|
107
|
+
</WalletSelectorProvider>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
@@ -3,6 +3,7 @@ import { useConfig as useBigmiConfig } from '@bigmi/react'
|
|
|
3
3
|
import type { SDKProvider } from '@openocean.finance/widget-sdk'
|
|
4
4
|
import {
|
|
5
5
|
ChainType,
|
|
6
|
+
Near,
|
|
6
7
|
EVM,
|
|
7
8
|
Solana,
|
|
8
9
|
UTXO,
|
|
@@ -22,6 +23,7 @@ export const SDKProviders = () => {
|
|
|
22
23
|
const { sdkConfig } = useWidgetConfig()
|
|
23
24
|
const { wallet } = useWallet()
|
|
24
25
|
const wagmiConfig = useWagmiConfig()
|
|
26
|
+
const nearProvider = Near()
|
|
25
27
|
const bigmiConfig = useBigmiConfig()
|
|
26
28
|
|
|
27
29
|
useEffect(() => {
|
|
@@ -63,6 +65,9 @@ export const SDKProviders = () => {
|
|
|
63
65
|
})
|
|
64
66
|
)
|
|
65
67
|
}
|
|
68
|
+
// Always register a Near (NVM) provider so balances can be queried for NEAR.
|
|
69
|
+
providers.push(nearProvider)
|
|
70
|
+
|
|
66
71
|
if (sdkConfig?.providers?.length) {
|
|
67
72
|
providers.push(...sdkConfig.providers)
|
|
68
73
|
}
|
|
@@ -11,7 +11,7 @@ interface ExternalWalletProvider {
|
|
|
11
11
|
internalChainTypes: ChainType[]
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const internalChainTypes = [ChainType.EVM, ChainType.SVM, ChainType.UTXO]
|
|
14
|
+
const internalChainTypes = [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.NVM]
|
|
15
15
|
|
|
16
16
|
export function useExternalWalletProvider(): ExternalWalletProvider {
|
|
17
17
|
const { walletConfig } = useWidgetConfig()
|
|
@@ -261,6 +261,8 @@ interface ExecuteRouteOptions {
|
|
|
261
261
|
onDisconnect?: (account: Account) => Promise<void>
|
|
262
262
|
onOpenWalletMenu?: () => void
|
|
263
263
|
solanaWallet?: any
|
|
264
|
+
// Near wallet-selector 实例从 React 层通过 options 传入,避免在此文件中直接调用 Hook
|
|
265
|
+
nearWallet?: any
|
|
264
266
|
}
|
|
265
267
|
|
|
266
268
|
interface ExtendedOpenOceanStep extends OpenOceanStep {
|
|
@@ -351,6 +353,7 @@ async function executeSolanaSwap(
|
|
|
351
353
|
const signedTx = await bridgeExecuteSwap({
|
|
352
354
|
quoteData: quoteData,
|
|
353
355
|
walletClient: adaptedWallet,
|
|
356
|
+
nearWallet: options.nearWallet,
|
|
354
357
|
})
|
|
355
358
|
if (!signedTx) {
|
|
356
359
|
throw new Error('Failed to sign transaction')
|
|
@@ -606,6 +609,7 @@ async function executeEvmSwap(
|
|
|
606
609
|
const result = await bridgeExecuteSwap({
|
|
607
610
|
quoteData: quoteData,
|
|
608
611
|
walletClient: walletClient,
|
|
612
|
+
nearWallet: options.nearWallet,
|
|
609
613
|
})
|
|
610
614
|
hash = result?.sourceTxHash || ''
|
|
611
615
|
} else {
|
|
@@ -1005,6 +1009,7 @@ async function executeBitcoinSwap(
|
|
|
1005
1009
|
const signedTx = await bridgeExecuteSwap({
|
|
1006
1010
|
quoteData: quoteData,
|
|
1007
1011
|
walletClient: adaptedWallet,
|
|
1012
|
+
nearWallet: options.nearWallet,
|
|
1008
1013
|
});
|
|
1009
1014
|
|
|
1010
1015
|
if (!signedTx) {
|
|
@@ -1043,6 +1048,133 @@ async function executeBitcoinSwap(
|
|
|
1043
1048
|
throw error
|
|
1044
1049
|
}
|
|
1045
1050
|
}
|
|
1051
|
+
|
|
1052
|
+
// Execute Near transaction via Near Intents adapter
|
|
1053
|
+
async function executeNearSwap(
|
|
1054
|
+
step: ExtendedOpenOceanStep,
|
|
1055
|
+
options: ExecuteRouteOptions,
|
|
1056
|
+
process: Process,
|
|
1057
|
+
route: ExtendedRoute
|
|
1058
|
+
): Promise<void> {
|
|
1059
|
+
try {
|
|
1060
|
+
const { type, transactionRequest } = step || {}
|
|
1061
|
+
|
|
1062
|
+
if ((type as any) === 'bridge') {
|
|
1063
|
+
|
|
1064
|
+
const { quoteData } = step || {}
|
|
1065
|
+
|
|
1066
|
+
if (!quoteData) {
|
|
1067
|
+
throw new Error('Missing Near quote data.')
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// NearIntentsAdapter 的 Near 分支只依赖 nearWallet,不依赖 walletClient,
|
|
1071
|
+
// 这里传一个占位对象即可。
|
|
1072
|
+
const dummyWalletClient: any = {}
|
|
1073
|
+
|
|
1074
|
+
const result = await bridgeExecuteSwap({
|
|
1075
|
+
quoteData,
|
|
1076
|
+
walletClient: dummyWalletClient,
|
|
1077
|
+
nearWallet: options.nearWallet,
|
|
1078
|
+
})
|
|
1079
|
+
|
|
1080
|
+
if (!result) {
|
|
1081
|
+
throw new Error('Failed to execute Near swap.')
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const hash = result.sourceTxHash
|
|
1085
|
+
|
|
1086
|
+
process.status = 'DONE'
|
|
1087
|
+
process.doneAt = Date.now()
|
|
1088
|
+
process.txHash = hash
|
|
1089
|
+
process.message = 'Transaction confirmed'
|
|
1090
|
+
step.execution!.status = 'DONE'
|
|
1091
|
+
options.updateRouteHook?.(route)
|
|
1092
|
+
} else {
|
|
1093
|
+
let transactions = JSON.parse(Buffer.from(transactionRequest.data, 'base64').toString())
|
|
1094
|
+
const wallet = options.nearWallet
|
|
1095
|
+
const txs = transactions.map((t: any, i: number) => {
|
|
1096
|
+
return {
|
|
1097
|
+
receiverId: t.receiverId,
|
|
1098
|
+
// nonceOffset: i + 1,
|
|
1099
|
+
signerId: wallet.signedAccountId,
|
|
1100
|
+
actions: t.functionCalls.map((fc: any) => {
|
|
1101
|
+
if (fc.deposit) {
|
|
1102
|
+
const depositNum = typeof fc.deposit === 'string' ? parseFloat(fc.deposit) : fc.deposit
|
|
1103
|
+
if (depositNum > 0) {
|
|
1104
|
+
const depositStr = depositNum.toFixed(24)
|
|
1105
|
+
fc.deposit = depositStr.replace('.', '').replace(/^0+/, '') || '0'
|
|
1106
|
+
} else {
|
|
1107
|
+
fc.deposit = '0'
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return {
|
|
1111
|
+
type: 'FunctionCall',
|
|
1112
|
+
params: {
|
|
1113
|
+
methodName: fc.methodName,
|
|
1114
|
+
args: {
|
|
1115
|
+
...fc.args,
|
|
1116
|
+
},
|
|
1117
|
+
gas: fc.gas,
|
|
1118
|
+
deposit: fc.deposit,
|
|
1119
|
+
},
|
|
1120
|
+
}
|
|
1121
|
+
})
|
|
1122
|
+
};
|
|
1123
|
+
})
|
|
1124
|
+
const txResult = await wallet.signAndSendTransactions({
|
|
1125
|
+
transactions: txs
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
let transaction: any = { hash: "" };
|
|
1129
|
+
if (txResult && txResult.length === 1) {
|
|
1130
|
+
transaction = txResult[txResult.length - 1].transaction || {};
|
|
1131
|
+
} else if (txResult && txResult.length > 1) {
|
|
1132
|
+
transaction = txResult.filter((item: any) => {
|
|
1133
|
+
const { actions = [] } = item && item.transaction || {};
|
|
1134
|
+
const _actions = actions.filter((fc: any) => {
|
|
1135
|
+
const { FunctionCall = {} } = fc;
|
|
1136
|
+
const { method_name } = FunctionCall;
|
|
1137
|
+
return method_name === 'ft_transfer_call';
|
|
1138
|
+
});
|
|
1139
|
+
return _actions && _actions.length > 0;
|
|
1140
|
+
});
|
|
1141
|
+
if (transaction && transaction.length) {
|
|
1142
|
+
transaction = transaction[0].transaction;
|
|
1143
|
+
} else {
|
|
1144
|
+
transaction = txResult[txResult.length - 1].transaction || {};
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
console.log('signAndSendTransactions', transaction);
|
|
1148
|
+
const { hash } = transaction;
|
|
1149
|
+
process.status = 'DONE'
|
|
1150
|
+
process.doneAt = Date.now()
|
|
1151
|
+
process.txHash = hash
|
|
1152
|
+
process.message = 'Transaction confirmed'
|
|
1153
|
+
step.execution!.status = 'DONE'
|
|
1154
|
+
options.updateRouteHook?.(route)
|
|
1155
|
+
}
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
console.error('Near swap execution failed:', error)
|
|
1158
|
+
process.status = 'FAILED'
|
|
1159
|
+
process.error =
|
|
1160
|
+
error instanceof Error || (error && (error as any)?.message)
|
|
1161
|
+
? {
|
|
1162
|
+
code: 'EXECUTION_ERROR',
|
|
1163
|
+
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
1164
|
+
htmlMessage:
|
|
1165
|
+
error instanceof Error ? error.message : 'Unknown error occurred',
|
|
1166
|
+
}
|
|
1167
|
+
: {
|
|
1168
|
+
code: 'UNKNOWN_ERROR',
|
|
1169
|
+
message: 'Unknown error occurred',
|
|
1170
|
+
htmlMessage: 'Unknown error occurred',
|
|
1171
|
+
}
|
|
1172
|
+
step.execution!.status = 'FAILED'
|
|
1173
|
+
options.updateRouteHook?.(route)
|
|
1174
|
+
throw error
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
}
|
|
1046
1178
|
// Execute transaction
|
|
1047
1179
|
async function executeSwap(
|
|
1048
1180
|
route: ExtendedRoute,
|
|
@@ -1081,13 +1213,14 @@ async function executeSwap(
|
|
|
1081
1213
|
}
|
|
1082
1214
|
throw new Error('Please connect wallet first')
|
|
1083
1215
|
}
|
|
1084
|
-
|
|
1085
1216
|
// Execute different transaction logic based on chain type
|
|
1086
1217
|
const currentStep = route.steps[0]
|
|
1087
1218
|
if (currentStep.action?.fromChainId === 1151111081099710) {
|
|
1088
1219
|
await executeSolanaSwap(currentStep, options, process, updatedRoute)
|
|
1089
1220
|
} else if (currentStep.action?.fromChainId === 20000000000001) {
|
|
1090
1221
|
await executeBitcoinSwap(currentStep, options, process, updatedRoute)
|
|
1222
|
+
} else if (currentStep.action?.fromChainId === 20000000000006) {
|
|
1223
|
+
await executeNearSwap(currentStep, options, process, updatedRoute)
|
|
1091
1224
|
} else {
|
|
1092
1225
|
await executeEvmSwap(currentStep, options, process, updatedRoute)
|
|
1093
1226
|
}
|
|
@@ -57,15 +57,68 @@ export class OpenOceanService {
|
|
|
57
57
|
disabledDexIds?: string
|
|
58
58
|
enabledDexIds?: string
|
|
59
59
|
referrer?: string
|
|
60
|
+
account?: string
|
|
61
|
+
inTokenDecimals?: number
|
|
62
|
+
outTokenDecimals?: number
|
|
60
63
|
}) {
|
|
64
|
+
const isNearChain = params.chain === '20000000000006' || params.chain === 'near'
|
|
65
|
+
// Near 链使用特殊的 API 端点和参数格式
|
|
66
|
+
if (isNearChain) {
|
|
67
|
+
const nearApiUrl = 'https://ethapi.openocean.finance/v1/near'
|
|
68
|
+
|
|
69
|
+
// 计算 amountAll(格式化后的金额,用于显示)
|
|
70
|
+
let amountAll = ''
|
|
71
|
+
if (params.inTokenDecimals !== undefined) {
|
|
72
|
+
const amountBigInt = BigInt(params.amount)
|
|
73
|
+
const decimals = BigInt(10 ** params.inTokenDecimals)
|
|
74
|
+
const wholePart = amountBigInt / decimals
|
|
75
|
+
const fractionalPart = amountBigInt % decimals
|
|
76
|
+
if (fractionalPart === 0n) {
|
|
77
|
+
amountAll = wholePart.toString()
|
|
78
|
+
} else {
|
|
79
|
+
const fractionalStr = fractionalPart.toString().padStart(params.inTokenDecimals, '0')
|
|
80
|
+
// 移除尾部的 0,但保留至少一位小数
|
|
81
|
+
const trimmedFractional = fractionalStr.replace(/0+$/, '') || '0'
|
|
82
|
+
amountAll = `${wholePart}.${trimmedFractional}`
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 转换 slippage:从 0.01 (1%) 转为 100 (百分比格式)
|
|
87
|
+
const slippagePercent = params.slippage
|
|
88
|
+
? Math.floor(Number(params.slippage) * 100).toString()
|
|
89
|
+
: '100' // 默认 1%
|
|
90
|
+
|
|
91
|
+
const queryParams = new URLSearchParams({
|
|
92
|
+
quoteType: 'swap',
|
|
93
|
+
inTokenSymbol: params.inTokenSymbol,
|
|
94
|
+
inTokenAddress: params.inTokenAddress,
|
|
95
|
+
outTokenSymbol: params.outTokenSymbol,
|
|
96
|
+
outTokenAddress: params.outTokenAddress,
|
|
97
|
+
amount: params.amount,
|
|
98
|
+
...(amountAll && { amountAll }),
|
|
99
|
+
slippage: slippagePercent,
|
|
100
|
+
gasPrice: params.gasPrice || '5000000000',
|
|
101
|
+
referrer: params.referrer || '0x3487ef9f9b36547e43268b8f0e2349a226c70b53',
|
|
102
|
+
disabledDexIds: params.disabledDexIds || '',
|
|
103
|
+
disableRfq: '', // Near API 特有参数
|
|
104
|
+
...(params.account && { account: params.account }),
|
|
105
|
+
})
|
|
106
|
+
const response = await fetch(`${nearApiUrl}/quote?${queryParams.toString()}`)
|
|
107
|
+
return {
|
|
108
|
+
data: await response.json(),
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 其他链使用原有逻辑
|
|
61
113
|
const apiUrl = this.getApiUrl(params.chain)
|
|
62
114
|
const slippage = (Number(params.slippage || 0.01)).toString();
|
|
115
|
+
|
|
63
116
|
const queryParams = new URLSearchParams({
|
|
64
117
|
quoteType: 'quote',
|
|
65
118
|
inTokenSymbol: params.inTokenSymbol,
|
|
66
|
-
inTokenAddress: params.inTokenAddress,
|
|
119
|
+
inTokenAddress: this.getSolanaAddress(params.chain, params.inTokenAddress),
|
|
67
120
|
outTokenSymbol: params.outTokenSymbol,
|
|
68
|
-
outTokenAddress: params.outTokenAddress,
|
|
121
|
+
outTokenAddress: this.getSolanaAddress(params.chain, params.outTokenAddress),
|
|
69
122
|
amountDecimals: params.amount,
|
|
70
123
|
slippage,
|
|
71
124
|
gasPriceDecimals: params.gasPrice || '',
|
|
@@ -73,7 +126,6 @@ export class OpenOceanService {
|
|
|
73
126
|
enabledDexIds: params.enabledDexIds || '',
|
|
74
127
|
referrer: params.referrer || '0x3487ef9f9b36547e43268b8f0e2349a226c70b53',
|
|
75
128
|
})
|
|
76
|
-
|
|
77
129
|
const response = await fetch(`${apiUrl}/quote?${queryParams.toString()}`)
|
|
78
130
|
return response.json()
|
|
79
131
|
}
|
|
@@ -93,7 +145,66 @@ export class OpenOceanService {
|
|
|
93
145
|
referrer?: string,
|
|
94
146
|
referrerFee?: string,
|
|
95
147
|
referrerFeeShare?: string
|
|
148
|
+
inTokenDecimals?: number
|
|
96
149
|
}) {
|
|
150
|
+
const isNearChain = params.chain === '20000000000006' || params.chain === 'near'
|
|
151
|
+
|
|
152
|
+
// Near 链使用特殊的 API 端点和参数格式
|
|
153
|
+
if (isNearChain) {
|
|
154
|
+
const nearApiUrl = 'https://ethapi.openocean.finance/v1/near'
|
|
155
|
+
|
|
156
|
+
// 计算 amountAll(格式化后的金额,用于显示)
|
|
157
|
+
let amountAll = ''
|
|
158
|
+
if (params.inTokenDecimals !== undefined) {
|
|
159
|
+
const amountBigInt = BigInt(params.amount)
|
|
160
|
+
const decimals = BigInt(10 ** params.inTokenDecimals)
|
|
161
|
+
const wholePart = amountBigInt / decimals
|
|
162
|
+
const fractionalPart = amountBigInt % decimals
|
|
163
|
+
if (fractionalPart === 0n) {
|
|
164
|
+
amountAll = wholePart.toString()
|
|
165
|
+
} else {
|
|
166
|
+
const fractionalStr = fractionalPart.toString().padStart(params.inTokenDecimals, '0')
|
|
167
|
+
// 移除尾部的 0,但保留至少一位小数
|
|
168
|
+
const trimmedFractional = fractionalStr.replace(/0+$/, '') || '0'
|
|
169
|
+
amountAll = `${wholePart}.${trimmedFractional}`
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 转换 slippage:从 0.01 (1%) 转为 100 (百分比格式)
|
|
174
|
+
const slippagePercent = params.slippage
|
|
175
|
+
? Math.floor(Number(params.slippage) * 100).toString()
|
|
176
|
+
: '100' // 默认 1%
|
|
177
|
+
|
|
178
|
+
const queryParams = new URLSearchParams({
|
|
179
|
+
quoteType: 'swap',
|
|
180
|
+
inTokenSymbol: params.inTokenSymbol,
|
|
181
|
+
inTokenAddress: params.inTokenAddress,
|
|
182
|
+
outTokenSymbol: params.outTokenSymbol,
|
|
183
|
+
outTokenAddress: params.outTokenAddress,
|
|
184
|
+
amount: params.amount,
|
|
185
|
+
...(amountAll && { amountAll }),
|
|
186
|
+
gasPrice: params.gasPrice || '5000000000',
|
|
187
|
+
disabledDexIds: params.disabledDexIds || '',
|
|
188
|
+
slippage: slippagePercent,
|
|
189
|
+
account: params.account,
|
|
190
|
+
referrer: params.referrer || '0x3487ef9f9b36547e43268b8f0e2349a226c70b53',
|
|
191
|
+
flags: '0', // Near API 特有参数
|
|
192
|
+
disableRfq: '', // Near API 特有参数
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const response = await fetch(`${nearApiUrl}/swap-quote?${queryParams.toString()}`)
|
|
196
|
+
const data = await response.json()
|
|
197
|
+
return {
|
|
198
|
+
|
|
199
|
+
data: {
|
|
200
|
+
...data,
|
|
201
|
+
data: data.transaction,
|
|
202
|
+
price_impact: 0
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 其他链使用原有逻辑
|
|
97
208
|
const apiUrl = this.getApiUrl(params.chain)
|
|
98
209
|
const slippage = (Number(params.slippage || 0.01)).toString();
|
|
99
210
|
const referrer: any = {
|
|
@@ -176,7 +287,7 @@ export class OpenOceanService {
|
|
|
176
287
|
}
|
|
177
288
|
|
|
178
289
|
static async getGasPrice(chain: string) {
|
|
179
|
-
if (!chain || chain === '1151111081099710' || chain === '20000000000001') {
|
|
290
|
+
if (!chain || chain === '1151111081099710' || chain === '20000000000001' || chain === '20000000000006') {
|
|
180
291
|
return {
|
|
181
292
|
data: {
|
|
182
293
|
gasPrice: '1000000000000000000',
|
|
@@ -186,6 +297,13 @@ export class OpenOceanService {
|
|
|
186
297
|
// const apiUrl = this.getApiUrl(chain)
|
|
187
298
|
const response = await fetch(`${this.API_V4_URL}/${chain}/gasPrice`)
|
|
188
299
|
const { data } = await response.json()
|
|
300
|
+
if (chain == '1') {
|
|
301
|
+
return {
|
|
302
|
+
standard: data?.standard?.maxFeePerGas || '101021713',
|
|
303
|
+
instant: data?.instant?.maxFeePerGas || '101021713',
|
|
304
|
+
fast: data?.fast?.maxFeePerGas || '101021713',
|
|
305
|
+
}
|
|
306
|
+
}
|
|
189
307
|
return data
|
|
190
308
|
}
|
|
191
309
|
|
|
@@ -255,9 +373,19 @@ export class OpenOceanService {
|
|
|
255
373
|
let url = `${this.API_V3_URL}/solana/getRpc`
|
|
256
374
|
const response = await fetch(url)
|
|
257
375
|
const data = await response.json()
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
376
|
+
if (data.data?.openapi_v1) {
|
|
377
|
+
let rpcUrl = data.data?.openapi_v1?.[0] || ''
|
|
378
|
+
this.solanaRpcUrl = rpcUrl
|
|
379
|
+
}
|
|
380
|
+
if (data.data?.openapi_v2) {
|
|
381
|
+
let rpcUrl = data.data?.openapi_v2?.[0] || ''
|
|
382
|
+
this.solanaRpcUrl = rpcUrl
|
|
383
|
+
}
|
|
384
|
+
if (data.data?.openapi_v3) {
|
|
385
|
+
let rpcUrl = data.data?.openapi_v3?.[0] || ''
|
|
386
|
+
this.solanaRpcUrl = rpcUrl
|
|
387
|
+
}
|
|
388
|
+
return this.solanaRpcUrl
|
|
261
389
|
}
|
|
262
390
|
}
|
|
263
391
|
|