@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.
165 lines • 7.59 kB
JavaScript
import { createAcrossClient } from '@across-protocol/app-sdk';
import { ChainId } from '@openocean.finance/widget-sdk';
import { ethers } from 'ethers';
import { 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 { BaseSwapAdapter, NOT_SUPPORTED_CHAINS_PRICE_SERVICE, } 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 = {
[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 {
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() {
return 'Across';
}
getIcon() {
return 'https://across.to/favicon.ico';
}
getSupportedChains() {
return [
ChainId.ETH,
ChainId.ARB,
ChainId.OPT,
ChainId.LNA,
ChainId.POL,
ChainId.BAS,
ChainId.SCL,
ChainId.BLS,
ChainId.UNI,
];
}
getSupportedTokens(_sourceChain, _destChain) {
return [];
}
async getQuote(params) {
try {
const resp = await this.acrossClient.getQuote({
route: {
originChainId: +params.fromChain,
destinationChainId: +params.toChain,
inputToken: params.fromToken.wrapped.address,
outputToken: params.toToken.wrapped.address,
isNative: params.fromToken.isNative,
},
inputAmount: params.amount,
recipient: multicalHandlerContract[params.toChain],
crossChainMessage: {
actions: [
{
target: params.toToken.wrapped.address,
callData: erc20Interface.encodeFunctionData('transfer', [CROSS_CHAIN_FEE_RECEIVER, 0n]),
value: '0',
update: updatedAmount => ({
callData: erc20Interface.encodeFunctionData('transfer', [
CROSS_CHAIN_FEE_RECEIVER,
(updatedAmount * BigInt(params.feeBps)) / 10000n,
]),
}),
},
],
fallbackRecipient: params.recipient,
},
});
// 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)) / 10000n;
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) / 10000,
};
}
catch (e) {
console.log('Across getQuote error', e);
throw e;
}
}
async executeSwap(quote, walletClient) {
return new Promise((resolve, reject) => {
this.acrossClient
.executeQuote({
walletClient: walletClient,
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) {
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',
};
}
}
}
//# sourceMappingURL=AcrossAdapter.js.map