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.

214 lines 10.2 kB
import { Transaction, VersionedTransaction } from '@solana/web3.js'; import { getPublicClient } from 'wagmi/actions'; import { formatUnits } from 'viem'; import { useConfig } from 'wagmi'; import { CROSS_CHAIN_FEE_RECEIVER, CROSS_CHAIN_FEE_RECEIVER_SOLANA, ZERO_ADDRESS } from '../constants/index.js'; import { MAINNET_NETWORKS } from '../constants/index.js'; import { BaseSwapAdapter, NOT_SUPPORTED_CHAINS_PRICE_SERVICE, } from './BaseSwapAdapter.js'; export class OrbiterAdapter extends BaseSwapAdapter { constructor() { super(); } getName() { return 'Orbiter'; } getIcon() { return 'https://www.orbiter.finance/favicon.ico'; } getSupportedChains() { return [...MAINNET_NETWORKS]; } getSupportedTokens(_sourceChain, _destChain) { return []; } async getQuote(params) { const fromToken = params.fromToken; const toToken = params.toToken; const body = { sourceChainId: params.fromChain === 1151111081099710 ? 'SOLANA_MAIN' : params.fromChain.toString(), destChainId: params.toChain === 1151111081099710 ? 'SOLANA_MAIN' : params.toChain.toString(), sourceToken: params.fromChain === 1151111081099710 ? params.fromToken.id : fromToken.isNative ? ZERO_ADDRESS : fromToken.address, destToken: params.toChain === 1151111081099710 ? params.toToken.id : toToken.isNative ? ZERO_ADDRESS : toToken.address, amount: params.amount.toString(), userAddress: params.sender, targetRecipient: params.recipient, slippage: params.slippage / 10000, feeConfig: { feeRecipient: params.fromChain === 1151111081099710 ? CROSS_CHAIN_FEE_RECEIVER_SOLANA : CROSS_CHAIN_FEE_RECEIVER, feePercent: (params.feeBps / 10000).toString(), }, channel: 'kyberswap', }; const res = await fetch(`https://api.orbiter.finance/quote`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(body), }) .then(res => res.json()) .then(res => res.result); const formattedOutputAmount = formatUnits(BigInt(res.details?.destTokenAmount || '0'), params.toToken.decimals); const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals); const inputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.fromChain) ? Number(res.details?.sourceAmountUSD || 0) : params.tokenInUsd * +formattedInputAmount; const outputUsd = NOT_SUPPORTED_CHAINS_PRICE_SERVICE.includes(params.toChain) ? Number(res.details?.destAmountUSD || 0) : params.tokenOutUsd * +formattedOutputAmount; const haveApproval = res.steps.some((step) => step.action === 'approve'); const approvalContract = res.steps.find((step) => step.action === 'swap' || step.action === 'bridge'); return { quoteParams: params, outputAmount: BigInt(res.details?.destTokenAmount || '0'), formattedOutputAmount, inputUsd, outputUsd, priceImpact: !inputUsd || !outputUsd ? NaN : ((inputUsd - outputUsd) * 100) / inputUsd, //rate: Number(resp.details?.rate || 0), rate: +formattedOutputAmount / +formattedInputAmount, gasFeeUsd: 0, timeEstimate: 10, contractAddress: haveApproval ? approvalContract?.tx.to || ZERO_ADDRESS : ZERO_ADDRESS, rawQuote: res, protocolFee: 0, platformFeePercent: (params.feeBps * 100) / 10000, }; } async executeSwap({ quote }, walletClient, _nearWallet, _sendBtcFn, sendSolanaFn, solanaConnection) { if (quote.quoteParams.fromChain === 1151111081099710) { if (!solanaConnection || !sendSolanaFn) throw new Error('Connection is not defined for Solana swap'); const encodedData = quote.rawQuote.steps?.[0]?.tx?.data; const txBuffer = Buffer.from(encodedData, 'base64'); // Try to deserialize as VersionedTransaction first let transaction; try { transaction = VersionedTransaction.deserialize(txBuffer); console.log('Parsed as VersionedTransaction'); } catch (versionedError) { console.log('Failed to parse as VersionedTransaction, trying legacy Transaction'); try { transaction = Transaction.from(txBuffer); console.log('Parsed as legacy Transaction'); } catch (legacyError) { throw new Error('Could not parse transaction as either VersionedTransaction or legacy Transaction'); } } console.log('Transaction parsed successfully:', transaction); const waitForConfirmation = async (txId) => { try { const latestBlockhash = await solanaConnection.getLatestBlockhash(); // Wait for confirmation with timeout const confirmation = await Promise.race([ solanaConnection.confirmTransaction({ signature: txId, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, }, 'confirmed'), new Promise((_, reject) => setTimeout(() => reject(new Error('Transaction confirmation timeout')), 60000)), ]); const confirmationResult = confirmation; if (confirmationResult.value.err) { throw new Error(`Transaction failed: ${JSON.stringify(confirmationResult.value.err)}`); } console.log('Transaction confirmed successfully!'); } catch (confirmError) { console.error('Transaction confirmation failed:', confirmError); // Check if transaction actually succeeded despite timeout const txStatus = await solanaConnection.getSignatureStatus(txId); if (txStatus?.value?.confirmationStatus !== 'confirmed') { throw new Error(`Transaction was not confirmed: ${confirmError.message}`); } } }; // Send through wallet adapter const signature = await sendSolanaFn(transaction, solanaConnection); await waitForConfirmation(signature); return { sender: quote.quoteParams.sender, id: signature, sourceTxHash: signature, 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(), }; } const steps = quote.rawQuote.steps.filter((st) => st.action !== 'approve'); // already approve before const account = walletClient.account?.address; if (!account) throw new Error('WalletClient account is not defined'); const txs = await Promise.all(steps.map(async (step) => { const tx = await walletClient.sendTransaction({ chain: undefined, account, to: step.tx.to, value: BigInt(step.tx.value), data: step.tx.data, kzg: undefined, }); return tx; })); if (!txs || txs.length === 0) throw new Error('No transactions found after executing swap steps'); return { sender: quote.quoteParams.sender, sourceTxHash: txs[txs.length - 1], adapter: this.getName(), id: txs[txs.length - 1], 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(), }; } async getTransactionStatus(p) { if (p.sourceChain !== 1151111081099710) { const publicClient = getPublicClient(useConfig(), { chainId: p.sourceChain, }); const receipt = await publicClient?.getTransactionReceipt({ hash: p.id, }); if (receipt.status === 'reverted') { return { txHash: '', status: 'Failed', }; } } const res = await fetch(`https://api.orbiter.finance/transaction/${p.id}`).then(r => r.json()); return { txHash: res.result.targetId || '', // this is from orbiter source code, their docs dont have info for this // https://github.com/Orbiter-Finance/OrbiterFE-V2/blob/2b35399aad581e666c45a829e0151485f4007c93/src/views/statistics/LatestTransactions.vue#L115 status: res.result.opStatus === -1 ? 'Failed' : res.result.opStatus === 80 ? 'Refunded' : res.result.opStatus !== 98 && res.result.opStatus !== 99 ? 'Processing' : 'Success', }; } } //# sourceMappingURL=OrbiterAdapter.js.map