From 49b351b3be43e5b9ab7733389804be4afc8dc9c8 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 3 Oct 2024 17:23:12 +0300 Subject: [PATCH 1/4] add across cross-chain provider --- .../evm-web3-public/evm-web3-public.ts | 19 ++ .../constants/cross-chain-providers.ts | 4 +- .../models/cross-chain-trade-type.ts | 3 +- .../across-cross-chain-provider.ts | 158 ++++++++++ .../across-cross-chain-trade.ts | 297 ++++++++++++++++++ .../constants/across-ccr-supported-chains.ts | 16 + .../constants/across-contract-addresses.ts | 16 + .../constants/across-deposit-abi.ts | 30 ++ .../models/across-fee-quote.ts | 38 +++ .../models/across-tx-status.ts | 4 + .../services/across-api-service.ts | 57 ++++ .../cross-chain-status-manager.ts | 12 +- .../models/cross-chain-trade-data.ts | 5 + 13 files changed, 656 insertions(+), 3 deletions(-) create mode 100644 src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts create mode 100644 src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-trade.ts create mode 100644 src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-ccr-supported-chains.ts create mode 100644 src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-contract-addresses.ts create mode 100644 src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-deposit-abi.ts create mode 100644 src/features/cross-chain/calculation-manager/providers/across-provider/models/across-fee-quote.ts create mode 100644 src/features/cross-chain/calculation-manager/providers/across-provider/models/across-tx-status.ts create mode 100644 src/features/cross-chain/calculation-manager/providers/across-provider/services/across-api-service.ts diff --git a/src/core/blockchain/web3-public-service/web3-public/evm-web3-public/evm-web3-public.ts b/src/core/blockchain/web3-public-service/web3-public/evm-web3-public/evm-web3-public.ts index 9c41c91fa48..9dfa25ed275 100644 --- a/src/core/blockchain/web3-public-service/web3-public/evm-web3-public/evm-web3-public.ts +++ b/src/core/blockchain/web3-public-service/web3-public/evm-web3-public/evm-web3-public.ts @@ -1,3 +1,4 @@ +import { AbiInput } from '@1inch/limit-order-protocol-utils'; import BigNumber from 'bignumber.js'; import { RubicSdkError, TimeoutError } from 'src/common/errors'; import { nativeTokensList } from 'src/common/tokens/constants/native-tokens'; @@ -683,4 +684,22 @@ export class EvmWeb3Public extends Web3Public { tokens.splice(nativeTokenIndex, 0, nativeToken); return tokens; } + + public async getTxDecodedData(hash: string, inputs: AbiInput[], key: string): Promise { + const receipt = await this.getTransactionReceipt(hash); + let decodedData: { [key: string]: string } = {}; + + for (const log of receipt.logs) { + try { + const data = this.web3.eth.abi.decodeLog(inputs, log.data, log.topics); + if (data?.[key]) { + decodedData = data; + } + } catch { + continue; + } + } + + return decodedData[key]!; + } } diff --git a/src/features/cross-chain/calculation-manager/constants/cross-chain-providers.ts b/src/features/cross-chain/calculation-manager/constants/cross-chain-providers.ts index 62ee428d6c1..86a2ea6216d 100644 --- a/src/features/cross-chain/calculation-manager/constants/cross-chain-providers.ts +++ b/src/features/cross-chain/calculation-manager/constants/cross-chain-providers.ts @@ -11,6 +11,7 @@ import { SquidrouterCrossChainProvider } from 'src/features/cross-chain/calculat import { SymbiosisCrossChainProvider } from 'src/features/cross-chain/calculation-manager/providers/symbiosis-provider/symbiosis-cross-chain-provider'; import { XyCrossChainProvider } from 'src/features/cross-chain/calculation-manager/providers/xy-provider/xy-cross-chain-provider'; +import { AcrossCrossChainProvider } from '../providers/across-provider/across-cross-chain-provider'; import { EddyBridgeProvider } from '../providers/eddy-bridge/eddy-bridge-provider'; import { LayerZeroBridgeProvider } from '../providers/layerzero-bridge/layerzero-bridge-provider'; import { MesonCrossChainProvider } from '../providers/meson-provider/meson-cross-chain-provider'; @@ -37,7 +38,8 @@ const proxyProviders = [ MesonCrossChainProvider, OwlToBridgeProvider, EddyBridgeProvider, - RouterCrossChainProvider + RouterCrossChainProvider, + AcrossCrossChainProvider ] as const; const nonProxyProviders = [ diff --git a/src/features/cross-chain/calculation-manager/models/cross-chain-trade-type.ts b/src/features/cross-chain/calculation-manager/models/cross-chain-trade-type.ts index 107aa327ca5..25be4a9b20f 100644 --- a/src/features/cross-chain/calculation-manager/models/cross-chain-trade-type.ts +++ b/src/features/cross-chain/calculation-manager/models/cross-chain-trade-type.ts @@ -21,7 +21,8 @@ export const CROSS_CHAIN_TRADE_TYPE = { ARCHON_BRIDGE: 'archon_bridge', MESON: 'meson', EDDY_BRIDGE: 'eddy_bridge', - ROUTER: 'router' + ROUTER: 'router', + ACROSS: 'across' } as const; export type CrossChainTradeType = diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts new file mode 100644 index 00000000000..eecf1d6da8f --- /dev/null +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts @@ -0,0 +1,158 @@ +import { MaxAmountError, MinAmountError } from 'src/common/errors'; +import { PriceToken, PriceTokenAmount } from 'src/common/tokens'; +import { wrappedAddress } from 'src/common/tokens/constants/wrapped-addresses'; +import { parseError } from 'src/common/utils/errors'; +import { BlockchainName, EvmBlockchainName } from 'src/core/blockchain/models/blockchain-name'; +import { blockchainId } from 'src/core/blockchain/utils/blockchains-info/constants/blockchain-id'; +import { Web3Pure } from 'src/core/blockchain/web3-pure/web3-pure'; +import { getFromWithoutFee } from 'src/features/common/utils/get-from-without-fee'; + +import { RequiredCrossChainOptions } from '../../models/cross-chain-options'; +import { CROSS_CHAIN_TRADE_TYPE } from '../../models/cross-chain-trade-type'; +import { CrossChainProvider } from '../common/cross-chain-provider'; +import { CalculationResult } from '../common/models/calculation-result'; +import { FeeInfo } from '../common/models/fee-info'; +import { RubicStep } from '../common/models/rubicStep'; +import { ProxyCrossChainEvmTrade } from '../common/proxy-cross-chain-evm-facade/proxy-cross-chain-evm-trade'; +import { AcrossCrossChainTrade } from './across-cross-chain-trade'; +import { + AccrossCcrSupportedChains, + acrossCcrSupportedChains +} from './constants/across-ccr-supported-chains'; +import { AcrossAmountLimits, AcrossFeeQuoteRequestParams } from './models/across-fee-quote'; +import { AcrossApiService } from './services/across-api-service'; + +export class AcrossCrossChainProvider extends CrossChainProvider { + public readonly type = CROSS_CHAIN_TRADE_TYPE.ACROSS; + + public isSupportedBlockchain(blockchain: BlockchainName): boolean { + return acrossCcrSupportedChains.some(supportedChain => supportedChain === blockchain); + } + + public async calculate( + from: PriceTokenAmount, + toToken: PriceToken, + options: RequiredCrossChainOptions + ): Promise { + const fromBlockchain = from.blockchain as AccrossCcrSupportedChains; + const useProxy = false; + + try { + const feeInfo = await this.getFeeInfo( + fromBlockchain, + options.providerAddress, + from, + useProxy + ); + const fromWithoutFee = getFromWithoutFee( + from, + feeInfo.rubicProxy?.platformFee?.percent + ); + + const quoteRequestParams: AcrossFeeQuoteRequestParams = { + originChainId: blockchainId[fromBlockchain], + destinationChainId: blockchainId[toToken.blockchain], + inputToken: from.isNative ? wrappedAddress[from.blockchain]! : from.address, + outputToken: toToken.isNative + ? wrappedAddress[toToken.blockchain]! + : toToken.address, + amount: fromWithoutFee.stringWeiAmount, + skipAmountLimit: true + }; + + const quote = await AcrossApiService.getFeeQuote(quoteRequestParams); + const toAmount = fromWithoutFee.weiAmount.minus(quote.totalRelayFee.total); + + const to = new PriceTokenAmount({ + ...toToken.asStruct, + weiAmount: toAmount + }); + + const routePath = await this.getRoutePath(from, to); + + const gasData = + options.gasCalculation === 'enabled' + ? await AcrossCrossChainTrade.getGasData( + from, + to, + feeInfo, + options.providerAddress, + quoteRequestParams + ) + : null; + + const trade = new AcrossCrossChainTrade( + { + from, + to, + toTokenAmountMin: to.tokenAmount, + priceImpact: from.calculatePriceImpactPercent(to), + gasData, + feeInfo, + slippage: options.slippageTolerance, + acrossFeeQuoteRequestParams: quoteRequestParams + }, + options.providerAddress, + routePath + ); + + try { + this.checkMinMaxAmounts(from, quote.limits); + } catch (err) { + return { + trade, + tradeType: this.type, + error: err + }; + } + + return { + trade, + tradeType: this.type + }; + } catch (err) { + return { + trade: null, + tradeType: this.type, + error: parseError(err) + }; + } + } + + protected async getRoutePath( + fromToken: PriceTokenAmount, + toToken: PriceTokenAmount + ): Promise { + return [{ type: 'cross-chain', provider: this.type, path: [fromToken, toToken] }]; + } + + protected async getFeeInfo( + fromBlockchain: AccrossCcrSupportedChains, + providerAddress: string, + percentFeeToken: PriceTokenAmount, + useProxy: boolean + ): Promise { + return ProxyCrossChainEvmTrade.getFeeInfo( + fromBlockchain, + providerAddress, + percentFeeToken, + useProxy + ); + } + + private checkMinMaxAmounts(from: PriceTokenAmount, limits: AcrossAmountLimits): void | never { + if (from.weiAmount.lt(limits.minDeposit)) { + throw new MinAmountError( + Web3Pure.fromWei(limits.minDeposit, from.decimals), + from.symbol + ); + } + + if (from.weiAmount.gt(limits.maxDeposit)) { + throw new MaxAmountError( + Web3Pure.fromWei(limits.maxDeposit, from.decimals), + from.symbol + ); + } + } +} diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-trade.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-trade.ts new file mode 100644 index 00000000000..e43397eabff --- /dev/null +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-trade.ts @@ -0,0 +1,297 @@ +import BigNumber from 'bignumber.js'; +import { FailedToCheckForTransactionReceiptError } from 'src/common/errors'; +import { PriceTokenAmount } from 'src/common/tokens'; +import { EvmBlockchainName } from 'src/core/blockchain/models/blockchain-name'; +import { BlockchainsInfo } from 'src/core/blockchain/utils/blockchains-info/blockchains-info'; +import { blockchainId } from 'src/core/blockchain/utils/blockchains-info/constants/blockchain-id'; +import { EvmWeb3Pure } from 'src/core/blockchain/web3-pure/typed-web3-pure/evm-web3-pure/evm-web3-pure'; +import { EvmEncodeConfig } from 'src/core/blockchain/web3-pure/typed-web3-pure/evm-web3-pure/models/evm-encode-config'; +import { Injector } from 'src/core/injector/injector'; +import { ContractParams } from 'src/features/common/models/contract-params'; +import { SwapTransactionOptions } from 'src/features/common/models/swap-transaction-options'; + +import { CROSS_CHAIN_TRADE_TYPE } from '../../models/cross-chain-trade-type'; +import { getCrossChainGasData } from '../../utils/get-cross-chain-gas-data'; +import { rubicProxyContractAddress } from '../common/constants/rubic-proxy-contract-address'; +import { evmCommonCrossChainAbi } from '../common/emv-cross-chain-trade/constants/evm-common-cross-chain-abi'; +import { gatewayRubicCrossChainAbi } from '../common/emv-cross-chain-trade/constants/gateway-rubic-cross-chain-abi'; +import { EvmCrossChainTrade } from '../common/emv-cross-chain-trade/evm-cross-chain-trade'; +import { GasData } from '../common/emv-cross-chain-trade/models/gas-data'; +import { BRIDGE_TYPE } from '../common/models/bridge-type'; +import { FeeInfo } from '../common/models/fee-info'; +import { GetContractParamsOptions } from '../common/models/get-contract-params-options'; +import { OnChainSubtype } from '../common/models/on-chain-subtype'; +import { RubicStep } from '../common/models/rubicStep'; +import { TradeInfo } from '../common/models/trade-info'; +import { ProxyCrossChainEvmTrade } from '../common/proxy-cross-chain-evm-facade/proxy-cross-chain-evm-trade'; +import { AccrossCcrSupportedChains } from './constants/across-ccr-supported-chains'; +import { acrossContractAddresses } from './constants/across-contract-addresses'; +import { acrossDepositAbi, acrossFundsDepositedInputs } from './constants/across-deposit-abi'; +import { AcrossFeeQuoteRequestParams, AcrossFeeQuoteResponse } from './models/across-fee-quote'; +import { AcrossApiService } from './services/across-api-service'; + +export class AcrossCrossChainTrade extends EvmCrossChainTrade { + /** @internal */ + public static async getGasData( + from: PriceTokenAmount, + to: PriceTokenAmount, + feeInfo: FeeInfo, + providerAddress: string, + acrossFeeQuoteRequestParams: AcrossFeeQuoteRequestParams + ): Promise { + try { + const trade = new AcrossCrossChainTrade( + { + from, + to, + priceImpact: 0, + toTokenAmountMin: new BigNumber(0), + gasData: null, + feeInfo, + slippage: 0, + acrossFeeQuoteRequestParams + }, + providerAddress, + [] + ); + + return getCrossChainGasData(trade); + } catch (_err) { + return null; + } + } + + private readonly uniqCodeWithSeparator = '1dc0de003c'; + + public acrossDepositId = 0; + + public readonly type = CROSS_CHAIN_TRADE_TYPE.ACROSS; + + public readonly isAggregator = false; + + public readonly bridgeType = BRIDGE_TYPE.ACROSS; + + public readonly from: PriceTokenAmount; + + public readonly to: PriceTokenAmount; + + public readonly toTokenAmountMin: BigNumber; + + public readonly priceImpact: number | null; + + public readonly gasData: GasData | null; + + public readonly feeInfo: FeeInfo; + + private readonly slippage: number; + + private readonly acrossFeeQuoteRequestParams: AcrossFeeQuoteRequestParams; + + public readonly onChainSubtype: OnChainSubtype = { from: undefined, to: undefined }; + + private get fromBlockchain(): AccrossCcrSupportedChains { + return this.from.blockchain as AccrossCcrSupportedChains; + } + + protected get fromContractAddress(): string { + return this.isProxyTrade + ? rubicProxyContractAddress[this.fromBlockchain].gateway + : acrossContractAddresses[this.fromBlockchain]; + } + + protected get methodName(): string { + return 'startBridgeTokensViaGenericCrossChain'; + } + + constructor( + ccrTrade: { + from: PriceTokenAmount; + to: PriceTokenAmount; + toTokenAmountMin: BigNumber; + priceImpact: number | null; + gasData: GasData | null; + feeInfo: FeeInfo; + slippage: number; + acrossFeeQuoteRequestParams: AcrossFeeQuoteRequestParams; + }, + providerAddress: string, + routePath: RubicStep[] + ) { + super(providerAddress, routePath); + + this.from = ccrTrade.from; + this.to = ccrTrade.to; + this.toTokenAmountMin = ccrTrade.toTokenAmountMin; + this.priceImpact = ccrTrade.priceImpact; + this.gasData = ccrTrade.gasData; + this.feeInfo = ccrTrade.feeInfo; + this.slippage = ccrTrade.slippage; + this.acrossFeeQuoteRequestParams = ccrTrade.acrossFeeQuoteRequestParams; + } + + protected async getContractParams(options: GetContractParamsOptions): Promise { + const receiverAddress = options?.receiverAddress || this.walletAddress; + const { + data, + value: providerValue, + to: providerRouter + } = await this.setTransactionConfig( + false, + options?.useCacheData || false, + options?.receiverAddress || this.walletAddress + ); + + const bridgeData = ProxyCrossChainEvmTrade.getBridgeData(options, { + walletAddress: receiverAddress, + fromTokenAmount: this.from, + toTokenAmount: this.to, + srcChainTrade: null, + providerAddress: this.providerAddress, + type: `native:${this.bridgeType}`, + fromAddress: this.walletAddress + }); + + const extraNativeFee = '0'; + const providerData = await ProxyCrossChainEvmTrade.getGenericProviderData( + providerRouter, + data, + this.from.blockchain, + providerRouter, + extraNativeFee + ); + + const methodArguments = [bridgeData, providerData]; + const value = this.getSwapValue(providerValue); + const transactionConfiguration = EvmWeb3Pure.encodeMethodCall( + rubicProxyContractAddress[this.from.blockchain].router, + evmCommonCrossChainAbi, + this.methodName, + methodArguments, + value + ); + + const sendingToken = this.from.isNative ? [] : [this.from.address]; + const sendingAmount = this.from.isNative ? [] : [this.from.stringWeiAmount]; + + return { + contractAddress: rubicProxyContractAddress[this.from.blockchain].gateway, + contractAbi: gatewayRubicCrossChainAbi, + methodName: 'startViaRubic', + methodArguments: [sendingToken, sendingAmount, transactionConfiguration.data], + value + }; + } + + protected async getTransactionConfigAndAmount( + receiverAddress?: string + ): Promise<{ config: EvmEncodeConfig; amount: string }> { + const feeQuote = await AcrossApiService.getFeeQuote({ + ...this.acrossFeeQuoteRequestParams, + recipient: receiverAddress, + depositMethod: 'depositV3' + }); + + const toAmount = this.from.weiAmount.minus(feeQuote.totalRelayFee.total); + + const callData = this.getAcrossCallData(feeQuote, toAmount, receiverAddress); + + return { config: callData, amount: toAmount.toFixed() }; + } + + public getTradeInfo(): TradeInfo { + return { + estimatedGas: this.estimatedGas, + feeInfo: this.feeInfo, + priceImpact: this.priceImpact, + slippage: this.slippage * 100, + routePath: this.routePath + }; + } + + private getAcrossCallData( + quote: AcrossFeeQuoteResponse, + toAmount: BigNumber, + receiverAddress?: string + ): EvmEncodeConfig { + const args = [ + this.walletAddress, + receiverAddress || this.walletAddress, + this.acrossFeeQuoteRequestParams.inputToken, + this.acrossFeeQuoteRequestParams.outputToken, + this.from.stringWeiAmount, + toAmount.toFixed(), + this.acrossFeeQuoteRequestParams.destinationChainId.toString(), + quote.exclusiveRelayer, + Number(quote.timestamp), + this.getFillDeadline(), + Number(quote.exclusivityDeadline), + '0x' + ]; + + const evmConfig = EvmWeb3Pure.encodeMethodCall( + acrossContractAddresses[this.fromBlockchain], + acrossDepositAbi, + 'depositV3', + args, + this.from.isNative ? this.from.stringWeiAmount : '0' + ); + + return { + ...evmConfig, + data: evmConfig.data + this.uniqCodeWithSeparator + }; + } + + private getFillDeadline(): number { + return Math.round(Date.now() / 1000) + 18_000; + } + + public async swap(options: SwapTransactionOptions = {}): Promise { + if (!options?.testMode) { + await this.checkTradeErrors(); + } + await this.checkReceiverAddress( + options.receiverAddress, + !BlockchainsInfo.isEvmBlockchainName(this.to.blockchain) + ); + const method = options?.testMode ? 'sendTransaction' : 'trySendTransaction'; + + const fromAddress = this.walletAddress; + + const { data, value, to } = await this.encode({ ...options, fromAddress }); + + const { onConfirm, gasPriceOptions } = options; + let transactionHash: string; + const onTransactionHash = (hash: string) => { + if (onConfirm) { + onConfirm(hash); + } + transactionHash = hash; + }; + + try { + await this.web3Private[method](to, { + data, + value, + onTransactionHash, + gasPriceOptions, + gasLimitRatio: this.gasLimitRatio, + ...(options?.useEip155 && { + chainId: `0x${blockchainId[this.from.blockchain].toString(16)}` + }) + }); + + const encodedId = await Injector.web3PublicService + .getWeb3Public(this.fromBlockchain) + .getTxDecodedData(transactionHash!, acrossFundsDepositedInputs, 'depositId'); + this.acrossDepositId = Number(encodedId); + + return transactionHash!; + } catch (err) { + if (err instanceof FailedToCheckForTransactionReceiptError) { + return transactionHash!; + } + throw err; + } + } +} diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-ccr-supported-chains.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-ccr-supported-chains.ts new file mode 100644 index 00000000000..700abf9d9b2 --- /dev/null +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-ccr-supported-chains.ts @@ -0,0 +1,16 @@ +import { BLOCKCHAIN_NAME } from 'src/core/blockchain/models/blockchain-name'; + +export const acrossCcrSupportedChains = [ + BLOCKCHAIN_NAME.ARBITRUM, + BLOCKCHAIN_NAME.BASE, + BLOCKCHAIN_NAME.BLAST, + BLOCKCHAIN_NAME.ETHEREUM, + BLOCKCHAIN_NAME.LINEA, + BLOCKCHAIN_NAME.MODE, + BLOCKCHAIN_NAME.OPTIMISM, + BLOCKCHAIN_NAME.POLYGON, + BLOCKCHAIN_NAME.SCROLL, + BLOCKCHAIN_NAME.ZK_SYNC +]; + +export type AccrossCcrSupportedChains = (typeof acrossCcrSupportedChains)[number]; diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-contract-addresses.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-contract-addresses.ts new file mode 100644 index 00000000000..f81a1e4a288 --- /dev/null +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-contract-addresses.ts @@ -0,0 +1,16 @@ +import { BLOCKCHAIN_NAME } from 'src/core/blockchain/models/blockchain-name'; + +import { AccrossCcrSupportedChains } from './across-ccr-supported-chains'; + +export const acrossContractAddresses: Record = { + [BLOCKCHAIN_NAME.ARBITRUM]: '0xe35e9842fceaca96570b734083f4a58e8f7c5f2a', + [BLOCKCHAIN_NAME.BASE]: '0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64', + [BLOCKCHAIN_NAME.BLAST]: '0x2D509190Ed0172ba588407D4c2df918F955Cc6E1', + [BLOCKCHAIN_NAME.ETHEREUM]: '0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5', + [BLOCKCHAIN_NAME.LINEA]: '0x7E63A5f1a8F0B4d0934B2f2327DAED3F6bb2ee75', + [BLOCKCHAIN_NAME.MODE]: '0x3baD7AD0728f9917d1Bf08af5782dCbD516cDd96', + [BLOCKCHAIN_NAME.OPTIMISM]: '0x6f26Bf09B1C792e3228e5467807a900A503c0281', + [BLOCKCHAIN_NAME.POLYGON]: '0x9295ee1d8C5b022Be115A2AD3c30C72E34e7F096', + [BLOCKCHAIN_NAME.SCROLL]: '0x3bad7ad0728f9917d1bf08af5782dcbd516cdd96', + [BLOCKCHAIN_NAME.ZK_SYNC]: '0xE0B015E54d54fc84a6cB9B666099c46adE9335FF' +}; diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-deposit-abi.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-deposit-abi.ts new file mode 100644 index 00000000000..8817b0ed6b1 --- /dev/null +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/constants/across-deposit-abi.ts @@ -0,0 +1,30 @@ +import { AbiInput, AbiItem } from 'web3-utils'; + +export const acrossDepositAbi: AbiItem[] = [ + { + inputs: [ + { internalType: 'address', name: 'depositor', type: 'address' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'address', name: 'inputToken', type: 'address' }, + { internalType: 'address', name: 'outputToken', type: 'address' }, + { internalType: 'uint256', name: 'inputAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'outputAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'destinationChainId', type: 'uint256' }, + { internalType: 'address', name: 'exclusiveRelayer', type: 'address' }, + { internalType: 'uint32', name: 'quoteTimestamp', type: 'uint32' }, + { internalType: 'uint32', name: 'fillDeadline', type: 'uint32' }, + { internalType: 'uint32', name: 'exclusivityDeadline', type: 'uint32' }, + { internalType: 'bytes', name: 'message', type: 'bytes' } + ], + outputs: [], + name: 'depositV3', + type: 'function', + stateMutability: 'payable' + } +]; + +export const acrossFundsDepositedInputs: AbiInput[] = [ + { indexed: true, internalType: 'uint256', name: 'destinationChainId', type: 'uint256' }, + { indexed: true, internalType: 'uint32', name: 'depositId', type: 'uint32' }, + { indexed: true, internalType: 'address', name: 'depositor', type: 'address' } +]; diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/models/across-fee-quote.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/models/across-fee-quote.ts new file mode 100644 index 00000000000..1d0c18bb492 --- /dev/null +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/models/across-fee-quote.ts @@ -0,0 +1,38 @@ +export interface AcrossFeeQuoteRequestParams { + inputToken: string; + outputToken: string; + originChainId: number; + destinationChainId: number; + amount: string; + skipAmountLimit: boolean; + recipient?: string; + depositMethod?: string; +} + +export interface AcrossFeeQuoteResponse { + totalRelayFee: AcrossFee; + relayerCapitalFee: AcrossFee; + relayerGasFee: AcrossFee; + lpFee: AcrossFee; + timestamp: string; + isAmountTooLow: boolean; + quoteBlock: string; + spokePoolAddress: string; + exclusiveRelayer: string; + exclusivityDeadline: string; + expectedFillTimeSec: string; + limits: AcrossAmountLimits; +} + +export interface AcrossAmountLimits { + minDeposit: string; + maxDeposit: string; + maxDepositInstant: string; + maxDepositShortDelay: string; + recommendedDepositInstant: string; +} + +interface AcrossFee { + pct: string; + total: string; +} diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/models/across-tx-status.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/models/across-tx-status.ts new file mode 100644 index 00000000000..6f453fbb069 --- /dev/null +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/models/across-tx-status.ts @@ -0,0 +1,4 @@ +export interface AcrossTxStatus { + status: 'pending' | 'filled' | 'expired'; + fillTx?: string; +} diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/services/across-api-service.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/services/across-api-service.ts new file mode 100644 index 00000000000..5fe6bda8e32 --- /dev/null +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/services/across-api-service.ts @@ -0,0 +1,57 @@ +import { TX_STATUS } from 'src/core/blockchain/web3-public-service/web3-public/models/tx-status'; +import { Injector } from 'src/core/injector/injector'; +import { TxStatusData } from 'src/features/common/status-manager/models/tx-status-data'; + +import { AcrossFeeQuoteRequestParams, AcrossFeeQuoteResponse } from '../models/across-fee-quote'; +import { AcrossTxStatus } from '../models/across-tx-status'; + +export class AcrossApiService { + private static endpoint = 'https://app.across.to/api'; + + public static async getFeeQuote( + params: AcrossFeeQuoteRequestParams + ): Promise { + return Injector.httpClient.get( + `${AcrossApiService.endpoint}/suggested-fees`, + { + params: { + ...params + } + } + ); + } + + public static async getTxStatus( + originChainId: number, + depositId: number + ): Promise { + const data = await Injector.httpClient.get( + `${AcrossApiService.endpoint}/deposit/status`, + { + params: { + originChainId, + depositId + } + } + ); + + if (data.status === 'filled' && data?.fillTx) { + return { + hash: data.fillTx, + status: TX_STATUS.SUCCESS + }; + } + + if (data.status === 'expired') { + return { + hash: null, + status: TX_STATUS.REVERT + }; + } + + return { + hash: null, + status: TX_STATUS.PENDING + }; + } +} diff --git a/src/features/cross-chain/status-manager/cross-chain-status-manager.ts b/src/features/cross-chain/status-manager/cross-chain-status-manager.ts index d91ea310baf..16998a3baff 100644 --- a/src/features/cross-chain/status-manager/cross-chain-status-manager.ts +++ b/src/features/cross-chain/status-manager/cross-chain-status-manager.ts @@ -62,6 +62,7 @@ import { } from 'src/features/cross-chain/status-manager/models/statuses-api'; import { XyApiResponse } from 'src/features/cross-chain/status-manager/models/xy-api-response'; +import { AcrossApiService } from '../calculation-manager/providers/across-provider/services/across-api-service'; import { ChangeNowCrossChainApiService } from '../calculation-manager/providers/changenow-provider/services/changenow-cross-chain-api-service'; import { getEddyBridgeDstSwapStatus } from '../calculation-manager/providers/eddy-bridge/utils/get-eddy-bridge-dst-status'; import { MesonCcrApiService } from '../calculation-manager/providers/meson-provider/services/meson-cross-chain-api-service'; @@ -99,7 +100,8 @@ export class CrossChainStatusManager { [CROSS_CHAIN_TRADE_TYPE.OWL_TO_BRIDGE]: this.getOwlToDstSwapStatus, [CROSS_CHAIN_TRADE_TYPE.EDDY_BRIDGE]: this.getEddyBridgeDstSwapStatus, [CROSS_CHAIN_TRADE_TYPE.STARGATE_V2]: this.getLayerZeroDstSwapStatus, - [CROSS_CHAIN_TRADE_TYPE.ROUTER]: this.getRouterDstSwapStatus + [CROSS_CHAIN_TRADE_TYPE.ROUTER]: this.getRouterDstSwapStatus, + [CROSS_CHAIN_TRADE_TYPE.ACROSS]: this.getAcrossDstSwapStatus }; /** @@ -737,4 +739,12 @@ export class CrossChainStatusManager { return txStatusData; } + + private getAcrossDstSwapStatus(data: CrossChainTradeData): Promise { + if (!data.acrossDepositId) { + throw new RubicSdkError('Must provide acrossDepositId'); + } + const srcChainId = blockchainId[data.fromBlockchain]; + return AcrossApiService.getTxStatus(srcChainId, data.acrossDepositId); + } } diff --git a/src/features/cross-chain/status-manager/models/cross-chain-trade-data.ts b/src/features/cross-chain/status-manager/models/cross-chain-trade-data.ts index c70ff8f7575..10cc361bfc4 100644 --- a/src/features/cross-chain/status-manager/models/cross-chain-trade-data.ts +++ b/src/features/cross-chain/status-manager/models/cross-chain-trade-data.ts @@ -64,4 +64,9 @@ export interface CrossChainTradeData { * Squidrouter request id. */ squidrouterRequestId?: string; + + /** + * Across deposit id. + */ + acrossDepositId?: number; } From ca98a9336ecfe2cddbbd077ea16cbba7ad195aad Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 3 Oct 2024 17:50:25 +0300 Subject: [PATCH 2/4] fix comments --- .../evm-web3-public/evm-web3-public.ts | 6 +- .../across-cross-chain-provider.ts | 6 +- .../across-cross-chain-trade.ts | 58 +------------------ .../cross-chain-status-manager.ts | 12 ++-- 4 files changed, 16 insertions(+), 66 deletions(-) diff --git a/src/core/blockchain/web3-public-service/web3-public/evm-web3-public/evm-web3-public.ts b/src/core/blockchain/web3-public-service/web3-public/evm-web3-public/evm-web3-public.ts index 9dfa25ed275..4c86351ef93 100644 --- a/src/core/blockchain/web3-public-service/web3-public/evm-web3-public/evm-web3-public.ts +++ b/src/core/blockchain/web3-public-service/web3-public/evm-web3-public/evm-web3-public.ts @@ -685,7 +685,11 @@ export class EvmWeb3Public extends Web3Public { return tokens; } - public async getTxDecodedData(hash: string, inputs: AbiInput[], key: string): Promise { + public async getTxDecodedLogData( + hash: string, + inputs: AbiInput[], + key: string + ): Promise { const receipt = await this.getTransactionReceipt(hash); let decodedData: { [key: string]: string } = {}; diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts index eecf1d6da8f..2c748cba05d 100644 --- a/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts @@ -60,8 +60,8 @@ export class AcrossCrossChainProvider extends CrossChainProvider { skipAmountLimit: true }; - const quote = await AcrossApiService.getFeeQuote(quoteRequestParams); - const toAmount = fromWithoutFee.weiAmount.minus(quote.totalRelayFee.total); + const feeQuote = await AcrossApiService.getFeeQuote(quoteRequestParams); + const toAmount = fromWithoutFee.weiAmount.minus(feeQuote.totalRelayFee.total); const to = new PriceTokenAmount({ ...toToken.asStruct, @@ -97,7 +97,7 @@ export class AcrossCrossChainProvider extends CrossChainProvider { ); try { - this.checkMinMaxAmounts(from, quote.limits); + this.checkMinMaxAmounts(from, feeQuote.limits); } catch (err) { return { trade, diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-trade.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-trade.ts index e43397eabff..b12e90c6678 100644 --- a/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-trade.ts +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-trade.ts @@ -1,14 +1,9 @@ import BigNumber from 'bignumber.js'; -import { FailedToCheckForTransactionReceiptError } from 'src/common/errors'; import { PriceTokenAmount } from 'src/common/tokens'; import { EvmBlockchainName } from 'src/core/blockchain/models/blockchain-name'; -import { BlockchainsInfo } from 'src/core/blockchain/utils/blockchains-info/blockchains-info'; -import { blockchainId } from 'src/core/blockchain/utils/blockchains-info/constants/blockchain-id'; import { EvmWeb3Pure } from 'src/core/blockchain/web3-pure/typed-web3-pure/evm-web3-pure/evm-web3-pure'; import { EvmEncodeConfig } from 'src/core/blockchain/web3-pure/typed-web3-pure/evm-web3-pure/models/evm-encode-config'; -import { Injector } from 'src/core/injector/injector'; import { ContractParams } from 'src/features/common/models/contract-params'; -import { SwapTransactionOptions } from 'src/features/common/models/swap-transaction-options'; import { CROSS_CHAIN_TRADE_TYPE } from '../../models/cross-chain-trade-type'; import { getCrossChainGasData } from '../../utils/get-cross-chain-gas-data'; @@ -26,7 +21,7 @@ import { TradeInfo } from '../common/models/trade-info'; import { ProxyCrossChainEvmTrade } from '../common/proxy-cross-chain-evm-facade/proxy-cross-chain-evm-trade'; import { AccrossCcrSupportedChains } from './constants/across-ccr-supported-chains'; import { acrossContractAddresses } from './constants/across-contract-addresses'; -import { acrossDepositAbi, acrossFundsDepositedInputs } from './constants/across-deposit-abi'; +import { acrossDepositAbi } from './constants/across-deposit-abi'; import { AcrossFeeQuoteRequestParams, AcrossFeeQuoteResponse } from './models/across-fee-quote'; import { AcrossApiService } from './services/across-api-service'; @@ -63,8 +58,6 @@ export class AcrossCrossChainTrade extends EvmCrossChainTrade { private readonly uniqCodeWithSeparator = '1dc0de003c'; - public acrossDepositId = 0; - public readonly type = CROSS_CHAIN_TRADE_TYPE.ACROSS; public readonly isAggregator = false; @@ -245,53 +238,4 @@ export class AcrossCrossChainTrade extends EvmCrossChainTrade { private getFillDeadline(): number { return Math.round(Date.now() / 1000) + 18_000; } - - public async swap(options: SwapTransactionOptions = {}): Promise { - if (!options?.testMode) { - await this.checkTradeErrors(); - } - await this.checkReceiverAddress( - options.receiverAddress, - !BlockchainsInfo.isEvmBlockchainName(this.to.blockchain) - ); - const method = options?.testMode ? 'sendTransaction' : 'trySendTransaction'; - - const fromAddress = this.walletAddress; - - const { data, value, to } = await this.encode({ ...options, fromAddress }); - - const { onConfirm, gasPriceOptions } = options; - let transactionHash: string; - const onTransactionHash = (hash: string) => { - if (onConfirm) { - onConfirm(hash); - } - transactionHash = hash; - }; - - try { - await this.web3Private[method](to, { - data, - value, - onTransactionHash, - gasPriceOptions, - gasLimitRatio: this.gasLimitRatio, - ...(options?.useEip155 && { - chainId: `0x${blockchainId[this.from.blockchain].toString(16)}` - }) - }); - - const encodedId = await Injector.web3PublicService - .getWeb3Public(this.fromBlockchain) - .getTxDecodedData(transactionHash!, acrossFundsDepositedInputs, 'depositId'); - this.acrossDepositId = Number(encodedId); - - return transactionHash!; - } catch (err) { - if (err instanceof FailedToCheckForTransactionReceiptError) { - return transactionHash!; - } - throw err; - } - } } diff --git a/src/features/cross-chain/status-manager/cross-chain-status-manager.ts b/src/features/cross-chain/status-manager/cross-chain-status-manager.ts index 16998a3baff..b25faf10cd9 100644 --- a/src/features/cross-chain/status-manager/cross-chain-status-manager.ts +++ b/src/features/cross-chain/status-manager/cross-chain-status-manager.ts @@ -9,6 +9,7 @@ import { JsonRpcProvider } from '@ethersproject/providers'; import { RubicSdkError } from 'src/common/errors'; import { BLOCKCHAIN_NAME, + EvmBlockchainName, TEST_EVM_BLOCKCHAIN_NAME } from 'src/core/blockchain/models/blockchain-name'; import { BlockchainsInfo } from 'src/core/blockchain/utils/blockchains-info/blockchains-info'; @@ -62,6 +63,7 @@ import { } from 'src/features/cross-chain/status-manager/models/statuses-api'; import { XyApiResponse } from 'src/features/cross-chain/status-manager/models/xy-api-response'; +import { acrossFundsDepositedInputs } from '../calculation-manager/providers/across-provider/constants/across-deposit-abi'; import { AcrossApiService } from '../calculation-manager/providers/across-provider/services/across-api-service'; import { ChangeNowCrossChainApiService } from '../calculation-manager/providers/changenow-provider/services/changenow-cross-chain-api-service'; import { getEddyBridgeDstSwapStatus } from '../calculation-manager/providers/eddy-bridge/utils/get-eddy-bridge-dst-status'; @@ -740,11 +742,11 @@ export class CrossChainStatusManager { return txStatusData; } - private getAcrossDstSwapStatus(data: CrossChainTradeData): Promise { - if (!data.acrossDepositId) { - throw new RubicSdkError('Must provide acrossDepositId'); - } + private async getAcrossDstSwapStatus(data: CrossChainTradeData): Promise { + const depositId = await Injector.web3PublicService + .getWeb3Public(data.fromBlockchain as EvmBlockchainName) + .getTxDecodedLogData(data.srcTxHash, acrossFundsDepositedInputs, 'depositId'); const srcChainId = blockchainId[data.fromBlockchain]; - return AcrossApiService.getTxStatus(srcChainId, data.acrossDepositId); + return AcrossApiService.getTxStatus(srcChainId, Number(depositId)); } } From bdfa2f6f0a61154e077a91a64bea31d19410997c Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 3 Oct 2024 17:54:10 +0300 Subject: [PATCH 3/4] minor fix --- .../status-manager/models/cross-chain-trade-data.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/features/cross-chain/status-manager/models/cross-chain-trade-data.ts b/src/features/cross-chain/status-manager/models/cross-chain-trade-data.ts index 10cc361bfc4..c70ff8f7575 100644 --- a/src/features/cross-chain/status-manager/models/cross-chain-trade-data.ts +++ b/src/features/cross-chain/status-manager/models/cross-chain-trade-data.ts @@ -64,9 +64,4 @@ export interface CrossChainTradeData { * Squidrouter request id. */ squidrouterRequestId?: string; - - /** - * Across deposit id. - */ - acrossDepositId?: number; } From 2274be4e1c6244208f0425fb7db3ef11ae518653 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 7 Oct 2024 17:54:18 +0300 Subject: [PATCH 4/4] enable proxy --- package.json | 2 +- .../providers/across-provider/across-cross-chain-provider.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index aecefc8f49d..b7aa6e8dd09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rubic-sdk", - "version": "5.37.1", + "version": "5.38.0-alpha-across.1", "description": "Simplify dApp creation", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts index 2c748cba05d..cfdd94eeeb2 100644 --- a/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts +++ b/src/features/cross-chain/calculation-manager/providers/across-provider/across-cross-chain-provider.ts @@ -35,7 +35,7 @@ export class AcrossCrossChainProvider extends CrossChainProvider { options: RequiredCrossChainOptions ): Promise { const fromBlockchain = from.blockchain as AccrossCcrSupportedChains; - const useProxy = false; + const useProxy = options?.useProxy?.[this.type] ?? true; try { const feeInfo = await this.getFeeInfo(