Skip to content

Commit

Permalink
Merge pull request #104 from Cryptorubic/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
ottebrut authored Jun 23, 2022
2 parents b5b4b6a + 4471941 commit f63c382
Show file tree
Hide file tree
Showing 21 changed files with 2,707 additions and 2,836 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16.6.0
v16.10.0
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,21 @@ Steps 1. and 2. is the same. You can use single sdk instance for instant trades
const toBlockchain = BLOCKCHAIN_NAME.BINANCE_SMART_CHAIN;
const toTokenAddress = '0xe9e7cea3dedca5984780bafc599bd69add087d56'; // BUSD
const trade = await sdk.crossChain.calculateTrade(
const wrappedTrades = await sdk.crossChain.calculateTrade(
{ blockchain: fromBlockchain, address: fromTokenAddress },
fromAmount,
{ blockchain: toBlockchain, address: toTokenAddress }
);
const bestTrade = wrappedTrades[0];
Object.entries(wrappedTrades).forEach((wrappedTrade) => {
if (wrappedTrade.trade) {
console.log(wrappedTrade.tradeType, `to amount: ${wrappedTrade.trade.to.tokenAmount.toFormat(3)}`);
}
if (wrappedTrade.error) {
console.log(wrappedTrade.tradeType, `error: ${wrappedTrade.error}`);
}
})
```

Step 4. is the same.
Expand Down Expand Up @@ -942,12 +952,13 @@ sdk.crossChain.calculateTrade(
blockchain: BLOCKCHAIN_NAME;
},
options?: CrossChainOptions
): Promise<WrappedCrossChainTrade>
): Promise<WrappedCrossChainTrade[]>
```
> ℹ️️ You have to set up **rpc provider 🌐** for network in which you will calculate trade.
Method calculates [WrappedCrossChainTrade](#wrapped-cross-chain-trade), which contains best cross chain provider with estimated output amount.
Method calculates array of [WrappedCrossChainTrade](#wrapped-cross-chain-trade), sorted by exchange courses.
First element of array is trade with best course.
**Method parameters:**
Expand Down Expand Up @@ -977,12 +988,12 @@ Method calculates [WrappedCrossChainTrade](#wrapped-cross-chain-trade), which co
```typescript
interface WrappedCrossChainTrade {
trade: CrossChainTrade | null;
minAmountError?: BigNumber;
maxAmountError?: BigNumber;
tradeType: CrossChainTradeType;
error?: RubicSdkError;
}
```
Wraps best calculated cross chain trade and possible min max amount errors. If `minAmountError` or `maxAmountError` are not undefined, then you must display an error, because [`swap`](#crosschaintradeswap-method) method will return error.
Wraps best calculated cross chain trade and possible error. If `error` field is not undefined, then you must display an error, because [`swap`](#crosschaintradeswap-method) method will return error.
---
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rubic-sdk",
"version": "2.0.1",
"version": "2.1.0",
"description": "Simplify dApp creation",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down Expand Up @@ -60,15 +60,15 @@
},
"dependencies": {
"assert": "^2.0.0",
"axios": "^0.22.0",
"axios": "^0.26.1",
"bignumber.js": "^9.0.1",
"ethers": "^5.6.8",
"symbiosis-js-sdk": "^2.6.5",
"web3": "~1.7.3"
},
"devDependencies": {
"@babel/core": "^7.0.0-0",
"@types/jest": "^27.0.3",
"@types/jest": "^28.1.2",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"bip39": "^3.0.4",
Expand All @@ -86,14 +86,14 @@
"ethereumjs-wallet": "^1.0.2",
"http-browserify": "^1.7.0",
"https-browserify": "^1.0.0",
"jest": "^27.1.1",
"jest": "^28.1.1",
"jest-mock-promise": "^2.0.2",
"prettier": "^2.2.1",
"rimraf": "^3.0.2",
"stream-browserify": "^3.0.0",
"terser-webpack-plugin": "^5.3.0",
"ts-essentials": "^9.0.0",
"ts-jest": "^27.1.2",
"ts-jest": "^28.0.5",
"ts-loader": "^9.3.0",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"tscpaths": "^0.0.9",
Expand Down
10 changes: 10 additions & 0 deletions src/common/errors/cross-chain/cross-chain-max-amount-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { RubicSdkError } from '@common/errors/rubic-sdk.error';
import BigNumber from 'bignumber.js';
import { PriceTokenAmount } from 'src/core';

export class CrossChainMaxAmountError extends RubicSdkError {
constructor(public readonly maxAmount: BigNumber, public readonly token: PriceTokenAmount) {
super(`Max amount is ${maxAmount.toFixed()} ${token.symbol}`);
Object.setPrototypeOf(this, CrossChainMaxAmountError.prototype);
}
}
10 changes: 10 additions & 0 deletions src/common/errors/cross-chain/cross-chain-min-amount-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { RubicSdkError } from '@common/errors/rubic-sdk.error';
import BigNumber from 'bignumber.js';
import { PriceTokenAmount } from 'src/core';

export class CrossChainMinAmountError extends RubicSdkError {
constructor(public readonly minAmount: BigNumber, public readonly token: PriceTokenAmount) {
super(`Min amount is ${minAmount.toFixed()} ${token.symbol}`);
Object.setPrototypeOf(this, CrossChainMinAmountError.prototype);
}
}
2 changes: 1 addition & 1 deletion src/common/models/http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface HttpClient {
url: string,
options?: {
headers?: {
[header: string]: string | string[];
[header: string]: string;
};
params?: {
[param: string]:
Expand Down
4 changes: 2 additions & 2 deletions src/common/utils/options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function combineOptions<T extends object>(
options: Partial<T> | undefined,
defaultOptions: Required<T>
): Required<T> {
defaultOptions: T
): T {
return {
...defaultOptions,
...options
Expand Down
2 changes: 1 addition & 1 deletion src/core/blockchain/constants/blockchains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const blockchains: ReadonlyArray<Blockchain> = [
id: 1313161554,
name: BLOCKCHAIN_NAME.AURORA,
nativeCoin: new Token({
blockchain: BLOCKCHAIN_NAME.ARBITRUM,
blockchain: BLOCKCHAIN_NAME.AURORA,
address: NATIVE_TOKEN_ADDRESS,
name: 'aETH',
symbol: 'aETH',
Expand Down
83 changes: 37 additions & 46 deletions src/features/cross-chain/cross-chain-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@ import {
import { SwapManagerCrossChainCalculationOptions } from '@features/cross-chain/models/swap-manager-cross-chain-options';
import pTimeout from '@common/utils/p-timeout';
import { CrossChainTradeProvider } from '@features/cross-chain/providers/common/cross-chain-trade-provider';
import { hasLengthAtLeast } from '@features/instant-trades/utils/type-guards';
import { WrappedCrossChainTrade } from '@features/cross-chain/providers/common/models/wrapped-cross-chain-trade';
import BigNumber from 'bignumber.js';
import { SymbiosisCrossChainTradeProvider } from '@features/cross-chain/providers/symbiosis-trade-provider/symbiosis-cross-chain-trade-provider';
import { MarkRequired } from 'ts-essentials';
import { RequiredCrossChainOptions } from '@features/cross-chain/models/cross-chain-options';
import { RubicCrossChainTradeProvider } from './providers/rubic-trade-provider/rubic-cross-chain-trade-provider';

type RequiredSwapManagerCalculationOptions = Required<SwapManagerCrossChainCalculationOptions>;
type RequiredSwapManagerCalculationOptions = MarkRequired<
SwapManagerCrossChainCalculationOptions,
'timeout' | 'disabledProviders'
> &
RequiredCrossChainOptions;

/**
* Contains method to calculate best cross chain trade.
Expand All @@ -50,7 +55,9 @@ export class CrossChainManager {
constructor(private readonly providerAddress: string) {}

/**
* Calculates best cross chain trade, based on calculated courses.
* Calculates cross chain trades and sorts them by exchange courses.
* Wrapped trade object may contain error, but sometimes course can be
* calculated even with thrown error (e.g. min/max amount error).
*
* @example
* ```ts
Expand All @@ -62,18 +69,28 @@ export class CrossChainManager {
* // BUSD
* const toTokenAddress = '0xe9e7cea3dedca5984780bafc599bd69add087d56';
*
* const trade = await sdk.crossChain.calculateTrade(
* const wrappedTrades = await sdk.crossChain.calculateTrade(
* { blockchain: fromBlockchain, address: fromTokenAddress },
* fromAmount,
* { blockchain: toBlockchain, address: toTokenAddress }
* );
*
* wrappedTrades.forEach(wrappedTrade => {
* if (wrappedTrade.trade) {
* console.log(wrappedTrade.tradeType, `to amount: ${wrappedTrade.trade.to.tokenAmount.toFormat(3)}`));
* }
* if (wrappedTrade.error) {
* console.error(wrappedTrade.tradeType, 'error: wrappedTrade.error');
* }
* });
*
* ```
*
* @param fromToken Token to sell.
* @param fromAmount Amount to sell.
* @param toToken Token to get.
* @param options Additional options.
* @returns Wrapped cross chain trade, with possible min or max amount errors.
* @returns Array of sorted wrapped cross chain trades with possible errors.
*/
public async calculateTrade(
fromToken:
Expand All @@ -90,7 +107,7 @@ export class CrossChainManager {
blockchain: BlockchainName;
},
options?: Omit<SwapManagerCrossChainCalculationOptions, 'providerAddress'>
): Promise<WrappedCrossChainTrade> {
): Promise<WrappedCrossChainTrade[]> {
if (toToken instanceof Token && fromToken.blockchain === toToken.blockchain) {
throw new RubicSdkError('Blockchains of from and to tokens must be different.');
}
Expand All @@ -107,73 +124,39 @@ export class CrossChainManager {
private getFullOptions(
options?: SwapManagerCrossChainCalculationOptions
): RequiredSwapManagerCalculationOptions {
return combineOptions(options, {
return combineOptions<RequiredSwapManagerCalculationOptions>(options, {
fromSlippageTolerance: CrossChainManager.defaultSlippageTolerance,
toSlippageTolerance: CrossChainManager.defaultSlippageTolerance,
gasCalculation: 'enabled',
disabledProviders: [],
timeout: CrossChainManager.defaultCalculationTimeout,
providerAddress: this.providerAddress,
slippageTolerance: CrossChainManager.defaultSlippageTolerance * 2,
deadline: CrossChainManager.defaultDeadline,
fromAddress: ''
deadline: CrossChainManager.defaultDeadline
});
}

private async calculateBestTradeFromTokens(
from: PriceTokenAmount,
to: PriceToken,
options: RequiredSwapManagerCalculationOptions
): Promise<WrappedCrossChainTrade> {
): Promise<WrappedCrossChainTrade[]> {
const wrappedTrades = await this.calculateTradeFromTokens(
from,
to,
this.getFullOptions(options)
);
if (!hasLengthAtLeast(wrappedTrades, 1)) {
throw new Error('[RUBIC SDK] Trades array has to be defined');
}

const transitTokenAmount = (
wrappedTrades.find(wrappedTrade => wrappedTrade.trade instanceof CelerCrossChainTrade)
?.trade as CelerCrossChainTrade
)?.fromTrade.toToken.tokenAmount;
const sortedTrades = wrappedTrades.sort((firstTrade, secondTrade) => {
return wrappedTrades.sort((firstTrade, secondTrade) => {
const firstTradeAmount = this.getProviderRatio(firstTrade.trade, transitTokenAmount);
const secondTradeAmount = this.getProviderRatio(secondTrade.trade, transitTokenAmount);

return firstTradeAmount.comparedTo(secondTradeAmount);
});

const filteredTrades = sortedTrades.filter(
trade => !trade?.minAmountError && !trade?.maxAmountError
);
if (filteredTrades.length) {
return {
trade: filteredTrades[0]!.trade!
};
}

let minAmountError: BigNumber | undefined;
let maxAmountError: BigNumber | undefined;
sortedTrades.forEach(trade => {
if (trade.minAmountError) {
minAmountError = minAmountError
? BigNumber.min(minAmountError, trade.minAmountError)
: trade.minAmountError;
}
if (trade.maxAmountError) {
maxAmountError = maxAmountError
? BigNumber.max(maxAmountError, trade.maxAmountError)
: trade.maxAmountError;
}
});

return {
trade: sortedTrades[0].trade,
minAmountError,
maxAmountError
};
}

private getProviderRatio(trade: CrossChainTrade | null, transitTokenAmount: BigNumber) {
Expand Down Expand Up @@ -219,7 +202,15 @@ export class CrossChainManager {
const calculationPromises = providers.map(async ([type, provider]) => {
try {
const calculation = provider.calculate(from, to, providersOptions);
return await pTimeout(calculation, timeout);
const wrappedTrade = await pTimeout(calculation, timeout);
if (!wrappedTrade) {
return null;
}

return {
...wrappedTrade,
tradeType: provider.type
};
} catch (e) {
console.debug(
`[RUBIC_SDK] Trade calculation error occurred for ${type} trade provider.`,
Expand All @@ -230,7 +221,7 @@ export class CrossChainManager {
});
const results = (await Promise.all(calculationPromises)).filter(notNull);
if (!results?.length) {
throw new Error('[RUBIC_SDK] No success providers calculation for the trade.');
throw new RubicSdkError('No success providers calculation for the trade');
}
return results;
}
Expand Down
11 changes: 10 additions & 1 deletion src/features/cross-chain/models/cross-chain-options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { MarkRequired } from 'ts-essentials';

export interface CrossChainOptions {
/**
* Slippage in source network (for Celer and Rubic).
Expand Down Expand Up @@ -39,4 +41,11 @@ export interface CrossChainOptions {
fromAddress?: string;
}

export type RequiredCrossChainOptions = Required<CrossChainOptions>;
export type RequiredCrossChainOptions = MarkRequired<
CrossChainOptions,
| 'fromSlippageTolerance'
| 'toSlippageTolerance'
| 'slippageTolerance'
| 'deadline'
| 'providerAddress'
>;
6 changes: 0 additions & 6 deletions src/features/cross-chain/models/min-max-amounts-errors.ts

This file was deleted.

Loading

0 comments on commit f63c382

Please sign in to comment.