@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.
217 lines • 9.7 kB
JavaScript
import { ChainId } from '@openocean.finance/widget-sdk';
import { formatUnits } from 'viem';
import { CROSS_CHAIN_FEE_RECEIVER, ZERO_ADDRESS } from '../constants/index.js';
import { BaseSwapAdapter, NonEvmChain, } from './BaseSwapAdapter.js';
const OPTIMEX_API = 'https://ks-provider.optimex.xyz/v1';
export class OptimexAdapter extends BaseSwapAdapter {
constructor() {
super();
this.tokens = [];
}
async getTokens() {
try {
const res = await fetch(`${OPTIMEX_API}/tokens`);
const { data } = await res.json();
this.tokens = data.tokens;
}
catch (error) {
console.error('Failed to initialize Optimex tokens:', error);
// Handle error appropriately
}
}
getName() {
return 'Optimex';
}
getIcon() {
return 'https://storage.googleapis.com/ks-setting-1d682dca/464ce79e-a906-4590-bf78-9054e606aa041749023419612.png';
}
getSupportedChains() {
return [NonEvmChain.Bitcoin, ChainId.ETH];
}
getSupportedTokens(_sourceChain, _destChain) {
return [];
}
async getQuote(params) {
if (!this.tokens?.length) {
await this.getTokens();
}
const isFromBtc = params.fromChain === NonEvmChain.Bitcoin;
const isToBtc = params.toChain === NonEvmChain.Bitcoin;
const fromToken = isFromBtc
? { token_id: 'BTC', token_symbol: 'BTC' }
: this.tokens.find(item => {
const address = params.fromToken.isNative ? 'native' : params.fromToken.wrapped.address;
return item.network_id === 'ethereum' && address.toLowerCase() === item.token_address.toLowerCase();
});
const fromTokenId = fromToken?.token_id;
const toToken = isToBtc
? { token_id: 'BTC', token_symbol: 'BTC' }
: this.tokens.find(item => {
const address = params.toToken.isNative ? 'native' : params.toToken.wrapped.address;
return item.network_id === 'ethereum' && address.toLowerCase() === item.token_address.toLowerCase();
});
const toTokenId = toToken?.token_id;
if (!fromTokenId || !toTokenId) {
console.log('optimex tokens', this.tokens);
throw new Error(`Optimex does not support ${!fromTokenId ? params.fromToken.symbol : params.toToken.symbol}`);
}
const [quoteRes, estimateRes, token0Usd, token1Usd] = await Promise.all([
fetch(`${OPTIMEX_API}/solver/indicative-quote`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
debug: false,
from_token_amount: params.amount,
from_token_id: fromTokenId,
to_token_id: toTokenId,
affiliate_fee_bps: params.feeBps.toString(),
}),
}).then(res => res.json()),
fetch(`${OPTIMEX_API}/trades/estimate?from_token=${fromTokenId}&to_token=${toTokenId}`).then(res => res.json()),
fetch(`https://api.optimex.xyz/v1/tokens/${fromToken.token_symbol}`)
.then(res => res.json())
.then(res => res?.data?.current_price || 0),
fetch(`https://api.optimex.xyz/v1/tokens/${toToken.token_symbol}`)
.then(res => res.json())
.then(res => res?.data?.current_price || 0),
]);
let txData = null;
if (params.sender && params.recipient && (isFromBtc ? params.publicKey : true)) {
const tradeTimeout = new Date();
tradeTimeout.setHours(tradeTimeout.getHours() + 2);
const scriptTimeout = new Date();
scriptTimeout.setHours(scriptTimeout.getHours() + 24);
const res = await fetch(`${OPTIMEX_API}/trades/initiate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
session_id: quoteRes.data.session_id,
from_user_address: params.sender,
amount_in: params.amount,
min_amount_out: ((BigInt(quoteRes.data.best_quote_after_fees) * (10000n - BigInt(params.slippage))) /
10000n).toString(),
to_user_address: params.recipient,
user_refund_pubkey: params.fromChain === NonEvmChain.Bitcoin ? params.publicKey : params.sender,
user_refund_address: params.sender,
creator_public_key: params.fromChain === NonEvmChain.Bitcoin ? params.publicKey : params.sender,
from_wallet_address: params.sender,
trade_timeout: Math.floor(tradeTimeout.getTime() / 1000),
script_timeout: Math.floor(scriptTimeout.getTime() / 1000),
affiliate_info: [
{
provider: 'KyberSwap',
rate: params.feeBps.toString(),
receiver: CROSS_CHAIN_FEE_RECEIVER,
network: 'ethereum',
},
],
}),
}).then(res => res.json());
if (res.data.deposit_address) {
txData = res.data;
}
}
const formattedOutputAmount = formatUnits(BigInt(quoteRes.data.best_quote_after_fees), params.toToken.decimals);
const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals);
const inputUsd = token0Usd * +formattedInputAmount;
const outputUsd = token1Usd * +formattedOutputAmount;
return {
quoteParams: params,
outputAmount: BigInt(quoteRes.data.best_quote_after_fees),
formattedOutputAmount,
inputUsd,
outputUsd,
priceImpact: !inputUsd || !outputUsd ? NaN : ((inputUsd - outputUsd) * 100) / inputUsd,
rate: +formattedOutputAmount / +formattedInputAmount,
gasFeeUsd: 0,
timeEstimate: estimateRes.data.estimated_time,
contractAddress: txData?.deposit_address || ZERO_ADDRESS,
rawQuote: { ...quoteRes.data, txData },
protocolFee: 0,
platformFeePercent: (params.feeBps * 100) / 10000,
};
}
async executeSwap({ quote }, walletClient, _nearWallet, sendBtcFn) {
const params = {
sender: quote.quoteParams.sender,
id: quote.rawQuote.txData.trade_id,
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(),
};
if (quote.quoteParams.fromChain === NonEvmChain.Bitcoin) {
if (!sendBtcFn)
throw new Error('sendBtcFn is not defined');
const res = await sendBtcFn({
recipient: quote.rawQuote.txData.deposit_address,
amount: quote.quoteParams.amount,
}).catch(e => {
throw e;
});
await fetch(`${OPTIMEX_API}/trades/${quote.rawQuote.txData.trade_id}/submit-tx`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tx_id: res,
}),
}).catch(e => {
console.log('submit tx error for optimex', e);
});
return {
...params,
sourceTxHash: res,
};
}
if (!walletClient || !walletClient.account)
throw new Error('Not connected');
const account = walletClient.account?.address;
const hash = await walletClient.sendTransaction({
to: quote.rawQuote.txData.deposit_address,
value: quote.quoteParams.fromToken.isNative ? BigInt(quote.quoteParams.amount) : undefined,
data: quote.rawQuote.txData.payload,
chain: undefined,
account,
kzg: undefined,
});
await fetch(`${OPTIMEX_API}/trades/${quote.rawQuote.txData.trade_id}/submit-tx`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tx_id: hash,
}),
}).catch(e => {
console.log('submit tx error for optimex', e);
});
return {
...params,
sourceTxHash: hash,
};
}
async getTransactionStatus(p) {
const res = await fetch(`${OPTIMEX_API}/trades/${p.id}`).then(res => res.json());
return {
txHash: res.data?.payment_bundle?.settlement_tx || '',
status: ['Done', 'PaymentConfirmed'].includes(res?.data?.state)
? 'Success'
: ['Aborted', 'ToBeAborted', 'Failed', 'Failure', 'UserCancelled'].includes(res?.data?.state)
? 'Failed'
: res?.data?.state === 'Refunded'
? 'Refunded'
: 'Processing',
};
}
}
//# sourceMappingURL=OptimexAdapter.js.map