@openocean.finance/widget 1.0.27 → 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 +217 -195
- 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 +305 -282
- package/src/stores/routes/createRouteExecutionStore.ts +13 -4
- package/src/types/widget.ts +3 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { ChainId } from '@openocean.finance/widget-sdk'
|
|
2
|
+
import { Currency } from '../constants/index.js'
|
|
3
|
+
|
|
4
|
+
import { WalletClient, formatUnits } from 'viem'
|
|
5
|
+
|
|
6
|
+
import { CROSS_CHAIN_FEE_RECEIVER, ZERO_ADDRESS } from '../constants/index.js'
|
|
7
|
+
|
|
8
|
+
import { Quote } from '../registry.js'
|
|
9
|
+
import {
|
|
10
|
+
BaseSwapAdapter,
|
|
11
|
+
Chain,
|
|
12
|
+
NonEvmChain,
|
|
13
|
+
NormalizedQuote,
|
|
14
|
+
NormalizedTxResponse,
|
|
15
|
+
QuoteParams,
|
|
16
|
+
SwapStatus,
|
|
17
|
+
} from './BaseSwapAdapter.js'
|
|
18
|
+
|
|
19
|
+
const OPTIMEX_API = 'https://ks-provider.optimex.xyz/v1'
|
|
20
|
+
|
|
21
|
+
interface OptimexToken {
|
|
22
|
+
id: number
|
|
23
|
+
network_id: 'ethereum' | 'bitcoin'
|
|
24
|
+
token_id: string
|
|
25
|
+
network_name: string
|
|
26
|
+
network_symbol: string
|
|
27
|
+
network_type: 'EVM' | 'BTC'
|
|
28
|
+
token_name: string
|
|
29
|
+
token_symbol: string
|
|
30
|
+
token_address: string
|
|
31
|
+
token_decimals: number
|
|
32
|
+
token_logo_uri: string
|
|
33
|
+
network_logo_uri: string
|
|
34
|
+
active: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class OptimexAdapter extends BaseSwapAdapter {
|
|
38
|
+
private tokens: OptimexToken[]
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
super()
|
|
42
|
+
this.tokens = []
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async getTokens() {
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(`${OPTIMEX_API}/tokens`)
|
|
48
|
+
const { data } = await res.json()
|
|
49
|
+
this.tokens = data.tokens
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to initialize Optimex tokens:', error)
|
|
52
|
+
// Handle error appropriately
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
getName(): string {
|
|
56
|
+
return 'Optimex'
|
|
57
|
+
}
|
|
58
|
+
getIcon(): string {
|
|
59
|
+
return 'https://storage.googleapis.com/ks-setting-1d682dca/464ce79e-a906-4590-bf78-9054e606aa041749023419612.png'
|
|
60
|
+
}
|
|
61
|
+
getSupportedChains(): Chain[] {
|
|
62
|
+
return [NonEvmChain.Bitcoin, ChainId.MAINNET]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getSupportedTokens(_sourceChain: Chain, _destChain: Chain): Currency[] {
|
|
66
|
+
return []
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async getQuote(params: QuoteParams): Promise<NormalizedQuote> {
|
|
70
|
+
if (!this.tokens?.length) {
|
|
71
|
+
await this.getTokens()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isFromBtc = params.fromChain === NonEvmChain.Bitcoin
|
|
75
|
+
const isToBtc = params.toChain === NonEvmChain.Bitcoin
|
|
76
|
+
const fromToken = isFromBtc
|
|
77
|
+
? { token_id: 'BTC', token_symbol: 'BTC' }
|
|
78
|
+
: this.tokens.find(item => {
|
|
79
|
+
const address = (params.fromToken as any).isNative ? 'native' : (params.fromToken as any).wrapped.address
|
|
80
|
+
return item.network_id === 'ethereum' && address.toLowerCase() === item.token_address.toLowerCase()
|
|
81
|
+
})
|
|
82
|
+
const fromTokenId = fromToken?.token_id
|
|
83
|
+
|
|
84
|
+
const toToken = isToBtc
|
|
85
|
+
? { token_id: 'BTC', token_symbol: 'BTC' }
|
|
86
|
+
: this.tokens.find(item => {
|
|
87
|
+
const address = (params.toToken as any).isNative ? 'native' : (params.toToken as any).wrapped.address
|
|
88
|
+
return item.network_id === 'ethereum' && address.toLowerCase() === item.token_address.toLowerCase()
|
|
89
|
+
})
|
|
90
|
+
const toTokenId = toToken?.token_id
|
|
91
|
+
|
|
92
|
+
if (!fromTokenId || !toTokenId) {
|
|
93
|
+
console.log('optimex tokens', this.tokens)
|
|
94
|
+
throw new Error(`Optimex does not support ${!fromTokenId ? params.fromToken.symbol : params.toToken.symbol}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const [quoteRes, estimateRes, token0Usd, token1Usd] = await Promise.all([
|
|
98
|
+
fetch(`${OPTIMEX_API}/solver/indicative-quote`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-Type': 'application/json',
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
debug: false,
|
|
105
|
+
from_token_amount: params.amount,
|
|
106
|
+
from_token_id: fromTokenId,
|
|
107
|
+
to_token_id: toTokenId,
|
|
108
|
+
affiliate_fee_bps: params.feeBps.toString(),
|
|
109
|
+
}),
|
|
110
|
+
}).then(res => res.json()),
|
|
111
|
+
fetch(`${OPTIMEX_API}/trades/estimate?from_token=${fromTokenId}&to_token=${toTokenId}`).then(res => res.json()),
|
|
112
|
+
fetch(`https://api.optimex.xyz/v1/tokens/${fromToken.token_symbol}`)
|
|
113
|
+
.then(res => res.json())
|
|
114
|
+
.then(res => res?.data?.current_price || 0),
|
|
115
|
+
fetch(`https://api.optimex.xyz/v1/tokens/${toToken.token_symbol}`)
|
|
116
|
+
.then(res => res.json())
|
|
117
|
+
.then(res => res?.data?.current_price || 0),
|
|
118
|
+
])
|
|
119
|
+
|
|
120
|
+
let txData: { deposit_address: string; payload?: string; trade_id: string } | null = null
|
|
121
|
+
|
|
122
|
+
if (params.sender && params.recipient && (isFromBtc ? params.publicKey : true)) {
|
|
123
|
+
const tradeTimeout = new Date()
|
|
124
|
+
tradeTimeout.setHours(tradeTimeout.getHours() + 2)
|
|
125
|
+
|
|
126
|
+
const scriptTimeout = new Date()
|
|
127
|
+
scriptTimeout.setHours(scriptTimeout.getHours() + 24)
|
|
128
|
+
|
|
129
|
+
const res = await fetch(`${OPTIMEX_API}/trades/initiate`, {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': 'application/json',
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
session_id: quoteRes.data.session_id,
|
|
136
|
+
from_user_address: params.sender,
|
|
137
|
+
amount_in: params.amount,
|
|
138
|
+
min_amount_out: (
|
|
139
|
+
(BigInt(quoteRes.data.best_quote_after_fees) * (10_000n - BigInt(params.slippage))) /
|
|
140
|
+
10_000n
|
|
141
|
+
).toString(),
|
|
142
|
+
to_user_address: params.recipient,
|
|
143
|
+
user_refund_pubkey: params.fromChain === NonEvmChain.Bitcoin ? params.publicKey : params.sender,
|
|
144
|
+
user_refund_address: params.sender,
|
|
145
|
+
creator_public_key: params.fromChain === NonEvmChain.Bitcoin ? params.publicKey : params.sender,
|
|
146
|
+
from_wallet_address: params.sender,
|
|
147
|
+
trade_timeout: Math.floor(tradeTimeout.getTime() / 1000),
|
|
148
|
+
script_timeout: Math.floor(scriptTimeout.getTime() / 1000),
|
|
149
|
+
affiliate_info: [
|
|
150
|
+
{
|
|
151
|
+
provider: 'KyberSwap',
|
|
152
|
+
rate: params.feeBps.toString(),
|
|
153
|
+
receiver: CROSS_CHAIN_FEE_RECEIVER,
|
|
154
|
+
network: 'ethereum',
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
}),
|
|
158
|
+
}).then(res => res.json())
|
|
159
|
+
|
|
160
|
+
if (res.data.deposit_address) {
|
|
161
|
+
txData = res.data
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const formattedOutputAmount = formatUnits(BigInt(quoteRes.data.best_quote_after_fees), params.toToken.decimals)
|
|
166
|
+
const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals)
|
|
167
|
+
const inputUsd = token0Usd * +formattedInputAmount
|
|
168
|
+
const outputUsd = token1Usd * +formattedOutputAmount
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
quoteParams: params,
|
|
172
|
+
outputAmount: BigInt(quoteRes.data.best_quote_after_fees),
|
|
173
|
+
formattedOutputAmount,
|
|
174
|
+
inputUsd,
|
|
175
|
+
outputUsd,
|
|
176
|
+
priceImpact: !inputUsd || !outputUsd ? NaN : ((inputUsd - outputUsd) * 100) / inputUsd,
|
|
177
|
+
rate: +formattedOutputAmount / +formattedInputAmount,
|
|
178
|
+
gasFeeUsd: 0,
|
|
179
|
+
timeEstimate: estimateRes.data.estimated_time,
|
|
180
|
+
contractAddress: txData?.deposit_address || ZERO_ADDRESS,
|
|
181
|
+
rawQuote: { ...quoteRes.data, txData },
|
|
182
|
+
|
|
183
|
+
protocolFee: 0,
|
|
184
|
+
platformFeePercent: (params.feeBps * 100) / 10000,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async executeSwap(
|
|
189
|
+
{ quote }: Quote,
|
|
190
|
+
walletClient: WalletClient,
|
|
191
|
+
_nearWallet: any,
|
|
192
|
+
sendBtcFn?: (params: { recipient: string; amount: string | number }) => Promise<string>,
|
|
193
|
+
): Promise<NormalizedTxResponse> {
|
|
194
|
+
const params = {
|
|
195
|
+
sender: quote.quoteParams.sender,
|
|
196
|
+
id: quote.rawQuote.txData.trade_id,
|
|
197
|
+
adapter: this.getName(),
|
|
198
|
+
sourceChain: quote.quoteParams.fromChain,
|
|
199
|
+
targetChain: quote.quoteParams.toChain,
|
|
200
|
+
inputAmount: quote.quoteParams.amount,
|
|
201
|
+
outputAmount: quote.outputAmount.toString(),
|
|
202
|
+
sourceToken: quote.quoteParams.fromToken,
|
|
203
|
+
targetToken: quote.quoteParams.toToken,
|
|
204
|
+
timestamp: new Date().getTime(),
|
|
205
|
+
}
|
|
206
|
+
if (quote.quoteParams.fromChain === NonEvmChain.Bitcoin) {
|
|
207
|
+
if (!sendBtcFn) throw new Error('sendBtcFn is not defined')
|
|
208
|
+
const res = await sendBtcFn({
|
|
209
|
+
recipient: quote.rawQuote.txData.deposit_address,
|
|
210
|
+
amount: quote.quoteParams.amount,
|
|
211
|
+
}).catch(e => {
|
|
212
|
+
throw e
|
|
213
|
+
})
|
|
214
|
+
await fetch(`${OPTIMEX_API}/trades/${quote.rawQuote.txData.trade_id}/submit-tx`, {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: {
|
|
217
|
+
'Content-Type': 'application/json',
|
|
218
|
+
},
|
|
219
|
+
body: JSON.stringify({
|
|
220
|
+
tx_id: res,
|
|
221
|
+
}),
|
|
222
|
+
}).catch(e => {
|
|
223
|
+
console.log('submit tx error for optimex', e)
|
|
224
|
+
})
|
|
225
|
+
return {
|
|
226
|
+
...params,
|
|
227
|
+
sourceTxHash: res,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!walletClient || !walletClient.account) throw new Error('Not connected')
|
|
232
|
+
|
|
233
|
+
const account = walletClient.account?.address as `0x${string}`
|
|
234
|
+
const hash = await walletClient.sendTransaction({
|
|
235
|
+
to: quote.rawQuote.txData.deposit_address,
|
|
236
|
+
value: (quote.quoteParams.fromToken as any).isNative ? BigInt(quote.quoteParams.amount) : undefined,
|
|
237
|
+
data: quote.rawQuote.txData.payload,
|
|
238
|
+
chain: undefined,
|
|
239
|
+
account,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
await fetch(`${OPTIMEX_API}/trades/${quote.rawQuote.txData.trade_id}/submit-tx`, {
|
|
243
|
+
method: 'POST',
|
|
244
|
+
headers: {
|
|
245
|
+
'Content-Type': 'application/json',
|
|
246
|
+
},
|
|
247
|
+
body: JSON.stringify({
|
|
248
|
+
tx_id: hash,
|
|
249
|
+
}),
|
|
250
|
+
}).catch(e => {
|
|
251
|
+
console.log('submit tx error for optimex', e)
|
|
252
|
+
})
|
|
253
|
+
return {
|
|
254
|
+
...params,
|
|
255
|
+
sourceTxHash: hash,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async getTransactionStatus(p: NormalizedTxResponse): Promise<SwapStatus> {
|
|
260
|
+
const res = await fetch(`${OPTIMEX_API}/trades/${p.id}`).then(res => res.json())
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
txHash: res.data?.payment_bundle?.settlement_tx || '',
|
|
264
|
+
status: ['Done', 'PaymentConfirmed'].includes(res?.data?.state)
|
|
265
|
+
? 'Success'
|
|
266
|
+
: ['Aborted', 'ToBeAborted', 'Failed', 'Failure', 'UserCancelled'].includes(res?.data?.state)
|
|
267
|
+
? 'Failed'
|
|
268
|
+
: res?.data?.state === 'Refunded'
|
|
269
|
+
? 'Refunded'
|
|
270
|
+
: 'Processing',
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
|
|
2
|
+
import { Currency } from '../constants/index.js'
|
|
3
|
+
import { useWalletSelector } from '@near-wallet-selector/react-hook'
|
|
4
|
+
import { WalletAdapterProps } from '@solana/wallet-adapter-base'
|
|
5
|
+
import { Connection, Transaction, VersionedTransaction } from '@solana/web3.js'
|
|
6
|
+
import { getPublicClient } from '@wagmi/core'
|
|
7
|
+
import { WalletClient, formatUnits } from 'viem'
|
|
8
|
+
import { useConfig } from 'wagmi'
|
|
9
|
+
import { CROSS_CHAIN_FEE_RECEIVER, CROSS_CHAIN_FEE_RECEIVER_SOLANA, ZERO_ADDRESS } from '../constants/index.js'
|
|
10
|
+
import { MAINNET_NETWORKS } from '../constants/index.js'
|
|
11
|
+
import { SolanaToken } from '../constants/index.js'
|
|
12
|
+
|
|
13
|
+
import { Quote } from '../registry.js'
|
|
14
|
+
import {
|
|
15
|
+
BaseSwapAdapter,
|
|
16
|
+
Chain,
|
|
17
|
+
NOT_SUPPORTED_CHAINS_PRICE_SERVICE,
|
|
18
|
+
NormalizedQuote,
|
|
19
|
+
NormalizedTxResponse,
|
|
20
|
+
QuoteParams,
|
|
21
|
+
SwapStatus,
|
|
22
|
+
} from './BaseSwapAdapter.js'
|
|
23
|
+
|
|
24
|
+
interface Step {
|
|
25
|
+
action: string
|
|
26
|
+
tx: {
|
|
27
|
+
data: string
|
|
28
|
+
to: string
|
|
29
|
+
value: string
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class OrbiterAdapter extends BaseSwapAdapter {
|
|
34
|
+
constructor() {
|
|
35
|
+
super()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getName(): string {
|
|
39
|
+
return 'Orbiter'
|
|
40
|
+
}
|
|
41
|
+
getIcon(): string {
|
|
42
|
+
return 'https://www.orbiter.finance/favicon.ico'
|
|
43
|
+
}
|
|
44
|
+
getSupportedChains(): Chain[] {
|
|
45
|
+
return [...MAINNET_NETWORKS]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getSupportedTokens(_sourceChain: Chain, _destChain: Chain): Currency[] {
|
|
49
|
+
return []
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async getQuote(params: QuoteParams): Promise<NormalizedQuote> {
|
|
53
|
+
const fromToken = params.fromToken as any
|
|
54
|
+
const toToken = params.toToken as any
|
|
55
|
+
const body = {
|
|
56
|
+
sourceChainId: params.fromChain === 'solana' ? 'SOLANA_MAIN' : params.fromChain.toString(),
|
|
57
|
+
destChainId: params.toChain === 'solana' ? 'SOLANA_MAIN' : params.toChain.toString(),
|
|
58
|
+
sourceToken:
|
|
59
|
+
params.fromChain === 'solana'
|
|
60
|
+
? (params.fromToken as SolanaToken).id
|
|
61
|
+
: fromToken.isNative
|
|
62
|
+
? ZERO_ADDRESS
|
|
63
|
+
: fromToken.address,
|
|
64
|
+
|
|
65
|
+
destToken:
|
|
66
|
+
params.toChain === 'solana'
|
|
67
|
+
? (params.toToken as SolanaToken).id
|
|
68
|
+
: toToken.isNative
|
|
69
|
+
? ZERO_ADDRESS
|
|
70
|
+
: toToken.address,
|
|
71
|
+
amount: params.amount.toString(),
|
|
72
|
+
userAddress: params.sender,
|
|
73
|
+
targetRecipient: params.recipient,
|
|
74
|
+
slippage: params.slippage / 10_000,
|
|
75
|
+
feeConfig: {
|
|
76
|
+
feeRecipient: params.fromChain === 'solana' ? CROSS_CHAIN_FEE_RECEIVER_SOLANA : CROSS_CHAIN_FEE_RECEIVER,
|
|
77
|
+
feePercent: (params.feeBps / 10000).toString(),
|
|
78
|
+
},
|
|
79
|
+
channel: 'kyberswap',
|
|
80
|
+
}
|
|
81
|
+
const res = await fetch(`https://api.orbiter.finance/quote`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: {
|
|
84
|
+
'Content-Type': 'application/json',
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify(body),
|
|
87
|
+
})
|
|
88
|
+
.then(res => res.json())
|
|
89
|
+
.then(res => res.result)
|
|
90
|
+
|
|
91
|
+
const formattedOutputAmount = formatUnits(BigInt(res.details?.destTokenAmount || '0'), params.toToken.decimals)
|
|
92
|
+
const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals)
|
|
93
|
+
|
|
94
|
+
const inputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.fromChain)
|
|
95
|
+
? Number(res.details?.sourceAmountUSD || 0)
|
|
96
|
+
: params.tokenInUsd * +formattedInputAmount
|
|
97
|
+
const outputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.toChain)
|
|
98
|
+
? Number(res.details?.destAmountUSD || 0)
|
|
99
|
+
: params.tokenOutUsd * +formattedOutputAmount
|
|
100
|
+
|
|
101
|
+
const haveApproval = res.steps.some((step: Step) => step.action === 'approve')
|
|
102
|
+
const approvalContract = res.steps.find((step: Step) => step.action === 'swap' || step.action === 'bridge')
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
quoteParams: params,
|
|
106
|
+
outputAmount: BigInt(res.details?.destTokenAmount || '0'),
|
|
107
|
+
formattedOutputAmount,
|
|
108
|
+
inputUsd,
|
|
109
|
+
outputUsd,
|
|
110
|
+
priceImpact: !inputUsd || !outputUsd ? NaN : ((inputUsd - outputUsd) * 100) / inputUsd,
|
|
111
|
+
//rate: Number(resp.details?.rate || 0),
|
|
112
|
+
rate: +formattedOutputAmount / +formattedInputAmount,
|
|
113
|
+
gasFeeUsd: 0,
|
|
114
|
+
timeEstimate: 10,
|
|
115
|
+
contractAddress: haveApproval ? approvalContract?.tx.to || ZERO_ADDRESS : ZERO_ADDRESS,
|
|
116
|
+
rawQuote: res,
|
|
117
|
+
protocolFee: 0,
|
|
118
|
+
platformFeePercent: (params.feeBps * 100) / 10000,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async executeSwap(
|
|
123
|
+
{ quote }: Quote,
|
|
124
|
+
walletClient: WalletClient,
|
|
125
|
+
_nearWallet?: ReturnType<typeof useWalletSelector>,
|
|
126
|
+
_sendBtcFn?: (params: { recipient: string; amount: string | number }) => Promise<string>,
|
|
127
|
+
sendSolanaFn?: WalletAdapterProps['sendTransaction'],
|
|
128
|
+
solanaConnection?: Connection,
|
|
129
|
+
): Promise<NormalizedTxResponse> {
|
|
130
|
+
if (quote.quoteParams.fromChain === 'solana') {
|
|
131
|
+
if (!solanaConnection || !sendSolanaFn) throw new Error('Connection is not defined for Solana swap')
|
|
132
|
+
const encodedData = quote.rawQuote.steps?.[0]?.tx?.data
|
|
133
|
+
const txBuffer = Buffer.from(encodedData, 'base64')
|
|
134
|
+
|
|
135
|
+
// Try to deserialize as VersionedTransaction first
|
|
136
|
+
let transaction
|
|
137
|
+
try {
|
|
138
|
+
transaction = VersionedTransaction.deserialize(txBuffer)
|
|
139
|
+
console.log('Parsed as VersionedTransaction')
|
|
140
|
+
} catch (versionedError) {
|
|
141
|
+
console.log('Failed to parse as VersionedTransaction, trying legacy Transaction')
|
|
142
|
+
try {
|
|
143
|
+
transaction = Transaction.from(txBuffer)
|
|
144
|
+
console.log('Parsed as legacy Transaction')
|
|
145
|
+
} catch (legacyError) {
|
|
146
|
+
throw new Error('Could not parse transaction as either VersionedTransaction or legacy Transaction')
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log('Transaction parsed successfully:', transaction)
|
|
151
|
+
|
|
152
|
+
const waitForConfirmation = async (txId: string) => {
|
|
153
|
+
try {
|
|
154
|
+
const latestBlockhash = await solanaConnection.getLatestBlockhash()
|
|
155
|
+
|
|
156
|
+
// Wait for confirmation with timeout
|
|
157
|
+
const confirmation = await Promise.race([
|
|
158
|
+
solanaConnection.confirmTransaction(
|
|
159
|
+
{
|
|
160
|
+
signature: txId,
|
|
161
|
+
blockhash: latestBlockhash.blockhash,
|
|
162
|
+
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
163
|
+
},
|
|
164
|
+
'confirmed',
|
|
165
|
+
),
|
|
166
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Transaction confirmation timeout')), 60000)),
|
|
167
|
+
])
|
|
168
|
+
|
|
169
|
+
const confirmationResult = confirmation as { value: { err: any } }
|
|
170
|
+
if (confirmationResult.value.err) {
|
|
171
|
+
throw new Error(`Transaction failed: ${JSON.stringify(confirmationResult.value.err)}`)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log('Transaction confirmed successfully!')
|
|
175
|
+
} catch (confirmError) {
|
|
176
|
+
console.error('Transaction confirmation failed:', confirmError)
|
|
177
|
+
|
|
178
|
+
// Check if transaction actually succeeded despite timeout
|
|
179
|
+
const txStatus = await solanaConnection.getSignatureStatus(txId)
|
|
180
|
+
if (txStatus?.value?.confirmationStatus !== 'confirmed') {
|
|
181
|
+
throw new Error(`Transaction was not confirmed: ${confirmError.message}`)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Send through wallet adapter
|
|
187
|
+
const signature = await sendSolanaFn(transaction, solanaConnection)
|
|
188
|
+
await waitForConfirmation(signature)
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
sender: quote.quoteParams.sender,
|
|
192
|
+
id: signature,
|
|
193
|
+
sourceTxHash: signature,
|
|
194
|
+
adapter: this.getName(),
|
|
195
|
+
sourceChain: quote.quoteParams.fromChain,
|
|
196
|
+
targetChain: quote.quoteParams.toChain,
|
|
197
|
+
inputAmount: quote.quoteParams.amount,
|
|
198
|
+
outputAmount: quote.outputAmount.toString(),
|
|
199
|
+
sourceToken: quote.quoteParams.fromToken,
|
|
200
|
+
targetToken: quote.quoteParams.toToken,
|
|
201
|
+
timestamp: new Date().getTime(),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const steps = quote.rawQuote.steps.filter((st: Step) => st.action !== 'approve') // already approve before
|
|
206
|
+
|
|
207
|
+
const account = walletClient.account?.address
|
|
208
|
+
if (!account) throw new Error('WalletClient account is not defined')
|
|
209
|
+
const txs = await Promise.all(
|
|
210
|
+
steps.map(async (step: Step) => {
|
|
211
|
+
const tx = await walletClient.sendTransaction({
|
|
212
|
+
chain: undefined,
|
|
213
|
+
account,
|
|
214
|
+
to: step.tx.to as `0x${string}`,
|
|
215
|
+
value: BigInt(step.tx.value),
|
|
216
|
+
data: step.tx.data as `0x${string}`,
|
|
217
|
+
})
|
|
218
|
+
return tx
|
|
219
|
+
}),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if (!txs || txs.length === 0) throw new Error('No transactions found after executing swap steps')
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
sender: quote.quoteParams.sender,
|
|
226
|
+
sourceTxHash: txs[txs.length - 1],
|
|
227
|
+
adapter: this.getName(),
|
|
228
|
+
id: txs[txs.length - 1],
|
|
229
|
+
sourceChain: quote.quoteParams.fromChain,
|
|
230
|
+
targetChain: quote.quoteParams.toChain,
|
|
231
|
+
inputAmount: quote.quoteParams.amount,
|
|
232
|
+
outputAmount: quote.outputAmount.toString(),
|
|
233
|
+
sourceToken: quote.quoteParams.fromToken,
|
|
234
|
+
targetToken: quote.quoteParams.toToken,
|
|
235
|
+
timestamp: new Date().getTime(),
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async getTransactionStatus(p: NormalizedTxResponse): Promise<SwapStatus> {
|
|
240
|
+
if (p.sourceChain !== 'solana') {
|
|
241
|
+
const publicClient = getPublicClient(useConfig(), {
|
|
242
|
+
chainId: p.sourceChain as any,
|
|
243
|
+
})
|
|
244
|
+
const receipt = await publicClient?.getTransactionReceipt({
|
|
245
|
+
hash: p.id as `0x${string}`,
|
|
246
|
+
})
|
|
247
|
+
if (receipt.status === 'reverted') {
|
|
248
|
+
return {
|
|
249
|
+
txHash: '',
|
|
250
|
+
status: 'Failed',
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const res = await fetch(`https://api.orbiter.finance/transaction/${p.id}`).then(r => r.json())
|
|
256
|
+
return {
|
|
257
|
+
txHash: res.result.targetId || '',
|
|
258
|
+
// this is from orbiter source code, their docs dont have info for this
|
|
259
|
+
// https://github.com/Orbiter-Finance/OrbiterFE-V2/blob/2b35399aad581e666c45a829e0151485f4007c93/src/views/statistics/LatestTransactions.vue#L115
|
|
260
|
+
status:
|
|
261
|
+
res.result.opStatus === -1
|
|
262
|
+
? 'Failed'
|
|
263
|
+
: res.result.opStatus === 80
|
|
264
|
+
? 'Refunded'
|
|
265
|
+
: res.result.opStatus !== 98 && res.result.opStatus !== 99
|
|
266
|
+
? 'Processing'
|
|
267
|
+
: 'Success',
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|