@openocean.finance/widget 1.0.44 → 1.0.46
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/Header/WalletHeader.js +4 -4
- package/dist/esm/components/Header/WalletHeader.js.map +1 -1
- package/dist/esm/components/TokenList/VirtualizedTokenList.js +1 -0
- package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
- package/dist/esm/components/TransactionDetails.js +3 -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/LifiAdapter.d.ts +3 -0
- package/dist/esm/cross/adapters/LifiAdapter.js +270 -44
- package/dist/esm/cross/adapters/LifiAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/RelayAdapter.js +3 -9
- package/dist/esm/cross/adapters/RelayAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/index.d.ts +1 -0
- package/dist/esm/cross/adapters/index.js +1 -1
- package/dist/esm/cross/adapters/index.js.map +1 -1
- package/dist/esm/cross/crossChainQuote.js +61 -2
- package/dist/esm/cross/crossChainQuote.js.map +1 -1
- package/dist/esm/cross/factory.d.ts +3 -1
- package/dist/esm/cross/factory.js +10 -14
- package/dist/esm/cross/factory.js.map +1 -1
- package/dist/esm/hooks/useSwapOnly.js +8 -2
- package/dist/esm/hooks/useSwapOnly.js.map +1 -1
- package/dist/esm/i18n/en.json +5 -3
- package/dist/esm/i18n/ja.json +3 -1
- package/dist/esm/i18n/zh.json +3 -1
- package/dist/esm/services/ExecuteRoute.d.ts +1 -0
- package/dist/esm/services/ExecuteRoute.js +128 -53
- package/dist/esm/services/ExecuteRoute.js.map +1 -1
- package/dist/esm/types/widget.d.ts +1 -1
- package/dist/esm/types/widget.js.map +1 -1
- package/package.json +4 -4
- package/src/components/Header/WalletHeader.tsx +4 -4
- package/src/components/TokenList/VirtualizedTokenList.tsx +15 -15
- package/src/components/TransactionDetails.tsx +4 -0
- package/src/config/version.ts +1 -1
- package/src/cross/adapters/LifiAdapter.ts +303 -51
- package/src/cross/adapters/RelayAdapter.ts +4 -12
- package/src/cross/adapters/index.ts +1 -1
- package/src/cross/crossChainQuote.ts +60 -2
- package/src/cross/factory.ts +12 -11
- package/src/hooks/useSwapOnly.ts +9 -2
- package/src/i18n/en.json +5 -3
- package/src/i18n/ja.json +3 -1
- package/src/i18n/zh.json +3 -1
- package/src/services/ExecuteRoute.ts +166 -54
- package/src/types/widget.ts +2 -1
|
@@ -2,6 +2,7 @@ import { createConfig, getQuote, getStatus } from '@lifi/sdk'
|
|
|
2
2
|
import { WalletAdapterProps } from '@solana/wallet-adapter-base'
|
|
3
3
|
import { Connection, Transaction, VersionedTransaction } from '@solana/web3.js'
|
|
4
4
|
import { WalletClient, formatUnits } from 'viem'
|
|
5
|
+
import * as bitcoin from 'bitcoinjs-lib'
|
|
5
6
|
|
|
6
7
|
import { CROSS_CHAIN_FEE_RECEIVER, ZERO_ADDRESS, Currency, SolanaToken, MAINNET_NETWORKS } from '../constants/index.js'
|
|
7
8
|
|
|
@@ -17,11 +18,25 @@ import {
|
|
|
17
18
|
SwapStatus,
|
|
18
19
|
} from './BaseSwapAdapter.js'
|
|
19
20
|
|
|
21
|
+
type DynamicSignPsbtParams = {
|
|
22
|
+
allowedSighash: number[]
|
|
23
|
+
unsignedPsbtBase64: string
|
|
24
|
+
signature: Array<{
|
|
25
|
+
address: string
|
|
26
|
+
signingIndexes: number[]
|
|
27
|
+
}>
|
|
28
|
+
}
|
|
29
|
+
type TransactionRequest = {
|
|
30
|
+
to: string
|
|
31
|
+
value: string
|
|
32
|
+
data: string
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
export class LifiAdapter extends BaseSwapAdapter {
|
|
21
36
|
constructor() {
|
|
22
37
|
super()
|
|
23
38
|
createConfig({
|
|
24
|
-
integrator: '
|
|
39
|
+
integrator: 'openocean',
|
|
25
40
|
})
|
|
26
41
|
}
|
|
27
42
|
|
|
@@ -32,34 +47,57 @@ export class LifiAdapter extends BaseSwapAdapter {
|
|
|
32
47
|
return 'https://storage.googleapis.com/ks-setting-1d682dca/aed3a971-48be-4c3c-9597-5ab78073fbf11745552578218.png'
|
|
33
48
|
}
|
|
34
49
|
getSupportedChains(): Chain[] {
|
|
35
|
-
return [NonEvmChain.Solana, ...MAINNET_NETWORKS]
|
|
50
|
+
return [NonEvmChain.Solana, NonEvmChain.Bitcoin, ...MAINNET_NETWORKS]
|
|
36
51
|
}
|
|
37
52
|
|
|
38
53
|
getSupportedTokens(_sourceChain: Chain, _destChain: Chain): Currency[] {
|
|
39
54
|
return []
|
|
40
55
|
}
|
|
41
56
|
|
|
57
|
+
|
|
58
|
+
|
|
42
59
|
async getQuote(params: QuoteParams): Promise<NormalizedQuote> {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
|
|
61
|
+
let fromToken = (params.fromToken as any).isNative
|
|
62
|
+
? ZERO_ADDRESS
|
|
63
|
+
: (params.fromToken as any).address
|
|
64
|
+
if (params.fromChain === 20000000000001 || params.fromChain === 1151111081099710) {
|
|
65
|
+
fromToken = (params.fromToken as any).address
|
|
66
|
+
}
|
|
67
|
+
let fromChain = params.fromChain === 1151111081099710 ? 'SOL' : +params.fromChain
|
|
68
|
+
if (params.fromChain === 20000000000001) {
|
|
69
|
+
fromChain = 'BTC'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
let toToken = (params.toToken as any).isNative
|
|
74
|
+
? ZERO_ADDRESS
|
|
75
|
+
: (params.toToken as any).address
|
|
76
|
+
if (params.toChain === 20000000000001 || params.toChain === 1151111081099710) {
|
|
77
|
+
toToken = (params.toToken as any).address
|
|
78
|
+
}
|
|
79
|
+
let toChain = params.toChain === 1151111081099710 ? 'SOL' : +params.toChain
|
|
80
|
+
if (params.toChain === 20000000000001) {
|
|
81
|
+
toChain = 'BTC'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
let quoteParams = {
|
|
86
|
+
fromChain, // Arbitrum
|
|
87
|
+
fromToken,
|
|
51
88
|
fromAmount: params.amount,
|
|
52
89
|
fromAddress: params.sender === ZERO_ADDRESS ? CROSS_CHAIN_FEE_RECEIVER : params.sender,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
toToken:
|
|
56
|
-
params.toChain === 1151111081099710
|
|
57
|
-
? (params.toToken as SolanaToken).id
|
|
58
|
-
: (params.toToken as any).isNative
|
|
59
|
-
? ZERO_ADDRESS
|
|
60
|
-
: (params.toToken as any).wrapped.address,
|
|
90
|
+
toChain,
|
|
91
|
+
toToken,
|
|
61
92
|
toAddress: params.recipient,
|
|
62
93
|
fee: params.feeBps / 10_000,
|
|
94
|
+
}
|
|
95
|
+
const r = await getQuote(quoteParams).catch(error => {
|
|
96
|
+
if (error && error.cause && error.cause.responseBody && error.cause.responseBody.message) {
|
|
97
|
+
throw new Error(error.cause.responseBody.message)
|
|
98
|
+
} else {
|
|
99
|
+
throw error
|
|
100
|
+
}
|
|
63
101
|
})
|
|
64
102
|
|
|
65
103
|
//const inputUsd = Number(r.estimate.fromAmountUSD || '0')
|
|
@@ -74,6 +112,9 @@ export class LifiAdapter extends BaseSwapAdapter {
|
|
|
74
112
|
? Number(r.estimate.toAmountUSD)
|
|
75
113
|
: params.tokenOutUsd * +formattedOutputAmount
|
|
76
114
|
|
|
115
|
+
const protocolFee: any = r.estimate.feeCosts?.reduce((acc: number, curr: any) => acc + Number(curr.amountUSD), 0) || 0
|
|
116
|
+
const gasFeeUsd = r.estimate.gasCosts.reduce((acc: number, curr: any) => acc + Number(curr.amountUSD), 0)
|
|
117
|
+
|
|
77
118
|
return {
|
|
78
119
|
quoteParams: params,
|
|
79
120
|
outputAmount: BigInt(r.estimate.toAmount),
|
|
@@ -84,13 +125,13 @@ export class LifiAdapter extends BaseSwapAdapter {
|
|
|
84
125
|
priceImpact: !inputUsd || !outputUsd ? NaN : ((inputUsd - outputUsd) * 100) / inputUsd,
|
|
85
126
|
rate: +formattedOutputAmount / +formattedInputAmount,
|
|
86
127
|
|
|
87
|
-
gasFeeUsd
|
|
128
|
+
gasFeeUsd,
|
|
88
129
|
|
|
89
130
|
timeEstimate: r.estimate.executionDuration,
|
|
90
131
|
contractAddress: r.transactionRequest?.to || r.estimate.approvalAddress,
|
|
91
132
|
rawQuote: r,
|
|
92
133
|
|
|
93
|
-
protocolFee
|
|
134
|
+
protocolFee,
|
|
94
135
|
platformFeePercent: (params.feeBps * 100) / 10_000,
|
|
95
136
|
}
|
|
96
137
|
}
|
|
@@ -103,8 +144,177 @@ export class LifiAdapter extends BaseSwapAdapter {
|
|
|
103
144
|
sendTransaction?: WalletAdapterProps['sendTransaction'],
|
|
104
145
|
connection?: Connection,
|
|
105
146
|
): Promise<NormalizedTxResponse> {
|
|
147
|
+
if (quote.quoteParams.fromChain === NonEvmChain.Bitcoin) {
|
|
148
|
+
// Get user address
|
|
149
|
+
const account = (walletClient as any).account?.address || quote.quoteParams.sender
|
|
150
|
+
if (!account) throw new Error('WalletClient account is not defined')
|
|
151
|
+
|
|
152
|
+
const transactionRequest: TransactionRequest = quote.rawQuote.transactionRequest
|
|
153
|
+
|
|
154
|
+
if (!transactionRequest.data) {
|
|
155
|
+
throw new Error('TransactionRequest data is missing')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Parse PSBT (may be base64 or hex)
|
|
159
|
+
let psbt: bitcoin.Psbt
|
|
160
|
+
try {
|
|
161
|
+
// Try to parse as base64
|
|
162
|
+
psbt = bitcoin.Psbt.fromBase64(transactionRequest.data, { network: bitcoin.networks.bitcoin })
|
|
163
|
+
} catch {
|
|
164
|
+
try {
|
|
165
|
+
// Try to parse as hex
|
|
166
|
+
psbt = bitcoin.Psbt.fromHex(transactionRequest.data, { network: bitcoin.networks.bitcoin })
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw new Error(`Failed to parse PSBT: ${error}`)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Detect wallet type
|
|
173
|
+
const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
|
|
174
|
+
let connectorName: string | undefined
|
|
175
|
+
if (anyWindow?.okxwallet?.bitcoin) connectorName = 'OKX Wallet'
|
|
176
|
+
else if (anyWindow?.unisat) connectorName = 'Unisat'
|
|
177
|
+
else if (anyWindow?.BitcoinProvider) connectorName = 'Xverse'
|
|
178
|
+
else if (anyWindow?.phantom?.bitcoin) connectorName = 'Phantom'
|
|
179
|
+
else throw new Error('No Bitcoin wallet found')
|
|
180
|
+
|
|
181
|
+
// Extract inputs to sign from PSBT
|
|
182
|
+
const inputsToSign: any[] = []
|
|
183
|
+
for (let index = 0; index < psbt.data.inputs.length; index++) {
|
|
184
|
+
const input = psbt.data.inputs[index]
|
|
185
|
+
let inputAddress: string
|
|
186
|
+
|
|
187
|
+
if (input.witnessUtxo) {
|
|
188
|
+
// Extract address from witnessUtxo
|
|
189
|
+
inputAddress = bitcoin.address.fromOutputScript(input.witnessUtxo.script, bitcoin.networks.bitcoin)
|
|
190
|
+
} else if (input.nonWitnessUtxo) {
|
|
191
|
+
// Extract address from nonWitnessUtxo (needs output index, simplified here)
|
|
192
|
+
// Usually needs to be extracted from transaction, using account address as default here
|
|
193
|
+
inputAddress = account.toString()
|
|
194
|
+
} else {
|
|
195
|
+
inputAddress = account.toString()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Only add inputs that belong to the current account
|
|
199
|
+
if (inputAddress === account.toString()) {
|
|
200
|
+
inputsToSign.push({
|
|
201
|
+
index,
|
|
202
|
+
address: inputAddress,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (inputsToSign.length === 0) {
|
|
208
|
+
throw new Error('No inputs found to sign')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const psbtBase64 = psbt.toBase64()
|
|
212
|
+
const psbtHex = psbt.toHex()
|
|
213
|
+
let signedPsbtBase64: string
|
|
214
|
+
|
|
215
|
+
// Sign PSBT using wallet
|
|
216
|
+
switch (connectorName) {
|
|
217
|
+
case 'OKX Wallet': {
|
|
218
|
+
const signOptions = {
|
|
219
|
+
autoFinalized: false,
|
|
220
|
+
toSignInputs: inputsToSign.map((item) => ({
|
|
221
|
+
index: item.index,
|
|
222
|
+
address: item.address,
|
|
223
|
+
sighashTypes: [1], // SIGHASH_ALL
|
|
224
|
+
})),
|
|
225
|
+
}
|
|
226
|
+
const response = await anyWindow.okxwallet.bitcoin.signPsbt(psbtHex, signOptions)
|
|
227
|
+
signedPsbtBase64 = this.convertHexToBase64(this.extractSignedPsbt(response) || '')
|
|
228
|
+
break
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
case 'Unisat': {
|
|
232
|
+
const signOptions = {
|
|
233
|
+
autoFinalized: false,
|
|
234
|
+
toSignInputs: inputsToSign.map((item) => ({
|
|
235
|
+
index: item.index,
|
|
236
|
+
address: item.address,
|
|
237
|
+
sighashTypes: [1],
|
|
238
|
+
})),
|
|
239
|
+
}
|
|
240
|
+
const response = await anyWindow.unisat.signPsbt(psbtHex, signOptions)
|
|
241
|
+
signedPsbtBase64 = this.convertHexToBase64(this.extractSignedPsbt(response) || '')
|
|
242
|
+
break
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
case 'Xverse': {
|
|
246
|
+
const response = await anyWindow.BitcoinProvider.request('signPsbt', {
|
|
247
|
+
psbt: psbtHex,
|
|
248
|
+
finalize: false,
|
|
249
|
+
toSignInputs: inputsToSign.map((item) => ({
|
|
250
|
+
index: item.index,
|
|
251
|
+
address: item.address,
|
|
252
|
+
})),
|
|
253
|
+
})
|
|
254
|
+
signedPsbtBase64 = this.extractSignedPsbt(response) || ''
|
|
255
|
+
break
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case 'Phantom': {
|
|
259
|
+
const phantom = anyWindow.phantom.bitcoin
|
|
260
|
+
if (!phantom?.signPSBT) throw new Error('Phantom wallet does not support signPSBT')
|
|
261
|
+
|
|
262
|
+
const response = await phantom.signPSBT(psbtBase64, {
|
|
263
|
+
autoFinalize: false,
|
|
264
|
+
inputsToSign: inputsToSign.map((item) => ({
|
|
265
|
+
index: item.index,
|
|
266
|
+
address: item.address,
|
|
267
|
+
sighashTypes: [1],
|
|
268
|
+
})),
|
|
269
|
+
})
|
|
270
|
+
signedPsbtBase64 = this.extractSignedPsbt(response) || ''
|
|
271
|
+
break
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
default:
|
|
275
|
+
throw new Error(`Unsupported wallet: ${connectorName}`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!signedPsbtBase64) {
|
|
279
|
+
throw new Error('Failed to sign PSBT')
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Finalize PSBT and extract transaction
|
|
283
|
+
const signedPsbt = bitcoin.Psbt.fromBase64(signedPsbtBase64, { network: bitcoin.networks.bitcoin })
|
|
284
|
+
signedPsbt.finalizeAllInputs()
|
|
285
|
+
const rawTx = signedPsbt.extractTransaction().toHex()
|
|
286
|
+
|
|
287
|
+
// Broadcast transaction to mempool.space
|
|
288
|
+
const txHash = await fetch('https://mempool.space/api/tx', {
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers: {
|
|
291
|
+
'Content-Type': 'text/plain',
|
|
292
|
+
},
|
|
293
|
+
body: rawTx,
|
|
294
|
+
}).then((r) => r.text())
|
|
295
|
+
|
|
296
|
+
if (!txHash || txHash.startsWith('<')) {
|
|
297
|
+
throw new Error(`Failed to broadcast transaction: ${txHash}`)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
sender: quote.quoteParams.sender,
|
|
302
|
+
id: txHash,
|
|
303
|
+
sourceTxHash: txHash,
|
|
304
|
+
adapter: this.getName(),
|
|
305
|
+
sourceChain: quote.quoteParams.fromChain,
|
|
306
|
+
targetChain: quote.quoteParams.toChain,
|
|
307
|
+
inputAmount: quote.quoteParams.amount,
|
|
308
|
+
outputAmount: quote.outputAmount.toString(),
|
|
309
|
+
sourceToken: quote.quoteParams.fromToken,
|
|
310
|
+
targetToken: quote.quoteParams.toToken,
|
|
311
|
+
timestamp: new Date().getTime(),
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
|
|
106
316
|
if (quote.quoteParams.fromChain === NonEvmChain.Solana) {
|
|
107
|
-
if (!
|
|
317
|
+
if (!walletClient.sendTransaction) throw new Error('Connection is not defined for Solana swap')
|
|
108
318
|
const txBuffer = Buffer.from(quote.rawQuote.transactionRequest.data, 'base64')
|
|
109
319
|
|
|
110
320
|
// Try to deserialize as VersionedTransaction first
|
|
@@ -125,39 +335,39 @@ export class LifiAdapter extends BaseSwapAdapter {
|
|
|
125
335
|
console.log('Transaction parsed successfully:', transaction)
|
|
126
336
|
|
|
127
337
|
// Send through wallet adapter
|
|
128
|
-
const
|
|
338
|
+
const tx: any = await walletClient.sendTransaction(transaction)
|
|
339
|
+
const signature = tx.signature
|
|
340
|
+
// try {
|
|
341
|
+
// const latestBlockhash = await connection.getLatestBlockhash()
|
|
129
342
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
),
|
|
143
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Transaction confirmation timeout')), 60000)),
|
|
144
|
-
])
|
|
145
|
-
|
|
146
|
-
const confirmationResult = confirmation as { value: { err: any } }
|
|
147
|
-
if (confirmationResult.value.err) {
|
|
148
|
-
throw new Error(`Transaction failed: ${JSON.stringify(confirmationResult.value.err)}`)
|
|
149
|
-
}
|
|
343
|
+
// // Wait for confirmation with timeout
|
|
344
|
+
// const confirmation = await Promise.race([
|
|
345
|
+
// connection.confirmTransaction(
|
|
346
|
+
// {
|
|
347
|
+
// signature,
|
|
348
|
+
// blockhash: latestBlockhash.blockhash,
|
|
349
|
+
// lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
350
|
+
// },
|
|
351
|
+
// 'confirmed',
|
|
352
|
+
// ),
|
|
353
|
+
// new Promise((_, reject) => setTimeout(() => reject(new Error('Transaction confirmation timeout')), 60000)),
|
|
354
|
+
// ])
|
|
150
355
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
356
|
+
// const confirmationResult = confirmation as { value: { err: any } }
|
|
357
|
+
// if (confirmationResult.value.err) {
|
|
358
|
+
// throw new Error(`Transaction failed: ${JSON.stringify(confirmationResult.value.err)}`)
|
|
359
|
+
// }
|
|
154
360
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
361
|
+
// console.log('Transaction confirmed successfully!')
|
|
362
|
+
// } catch (confirmError) {
|
|
363
|
+
// console.error('Transaction confirmation failed:', confirmError)
|
|
364
|
+
|
|
365
|
+
// // Check if transaction actually succeeded despite timeout
|
|
366
|
+
// const txStatus = await connection.getSignatureStatus(signature)
|
|
367
|
+
// if (txStatus?.value?.confirmationStatus !== 'confirmed') {
|
|
368
|
+
// throw new Error(`Transaction was not confirmed: ${confirmError.message}`)
|
|
369
|
+
// }
|
|
370
|
+
// }
|
|
161
371
|
return {
|
|
162
372
|
sender: quote.quoteParams.sender,
|
|
163
373
|
id: signature,
|
|
@@ -211,4 +421,46 @@ export class LifiAdapter extends BaseSwapAdapter {
|
|
|
211
421
|
status: res.status === 'DONE' ? 'Success' : res.status === 'FAILED' ? 'Failed' : 'Processing',
|
|
212
422
|
}
|
|
213
423
|
}
|
|
424
|
+
|
|
425
|
+
// Helper function: Create PSBT signature options
|
|
426
|
+
private createPsbtOptions(psbt: bitcoin.Psbt, request: DynamicSignPsbtParams) {
|
|
427
|
+
const psbtSignOptions: any = {
|
|
428
|
+
autoFinalized: false,
|
|
429
|
+
}
|
|
430
|
+
if (request.signature) {
|
|
431
|
+
const toSignInputs: any[] = []
|
|
432
|
+
for (const signature of request.signature) {
|
|
433
|
+
if (signature.signingIndexes?.length) {
|
|
434
|
+
for (const index of signature.signingIndexes) {
|
|
435
|
+
toSignInputs.push({
|
|
436
|
+
address: signature.address,
|
|
437
|
+
disableAddressValidation: (signature as any).disableAddressValidation,
|
|
438
|
+
index,
|
|
439
|
+
sighashTypes: request.allowedSighash,
|
|
440
|
+
})
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
psbtSignOptions.toSignInputs = toSignInputs
|
|
445
|
+
}
|
|
446
|
+
return psbtSignOptions
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Helper function: Convert hex to base64
|
|
450
|
+
private convertHexToBase64(hexString: string): string {
|
|
451
|
+
try {
|
|
452
|
+
const buffer = Buffer.from(hexString, 'hex')
|
|
453
|
+
return buffer.toString('base64')
|
|
454
|
+
} catch (error) {
|
|
455
|
+
// If conversion fails, it may already be base64
|
|
456
|
+
return hexString
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Helper function: Extract signed PSBT from wallet response
|
|
461
|
+
private extractSignedPsbt(response: any): string | null {
|
|
462
|
+
if (!response) return null
|
|
463
|
+
if (typeof response === 'string') return response
|
|
464
|
+
return response.signedPsbtHex || response.signedPsbtBase64 || response.signedPsbt || null
|
|
465
|
+
}
|
|
214
466
|
}
|
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
sonic,
|
|
26
26
|
unichain,
|
|
27
27
|
zksync,
|
|
28
|
-
celo
|
|
28
|
+
celo,
|
|
29
|
+
monad
|
|
29
30
|
} from 'viem/chains'
|
|
30
31
|
import type { Currency } from '../constants/index.js'
|
|
31
32
|
|
|
@@ -104,7 +105,6 @@ const plasma = defineChain({
|
|
|
104
105
|
const SolanaChainId = 792703809
|
|
105
106
|
const BitcoinChainId = 8253038
|
|
106
107
|
const BitcoinAddress = 'bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8'
|
|
107
|
-
const MonadChainId = 143
|
|
108
108
|
|
|
109
109
|
const solanaChain = {
|
|
110
110
|
id: SolanaChainId,
|
|
@@ -119,15 +119,6 @@ const bitcoinChain = {
|
|
|
119
119
|
displayName: 'Bitcoin',
|
|
120
120
|
vmType: 'bvm' as const,
|
|
121
121
|
} as RelayChain
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const MonadChain = {
|
|
125
|
-
id: MonadChainId,
|
|
126
|
-
name: 'Monad',
|
|
127
|
-
displayName: 'Monad',
|
|
128
|
-
vmType: 'evm' as const,
|
|
129
|
-
} as RelayChain
|
|
130
|
-
|
|
131
122
|
export class RelayAdapter extends BaseSwapAdapter {
|
|
132
123
|
constructor() {
|
|
133
124
|
super()
|
|
@@ -155,9 +146,10 @@ export class RelayAdapter extends BaseSwapAdapter {
|
|
|
155
146
|
hyperEvm,
|
|
156
147
|
plasma,
|
|
157
148
|
celo,
|
|
149
|
+
monad,
|
|
158
150
|
]
|
|
159
151
|
.map(convertViemChainToRelayChain)
|
|
160
|
-
.concat(solanaChain as any, bitcoinChain as any
|
|
152
|
+
.concat(solanaChain as any, bitcoinChain as any),
|
|
161
153
|
})
|
|
162
154
|
}
|
|
163
155
|
|
|
@@ -5,6 +5,6 @@ export * from './RelayAdapter.js'
|
|
|
5
5
|
export * from './MayanAdapter.js'
|
|
6
6
|
// export * from './SymbiosisAdapter.js'
|
|
7
7
|
// export * from './DebridgeAdapter.js'
|
|
8
|
-
|
|
8
|
+
export * from './LifiAdapter.js'
|
|
9
9
|
// export * from './KyberSwapAdapter.js'
|
|
10
10
|
export * from './NearIntentsAdapter.js'
|
|
@@ -90,7 +90,7 @@ export async function getCrossChainQuote({
|
|
|
90
90
|
quoteResults.push({ adapter, quote })
|
|
91
91
|
}
|
|
92
92
|
} catch (error) {
|
|
93
|
-
errorMsg = error instanceof Error ? error.message : 'Unknown error'
|
|
93
|
+
errorMsg = error instanceof Error || error?.message ? error.message : 'Unknown error'
|
|
94
94
|
// Ignore individual adapter errors, continue getting quotes from other adapters
|
|
95
95
|
console.warn(`Failed to get quote from ${adapter.getName()}:`, error)
|
|
96
96
|
}
|
|
@@ -148,7 +148,65 @@ export async function getCrossChainQuote({
|
|
|
148
148
|
}
|
|
149
149
|
} catch (error) {
|
|
150
150
|
console.error('Error getting cross-chain quote:', error)
|
|
151
|
-
|
|
151
|
+
let errors = [
|
|
152
|
+
{
|
|
153
|
+
message: 'Please connect your wallet first.',
|
|
154
|
+
conditions: [
|
|
155
|
+
'refundTo should not be empty',
|
|
156
|
+
'User is required',
|
|
157
|
+
'fromAddress" is missing'
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
message: 'The input amount is too low.',
|
|
162
|
+
conditions: [
|
|
163
|
+
'Swap output amount is too small to cover fees required to execute swap',
|
|
164
|
+
'Amount is too low for bridge',
|
|
165
|
+
'Amount too small',
|
|
166
|
+
'Amount too low',
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
message: 'Insufficient balance in your wallet.',
|
|
171
|
+
conditions: [
|
|
172
|
+
'Insufficient funds',
|
|
173
|
+
'Failed to get quote',
|
|
174
|
+
'Insufficient balance',
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
message: 'Please enter valid recipient address.',
|
|
179
|
+
conditions: [
|
|
180
|
+
'recipient should not be empty',
|
|
181
|
+
'Recipient is required',
|
|
182
|
+
'Invalid toAddress:'
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
message: 'Not supported token',
|
|
187
|
+
conditions: [
|
|
188
|
+
'not supported to token',
|
|
189
|
+
'not supported from token',
|
|
190
|
+
'no routes found',
|
|
191
|
+
'SDK version too old for monad and solana fast mctp route'
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
message: 'There’s no routes available for the selected token pairs',
|
|
196
|
+
conditions: [
|
|
197
|
+
'No available quotes for the requested transfer',
|
|
198
|
+
],
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
if (error instanceof Error && error.message) {
|
|
202
|
+
const newError = errors.find(e => e.conditions.some(condition => error.message.indexOf(condition) !== -1))
|
|
203
|
+
if (newError) {
|
|
204
|
+
throw new Error(newError.message)
|
|
205
|
+
} else {
|
|
206
|
+
throw new Error(error.message)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throw new Error('Unknown error')
|
|
152
210
|
}
|
|
153
211
|
}
|
|
154
212
|
|
package/src/cross/factory.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
// AcrossAdapter,
|
|
5
5
|
// DeBridgeAdapter,
|
|
6
|
-
|
|
6
|
+
LifiAdapter,
|
|
7
7
|
MayanAdapter,
|
|
8
8
|
RelayAdapter,
|
|
9
9
|
NearIntentsAdapter,
|
|
@@ -22,7 +22,7 @@ export class CrossChainSwapFactory {
|
|
|
22
22
|
private static mayanInstance: MayanAdapter
|
|
23
23
|
// private static symbiosisInstance: SymbiosisAdapter
|
|
24
24
|
// private static debridgeInstance: DeBridgeAdapter
|
|
25
|
-
|
|
25
|
+
private static lifiInstance: LifiAdapter
|
|
26
26
|
// private static optimexInstance: OptimexAdapter
|
|
27
27
|
// private static orbiterInstance: OrbiterAdapter
|
|
28
28
|
|
|
@@ -77,12 +77,12 @@ export class CrossChainSwapFactory {
|
|
|
77
77
|
// return CrossChainSwapFactory.debridgeInstance
|
|
78
78
|
// }
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
static getLifiInstance(): LifiAdapter {
|
|
81
|
+
if (!CrossChainSwapFactory.lifiInstance) {
|
|
82
|
+
CrossChainSwapFactory.lifiInstance = new LifiAdapter()
|
|
83
|
+
}
|
|
84
|
+
return CrossChainSwapFactory.lifiInstance
|
|
85
|
+
}
|
|
86
86
|
|
|
87
87
|
// static getOptimexAdapter(): OptimexAdapter {
|
|
88
88
|
// if (!CrossChainSwapFactory.optimexInstance) {
|
|
@@ -104,12 +104,13 @@ export class CrossChainSwapFactory {
|
|
|
104
104
|
CrossChainSwapFactory.getMayanAdapter(),
|
|
105
105
|
CrossChainSwapFactory.getRelayAdapter(),
|
|
106
106
|
CrossChainSwapFactory.getNearIntentsAdapter(),
|
|
107
|
+
CrossChainSwapFactory.getLifiInstance(),
|
|
108
|
+
|
|
107
109
|
|
|
108
110
|
// CrossChainSwapFactory.getAcrossAdapter(),
|
|
109
111
|
// CrossChainSwapFactory.getXyFinanceAdapter(),
|
|
110
112
|
// CrossChainSwapFactory.getSymbiosisAdapter(),
|
|
111
113
|
// CrossChainSwapFactory.getDebridgeInstance(),
|
|
112
|
-
// CrossChainSwapFactory.getLifiInstance(),
|
|
113
114
|
// CrossChainSwapFactory.getOptimexAdapter(),
|
|
114
115
|
// CrossChainSwapFactory.getOrbiterAdapter(),
|
|
115
116
|
]
|
|
@@ -132,8 +133,8 @@ export class CrossChainSwapFactory {
|
|
|
132
133
|
// return CrossChainSwapFactory.getSymbiosisAdapter()
|
|
133
134
|
// case 'debridge':
|
|
134
135
|
// return CrossChainSwapFactory.getDebridgeInstance()
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
case 'lifi':
|
|
137
|
+
return CrossChainSwapFactory.getLifiInstance()
|
|
137
138
|
// case 'optimex':
|
|
138
139
|
// return CrossChainSwapFactory.getOptimexAdapter()
|
|
139
140
|
// case 'orbiter':
|
package/src/hooks/useSwapOnly.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { useSplitSubvariantStore } from '../stores/settings/useSplitSubvariantStore.js'
|
|
2
|
+
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
3
|
+
|
|
2
4
|
|
|
3
5
|
export const useSwapOnly = () => {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
+
const { subvariant } = useWidgetConfig()
|
|
7
|
+
const [state, setState] = useSplitSubvariantStore((storeState) => [storeState.state, storeState.setState])
|
|
8
|
+
if (subvariant === 'swap' && state !== 'swap') {
|
|
9
|
+
setState('swap')
|
|
10
|
+
return true
|
|
11
|
+
}
|
|
12
|
+
return state === 'swap' || subvariant === 'swap'
|
|
6
13
|
}
|
package/src/i18n/en.json
CHANGED
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"emptyTokenList": "We couldn't find tokens on {{chainName}} chain or you don't have any. Please search by contract address if your token doesn't appear or choose another chain.",
|
|
91
91
|
"emptyTransactionHistory": "Transaction history is only stored locally and will be deleted if you clear your browser data.",
|
|
92
92
|
"routeNotFound": "Reasons for that could be: low liquidity, amount selected is too low, gas costs are too high or there are no routes for the selected combination.",
|
|
93
|
-
"toAddressIsRequired": "
|
|
93
|
+
"toAddressIsRequired": "Please enter valid recipient address"
|
|
94
94
|
},
|
|
95
95
|
"title": {
|
|
96
96
|
"autoRefuel": "Get {{chainName}} gas",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"deleteTransactionHistory": "Transaction history is only stored locally and can't be recovered if you delete it.",
|
|
124
124
|
"fundsLossPrevention": "Always ensure smart contract accounts are properly set up on the destination chain and avoid direct transfers to exchanges to prevent fund loss.",
|
|
125
125
|
"highValueLoss": "The value of the received tokens is significantly lower than the exchanged tokens and transaction cost.",
|
|
126
|
-
"insufficientFunds": "Insufficient balance.",
|
|
126
|
+
"insufficientFunds": "Insufficient balance in your wallet.",
|
|
127
127
|
"insufficientGas": "You don't have enough gas to complete the transaction. You need to add at least:",
|
|
128
128
|
"rateChanged": "The exchange rate has changed. By continuing the transaction, you'll accept the new rate.",
|
|
129
129
|
"resetSettings": "This will reset your route priority, slippage, gas price, enabled bridges and exchanges.",
|
|
@@ -320,7 +320,9 @@
|
|
|
320
320
|
"routePriority": "Route priority",
|
|
321
321
|
"slippage": "Slippage Tolerance",
|
|
322
322
|
"custom": "Custom",
|
|
323
|
-
"resetSettings": "You're using custom settings that can affect the number or sorting of available routes."
|
|
323
|
+
"resetSettings": "You're using custom settings that can affect the number or sorting of available routes.",
|
|
324
|
+
"dynamicMode": "Dynamic Mode",
|
|
325
|
+
"dynamicModeTooltip": "Automatically minimizes slippage to enhance the MEV protection through running simulations."
|
|
324
326
|
},
|
|
325
327
|
"sendToWallet": {
|
|
326
328
|
"addBookmark": "Add bookmark",
|
package/src/i18n/ja.json
CHANGED
|
@@ -302,7 +302,9 @@
|
|
|
302
302
|
"routePriority": "ルート優先度",
|
|
303
303
|
"slippage": "",
|
|
304
304
|
"custom": "カスタム設定",
|
|
305
|
-
"resetSettings": "利用可能なルートの数とソートに影響する可能性があるカスタム設定を使用しています。"
|
|
305
|
+
"resetSettings": "利用可能なルートの数とソートに影響する可能性があるカスタム設定を使用しています。",
|
|
306
|
+
"dynamicMode": "動的モード",
|
|
307
|
+
"dynamicModeTooltip": "シミュレーションを実行することで、MEV 保護を強化するためのスリッページを自動的に最小化します。"
|
|
306
308
|
},
|
|
307
309
|
"sendToWallet": {
|
|
308
310
|
"addBookmark": "ブックマークに追加",
|