Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/kyberswap-interface/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@ VITE_AFFILIATE_SERVICE=https://affiliate-service.kyberswap.com/api
VITE_SOLANA_RPC=https://solana-rpc.kyberswap.com

VITE_SMART_EXIT_API_URL=https://conditional-order.kyberswap.com/api
VITE_CROSSCHAIN_AGGREGATOR_API=https://crosschain-aggregator.kyberswap.com
# VITE_CROSSCHAIN_AGGREGATOR_API=https://pre-crosschain-aggregator.kyberengineering.io
# VITE_CROSSCHAIN_AGGREGATOR_API=https://crosschain-aggregator.kyberswap.com
VITE_CROSSCHAIN_AGGREGATOR_API=https://pre-crosschain-aggregator.kyberengineering.io
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import axios from 'axios'

import { AcrossDepositStatusResponse } from 'pages/CrossChainSwap/adapters/AcrossAdapter/types'

const ACROSS_API_BASE_URL = 'https://app.across.to/api'

export const getAcrossDepositStatus = async (depositTxnRef: string): Promise<AcrossDepositStatusResponse> => {
const { data, status } = await axios.get<AcrossDepositStatusResponse>(`${ACROSS_API_BASE_URL}/deposit/status`, {
params: { depositTxnRef },
validateStatus: status => status < 500,
})

if (status >= 400 && !data?.error) {
throw new Error(`Across deposit status failed with HTTP ${status}`)
}

return data
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { AcrossClient, createAcrossClient } from '@across-protocol/app-sdk'
import { ChainId, Currency, Token } from '@kyberswap/ks-sdk-core'
import { WalletAdapterProps } from '@solana/wallet-adapter-base'
import { Connection } from '@solana/web3.js'
import { WalletClient, formatUnits } from 'viem'
import axios from 'axios'
import { type Address, WalletClient, formatUnits } from 'viem'
import {
arbitrum,
base,
Expand All @@ -21,6 +22,13 @@ import {

import { CROSS_CHAIN_FEE_RECEIVER, ZERO_ADDRESS } from 'constants/index'
import { NETWORKS_INFO } from 'hooks/useChainsConfig'
import { getAcrossDepositStatus } from 'pages/CrossChainSwap/adapters/AcrossAdapter/api'
import {
AcrossSuggestedFeesQuote,
AcrossSwapQuote,
AcrossWalletClient,
} from 'pages/CrossChainSwap/adapters/AcrossAdapter/types'
import { getAcrossFillTxHash, mapAcrossDepositStatus } from 'pages/CrossChainSwap/adapters/AcrossAdapter/utils'
import {
BaseSwapAdapter,
Chain,
Expand All @@ -39,6 +47,9 @@ import { isEvmChain } from 'utils'

const API_URL = 'https://app.across.to/api/suggested-fees'

const getAcrossTokenAddress = (token: Currency): Address =>
(token.isNative ? ZERO_ADDRESS : token.wrapped.address) as Address

export class AcrossAdapter extends BaseSwapAdapter {
private acrossClient: AcrossClient

Expand Down Expand Up @@ -119,35 +130,52 @@ export class AcrossAdapter extends BaseSwapAdapter {

async getQuote(params: QuoteParams): Promise<NormalizedQuote> {
try {
let res
const isFromSol = params.fromChain === NonEvmChain.Solana
let rawQuote: AcrossSuggestedFeesQuote | AcrossSwapQuote
let outputAmount: bigint
let contractAddress: string
let timeEstimate: number
let gasFeeUsd = 0

if (isFromSol && isEvmChain(params.toChain)) {
const fromToken = params.fromToken as SolanaToken
const toToken = params.toToken as Token
const reqParams = new URLSearchParams({
inputToken: (params.fromToken as SolanaToken).id,
outputToken: (params.toToken as Token).wrapped.address,
inputToken: fromToken.id,
outputToken: toToken.wrapped.address,
destinationChainId: params.toChain.toString(),
originChainId: '34268394551451',
amount: params.amount,
skipAmountLimit: 'true',
allowUnmatchedDecimals: 'true',
})

res = await fetch(`${API_URL}?${reqParams}`).then(res => res.json())
const { data } = await axios.get<AcrossSuggestedFeesQuote>(API_URL, { params: reqParams })
rawQuote = data
outputAmount = BigInt(data.outputAmount)
contractAddress = ZERO_ADDRESS
timeEstimate = data.estimatedFillTimeSec
} else {
const p = params as EvmQuoteParams
res = await this.acrossClient.getSwapQuote({
const quoteParams = params as EvmQuoteParams
const swapQuote = await this.acrossClient.getSwapQuote({
route: {
originChainId: +params.fromChain,
destinationChainId: +params.toChain,
inputToken: (p.fromToken.isNative ? ZERO_ADDRESS : p.fromToken.wrapped.address) as `0x${string}`,
outputToken: (p.toToken.isNative ? ZERO_ADDRESS : p.toToken.wrapped.address) as `0x${string}`,
inputToken: getAcrossTokenAddress(quoteParams.fromToken),
outputToken: getAcrossTokenAddress(quoteParams.toToken),
},
amount: params.amount,
appFee: params.feeBps / 10_000,
appFeeRecipient: CROSS_CHAIN_FEE_RECEIVER,
slippage: params.slippage / 10_000, // https://docs.across.to/reference/api-reference#get-swap-approval
depositor: params.sender,
})

rawQuote = swapQuote
outputAmount = BigInt(swapQuote.expectedOutputAmount)
contractAddress = swapQuote.checks.allowance.spender
timeEstimate = swapQuote.expectedFillTime
gasFeeUsd = Number(swapQuote.fees.originGas.amountUsd || 0)
}

// across only have bridge then we can treat token in and out price usd are the same in case price service is not supported
Expand All @@ -161,7 +189,6 @@ export class AcrossAdapter extends BaseSwapAdapter {
? params.tokenInUsd
: params.tokenOutUsd

const outputAmount = BigInt(isFromSol ? res.outputAmount : res.expectedOutputAmount)
const formattedOutputAmount = formatUnits(outputAmount, params.toToken.decimals)
const formattedInputAmount = formatUnits(BigInt(params.amount), params.fromToken.decimals)

Expand All @@ -172,57 +199,59 @@ export class AcrossAdapter extends BaseSwapAdapter {
quoteParams: params,
outputAmount,
formattedOutputAmount,
inputUsd: tokenInUsd * +formatUnits(BigInt(params.amount), params.fromToken.decimals),
outputUsd: tokenOutUsd * +formattedOutputAmount,
inputUsd,
outputUsd,
rate: +formattedOutputAmount / +formattedInputAmount,
timeEstimate: isFromSol ? res.estimatedFillTimeSec : res.expectedFillTime,
timeEstimate,
priceImpact: !inputUsd || !outputUsd ? NaN : ((inputUsd - outputUsd) * 100) / inputUsd,
// TODO: what is gas fee for across
gasFeeUsd: 0,
contractAddress: isFromSol ? ZERO_ADDRESS : res.checks.allowance.spender,
rawQuote: res,
gasFeeUsd,
contractAddress,
rawQuote,

protocolFee: 0,
platformFeePercent: (params.feeBps * 100) / 10_000,
}
} catch (e) {
console.log('Across getQuote error', e)
throw e
} catch (error) {
console.log('Across getQuote error', error)
throw error
}
}

async executeSwap(
quote: Quote,
walletClient: WalletClient,
_nearWalletClient?: any,
_nearWalletClient?: unknown,
_sendBtcFn?: (params: { recipient: string; amount: string | number }) => Promise<string>,
_sendTransaction?: WalletAdapterProps['sendTransaction'],
_connection?: Connection,
): Promise<NormalizedTxResponse> {
const normalizedQuote = quote.quote
const quoteParams = normalizedQuote.quoteParams

// For EVM chains, use the original implementation
return new Promise<NormalizedTxResponse>((resolve, reject) => {
this.acrossClient
.executeSwapQuote({
walletClient: walletClient as any,
swapQuote: quote.quote.rawQuote as any,
walletClient: walletClient as AcrossWalletClient,
swapQuote: normalizedQuote.rawQuote as AcrossSwapQuote,
onProgress: progress => {
if (progress.step === 'swap' && 'txHash' in progress) {
resolve({
sender: quote.quote.quoteParams.sender,
sender: 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,
sourceChain: quoteParams.fromChain,
targetChain: quoteParams.toChain,
inputAmount: quoteParams.amount,
outputAmount: normalizedQuote.outputAmount.toString(),
sourceToken: quoteParams.fromToken,
targetToken: quoteParams.toToken,
timestamp: new Date().getTime(),
amountInUsd: quote.quote.inputUsd,
amountOutUsd: quote.quote.outputUsd,
platformFeePercent: quote.quote.platformFeePercent,
recipient: quote.quote.quoteParams.recipient,
amountInUsd: normalizedQuote.inputUsd,
amountOutUsd: normalizedQuote.outputUsd,
platformFeePercent: normalizedQuote.platformFeePercent,
recipient: quoteParams.recipient,
})
}
},
Expand All @@ -232,12 +261,11 @@ export class AcrossAdapter extends BaseSwapAdapter {
}
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(),
)
const res = await getAcrossDepositStatus(params.sourceTxHash)

return {
txHash: res.fillTx || '',
status: res.status === 'refunded' ? 'Refunded' : res.status === 'filled' ? 'Success' : 'Processing',
txHash: getAcrossFillTxHash(res),
status: mapAcrossDepositStatus(res, { txTimestamp: params.timestamp }),
}
} catch (error) {
console.error('Error fetching transaction status:', error)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { AcrossClient, ExecuteQuoteParams, ExecuteSwapQuoteParams } from '@across-protocol/app-sdk'

export type AcrossDepositStatus = 'pending' | 'filled' | 'expired' | 'refunded' | 'slowFillRequested'

export interface AcrossDepositStatusResponse {
status?: AcrossDepositStatus
fillTxnRef?: string
fillTx?: string
originChainId?: number
destinationChainId?: number
depositId?: string | number
depositTxnRef?: string
depositRefundTxnRef?: string
actionsSucceeded?: boolean
error?: string
message?: string
}

export interface AcrossSuggestedFeesQuote {
outputAmount: string
estimatedFillTimeSec: number
}

export type AcrossSwapQuote = Awaited<ReturnType<AcrossClient['getSwapQuote']>>
export type AcrossWalletClient = ExecuteQuoteParams['walletClient']
export type AcrossDeposit = ExecuteQuoteParams['deposit']
export type AcrossSwapExecutionProgress = Parameters<NonNullable<ExecuteSwapQuoteParams['onProgress']>>[0]
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AcrossDepositStatusResponse } from 'pages/CrossChainSwap/adapters/AcrossAdapter/types'
import { SwapStatus } from 'pages/CrossChainSwap/adapters/BaseSwapAdapter'

const ACROSS_STATUS_ERROR_GRACE_PERIOD = 2 * 60 * 60 * 1_000

interface AcrossDepositStatusOptions {
txTimestamp?: number
now?: number
}

export const getAcrossFillTxHash = (statusResponse: AcrossDepositStatusResponse): string => {
return statusResponse.fillTxnRef || statusResponse.fillTx || ''
}

export const mapAcrossDepositStatus = (
statusResponse: AcrossDepositStatusResponse,
options: AcrossDepositStatusOptions = {},
): SwapStatus['status'] => {
if (statusResponse.error) {
const { txTimestamp, now = Date.now() } = options
const isWithinIndexingGracePeriod = txTimestamp ? now - txTimestamp < ACROSS_STATUS_ERROR_GRACE_PERIOD : false

if (isWithinIndexingGracePeriod) {
return 'Processing'
}

return 'Failed'
}

switch (statusResponse.status) {
case 'filled':
return 'Success'
case 'refunded':
return 'Refunded'
case 'expired':
return 'Failed'
case 'pending':
case 'slowFillRequested':
default:
return 'Processing'
}
}
Loading
Loading