@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
JavaScript
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