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.

187 lines 7.79 kB
import { addresses, fetchQuote, getSwapFromEvmTxPayload, } from '@mayanfinance/swap-sdk'; import { ChainId } from '@openocean.finance/widget-sdk'; import { formatUnits } from 'viem'; import { CROSS_CHAIN_FEE_RECEIVER, CROSS_CHAIN_FEE_RECEIVER_SOLANA, ZERO_ADDRESS, } from '../constants/index.js'; import { BaseSwapAdapter, } from './BaseSwapAdapter.js'; const mappingChain = { [ChainId.ETH]: 'ethereum', [ChainId.BSC]: 'bsc', [ChainId.POL]: 'polygon', [ChainId.AVA]: 'avalanche', [ChainId.ARB]: 'arbitrum', [ChainId.OPT]: 'optimism', [ChainId.BAS]: 'base', [ChainId.UNI]: 'unichain', // [ChainId.LIN]: 'linea', [ChainId.HYE]: 'hyperevm', [ChainId.SOL]: 'solana', [ChainId.SUI]: 'sui', // [ChainId.PLA]: 'plasma', }; function getMayanApiKey() { try { return typeof process !== 'undefined' ? process.env?.MAYAN_API_KEY : undefined; } catch { return undefined; } } function getMayanFetchQuoteOptions() { const apiKey = getMayanApiKey(); return apiKey ? { apiKey } : undefined; } export class MayanAdapter extends BaseSwapAdapter { getName() { return 'Mayan'; } getIcon() { return 'https://swap.mayan.finance/favicon.ico'; } getSupportedChains() { return [...Object.keys(mappingChain).map(Number)]; } getSupportedTokens(_sourceChain, _destChain) { return []; } async getQuote(params) { const fromChainName = mappingChain[params.fromChain]; const toChainName = mappingChain[params.toChain]; if (!fromChainName || !toChainName) { throw new Error('No quotes found'); } let slippageBps = params.slippage > 0 ? params.slippage / 100 : 'auto'; if (+slippageBps > 5) { slippageBps = 5; } const quoteParams = { amountIn64: params.amount, fromToken: params.fromToken.isNative ? ZERO_ADDRESS : params.fromToken.address, toToken: params.toToken.isNative ? ZERO_ADDRESS : params.toToken.address, fromChain: fromChainName, toChain: toChainName, slippageBps: slippageBps, referrer: CROSS_CHAIN_FEE_RECEIVER, referrerBps: params.feeBps, ...(params.recipient ? { destinationAddress: params.recipient } : {}), }; const quotes = await fetchQuote(quoteParams, getMayanFetchQuoteOptions()); if (!quotes?.[0]) { throw new Error('No quotes found'); } const topQuote = quotes[0]; const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals); const outputAmount = BigInt(topQuote.expectedAmountOutBaseUnits); const formattedOutputAmount = formatUnits(outputAmount, params.toToken.decimals); const tokenInUsd = params.tokenInUsd; const tokenOutUsd = params.tokenOutUsd; const inputUsd = tokenInUsd * +formattedInputAmount; const outputUsd = tokenOutUsd * +formattedOutputAmount; const usdPriceImpact = !inputUsd || !outputUsd ? Number.NaN : ((inputUsd - outputUsd) * 100) / inputUsd; const sdkPriceImpact = topQuote.priceImpact != null ? Number(topQuote.priceImpact) : Number.NaN; return { quoteParams: params, outputAmount, formattedOutputAmount, inputUsd, outputUsd, priceImpact: Number.isFinite(sdkPriceImpact) ? sdkPriceImpact : usdPriceImpact, rate: +formattedInputAmount > 0 ? +formattedOutputAmount / +formattedInputAmount : 0, gasFeeUsd: 0, timeEstimate: topQuote.etaSeconds, contractAddress: addresses.MAYAN_FORWARDER_CONTRACT, rawQuote: topQuote, protocolFee: topQuote.clientRelayerFeeSuccess || 0, platformFeePercent: (params.feeBps * 100) / 10000, }; } async executeSwap({ quote }, walletClient) { const account = quote.quoteParams.sender; if (!account) { throw new Error('WalletClient account is not defined'); } if (!quote.quoteParams.fromChain) { throw new Error(`Invalid fromChain: ${quote.quoteParams.fromChain}`); } const fromChain = quote.quoteParams.fromChain === ChainId.SOL ? 'solana' : quote.quoteParams.fromChain; if (fromChain === 'solana') { // const res = getSwapSolana({ // amountIn64: quote.quoteParams.amount, // fromToken: quote.quoteParams.fromToken, // minMiddleAmount: 0, // middleToken: quote.quoteParams.toToken, // userWallet: account, // slippageBps: quote.quoteParams.slippage, // referrerAddress: CROSS_CHAIN_FEE_RECEIVER, // }) } else { const mayanQuote = quote.rawQuote; // Gasless Swift:需 EIP-712 签名 + submitSwiftEvmSwap,不能走普通 sendTransaction(见 SDK swapFromEvm) if (mayanQuote.type === 'SWIFT' && mayanQuote.gasless) { throw new Error('Mayan gasless Swift 订单需使用 @mayanfinance/swap-sdk 的 swapFromEvm(EIP-712 + 提交订单),当前适配器仅支持链上交易 payload'); } const mayanApiKey = getMayanApiKey(); // v13+ getSwapFromEvmTxPayload 为异步;referrerAddresses 需按网络类型提供(文档) const res = await getSwapFromEvmTxPayload(mayanQuote, account, quote.quoteParams.recipient, { evm: CROSS_CHAIN_FEE_RECEIVER, solana: CROSS_CHAIN_FEE_RECEIVER_SOLANA, }, account, fromChain, null, null, mayanApiKey ? { apiKey: mayanApiKey } : undefined); if (res.to && res.value && res.data) { const tx = await walletClient.sendTransaction({ chain: undefined, account: account, to: res.to, value: BigInt(res.value), data: res.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: quote.quoteParams.fromToken, targetToken: quote.quoteParams.toToken, timestamp: new Date().getTime(), }; } } throw new Error('Can not get Mayan data to swap'); } async getTransactionStatus(p) { const res = await fetch(`https://explorer-api.mayan.finance/v3/swap/trx/${p.id}`).then((r) => r.json()); const clientStatus = res.clientStatus ?? res.status; const legacy = res.status; let status = 'Processing'; if (clientStatus === 'COMPLETED' || legacy === 'ORDER_SETTLED') { status = 'Success'; } else if (clientStatus === 'REFUNDED' || legacy === 'ORDER_REFUNDED') { status = 'Refunded'; } else if (legacy === 'ORDER_CANCELED') { status = 'Failed'; } return { txHash: res.fulfillTxHash || '', status, }; } } //# sourceMappingURL=MayanAdapter.js.map