@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
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import type { useWalletSelector } from '@near-wallet-selector/react-hook'
|
|
2
|
+
import { ChainId } from '@openocean.finance/widget-sdk'
|
|
3
|
+
import type { WalletAdapterProps } from '@solana/wallet-adapter-base'
|
|
4
|
+
import {
|
|
5
|
+
type Connection,
|
|
6
|
+
Transaction,
|
|
7
|
+
VersionedTransaction,
|
|
8
|
+
} from '@solana/web3.js'
|
|
9
|
+
import { type WalletClient, formatUnits } from 'viem'
|
|
10
|
+
import type { Currency } from '../constants/index.js'
|
|
11
|
+
|
|
12
|
+
import { TOKEN_API_URL } from '../constants/index.js'
|
|
13
|
+
import {
|
|
14
|
+
CROSS_CHAIN_FEE_RECEIVER,
|
|
15
|
+
CROSS_CHAIN_FEE_RECEIVER_SOLANA,
|
|
16
|
+
ZERO_ADDRESS,
|
|
17
|
+
} from '../constants/index.js'
|
|
18
|
+
import { NativeCurrencies } from '../constants/index.js'
|
|
19
|
+
import type { SolanaToken } from '../constants/index.js'
|
|
20
|
+
|
|
21
|
+
import type { Quote } from '../registry.js'
|
|
22
|
+
import {
|
|
23
|
+
BaseSwapAdapter,
|
|
24
|
+
type Chain,
|
|
25
|
+
NOT_SUPPORTED_CHAINS_PRICE_SERVICE,
|
|
26
|
+
NonEvmChain,
|
|
27
|
+
type NormalizedQuote,
|
|
28
|
+
type NormalizedTxResponse,
|
|
29
|
+
type QuoteParams,
|
|
30
|
+
type SwapStatus,
|
|
31
|
+
} from './BaseSwapAdapter.js'
|
|
32
|
+
|
|
33
|
+
const DEBRIDGE_API = 'https://dln.debridge.finance/v1.0/dln/order'
|
|
34
|
+
|
|
35
|
+
const mappingChainId: Record<string, number> = {
|
|
36
|
+
[ChainId.DAI]: 100000002,
|
|
37
|
+
[ChainId.MAM]: 100000004, // Metis
|
|
38
|
+
[ChainId.SON]: 100000014, // Sonic
|
|
39
|
+
[ChainId.ABS]: 100000017, // Abstract
|
|
40
|
+
[ChainId.BER]: 100000020, // Berachain
|
|
41
|
+
[ChainId.BOB]: 100000021, // BOB
|
|
42
|
+
[ChainId.MNT]: 100000023, // Mantle
|
|
43
|
+
[NonEvmChain.Solana]: 7565164,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class DeBridgeAdapter extends BaseSwapAdapter {
|
|
47
|
+
constructor() {
|
|
48
|
+
super()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getName(): string {
|
|
52
|
+
return 'deBridge'
|
|
53
|
+
}
|
|
54
|
+
getIcon(): string {
|
|
55
|
+
return 'https://app.debridge.finance/assets/images/meta-deswap/favicon-32x32.png'
|
|
56
|
+
}
|
|
57
|
+
getSupportedChains(): Chain[] {
|
|
58
|
+
return [
|
|
59
|
+
ChainId.ETH,
|
|
60
|
+
ChainId.BSC,
|
|
61
|
+
ChainId.POL,
|
|
62
|
+
ChainId.AVA,
|
|
63
|
+
ChainId.ARB,
|
|
64
|
+
ChainId.OPT,
|
|
65
|
+
ChainId.ONE,
|
|
66
|
+
ChainId.FSN,
|
|
67
|
+
ChainId.MOR,
|
|
68
|
+
ChainId.CEL,
|
|
69
|
+
ChainId.FUS,
|
|
70
|
+
ChainId.TLO,
|
|
71
|
+
ChainId.CRO,
|
|
72
|
+
ChainId.BOB,
|
|
73
|
+
ChainId.RSK,
|
|
74
|
+
ChainId.VEL,
|
|
75
|
+
ChainId.MOO,
|
|
76
|
+
ChainId.MAM,
|
|
77
|
+
ChainId.AUR,
|
|
78
|
+
ChainId.EVM,
|
|
79
|
+
ChainId.ARN,
|
|
80
|
+
ChainId.ERA,
|
|
81
|
+
ChainId.PZE,
|
|
82
|
+
ChainId.LNA,
|
|
83
|
+
ChainId.BAS,
|
|
84
|
+
ChainId.SCL,
|
|
85
|
+
ChainId.MOD,
|
|
86
|
+
ChainId.MNT,
|
|
87
|
+
ChainId.BLS,
|
|
88
|
+
ChainId.SEI,
|
|
89
|
+
ChainId.FRA,
|
|
90
|
+
ChainId.TAI,
|
|
91
|
+
ChainId.GRA,
|
|
92
|
+
ChainId.IMX,
|
|
93
|
+
ChainId.KAI,
|
|
94
|
+
ChainId.XLY,
|
|
95
|
+
// NonEvmChain.Solana,
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getSupportedTokens(_sourceChain: Chain, _destChain: Chain): Currency[] {
|
|
100
|
+
return []
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getQuote(params: QuoteParams): Promise<NormalizedQuote> {
|
|
104
|
+
const fromToken = params.fromToken as any
|
|
105
|
+
const toToken = params.toToken as any
|
|
106
|
+
|
|
107
|
+
let p: Record<string, string | boolean | number> = {
|
|
108
|
+
srcChainId: mappingChainId[params.fromChain] || params.fromChain,
|
|
109
|
+
srcChainTokenIn:
|
|
110
|
+
params.fromChain === 'solana'
|
|
111
|
+
? (params.fromToken as SolanaToken).id
|
|
112
|
+
: fromToken.isNative
|
|
113
|
+
? ZERO_ADDRESS
|
|
114
|
+
: fromToken.address,
|
|
115
|
+
|
|
116
|
+
srcChainTokenInAmount: params.amount,
|
|
117
|
+
|
|
118
|
+
dstChainId: mappingChainId[params.toChain] || params.toChain,
|
|
119
|
+
dstChainTokenOut:
|
|
120
|
+
params.toChain === 'solana'
|
|
121
|
+
? (params.toToken as SolanaToken).id
|
|
122
|
+
: toToken.isNative
|
|
123
|
+
? ZERO_ADDRESS
|
|
124
|
+
: toToken.address,
|
|
125
|
+
|
|
126
|
+
dstChainTokenOutAmount: 'auto',
|
|
127
|
+
|
|
128
|
+
enableEstimate: false,
|
|
129
|
+
prependOperatingExpenses: false,
|
|
130
|
+
|
|
131
|
+
referralCode: 31982,
|
|
132
|
+
affiliateFeePercent: (params.feeBps * 100) / 10_000,
|
|
133
|
+
affiliateFeeRecipient:
|
|
134
|
+
params.fromChain === 'solana'
|
|
135
|
+
? CROSS_CHAIN_FEE_RECEIVER_SOLANA
|
|
136
|
+
: CROSS_CHAIN_FEE_RECEIVER,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let path = 'quote'
|
|
140
|
+
if (params.recipient && params.sender && params.sender !== ZERO_ADDRESS) {
|
|
141
|
+
path = 'create-tx'
|
|
142
|
+
p = {
|
|
143
|
+
...p,
|
|
144
|
+
srcChainOrderAuthorityAddress: params.sender,
|
|
145
|
+
dstChainOrderAuthorityAddress: params.recipient,
|
|
146
|
+
dstChainTokenOutRecipient: params.recipient,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Convert the parameters object to URL query string
|
|
151
|
+
const queryParams = new URLSearchParams()
|
|
152
|
+
for (const [key, value] of Object.entries(p)) {
|
|
153
|
+
queryParams.append(key, String(value))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const r = await fetch(
|
|
157
|
+
`${DEBRIDGE_API}/${path}?${queryParams.toString()}`
|
|
158
|
+
).then((res) => res.json())
|
|
159
|
+
if (!r.estimation) {
|
|
160
|
+
throw new Error(r.errorMessage)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
//const inputUsd = r.estimation.srcChainTokenIn.approximateUsdValue
|
|
164
|
+
//const outputUsd = r.estimation.dstChainTokenOut.recommendedApproximateUsdValue
|
|
165
|
+
|
|
166
|
+
const formattedInputAmount = formatUnits(
|
|
167
|
+
BigInt(params.amount),
|
|
168
|
+
params.fromToken.decimals
|
|
169
|
+
)
|
|
170
|
+
const formattedOutputAmount = formatUnits(
|
|
171
|
+
BigInt(r.estimation.dstChainTokenOut.recommendedAmount),
|
|
172
|
+
params.toToken.decimals
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
const inputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(
|
|
176
|
+
params.fromChain
|
|
177
|
+
)
|
|
178
|
+
? r.estimation.srcChainTokenIn.approximateUsdValue
|
|
179
|
+
: params.tokenInUsd * +formattedInputAmount
|
|
180
|
+
const outputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(
|
|
181
|
+
params.toChain
|
|
182
|
+
)
|
|
183
|
+
? r.estimation.dstChainTokenOut.recommendedApproximateUsdValue
|
|
184
|
+
: params.tokenOutUsd * +formattedOutputAmount
|
|
185
|
+
|
|
186
|
+
const fixFee = r.fixFee
|
|
187
|
+
|
|
188
|
+
const wrappedAddress =
|
|
189
|
+
NativeCurrencies[params.fromChain as ChainId].wrapped.address
|
|
190
|
+
const nativePrice = await fetch(
|
|
191
|
+
`${TOKEN_API_URL}/v1/public/tokens/prices`,
|
|
192
|
+
{
|
|
193
|
+
method: 'POST',
|
|
194
|
+
body: JSON.stringify({
|
|
195
|
+
[params.fromChain]: [wrappedAddress],
|
|
196
|
+
}),
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
.then((res) => res.json())
|
|
200
|
+
.then((res) => {
|
|
201
|
+
return res?.data?.[params.fromChain]?.[wrappedAddress]?.PriceBuy || 0
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const nativeDecimals =
|
|
205
|
+
params.fromChain === 'solana'
|
|
206
|
+
? 9
|
|
207
|
+
: NativeCurrencies[params.fromChain as ChainId].decimals
|
|
208
|
+
const protocolFee =
|
|
209
|
+
Number(nativePrice) * (Number(fixFee) / 10 ** nativeDecimals)
|
|
210
|
+
const protocolFeeString = `${Number(fixFee) / 10 ** nativeDecimals} ${
|
|
211
|
+
params.fromChain === 'solana'
|
|
212
|
+
? 'SOL'
|
|
213
|
+
: NativeCurrencies[params.fromChain as ChainId].symbol
|
|
214
|
+
}`
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
quoteParams: params,
|
|
218
|
+
outputAmount: BigInt(r.estimation.dstChainTokenOut.recommendedAmount),
|
|
219
|
+
|
|
220
|
+
formattedOutputAmount,
|
|
221
|
+
|
|
222
|
+
inputUsd,
|
|
223
|
+
outputUsd,
|
|
224
|
+
|
|
225
|
+
priceImpact:
|
|
226
|
+
!inputUsd || !outputUsd
|
|
227
|
+
? Number.NaN
|
|
228
|
+
: ((inputUsd - outputUsd) * 100) / inputUsd,
|
|
229
|
+
rate: +formattedOutputAmount / +formattedInputAmount,
|
|
230
|
+
|
|
231
|
+
gasFeeUsd: 0,
|
|
232
|
+
timeEstimate: r.order.approximateFulfillmentDelay,
|
|
233
|
+
contractAddress: r.tx.allowanceTarget || r.tx.to,
|
|
234
|
+
rawQuote: r,
|
|
235
|
+
|
|
236
|
+
protocolFee,
|
|
237
|
+
protocolFeeString,
|
|
238
|
+
platformFeePercent: (params.feeBps * 100) / 10_000,
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async executeSwap(
|
|
243
|
+
{ quote }: Quote,
|
|
244
|
+
walletClient: WalletClient,
|
|
245
|
+
_nearWallet?: ReturnType<typeof useWalletSelector>,
|
|
246
|
+
_sendBtcFn?: (params: {
|
|
247
|
+
recipient: string
|
|
248
|
+
amount: string | number
|
|
249
|
+
}) => Promise<string>,
|
|
250
|
+
sendSolanaFn?: WalletAdapterProps['sendTransaction'],
|
|
251
|
+
solanaConnection?: Connection
|
|
252
|
+
): Promise<NormalizedTxResponse> {
|
|
253
|
+
if (quote.quoteParams.fromChain === 'solana') {
|
|
254
|
+
if (!solanaConnection || !sendSolanaFn)
|
|
255
|
+
throw new Error('Connection is not defined for Solana swap')
|
|
256
|
+
const txBuffer = Buffer.from(quote.rawQuote.tx.data.slice(2), 'hex')
|
|
257
|
+
|
|
258
|
+
// Try to deserialize as VersionedTransaction first
|
|
259
|
+
let transaction
|
|
260
|
+
try {
|
|
261
|
+
transaction = VersionedTransaction.deserialize(txBuffer)
|
|
262
|
+
console.log('Parsed as VersionedTransaction')
|
|
263
|
+
} catch (versionedError) {
|
|
264
|
+
console.log(
|
|
265
|
+
'Failed to parse as VersionedTransaction, trying legacy Transaction'
|
|
266
|
+
)
|
|
267
|
+
try {
|
|
268
|
+
transaction = Transaction.from(txBuffer)
|
|
269
|
+
console.log('Parsed as legacy Transaction')
|
|
270
|
+
} catch (legacyError) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
'Could not parse transaction as either VersionedTransaction or legacy Transaction'
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log('Transaction parsed successfully:', transaction)
|
|
278
|
+
|
|
279
|
+
// Send through wallet adapter
|
|
280
|
+
const signature = await sendSolanaFn(transaction, solanaConnection)
|
|
281
|
+
const waitForConfirmation = async (txId: string) => {
|
|
282
|
+
try {
|
|
283
|
+
const latestBlockhash = await solanaConnection.getLatestBlockhash()
|
|
284
|
+
|
|
285
|
+
// Wait for confirmation with timeout
|
|
286
|
+
const confirmation = await Promise.race([
|
|
287
|
+
solanaConnection.confirmTransaction(
|
|
288
|
+
{
|
|
289
|
+
signature: txId,
|
|
290
|
+
blockhash: latestBlockhash.blockhash,
|
|
291
|
+
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
292
|
+
},
|
|
293
|
+
'confirmed'
|
|
294
|
+
),
|
|
295
|
+
new Promise((_, reject) =>
|
|
296
|
+
setTimeout(
|
|
297
|
+
() => reject(new Error('Transaction confirmation timeout')),
|
|
298
|
+
60000
|
|
299
|
+
)
|
|
300
|
+
),
|
|
301
|
+
])
|
|
302
|
+
|
|
303
|
+
const confirmationResult = confirmation as { value: { err: any } }
|
|
304
|
+
if (confirmationResult.value.err) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Transaction failed: ${JSON.stringify(confirmationResult.value.err)}`
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log('Transaction confirmed successfully!')
|
|
311
|
+
} catch (confirmError) {
|
|
312
|
+
console.error('Transaction confirmation failed:', confirmError)
|
|
313
|
+
|
|
314
|
+
// Check if transaction actually succeeded despite timeout
|
|
315
|
+
const txStatus = await solanaConnection.getSignatureStatus(txId)
|
|
316
|
+
if (txStatus?.value?.confirmationStatus !== 'confirmed') {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Transaction was not confirmed: ${confirmError.message}`
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await waitForConfirmation(signature)
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
sender: quote.quoteParams.sender,
|
|
328
|
+
id: quote.rawQuote.orderId, // specific id for debridge
|
|
329
|
+
sourceTxHash: signature,
|
|
330
|
+
adapter: this.getName(),
|
|
331
|
+
sourceChain: quote.quoteParams.fromChain,
|
|
332
|
+
targetChain: quote.quoteParams.toChain,
|
|
333
|
+
inputAmount: quote.quoteParams.amount,
|
|
334
|
+
outputAmount: quote.outputAmount.toString(),
|
|
335
|
+
sourceToken: quote.quoteParams.fromToken,
|
|
336
|
+
targetToken: quote.quoteParams.toToken,
|
|
337
|
+
timestamp: new Date().getTime(),
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const account = walletClient.account?.address
|
|
342
|
+
if (!account) throw new Error('WalletClient account is not defined')
|
|
343
|
+
|
|
344
|
+
const tx = await walletClient.sendTransaction({
|
|
345
|
+
chain: undefined,
|
|
346
|
+
account,
|
|
347
|
+
to: quote.rawQuote.tx.to,
|
|
348
|
+
value: BigInt(quote.rawQuote.tx.value),
|
|
349
|
+
data: quote.rawQuote.tx.data,
|
|
350
|
+
})
|
|
351
|
+
return {
|
|
352
|
+
sender: quote.quoteParams.sender,
|
|
353
|
+
id: quote.rawQuote.orderId, // specific id for each provider
|
|
354
|
+
sourceTxHash: tx,
|
|
355
|
+
adapter: this.getName(),
|
|
356
|
+
sourceChain: quote.quoteParams.fromChain,
|
|
357
|
+
targetChain: quote.quoteParams.toChain,
|
|
358
|
+
inputAmount: quote.quoteParams.amount,
|
|
359
|
+
outputAmount: quote.outputAmount.toString(),
|
|
360
|
+
sourceToken: quote.quoteParams.fromToken,
|
|
361
|
+
targetToken: quote.quoteParams.toToken,
|
|
362
|
+
timestamp: new Date().getTime(),
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async getTransactionStatus(p: NormalizedTxResponse): Promise<SwapStatus> {
|
|
367
|
+
const r = await fetch(`${DEBRIDGE_API}/${p.id}/status`).then((res) =>
|
|
368
|
+
res.json()
|
|
369
|
+
)
|
|
370
|
+
return {
|
|
371
|
+
status: r.status === 'Fulfilled' ? 'Success' : 'Processing',
|
|
372
|
+
txHash: p.id,
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { createConfig, getQuote, getStatus } from '@lifi/sdk'
|
|
2
|
+
import { WalletAdapterProps } from '@solana/wallet-adapter-base'
|
|
3
|
+
import { Connection, Transaction, VersionedTransaction } from '@solana/web3.js'
|
|
4
|
+
import { WalletClient, formatUnits } from 'viem'
|
|
5
|
+
|
|
6
|
+
import { CROSS_CHAIN_FEE_RECEIVER, ZERO_ADDRESS, Currency, SolanaToken, MAINNET_NETWORKS } from '../constants/index.js'
|
|
7
|
+
|
|
8
|
+
import { Quote } from '../registry.js'
|
|
9
|
+
import {
|
|
10
|
+
BaseSwapAdapter,
|
|
11
|
+
Chain,
|
|
12
|
+
NOT_SUPPORTED_CHAINS_PRICE_SERVICE,
|
|
13
|
+
NonEvmChain,
|
|
14
|
+
NormalizedQuote,
|
|
15
|
+
NormalizedTxResponse,
|
|
16
|
+
QuoteParams,
|
|
17
|
+
SwapStatus,
|
|
18
|
+
} from './BaseSwapAdapter.js'
|
|
19
|
+
|
|
20
|
+
export class LifiAdapter extends BaseSwapAdapter {
|
|
21
|
+
constructor() {
|
|
22
|
+
super()
|
|
23
|
+
createConfig({
|
|
24
|
+
integrator: 'kyberswap',
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getName(): string {
|
|
29
|
+
return 'LIFI'
|
|
30
|
+
}
|
|
31
|
+
getIcon(): string {
|
|
32
|
+
return 'https://storage.googleapis.com/ks-setting-1d682dca/aed3a971-48be-4c3c-9597-5ab78073fbf11745552578218.png'
|
|
33
|
+
}
|
|
34
|
+
getSupportedChains(): Chain[] {
|
|
35
|
+
return [NonEvmChain.Solana, ...MAINNET_NETWORKS]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getSupportedTokens(_sourceChain: Chain, _destChain: Chain): Currency[] {
|
|
39
|
+
return []
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getQuote(params: QuoteParams): Promise<NormalizedQuote> {
|
|
43
|
+
const r = await getQuote({
|
|
44
|
+
fromChain: params.fromChain === 'solana' ? 'SOL' : +params.fromChain, // Arbitrum
|
|
45
|
+
fromToken:
|
|
46
|
+
params.fromChain === 'solana'
|
|
47
|
+
? (params.fromToken as SolanaToken).id
|
|
48
|
+
: (params.fromToken as any).isNative
|
|
49
|
+
? ZERO_ADDRESS
|
|
50
|
+
: (params.fromToken as any).wrapped.address,
|
|
51
|
+
fromAmount: params.amount,
|
|
52
|
+
fromAddress: params.sender === ZERO_ADDRESS ? CROSS_CHAIN_FEE_RECEIVER : params.sender,
|
|
53
|
+
|
|
54
|
+
toChain: params.toChain === 'solana' ? 'SOL' : +params.toChain,
|
|
55
|
+
toToken:
|
|
56
|
+
params.toChain === 'solana'
|
|
57
|
+
? (params.toToken as SolanaToken).id
|
|
58
|
+
: (params.toToken as any).isNative
|
|
59
|
+
? ZERO_ADDRESS
|
|
60
|
+
: (params.toToken as any).wrapped.address,
|
|
61
|
+
toAddress: params.recipient,
|
|
62
|
+
fee: params.feeBps / 10_000,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
//const inputUsd = Number(r.estimate.fromAmountUSD || '0')
|
|
66
|
+
//const outputUsd = Number(r.estimate.toAmountUSD || '0')
|
|
67
|
+
const formattedOutputAmount = formatUnits(BigInt(r.estimate.toAmount), params.toToken.decimals)
|
|
68
|
+
const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals)
|
|
69
|
+
|
|
70
|
+
const inputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.fromChain)
|
|
71
|
+
? Number(r.estimate.fromAmountUSD)
|
|
72
|
+
: params.tokenInUsd * +formattedInputAmount
|
|
73
|
+
const outputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.toChain)
|
|
74
|
+
? Number(r.estimate.toAmountUSD)
|
|
75
|
+
: params.tokenOutUsd * +formattedOutputAmount
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
quoteParams: params,
|
|
79
|
+
outputAmount: BigInt(r.estimate.toAmount),
|
|
80
|
+
formattedOutputAmount: formatUnits(BigInt(r.estimate.toAmount), params.toToken.decimals),
|
|
81
|
+
inputUsd,
|
|
82
|
+
outputUsd,
|
|
83
|
+
|
|
84
|
+
priceImpact: !inputUsd || !outputUsd ? NaN : ((inputUsd - outputUsd) * 100) / inputUsd,
|
|
85
|
+
rate: +formattedOutputAmount / +formattedInputAmount,
|
|
86
|
+
|
|
87
|
+
gasFeeUsd: 0,
|
|
88
|
+
|
|
89
|
+
timeEstimate: r.estimate.executionDuration,
|
|
90
|
+
contractAddress: r.transactionRequest?.to || r.estimate.approvalAddress,
|
|
91
|
+
rawQuote: r,
|
|
92
|
+
|
|
93
|
+
protocolFee: 0,
|
|
94
|
+
platformFeePercent: (params.feeBps * 100) / 10_000,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async executeSwap(
|
|
99
|
+
{ quote }: Quote,
|
|
100
|
+
walletClient: WalletClient,
|
|
101
|
+
_nearWalletClient?: any,
|
|
102
|
+
_sendBtcFn?: (params: { recipient: string; amount: string | number }) => Promise<string>,
|
|
103
|
+
sendTransaction?: WalletAdapterProps['sendTransaction'],
|
|
104
|
+
connection?: Connection,
|
|
105
|
+
): Promise<NormalizedTxResponse> {
|
|
106
|
+
if (quote.quoteParams.fromChain === NonEvmChain.Solana) {
|
|
107
|
+
if (!connection || !sendTransaction) throw new Error('Connection is not defined for Solana swap')
|
|
108
|
+
const txBuffer = Buffer.from(quote.rawQuote.transactionRequest.data, 'base64')
|
|
109
|
+
|
|
110
|
+
// Try to deserialize as VersionedTransaction first
|
|
111
|
+
let transaction
|
|
112
|
+
try {
|
|
113
|
+
transaction = VersionedTransaction.deserialize(txBuffer)
|
|
114
|
+
console.log('Parsed as VersionedTransaction')
|
|
115
|
+
} catch (versionedError) {
|
|
116
|
+
console.log('Failed to parse as VersionedTransaction, trying legacy Transaction')
|
|
117
|
+
try {
|
|
118
|
+
transaction = Transaction.from(txBuffer)
|
|
119
|
+
console.log('Parsed as legacy Transaction')
|
|
120
|
+
} catch (legacyError) {
|
|
121
|
+
throw new Error('Could not parse transaction as either VersionedTransaction or legacy Transaction')
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log('Transaction parsed successfully:', transaction)
|
|
126
|
+
|
|
127
|
+
// Send through wallet adapter
|
|
128
|
+
const signature = await sendTransaction(transaction, connection)
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const latestBlockhash = await connection.getLatestBlockhash()
|
|
132
|
+
|
|
133
|
+
// Wait for confirmation with timeout
|
|
134
|
+
const confirmation = await Promise.race([
|
|
135
|
+
connection.confirmTransaction(
|
|
136
|
+
{
|
|
137
|
+
signature,
|
|
138
|
+
blockhash: latestBlockhash.blockhash,
|
|
139
|
+
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
140
|
+
},
|
|
141
|
+
'confirmed',
|
|
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
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('Transaction confirmed successfully!')
|
|
152
|
+
} catch (confirmError) {
|
|
153
|
+
console.error('Transaction confirmation failed:', confirmError)
|
|
154
|
+
|
|
155
|
+
// Check if transaction actually succeeded despite timeout
|
|
156
|
+
const txStatus = await connection.getSignatureStatus(signature)
|
|
157
|
+
if (txStatus?.value?.confirmationStatus !== 'confirmed') {
|
|
158
|
+
throw new Error(`Transaction was not confirmed: ${confirmError.message}`)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
sender: quote.quoteParams.sender,
|
|
163
|
+
id: signature,
|
|
164
|
+
sourceTxHash: signature,
|
|
165
|
+
adapter: this.getName(),
|
|
166
|
+
sourceChain: quote.quoteParams.fromChain,
|
|
167
|
+
targetChain: quote.quoteParams.toChain,
|
|
168
|
+
inputAmount: quote.quoteParams.amount,
|
|
169
|
+
outputAmount: quote.outputAmount.toString(),
|
|
170
|
+
sourceToken: quote.quoteParams.fromToken,
|
|
171
|
+
targetToken: quote.quoteParams.toToken,
|
|
172
|
+
timestamp: new Date().getTime(),
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const account = walletClient.account?.address
|
|
177
|
+
if (!account) throw new Error('WalletClient account is not defined')
|
|
178
|
+
const tx = await walletClient.sendTransaction({
|
|
179
|
+
chain: undefined,
|
|
180
|
+
account,
|
|
181
|
+
to: quote.rawQuote.transactionRequest.to,
|
|
182
|
+
value: BigInt(quote.rawQuote.transactionRequest.value),
|
|
183
|
+
data: quote.rawQuote.transactionRequest.data,
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
sender: quote.quoteParams.sender,
|
|
188
|
+
id: tx, // specific id for each provider
|
|
189
|
+
sourceTxHash: tx,
|
|
190
|
+
adapter: this.getName(),
|
|
191
|
+
sourceChain: quote.quoteParams.fromChain,
|
|
192
|
+
targetChain: quote.quoteParams.toChain,
|
|
193
|
+
inputAmount: quote.quoteParams.amount,
|
|
194
|
+
outputAmount: quote.outputAmount.toString(),
|
|
195
|
+
sourceToken: quote.quoteParams.fromToken,
|
|
196
|
+
targetToken: quote.quoteParams.toToken,
|
|
197
|
+
timestamp: new Date().getTime(),
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async getTransactionStatus(p: NormalizedTxResponse): Promise<SwapStatus> {
|
|
202
|
+
const res = await getStatus({
|
|
203
|
+
fromChain: p.sourceChain === 'solana' ? 'SOL' : +p.sourceChain,
|
|
204
|
+
toChain: p.targetChain === 'solana' ? 'SOL' : +p.targetChain,
|
|
205
|
+
txHash: p.sourceTxHash,
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
txHash: (res as any)?.receiving?.txHash || '',
|
|
210
|
+
status: res.status === 'DONE' ? 'Success' : res.status === 'FAILED' ? 'Failed' : 'Processing',
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|