UNPKG

@openocean.finance/widget

Version:

Openocean Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.

265 lines 11.7 kB
import { ChainId } from '@openocean.finance/widget-sdk'; import { Transaction, VersionedTransaction, } from '@solana/web3.js'; import { formatUnits } from 'viem'; import { TOKEN_API_URL } from '../constants/index.js'; import { CROSS_CHAIN_FEE_RECEIVER, CROSS_CHAIN_FEE_RECEIVER_SOLANA, ZERO_ADDRESS, } from '../constants/index.js'; import { NativeCurrencies } from '../constants/index.js'; import { BaseSwapAdapter, NOT_SUPPORTED_CHAINS_PRICE_SERVICE, NonEvmChain, } from './BaseSwapAdapter.js'; const DEBRIDGE_API = 'https://dln.debridge.finance/v1.0/dln/order'; const mappingChainId = { [ChainId.DAI]: 100000002, [ChainId.MAM]: 100000004, // Metis [ChainId.SON]: 100000014, // Sonic [ChainId.ABS]: 100000017, // Abstract [ChainId.BER]: 100000020, // Berachain [ChainId.BOB]: 100000021, // BOB [ChainId.MNT]: 100000023, // Mantle [NonEvmChain.Solana]: 7565164, }; export class DeBridgeAdapter extends BaseSwapAdapter { constructor() { super(); } getName() { return 'deBridge'; } getIcon() { return 'https://app.debridge.finance/assets/images/meta-deswap/favicon-32x32.png'; } getSupportedChains() { return [ ChainId.ETH, ChainId.BSC, ChainId.POL, ChainId.AVA, ChainId.ARB, ChainId.OPT, ChainId.ONE, ChainId.FSN, ChainId.MOR, ChainId.CEL, ChainId.FUS, ChainId.TLO, ChainId.CRO, ChainId.BOB, ChainId.RSK, ChainId.VEL, ChainId.MOO, ChainId.MAM, ChainId.AUR, ChainId.EVM, ChainId.ARN, ChainId.ERA, ChainId.PZE, ChainId.LNA, ChainId.BAS, ChainId.SCL, ChainId.MOD, ChainId.MNT, ChainId.BLS, ChainId.SEI, ChainId.FRA, ChainId.TAI, ChainId.GRA, ChainId.IMX, ChainId.KAI, ChainId.XLY, // NonEvmChain.Solana, ]; } getSupportedTokens(_sourceChain, _destChain) { return []; } async getQuote(params) { const fromToken = params.fromToken; const toToken = params.toToken; let p = { srcChainId: mappingChainId[params.fromChain] || params.fromChain, srcChainTokenIn: params.fromChain === 1151111081099710 ? params.fromToken.id : fromToken.isNative ? ZERO_ADDRESS : fromToken.address, srcChainTokenInAmount: params.amount, dstChainId: mappingChainId[params.toChain] || params.toChain, dstChainTokenOut: params.toChain === 1151111081099710 ? params.toToken.id : toToken.isNative ? ZERO_ADDRESS : toToken.address, dstChainTokenOutAmount: 'auto', enableEstimate: false, prependOperatingExpenses: false, referralCode: 31982, affiliateFeePercent: (params.feeBps * 100) / 10000, affiliateFeeRecipient: params.fromChain === 1151111081099710 ? CROSS_CHAIN_FEE_RECEIVER_SOLANA : CROSS_CHAIN_FEE_RECEIVER, }; let path = 'quote'; if (params.recipient && params.sender && params.sender !== ZERO_ADDRESS) { path = 'create-tx'; p = { ...p, srcChainOrderAuthorityAddress: params.sender, dstChainOrderAuthorityAddress: params.recipient, dstChainTokenOutRecipient: params.recipient, }; } // Convert the parameters object to URL query string const queryParams = new URLSearchParams(); for (const [key, value] of Object.entries(p)) { queryParams.append(key, String(value)); } const r = await fetch(`${DEBRIDGE_API}/${path}?${queryParams.toString()}`).then((res) => res.json()); if (!r.estimation) { throw new Error(r.errorMessage); } //const inputUsd = r.estimation.srcChainTokenIn.approximateUsdValue //const outputUsd = r.estimation.dstChainTokenOut.recommendedApproximateUsdValue const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals); const formattedOutputAmount = formatUnits(BigInt(r.estimation.dstChainTokenOut.recommendedAmount), params.toToken.decimals); const inputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.fromChain) ? r.estimation.srcChainTokenIn.approximateUsdValue : params.tokenInUsd * +formattedInputAmount; const outputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.toChain) ? r.estimation.dstChainTokenOut.recommendedApproximateUsdValue : params.tokenOutUsd * +formattedOutputAmount; const fixFee = r.fixFee; const wrappedAddress = NativeCurrencies[params.fromChain].wrapped.address; const nativePrice = await fetch(`${TOKEN_API_URL}/v1/public/tokens/prices`, { method: 'POST', body: JSON.stringify({ [params.fromChain]: [wrappedAddress], }), }) .then((res) => res.json()) .then((res) => { return res?.data?.[params.fromChain]?.[wrappedAddress]?.PriceBuy || 0; }); const nativeDecimals = params.fromChain === 1151111081099710 ? 9 : NativeCurrencies[params.fromChain].decimals; const protocolFee = Number(nativePrice) * (Number(fixFee) / 10 ** nativeDecimals); const protocolFeeString = `${Number(fixFee) / 10 ** nativeDecimals} ${params.fromChain === 1151111081099710 ? 'SOL' : NativeCurrencies[params.fromChain].symbol}`; return { quoteParams: params, outputAmount: BigInt(r.estimation.dstChainTokenOut.recommendedAmount), formattedOutputAmount, inputUsd, outputUsd, priceImpact: !inputUsd || !outputUsd ? Number.NaN : ((inputUsd - outputUsd) * 100) / inputUsd, rate: +formattedOutputAmount / +formattedInputAmount, gasFeeUsd: 0, timeEstimate: r.order.approximateFulfillmentDelay, contractAddress: r.tx.allowanceTarget || r.tx.to, rawQuote: r, protocolFee, protocolFeeString, platformFeePercent: (params.feeBps * 100) / 10000, }; } async executeSwap({ quote }, walletClient, _nearWallet, _sendBtcFn, sendSolanaFn, solanaConnection) { if (quote.quoteParams.fromChain === 1151111081099710) { if (!solanaConnection || !sendSolanaFn) throw new Error('Connection is not defined for Solana swap'); const txBuffer = Buffer.from(quote.rawQuote.tx.data.slice(2), 'hex'); // Try to deserialize as VersionedTransaction first let transaction; try { transaction = VersionedTransaction.deserialize(txBuffer); console.log('Parsed as VersionedTransaction'); } catch (versionedError) { console.log('Failed to parse as VersionedTransaction, trying legacy Transaction'); try { transaction = Transaction.from(txBuffer); console.log('Parsed as legacy Transaction'); } catch (legacyError) { throw new Error('Could not parse transaction as either VersionedTransaction or legacy Transaction'); } } console.log('Transaction parsed successfully:', transaction); // Send through wallet adapter const signature = await sendSolanaFn(transaction, solanaConnection); const waitForConfirmation = async (txId) => { try { const latestBlockhash = await solanaConnection.getLatestBlockhash(); // Wait for confirmation with timeout const confirmation = await Promise.race([ solanaConnection.confirmTransaction({ signature: txId, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, }, 'confirmed'), new Promise((_, reject) => setTimeout(() => reject(new Error('Transaction confirmation timeout')), 60000)), ]); const confirmationResult = confirmation; if (confirmationResult.value.err) { throw new Error(`Transaction failed: ${JSON.stringify(confirmationResult.value.err)}`); } console.log('Transaction confirmed successfully!'); } catch (confirmError) { console.error('Transaction confirmation failed:', confirmError); // Check if transaction actually succeeded despite timeout const txStatus = await solanaConnection.getSignatureStatus(txId); if (txStatus?.value?.confirmationStatus !== 'confirmed') { throw new Error(`Transaction was not confirmed: ${confirmError.message}`); } } }; await waitForConfirmation(signature); return { sender: quote.quoteParams.sender, id: quote.rawQuote.orderId, // specific id for debridge sourceTxHash: signature, adapter: this.getName(), sourceChain: quote.quoteParams.fromChain, targetChain: quote.quoteParams.toChain, inputAmount: quote.quoteParams.amount, outputAmount: quote.outputAmount.toString(), sourceToken: quote.quoteParams.fromToken, targetToken: quote.quoteParams.toToken, timestamp: new Date().getTime(), }; } const account = walletClient.account?.address; if (!account) throw new Error('WalletClient account is not defined'); const tx = await walletClient.sendTransaction({ chain: undefined, account: account, to: quote.rawQuote.tx.to, value: BigInt(quote.rawQuote.tx.value), data: quote.rawQuote.tx.data, kzg: undefined, }); return { sender: quote.quoteParams.sender, id: quote.rawQuote.orderId, // specific id for each provider sourceTxHash: tx, adapter: this.getName(), sourceChain: quote.quoteParams.fromChain, targetChain: quote.quoteParams.toChain, inputAmount: quote.quoteParams.amount, outputAmount: quote.outputAmount.toString(), sourceToken: quote.quoteParams.fromToken, targetToken: quote.quoteParams.toToken, timestamp: new Date().getTime(), }; } async getTransactionStatus(p) { const r = await fetch(`${DEBRIDGE_API}/${p.id}/status`).then((res) => res.json()); return { status: r.status === 'Fulfilled' ? 'Success' : 'Processing', txHash: p.id, }; } } //# sourceMappingURL=DebridgeAdapter.js.map