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.

325 lines (307 loc) 8.58 kB
import { MAINNET_RELAY_API, type RelayChain, convertViemChainToRelayChain, createClient, getClient, type QuoteBodyOptions, } from '@relayprotocol/relay-sdk' import { type WalletClient, formatUnits } from 'viem' import { arbitrum, avalanche, base, berachain, blast, bsc, fantom, linea, mainnet, mantle, optimism, polygon, ronin, scroll, sonic, unichain, zksync, celo, monad, } from 'viem/chains' import type { Currency } from '../constants/index.js' import { CROSS_CHAIN_FEE_RECEIVER, MAINNET_NETWORKS, ZERO_ADDRESS, } from '../constants/index.js' import type { Quote } from '../registry.js' import { BaseSwapAdapter, type Chain, NOT_SUPPORTED_CHAINS_PRICE_SERVICE, NonEvmChain, NewEvmChain, type NormalizedQuote, type NormalizedTxResponse, type QuoteParams, type SwapStatus, } from './BaseSwapAdapter.js' import { defineChain } from 'viem' import { ChainId } from '../../index.js' const hyperEvm = defineChain({ id: 999, name: 'HyperEvm', network: 'hyperevm', nativeCurrency: { decimals: 18, name: 'HYPE', symbol: 'HYPE', }, rpcUrls: { default: { http: ['https://rpc.hyperliquid.xyz/evm'], }, public: { http: ['https://rpc.hyperliquid.xyz/evm'], }, }, blockExplorers: { default: { name: 'Explorer', url: 'https://hyperevmscan.io' }, }, }) const plasma = defineChain({ id: 9745, name: 'Plasma', blockTime: 1000, nativeCurrency: { name: 'Plasma', symbol: 'XPL', decimals: 18, }, rpcUrls: { default: { http: ['https://rpc.plasma.to'], }, }, blockExplorers: { default: { name: 'PlasmaScan', url: 'https://plasmascan.to', }, }, contracts: { multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11', blockCreated: 0, }, }, }) const SolanaChainId = 792703809 const BitcoinChainId = 8253038 const BitcoinAddress = 'bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8' const solanaChain = { id: SolanaChainId, name: 'Solana', displayName: 'Solana', vmType: 'svm' as const, } as RelayChain const bitcoinChain = { id: BitcoinChainId, name: 'Bitcoin', displayName: 'Bitcoin', vmType: 'bvm' as const, } as RelayChain export class RelayAdapter extends BaseSwapAdapter { constructor() { super() createClient({ baseApiUrl: MAINNET_RELAY_API, source: 'openocean', chains: [ arbitrum, avalanche, base, berachain, blast, bsc, fantom, linea, mainnet, mantle, optimism, polygon, scroll, sonic, zksync, ronin, unichain, hyperEvm, plasma, celo, monad, ] .map(convertViemChainToRelayChain) .concat(solanaChain as any, bitcoinChain as any), }) } getName(): string { return 'Relay' } getIcon(): string { return 'https://storage.googleapis.com/ks-setting-1d682dca/84e906bb-eaeb-45d3-a64c-2aa9c84eb3ea1747759080942.png' } getSupportedChains(): Chain[] { return [NonEvmChain.Solana, NonEvmChain.Bitcoin, NewEvmChain.Plasma, NewEvmChain.Monad, ...MAINNET_NETWORKS.filter(chain => chain !== ChainId.FLR)] // return [...MAINNET_NETWORKS] } getSupportedTokens(_sourceChain: Chain, _destChain: Chain): Currency[] { return [] } async getQuote(params: QuoteParams): Promise<NormalizedQuote> { const evmFromToken = params.fromToken as Currency const evmToToken = params.toToken as Currency let currency = evmFromToken.isNative ? ZERO_ADDRESS : evmFromToken.address let toCurrency = evmToToken.isNative ? ZERO_ADDRESS : evmToToken.address let chainId = +params.fromChain if (params.fromChain === NonEvmChain.Solana) { chainId = SolanaChainId } else if (params.fromChain === NonEvmChain.Bitcoin) { chainId = BitcoinChainId currency = BitcoinAddress } let toChainId = +params.toChain if (params.toChain === NonEvmChain.Solana) { toChainId = SolanaChainId } else if (params.toChain === NonEvmChain.Bitcoin) { toChainId = BitcoinChainId toCurrency = BitcoinAddress } const quoteParams = { chainId: chainId, toChainId: toChainId, currency, toCurrency, amount: params.amount, tradeType: 'EXACT_INPUT' as QuoteBodyOptions['tradeType'], wallet: params.walletClient, recipient: params.recipient, user: params.sender, options: { appFees: [ { recipient: CROSS_CHAIN_FEE_RECEIVER, // recipient: // params.fromChain === NonEvmChain.Solana // ? CROSS_CHAIN_FEE_RECEIVER_SOLANA // : CROSS_CHAIN_FEE_RECEIVER, fee: params.feeBps.toString(), }, ], protocolVersion: 'preferV2' as 'preferV2' | 'v1' | 'v2', // includedSwapSources: ['open-ocean'], }, } const resp = await getClient().actions.getQuote(quoteParams) const formattedOutputAmount = formatUnits( BigInt(resp.details?.currencyOut?.amount || '0'), params.toToken.decimals ) const formattedInputAmount = formatUnits( BigInt(params.amount), params.fromToken.decimals ) const inputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes( params.fromChain ) ? Number(resp.details?.currencyIn?.amountUsd || 0) : params.tokenInUsd * +formattedInputAmount const outputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes( params.toChain ) ? Number(resp.details?.currencyOut?.amountUsd || 0) : params.tokenOutUsd * +formattedOutputAmount if (params.walletClient) { params.walletClient = { account: { address: params.sender, }, } as any } const protocolFee: any = resp.fees?.relayer?.amountUsd || 0 return { quoteParams: params, outputAmount: BigInt(resp.details?.currencyOut?.amount || '0'), formattedOutputAmount, inputUsd, outputUsd, priceImpact: !inputUsd || !outputUsd ? Number.NaN : ((inputUsd - outputUsd) * 100) / inputUsd, //rate: Number(resp.details?.rate || 0), rate: +formattedOutputAmount / +formattedInputAmount, gasFeeUsd: Number(resp.fees?.gas?.amountUsd || 0), timeEstimate: resp.details?.timeEstimate || 0, // Relay dont need to approve, we send token to contract directly contractAddress: ZERO_ADDRESS, rawQuote: resp, protocolFee: protocolFee, platformFeePercent: (params.feeBps * 100) / 10000, } } async executeSwap( { quote }: Quote, walletClient: WalletClient ): Promise<NormalizedTxResponse> { return new Promise<NormalizedTxResponse>((resolve, reject) => { getClient() .actions.execute({ quote: quote.rawQuote, wallet: walletClient, onProgress: ({ currentStep }) => { if ( currentStep?.id === 'deposit' && currentStep.requestId && currentStep.kind === 'transaction' ) { const txHash = currentStep.items?.[0]?.txHashes?.[0].txHash if (txHash) { resolve({ sender: quote.quoteParams.sender, sourceTxHash: txHash, adapter: this.getName(), id: currentStep.requestId, 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(), }) } } }, }) .catch((e) => { reject(e) // Make sure errors from execute are also caught }) }) } async getTransactionStatus(p: NormalizedTxResponse): Promise<SwapStatus> { const res = await fetch( `https://api.relay.link/intents/status/v2?requestId=${p.id}` ).then((r) => r.json()) return { txHash: res.txHashes?.[0] || '', status: res.status === 'success' ? 'Success' : res.status === 'refund' ? 'Refunded' : res.status === 'failure' ? 'Failed' : 'Processing', } } }