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.

193 lines (177 loc) 7.14 kB
import { AcrossClient, createAcrossClient } from '@across-protocol/app-sdk' import { Currency } from '../constants/index.js' import { ChainId } from '@openocean.finance/widget-sdk' import { ethers } from 'ethers' import { WalletClient, formatUnits } from 'viem' import { arbitrum, base, blast, linea, mainnet, optimism, polygon, scroll, unichain, zksync } from 'viem/chains' import { CROSS_CHAIN_FEE_RECEIVER } from '../constants/index.js' import { Quote } from '../registry.js' import { BaseSwapAdapter, Chain, EvmQuoteParams, NOT_SUPPORTED_CHAINS_PRICE_SERVICE, NormalizedQuote, NormalizedTxResponse, SwapStatus, } from './BaseSwapAdapter.js' // Define the ABI of the functions const transferFunction = 'function transfer(address to, uint256 amount)' // Create Interface instances const erc20Interface = new ethers.Interface([transferFunction]) const multicalHandlerContract: Record<string, `0x${string}`> = { [ChainId.ETH]: '0x924a9f036260DdD5808007E1AA95f08eD08aA569', [ChainId.ARB]: '0x924a9f036260DdD5808007E1AA95f08eD08aA569', [ChainId.OPT]: '0x924a9f036260DdD5808007E1AA95f08eD08aA569', [ChainId.LNA]: '0x1015c58894961F4F7Dd7D68ba033e28Ed3ee1cDB', [ChainId.POL]: '0x924a9f036260DdD5808007E1AA95f08eD08aA569', // [ChainId.ZKSYNC]: '0x863859ef502F0Ee9676626ED5B418037252eFeb2', [ChainId.BAS]: '0x1015c58894961F4F7Dd7D68ba033e28Ed3ee1cDB', [ChainId.SCL]: '0x1015c58894961F4F7Dd7D68ba033e28Ed3ee1cDB', [ChainId.BLS]: '0x1015c58894961F4F7Dd7D68ba033e28Ed3ee1cDB', [ChainId.UNI]: '0x1015c58894961F4F7Dd7D68ba033e28Ed3ee1cDB', } export class AcrossAdapter extends BaseSwapAdapter { private acrossClient: AcrossClient constructor() { super() this.acrossClient = createAcrossClient({ integratorId: `0x008a`, chains: [mainnet, arbitrum, optimism, linea, polygon, zksync, base, scroll, blast, unichain], rpcUrls: { [ChainId.BAS]: 'https://base.drpc.org', }, }) } getName(): string { return 'Across' } getIcon(): string { return 'https://across.to/favicon.ico' } getSupportedChains(): Chain[] { return [ ChainId.ETH, ChainId.ARB, ChainId.OPT, ChainId.LNA, ChainId.POL, ChainId.BAS, ChainId.SCL, ChainId.BLS, ChainId.UNI, ] } getSupportedTokens(_sourceChain: Chain, _destChain: Chain): Currency[] { return [] } async getQuote(params: EvmQuoteParams): Promise<NormalizedQuote> { try { const resp = await this.acrossClient.getQuote({ route: { originChainId: +params.fromChain, destinationChainId: +params.toChain, inputToken: params.fromToken.wrapped.address as `0x${string}`, outputToken: params.toToken.wrapped.address as `0x${string}`, isNative: params.fromToken.isNative, }, inputAmount: params.amount, recipient: multicalHandlerContract[params.toChain], crossChainMessage: { actions: [ { target: params.toToken.wrapped.address as `0x${string}`, callData: erc20Interface.encodeFunctionData('transfer', [CROSS_CHAIN_FEE_RECEIVER, 0n]) as `0x${string}`, value: '0', update: updatedAmount => ({ callData: erc20Interface.encodeFunctionData('transfer', [ CROSS_CHAIN_FEE_RECEIVER, (updatedAmount * BigInt(params.feeBps)) / 10_000n, ]) as `0x${string}`, }), }, ], fallbackRecipient: params.recipient as `0x${string}`, }, }) // across only have bridge then we can treat token in and out price usd are the same in case price service is not supported const isSameToken = params.fromToken.symbol === params.toToken.symbol const tokenInUsd = isSameToken && NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.fromChain) && params.tokenOutUsd ? params.tokenOutUsd : params.tokenInUsd const tokenOutUsd = isSameToken && NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.toChain) && params.tokenInUsd ? params.tokenInUsd : params.tokenOutUsd const feeAmount = (BigInt(resp.deposit.outputAmount) * BigInt(params.feeBps)) / 10_000n const formattedOutputAmount = formatUnits(BigInt(resp.deposit.outputAmount) - feeAmount, params.toToken.decimals) const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals) const inputUsd = tokenInUsd * +formattedInputAmount const outputUsd = tokenOutUsd * +formattedOutputAmount return { quoteParams: params, outputAmount: BigInt(resp.deposit.outputAmount) - feeAmount, formattedOutputAmount, inputUsd: tokenInUsd * +formatUnits(BigInt(params.amount), params.fromToken.decimals), outputUsd: tokenOutUsd * +formattedOutputAmount, rate: +formattedOutputAmount / +formattedInputAmount, timeEstimate: resp.estimatedFillTimeSec, priceImpact: !inputUsd || !outputUsd ? NaN : ((inputUsd - outputUsd) * 100) / inputUsd, // TODO: what is gas fee for across gasFeeUsd: 0, contractAddress: resp.deposit.spokePoolAddress, rawQuote: resp, protocolFee: 0, platformFeePercent: (params.feeBps * 100) / 10_000, } } catch (e) { console.log('Across getQuote error', e) throw e } } async executeSwap(quote: Quote, walletClient: WalletClient): Promise<NormalizedTxResponse> { return new Promise<NormalizedTxResponse>((resolve, reject) => { this.acrossClient .executeQuote({ walletClient: walletClient as any, deposit: quote.quote.rawQuote.deposit, onProgress: progress => { if (progress.step === 'deposit' && 'txHash' in progress) { resolve({ sender: quote.quote.quoteParams.sender, sourceTxHash: progress.txHash, adapter: this.getName(), id: progress.txHash, sourceChain: quote.quote.quoteParams.fromChain, targetChain: quote.quote.quoteParams.toChain, inputAmount: quote.quote.quoteParams.amount, outputAmount: quote.quote.outputAmount.toString(), sourceToken: quote.quote.quoteParams.fromToken, targetToken: quote.quote.quoteParams.toToken, timestamp: new Date().getTime(), }) } }, }) .catch(reject) }) } async getTransactionStatus(params: NormalizedTxResponse): Promise<SwapStatus> { try { const res = await fetch(`https://app.across.to/api/deposit/status?depositTxHash=${params.sourceTxHash}`).then( res => res.json(), ) return { txHash: res.fillTx || '', status: res.status === 'filled' ? 'Success' : 'Processing', } } catch (error) { console.error('Error fetching transaction status:', error) return { txHash: '', status: 'Processing', } } } }