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.

267 lines (254 loc) 8.03 kB
import type { WalletClient } from 'viem' import type { QuoteParams } from './adapters/index.js' import { CrossChainSwapFactory } from './factory.js' import { CrossChainSwapAdapterRegistry, type Quote } from './registry.js' /** * Get cross-chain aggregated quotes (supports multiple adapter comparison) * @param params - Cross-chain quote parameters * @returns Best quote result, compatible with useRoutes.ts requirements */ export async function getCrossChainQuote({ fromMsg, toMsg, inAmount, slippage_tolerance, account, recipient, tokenInUsd = 0, tokenOutUsd = 0, feeBps = 10, walletClient, publicKey, nearTokens = [], }: { fromMsg: any toMsg: any inAmount: string slippage_tolerance: string | number account: string recipient?: string tokenInUsd?: number tokenOutUsd?: number feeBps?: number walletClient?: any publicKey?: string nearTokens?: any[] }): Promise<any | null> { try { // 1. Register all Adapters const registry = new CrossChainSwapAdapterRegistry() CrossChainSwapFactory.getAllAdapters().forEach((adapter) => { registry.registerAdapter(adapter) }) let slippage = typeof slippage_tolerance === 'string' ? Number.parseFloat(slippage_tolerance) : slippage_tolerance slippage = Math.floor(slippage * 10000) / 100 // 2. Construct QuoteParams const params: QuoteParams = { feeBps, fromChain: fromMsg.chainId, toChain: toMsg.chainId, fromToken: fromMsg, toToken: toMsg, amount: inAmount, slippage: slippage, walletClient, tokenInUsd, tokenOutUsd, sender: account, recipient: recipient || '', publicKey, isNative: fromMsg.isNative || toMsg.isNative, } if (fromMsg.chainId === 'near' || toMsg.chainId === 'near') { ; (params as any).nearTokens = nearTokens } // 3. Get all quotes const adapters = registry.getAllAdapters().filter((adapter) => { return ( adapter.getSupportedChains().includes(params.fromChain) && adapter.getSupportedChains().includes(params.toChain) ) }) if (adapters.length === 0) { console.warn('No supported adapters found for the specified chains') return null } const quoteResults: Quote[] = [] let errorMsg = '' // Get quotes from all adapters in parallel const quotePromises = adapters.map(async (adapter) => { try { const quote = await adapter.getQuote(params) if (quote) { quoteResults.push({ adapter, quote }) } } catch (error) { errorMsg = error instanceof Error ? error.message : (error && typeof error === 'object' && 'message' in error ? String(error.message) : 'Unknown error') // Ignore individual adapter errors, continue getting quotes from other adapters console.warn(`Failed to get quote from ${adapter.getName()}:`, error) } }) await Promise.all(quotePromises) if (quoteResults.length === 0) { console.warn('No valid quotes found from any adapter') throw new Error(errorMsg) // return { error: errorMsg } } // 4. Select best quote (highest outputAmount) quoteResults.sort((a, b) => a.quote.outputAmount < b.quote.outputAmount ? 1 : -1 ) const best = quoteResults[0] // 5. Construct return structure compatible with useRoutes.ts return { data: { minOutAmount: best.quote.outputAmount || '0', outAmount: best.quote.outputAmount || '0', fromTokenUSD: best.quote.inputUsd?.toString() || '0', toTokenUSD: best.quote.outputUsd?.toString() || '0', quoteAdapterName: best.adapter?.getName() || '', quoteAdapterKey: best.adapter?.getName()?.toLowerCase() || '', chainId: fromMsg.chainId, from: account, to: best.quote.contractAddress || '0x0', value: best.quote.rawQuote.txParams?.value || best.quote.rawQuote.transactionRequest?.value || best.quote.rawQuote.tx?.value || '0x0', data: best.quote.rawQuote.txParams?.data || best.quote.rawQuote.transactionRequest?.data || best.quote.rawQuote.tx?.data || '0x', // gasLimit: best.quote.rawQuote.tx?.gasLimit || '0', // gas: best.quote.rawQuote.tx?.gas || '0', // nonce: best.quote.rawQuote.tx?.nonce || '0', approveContract: best.quote.contractAddress || '0x0', executionDuration: best.quote.timeEstimate || 300, // Fee information feeCosts: [ { name: `${best.adapter?.getName() || ''} Fee`, description: 'Protocol fee', token: fromMsg, amount: '0', amountUSD: best.quote.protocolFee, percentage: '0', included: false, }, ], // Original quote data quoteRawData: best.quote, }, } } catch (error) { console.error('Error getting cross-chain quote:', error) let errors = [ { message: 'Please connect your wallet first.', conditions: [ 'refundTo should not be empty', 'User is required', 'fromAddress" is missing' ], }, { message: 'The input amount is too low.', conditions: [ 'Swap output amount is too small to cover fees required to execute swap', 'Amount is too low for bridge', 'Amount too small', 'Amount too low', ], }, { message: 'Insufficient balance in your wallet.', conditions: [ 'Insufficient funds', 'Failed to get quote', 'Insufficient balance', ], }, { message: 'Please enter valid recipient address.', conditions: [ 'recipient should not be empty', 'Recipient is required', 'Invalid toAddress:' ], }, { message: 'Not supported token', conditions: [ 'not supported to token', 'not supported from token', 'no routes found', 'SDK version too old for monad and solana fast mctp route' ], }, { message: 'Pegasus API key is invalid or missing.', conditions: [ 'Pegasus API key is missing', 'Invalid API key', 'Missing API key', 'UNAUTHORIZED', ], }, { message: 'There’s no routes available for the selected token pairs', conditions: [ 'No available quotes for the requested transfer', ], } ] if (error instanceof Error && error.message) { const newError = errors.find(e => e.conditions.some(condition => error.message.indexOf(condition) !== -1)) if (newError) { throw new Error(newError.message) } else { throw new Error(error.message) } } throw new Error('Unknown error') } } export const bridgeExecuteSwap = async ({ quoteData, walletClient, nearWallet, }: { quoteData: any walletClient: WalletClient // Near Intents adapter 需要的 Near wallet-selector 实例,其它适配器会忽略 nearWallet?: any }) => { const adapterName = quoteData.quoteAdapterKey const adapter = CrossChainSwapFactory.getAdapterByName(adapterName) if (adapter) { const result = await (adapter as any)?.executeSwap( { adapter, quote: quoteData.quoteRawData as any, }, walletClient, nearWallet ) return result } return null } // Login URL mapping for each adapter export const ADAPTER_LOGIN_URLS: Record<string, string> = { debridge: 'https://app.debridge.finance/', across: 'https://across.to/', lifi: 'https://jumper.exchange/', mayan: 'https://swap.mayan.finance/', relay: 'https://app.relay.link/', xyfinance: 'https://xy.finance/', kyberswap: 'https://kyberswap.com/', pegasus: 'https://pegasusfi.openocean.finance/', }