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.

210 lines (184 loc) 7.38 kB
import { ChainId } from '@openocean.finance/widget-sdk' import { Currency } from '../constants/index.js' import { getPublicClient } from 'wagmi/actions' import { WalletClient, formatUnits } from 'viem' import { useConfig } from 'wagmi' import { CROSS_CHAIN_FEE_RECEIVER, ETHER_ADDRESS } from '../constants/index.js' import { Quote } from '../registry.js' import { BaseSwapAdapter, Chain, EvmQuoteParams, NOT_SUPPORTED_CHAINS_PRICE_SERVICE, NormalizedQuote, NormalizedTxResponse, SwapStatus, } from './BaseSwapAdapter.js' const XY_FINANCE_API = 'https://aggregator-api.xy.finance/v1' export class XYFinanceAdapter extends BaseSwapAdapter { constructor() { super() } getName(): string { return 'XYFinance' } getIcon(): string { return 'https://xy.finance/img/favicon.ico' } getSupportedChains(): Chain[] { return [ ChainId.ETH, ChainId.BSC, ChainId.POL, ChainId.AVA, ChainId.ARB, ChainId.OPT, ChainId.LNA, ChainId.BAS, ChainId.SCL, ChainId.BLS, ChainId.UNI, ] } getSupportedTokens(_sourceChain: Chain, _destChain: Chain): Currency[] { return [] } async getQuote(params: EvmQuoteParams): Promise<NormalizedQuote> { const p = { srcChainId: params.fromChain, srcQuoteTokenAddress: params.fromToken.isNative ? ETHER_ADDRESS : params.fromToken.address, srcQuoteTokenAmount: params.amount, dstChainId: params.toChain, dstQuoteTokenAddress: params.toToken.isNative ? ETHER_ADDRESS : params.toToken.address, slippage: (params.slippage * 100) / 10_000, srcSwapProviders: 'KyberSwap V1 DexAggregator', // bridgeProviders: 'yBridge', affiliate: CROSS_CHAIN_FEE_RECEIVER, //represents the fee you wish to collect. It is an integer between 0 and 100,000. In this range, 100,000 corresponds to 10%, 10,000 represents 1%, and so on in a similar fashion. commissionRate: (params.feeBps / 10_000) * 1_000_000, } // 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 resp = await fetch(`${XY_FINANCE_API}/quote?${queryParams.toString()}`).then(res => res.json()) const r = resp?.routes?.sort((a: any, b: any) => { return +(BigInt(b.dstQuoteTokenAmount) - BigInt(a.dstQuoteTokenAmount)).toString() })?.[0] if (!r) { throw new Error('No route found') } const formattedOutputAmount = formatUnits(BigInt(r.dstQuoteTokenAmount), params.toToken.decimals) const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals) const tokenInUsd = params.tokenInUsd const tokenOutUsd = params.tokenOutUsd const inputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.fromChain) ? Number(r.srcQuoteTokenUsdValue) : tokenInUsd * +formattedInputAmount const outputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.toChain) ? Number(r.dstQuoteTokenUsdValue) : tokenOutUsd * +formattedOutputAmount return { quoteParams: params, outputAmount: BigInt(r.dstQuoteTokenAmount), formattedOutputAmount, inputUsd, outputUsd, priceImpact: !inputUsd || !outputUsd ? NaN : ((inputUsd - outputUsd) * 100) / inputUsd, rate: +formattedOutputAmount / +formattedInputAmount, gasFeeUsd: 0, timeEstimate: r.estimatedTransferTime, contractAddress: r.contractAddress, rawQuote: r, protocolFee: 0, platformFeePercent: (params.feeBps * 100) / 10_000, } } async executeSwap({ quote }: Quote, walletClient: WalletClient): Promise<NormalizedTxResponse> { const account = walletClient.account?.address if (!account) throw new Error('WalletClient account is not defined') const fromToken = quote.quoteParams.fromToken as Currency const toToken = quote.quoteParams.toToken as Currency const buildSlippage = Math.floor(quote.quoteParams.slippage * 0.9) > 1 ? Math.floor(quote.quoteParams.slippage * 0.9) : quote.quoteParams.slippage const p = { srcChainId: quote.quoteParams.fromChain, srcQuoteTokenAddress: fromToken.isNative ? ETHER_ADDRESS : fromToken.address, srcQuoteTokenAmount: quote.quoteParams.amount, dstChainId: quote.quoteParams.toChain, dstQuoteTokenAddress: toToken.isNative ? ETHER_ADDRESS : toToken.address, // slippage: quote.quoteParams.slippage, slippage: (buildSlippage * 100) / 10_000, receiver: quote.quoteParams.recipient, bridgeProvider: quote.rawQuote.bridgeDescription.provider, srcBridgeTokenAddress: quote.rawQuote.bridgeDescription.srcBridgeTokenAddress, dstBridgeTokenAddress: quote.rawQuote.bridgeDescription.dstBridgeTokenAddress, affiliate: CROSS_CHAIN_FEE_RECEIVER, //represents the fee you wish to collect. It is an integer between 0 and 100,000. In this range, 100,000 corresponds to 10%, 10,000 represents 1%, and so on in a similar fashion. commissionRate: (quote.quoteParams.feeBps / 10_000) * 1_000_000, ...(quote.rawQuote.srcSwapDescription ? { srcSwapProvider: quote.rawQuote.srcSwapDescription.provider } : {}), ...(quote.rawQuote.dstSwapDescription ? { dstSwapProvider: quote.rawQuote.dstSwapDescription?.provider, } : {}), } // 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 resp = await fetch(`${XY_FINANCE_API}/buildTx?${queryParams.toString()}`).then(res => res.json()) if (BigInt(resp.route.minReceiveAmount) < BigInt(quote.rawQuote.minReceiveAmount)) { throw new Error('Rate has changed') } if (resp.tx) { const tx = await walletClient.sendTransaction({ chain: undefined, account, to: resp.tx.to, value: resp.tx.value, data: resp.tx.data, kzg: undefined, }) return { sender: quote.quoteParams.sender, id: tx, // 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: fromToken, targetToken: toToken, timestamp: new Date().getTime(), } } throw new Error('No transaction found') } async getTransactionStatus(p: NormalizedTxResponse): Promise<SwapStatus> { const publicClient = getPublicClient(useConfig(), { chainId: p.sourceChain as any, }) const receipt = await publicClient?.getTransactionReceipt({ hash: p.id as `0x${string}`, }) if (receipt.status === 'reverted') { return { txHash: '', status: 'Failed', } } const res = await fetch(`${XY_FINANCE_API}/crossChainStatus?srcChainId=${p.sourceChain}&srcTxHash=${p.id}`).then( r => r.json(), ) return { txHash: res.tx || '', status: res.status === 'Done' ? 'Success' : res.status === 'Refunded' ? 'Refunded' : 'Processing', } } }