pull main

This commit is contained in:
Olga Kanishcheva
2024-10-08 15:48:40 +04:00
43 changed files with 903 additions and 479 deletions

View File

@@ -31,10 +31,15 @@ export default class Orion {
// TODO: get tradable pairs (aggregated)
public logger: ((message: string) => void) | undefined;
constructor(
envOrConfig: KnownEnv | EnvConfig = 'production',
overrides?: DeepPartial<EnvConfig>
overrides?: DeepPartial<EnvConfig>,
logger?: ((message: string) => void) | undefined
) {
this.logger = logger;
let config: EnvConfig;
if (typeof envOrConfig === 'string') {
const envConfig = envs[envOrConfig];
@@ -104,7 +109,7 @@ export default class Orion {
// api: networkConfig.api,
nodeJsonRpc: networkConfig.nodeJsonRpc,
services: networkConfig.services,
});
}, logger);
return {
...acc,
[chainId]: unit,

View File

@@ -0,0 +1,27 @@
import { SwapExecutor__factory, AeroPool__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
import { type BigNumberish, JsonRpcProvider } from "ethers"
import { SafeArray } from "../../../utils/safeGetters.js"
import { addCallParams } from "./utils.js"
import type { SingleSwap } from "../../../types.js"
export async function generateAeroCalls(
path: SafeArray<SingleSwap>,
amount: BigNumberish,
recipient: string,
provider: JsonRpcProvider
) {
const pools: string[] = [];
const direct: boolean[] = [];
for (const swap of path) {
pools.push(swap.pool);
const token0 = await AeroPool__factory.connect(swap.pool, provider).token0();
direct.push(swap.assetIn.toLowerCase() === token0.toLowerCase());
}
const executorInterface = SwapExecutor__factory.createInterface()
let calldata = executorInterface.encodeFunctionData('swapAeroMulti', [pools, direct, amount, recipient]);
calldata = addCallParams(calldata)
return [calldata]
}

View File

@@ -45,5 +45,5 @@ export async function generateCurveStableSwapCall(
}
calls.push(calldata)
return calls
return calls
}

View File

@@ -15,7 +15,7 @@ export function generateTransferCall(
target,
amount
])
return addCallParams(calldata, callParams)
}
@@ -31,6 +31,6 @@ export function generateApproveCall(
target,
amount
])
return addCallParams(calldata, callParams)
}

View File

@@ -16,6 +16,6 @@ export function generateFeePaymentCall(
token,
amount
])
return addCallParams(calldata, callParams)
}

View File

@@ -3,6 +3,15 @@ import { SafeArray } from "../../../utils/safeGetters.js"
import { type BytesLike, type BigNumberish, concat, ethers, toBeHex } from "ethers"
import { addCallParams } from "./utils.js"
import type { SingleSwap } from "../../../types.js"
import { BigNumber } from 'bignumber.js';
const BILLION = 1000000000;
const TEN_THOUSANDS = 10000;
function countScaledFee(fee: string) {
// The count is needed for the swapUniV2Scaled function, where the denominator is one billion
return new BigNumber(fee).multipliedBy(BILLION).div(TEN_THOUSANDS).toNumber();
}
export async function generateUni2Calls(
path: SafeArray<SingleSwap>,
@@ -19,17 +28,21 @@ export async function generateUni2Calls(
currentSwap.pool,
currentSwap.assetIn,
currentSwap.assetOut,
nextSwap.pool
nextSwap.pool,
currentSwap.fee
)
calls.push(call)
}
}
const lastSwap = path.last();
const calldata = executorInterface.encodeFunctionData('swapUniV2', [
const fee = lastSwap.fee ?? 30;
const scaledFee = countScaledFee(fee.toString());
const calldata = executorInterface.encodeFunctionData('swapUniV2Scaled', [
lastSwap.pool,
lastSwap.assetIn,
lastSwap.assetOut,
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat(['0x03', recipient])]),
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(scaledFee), recipient])]),
])
calls.push(addCallParams(calldata))
@@ -41,14 +54,15 @@ export function generateUni2Call(
assetIn: string,
assetOut: string,
recipient: string,
fee: BigNumberish = 3,
fee: BigNumberish = 30,
) {
const executorInterface = SwapExecutor__factory.createInterface()
const calldata = executorInterface.encodeFunctionData('swapUniV2', [
const scaledFee = countScaledFee(fee.toString());
const calldata = executorInterface.encodeFunctionData('swapUniV2Scaled', [
pool,
assetIn,
assetOut,
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(fee), recipient])]),
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(scaledFee), recipient])]),
])
return addCallParams(calldata)
}
}

View File

@@ -12,7 +12,7 @@ export function generateWrapAndTransferCall(
const calldata = executorInterface.encodeFunctionData('wrapAndTransfer', [
target,
])
return addCallParams(calldata, callParams)
}
@@ -27,6 +27,6 @@ export function generateUnwrapAndTransferCall(
target,
amount
])
return addCallParams(calldata, callParams)
}

View File

@@ -1,49 +1,51 @@
import type { LibValidator } from "@orionprotocol/contracts/lib/ethers-v6/Exchange.js";
import { ethers, ZeroAddress } from "ethers";
import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from "ethers";
import cloneDeep from "lodash.clonedeep";
import { safeGet, SafeArray } from "../../utils/safeGetters.js";
import { simpleFetch } from "simple-typed-fetch";
import type Unit from "../index.js";
import { generateUni2Calls, generateUni2Call } from "./callGenerators/uniswapV2.js";
import type { LibValidator } from '@orionprotocol/contracts/lib/ethers-v6/Exchange.js';
import { ethers, ZeroAddress } from 'ethers';
import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from 'ethers';
import cloneDeep from 'lodash.clonedeep';
import { safeGet, SafeArray } from '../../utils/safeGetters.js';
import { simpleFetch } from 'simple-typed-fetch';
import type Unit from '../index.js';
import { generateUni2Calls, generateUni2Call } from './callGenerators/uniswapV2.js';
import {
generateUni3Calls,
generateOrion3Calls,
generateUni3Call,
generateOrion3Call,
} from "./callGenerators/uniswapV3.js";
import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from "./callGenerators/utils.js";
import { generateTransferCall } from "./callGenerators/erc20.js";
import { generateCurveStableSwapCall } from "./callGenerators/curve.js";
import type { SingleSwap } from "../../types.js";
import { addressLikeToString } from "../../utils/addressLikeToString.js";
import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from "./callGenerators/weth.js";
import { getExchangeAllowance, getTotalBalance } from "../../utils/getBalance.js";
import { generateFeePaymentCall } from "./callGenerators/feePayment.js";
} from './callGenerators/uniswapV3.js';
import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from './callGenerators/utils.js';
import { generateTransferCall } from './callGenerators/erc20.js';
import { generateCurveStableSwapCall } from './callGenerators/curve.js';
import type { SingleSwap } from '../../types.js';
import { addressLikeToString } from '../../utils/addressLikeToString.js';
import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from './callGenerators/weth.js';
import { getExchangeAllowance, getTotalBalance } from '../../utils/getBalance.js';
import { generateFeePaymentCall } from './callGenerators/feePayment.js';
import { generateAeroCalls } from './callGenerators/aero.js';
export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3";
export type Factory = 'UniswapV2' | 'UniswapV3' | 'Curve' | 'OrionV2' | 'OrionV3' | 'Aero';
type BaseGenerateSwapCalldataParams = {
amount: BigNumberish;
minReturnAmount: BigNumberish;
initiatorAddress: string;
receiverAddress: string;
path: ArrayLike<SingleSwap>;
matcher?: AddressLike,
feeToken?: AddressLike,
fee?: BigNumberish;
amount: BigNumberish
minReturnAmount: BigNumberish
initiatorAddress: string
receiverAddress: string
path: ArrayLike<SingleSwap>
matcher?: AddressLike
feeToken?: AddressLike
fee?: BigNumberish
}
export type GenerateSwapCalldataWithUnitParams = BaseGenerateSwapCalldataParams & {
unit: Unit;
unit: Unit
};
export type GenerateSwapCalldataParams = BaseGenerateSwapCalldataParams & {
exchangeContractAddress: AddressLike;
wethAddress: AddressLike;
curveRegistryAddress: AddressLike;
swapExecutorContractAddress: AddressLike;
provider: JsonRpcProvider;
exchangeContractAddress: AddressLike
wethAddress: AddressLike
curveRegistryAddress: AddressLike
swapExecutorContractAddress: AddressLike
provider: JsonRpcProvider
logger?: ((message: string) => void) | undefined
};
export async function generateSwapCalldataWithUnit({
@@ -57,29 +59,27 @@ export async function generateSwapCalldataWithUnit({
fee = 0,
unit,
}: GenerateSwapCalldataWithUnitParams): Promise<{
calldata: string;
swapDescription: LibValidator.SwapDescriptionStruct;
value: bigint;
calldata: string
swapDescription: LibValidator.SwapDescriptionStruct
value: bigint
}> {
if (arrayLikePath == undefined || arrayLikePath.length == 0) {
throw new Error("Empty path");
throw new Error('Empty path');
}
const wethAddress = safeGet(unit.contracts, "WETH");
const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry");
const { assetToAddress, swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch(
const wethAddress = safeGet(unit.contracts, 'WETH');
const curveRegistryAddress = safeGet(unit.contracts, 'curveRegistry');
const { swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch(
unit.blockchainService.getInfo
)();
const arrayLikePathCopy = cloneDeep(arrayLikePath);
let path = SafeArray.from(arrayLikePathCopy);
path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => {
swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn
swapInfo.assetOut = assetToAddress[swapInfo.assetOut] ?? swapInfo.assetOut
swapInfo.assetIn = swapInfo.assetIn.toLowerCase()
swapInfo.assetOut = swapInfo.assetOut.toLowerCase()
return swapInfo;
});
path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => ({
...swapInfo,
assetIn: swapInfo.assetAddressIn.toLowerCase(),
assetOut: swapInfo.assetAddressOut.toLowerCase(),
}));
return await generateSwapCalldata({
amount,
@@ -95,6 +95,7 @@ export async function generateSwapCalldataWithUnit({
curveRegistryAddress,
swapExecutorContractAddress,
provider: unit.provider,
logger: unit.logger,
});
}
@@ -112,21 +113,30 @@ export async function generateSwapCalldata({
curveRegistryAddress: curveRegistryAddressLike,
swapExecutorContractAddress: swapExecutorContractAddressLike,
provider,
logger,
}: GenerateSwapCalldataParams): Promise<{
calldata: string;
swapDescription: LibValidator.SwapDescriptionStruct;
value: bigint;
calldata: string
swapDescription: LibValidator.SwapDescriptionStruct
value: bigint
}> {
const wethAddress = await addressLikeToString(wethAddressLike);
logger?.(`wethAddress: ${wethAddress}`);
const curveRegistryAddress = await addressLikeToString(curveRegistryAddressLike);
logger?.(`curveRegistryAddress: ${curveRegistryAddress}`);
const swapExecutorContractAddress = await addressLikeToString(swapExecutorContractAddressLike);
logger?.(`swapExecutorContractAddress, ${swapExecutorContractAddress}`);
const feeToken = await addressLikeToString(feeTokenAddressLike);
logger?.(`feeToken, ${feeToken}`);
const matcher = await addressLikeToString(matcherAddressLike);
logger?.(`matcher: ${matcher}`);
logger?.(`arrayLikePath: ${arrayLikePath}`);
let path = SafeArray.from(arrayLikePath).map((swapInfo) => {
logger?.(`swapInfo: ${swapInfo}`);
swapInfo.assetIn = swapInfo.assetIn.toLowerCase()
swapInfo.assetOut = swapInfo.assetOut.toLowerCase()
return swapInfo;
});
logger?.(`path: ${path}`);
const { assetIn: srcToken } = path.first();
const { assetOut: dstToken } = path.last();
@@ -140,14 +150,18 @@ export async function generateSwapCalldata({
minReturnAmount,
flags: 0,
};
logger?.(`swapDescription: ${swapDescription}`);
const amountNativeDecimals = await exchangeToNativeDecimals(srcToken, amount, provider);
logger?.(`amountNativeDecimals: ${amountNativeDecimals}`);
const feeNativeDecimals = await exchangeToNativeDecimals(feeToken, fee, provider)
logger?.(`feeNativeDecimals: ${feeNativeDecimals}`);
path = SafeArray.from(arrayLikePath).map((singleSwap) => {
if (singleSwap.assetIn == ethers.ZeroAddress) singleSwap.assetIn = wethAddress;
if (singleSwap.assetOut == ethers.ZeroAddress) singleSwap.assetOut = wethAddress;
return singleSwap;
});
logger?.(`path2: ${path}`);
let calls: BytesLike[];
({ swapDescription, calls } = await processSwaps(
@@ -162,19 +176,26 @@ export async function generateSwapCalldata({
curveRegistryAddress,
provider
));
logger?.(`swapDescription: ${swapDescription}`);
logger?.(`calls: ${calls}`);
const calldata = generateCalls(calls);
logger?.(`calldata: ${calldata}`);
const { useExchangeBalance, additionalTransferAmount } = await shouldUseExchangeBalance(
srcToken,
initiatorAddress,
exchangeContractAddress,
amountNativeDecimals,
provider
provider,
logger
);
logger?.(`useExchangeBalance: ${useExchangeBalance}`);
logger?.(`additionalTransferAmount: ${additionalTransferAmount}`);
if (useExchangeBalance) {
swapDescription.flags = 1n << 255n;
}
const value = srcToken == ZeroAddress ? additionalTransferAmount : 0n;
logger?.(`value: ${value}`);
return { swapDescription, calldata, value };
}
@@ -214,7 +235,7 @@ async function processSwaps(
));
}
({swapDescription, calls} = await payFeeToMatcher(matcher, feeToken, fee, calls, swapDescription));
({ swapDescription, calls } = await payFeeToMatcher(matcher, feeToken, fee, calls, swapDescription));
({ swapDescription, calls } = wrapOrUnwrapIfNeeded(
amount,
@@ -238,27 +259,31 @@ async function processSingleFactorySwaps(
) {
let calls: BytesLike[] = [];
switch (factory) {
case "OrionV2": {
case 'OrionV2': {
swapDescription.srcReceiver = path.first().pool;
calls = await generateUni2Calls(path, swapExecutorContractAddress);
break;
}
case "UniswapV2": {
case 'UniswapV2': {
swapDescription.srcReceiver = path.first().pool;
calls = await generateUni2Calls(path, swapExecutorContractAddress);
break;
}
case "UniswapV3": {
case 'UniswapV3': {
calls = await generateUni3Calls(path, amount, swapExecutorContractAddress, provider);
break;
}
case "OrionV3": {
case 'OrionV3': {
calls = await generateOrion3Calls(path, amount, swapExecutorContractAddress, provider);
break;
}
case "Curve": {
case "Aero": {
calls = await generateAeroCalls(path, amount, swapExecutorContractAddress, provider);
break;
}
case 'Curve': {
if (path.length > 1) {
throw new Error("Supporting only single stable swap on curve");
throw new Error('Supporting only single stable swap on curve');
}
calls = await generateCurveStableSwapCall(
amount,
@@ -285,37 +310,37 @@ async function processMultiFactorySwaps(
curveRegistryAddress: string,
provider: JsonRpcProvider
) {
let calls: BytesLike[] = [];
const calls: BytesLike[] = [];
for (const swap of path) {
switch (swap.factory) {
case "OrionV2": {
case 'OrionV2': {
let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0);
transferCall = pathCallWithBalance(transferCall, swap.assetIn);
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress, swap.fee);
calls.push(transferCall, uni2Call);
break;
}
case "UniswapV2": {
case 'UniswapV2': {
let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0);
transferCall = pathCallWithBalance(transferCall, swap.assetIn);
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress, swap.fee);
calls.push(transferCall, uni2Call);
break;
}
case "UniswapV3": {
case 'UniswapV3': {
let uni3Call = await generateUni3Call(swap, 0, swapExecutorContractAddress, provider);
uni3Call = pathCallWithBalance(uni3Call, swap.assetIn);
calls.push(uni3Call);
break;
}
case "OrionV3": {
case 'OrionV3': {
let orion3Call = await generateOrion3Call(swap, 0, swapExecutorContractAddress, provider);
orion3Call = pathCallWithBalance(orion3Call, swap.assetIn);
calls.push(orion3Call);
break;
}
case "Curve": {
let curveCalls = await generateCurveStableSwapCall(
case 'Curve': {
const curveCalls = await generateCurveStableSwapCall(
amount,
swapExecutorContractAddress,
swap,
@@ -346,7 +371,7 @@ async function payFeeToMatcher(
const feePaymentCall = generateFeePaymentCall(matcher, feeToken, feeAmount)
calls.push(feePaymentCall)
}
return {swapDescription, calls}
return { swapDescription, calls }
}
function wrapOrUnwrapIfNeeded(
@@ -356,7 +381,7 @@ function wrapOrUnwrapIfNeeded(
swapExecutorContractAddress: string,
wethAddress: string
) {
const {dstReceiver, srcReceiver, srcToken, dstToken} = swapDescription;
const { dstReceiver, srcReceiver, srcToken, dstToken } = swapDescription;
if (srcToken === ZeroAddress) {
const wrapCall = generateWrapAndTransferCall(srcReceiver, { value: amount });
swapDescription.srcReceiver = swapExecutorContractAddress;
@@ -379,7 +404,8 @@ async function shouldUseExchangeBalance(
initiatorAddress: AddressLike,
exchangeContractAddress: AddressLike,
amount: bigint,
provider: JsonRpcProvider
provider: JsonRpcProvider,
logger?: ((message: string) => void) | undefined
) {
const { walletBalance, exchangeBalance } = await getTotalBalance(
srcToken,
@@ -387,17 +413,19 @@ async function shouldUseExchangeBalance(
exchangeContractAddress,
provider
);
const exchangeAllowance = await getExchangeAllowance(srcToken, initiatorAddress, exchangeContractAddress, provider);
logger?.('test_123');
if (walletBalance + exchangeBalance < amount) {
throw new Error(
`Not enough balance to make swap, totalBalance - ${walletBalance + exchangeBalance} swapAmount - ${amount}`
`Not enough balance to make swap, walletBalance: ${walletBalance} exchangeBalance: ${exchangeBalance} totalBalance - ${walletBalance + exchangeBalance} swapAmount - ${amount}`
);
}
let useExchangeBalance = true;
let additionalTransferAmount = 0n;
if (exchangeBalance == 0n) {
if (walletBalance >= amount || exchangeBalance == 0n) {
useExchangeBalance = false;
additionalTransferAmount = amount;
} else {

View File

@@ -8,7 +8,6 @@ import type { BlockchainService } from '../../services/BlockchainService/index.j
import { calculateFeeInFeeAsset, denormalizeNumber, getNativeCryptocurrencyName } from '../../utils/index.js';
export type GetSwapInfoParams = {
type: 'exactSpend' | 'exactReceive'
assetIn: string
assetOut: string
amount: BigNumber.Value
@@ -18,12 +17,12 @@ export type GetSwapInfoParams = {
options?: {
instantSettlement?: boolean
poolOnly?: boolean
},
walletAddress?: string,
}
walletAddress?: string
isTradeBuy?: boolean
}
export default async function getSwapInfo({
type,
assetIn,
assetOut,
amount,
@@ -32,6 +31,7 @@ export default async function getSwapInfo({
aggregator,
options,
walletAddress,
isTradeBuy = false,
}: GetSwapInfoParams) {
if (amount === '') throw new Error('Amount can not be empty');
if (assetIn === '') throw new Error('AssetIn can not be empty');
@@ -61,7 +61,6 @@ export default async function getSwapInfo({
}
const swapInfo = await simpleFetch(aggregator.getSwapInfo)(
type,
assetIn,
assetOut,
amountBN.toString(),
@@ -69,6 +68,7 @@ export default async function getSwapInfo({
options?.poolOnly !== undefined && options.poolOnly
? 'pools'
: undefined,
isTradeBuy,
);
const { exchanges: swapExchanges } = swapInfo;
@@ -76,16 +76,6 @@ export default async function getSwapInfo({
const poolExchangesList = factories !== undefined ? Object.keys(factories) : [];
const [firstSwapExchange] = swapExchanges;
// if (swapInfo.type === 'exactReceive' && amountBN.lt(swapInfo.minAmountOut)) {
// throw new Error(`Amount is too low. Min amountOut is ${swapInfo.minAmountOut} ${assetOut}`);
// }
// if (swapInfo.type === 'exactSpend' && amountBN.lt(swapInfo.minAmountIn)) {
// throw new Error(`Amount is too low. Min amountIn is ${swapInfo.minAmountIn} ${assetIn}`);
// }
// if (swapInfo.orderInfo === null) throw new Error(swapInfo.executionInfo);
let route: 'pool' | 'aggregator';
if (options?.poolOnly !== undefined && options.poolOnly) {
route = 'pool';

View File

@@ -47,7 +47,7 @@ export default class Exchange {
public generateSwapCalldata(params: PureGenerateSwapCalldataParams) {
return generateSwapCalldataWithUnit({
...params,
unit: this.unit
unit: this.unit,
})
}

View File

@@ -18,7 +18,6 @@ import type { SingleSwap } from '../../types.js';
import { must, safeGet } from '../../utils/safeGetters.js';
export type SwapLimitParams = {
type: 'exactSpend' | 'exactReceive'
assetIn: string
assetOut: string
price: BigNumber.Value
@@ -35,6 +34,7 @@ export type SwapLimitParams = {
route?: 'aggregator' | 'pool'
}
}
isTradeBuy?: boolean
}
type AggregatorOrder = {
@@ -58,7 +58,6 @@ const isValidSingleSwap = (singleSwap: Omit<SingleSwap, 'factory'> & { factory:
}
export default async function swapLimit({
type,
assetIn,
assetOut,
price,
@@ -67,6 +66,7 @@ export default async function swapLimit({
signer,
unit,
options,
isTradeBuy = false,
}: SwapLimitParams): Promise<Swap> {
if (options?.developer) options.logger?.('YOU SPECIFIED A DEVELOPER OPTIONS. BE CAREFUL!');
if (amount === '') throw new Error('Amount can not be empty');
@@ -138,7 +138,6 @@ export default async function swapLimit({
);
const swapInfo = await simpleFetch(aggregator.getSwapInfo)(
type,
assetIn,
assetOut,
amountBN.toString(),
@@ -146,6 +145,7 @@ export default async function swapLimit({
options?.poolOnly !== undefined && options.poolOnly
? 'pools'
: undefined,
isTradeBuy,
);
const { exchanges: swapExchanges, exchangeContractPath } = swapInfo;
@@ -154,11 +154,11 @@ export default async function swapLimit({
if (swapExchanges.length > 0) options?.logger?.(`Swap exchanges: ${swapExchanges.join(', ')}`);
if (swapInfo.type === 'exactReceive' && amountBN.lt(swapInfo.minAmountOut)) {
if (swapInfo?.isTradeBuy && amountBN.lt(swapInfo.minAmountOut)) {
throw new Error(`Amount is too low. Min amountOut is ${swapInfo.minAmountOut} ${assetOut}`);
}
if (swapInfo.type === 'exactSpend' && amountBN.lt(swapInfo.minAmountIn)) {
if (!(swapInfo?.isTradeBuy) && amountBN.lt(swapInfo.minAmountIn)) {
throw new Error(`Amount is too low. Min amountIn is ${swapInfo.minAmountIn} ${assetIn}`);
}
@@ -200,9 +200,9 @@ export default async function swapLimit({
options?.logger?.(`Safe price is ${swapInfo.orderInfo.safePrice} ${quoteAssetName}`);
// BTEMP — better than or equal market price
const priceIsBTEMP = type === 'exactSpend'
? priceBN.lte(swapInfo.orderInfo.safePrice)
: priceBN.gte(swapInfo.orderInfo.safePrice);
const priceIsBTEMP = isTradeBuy
? priceBN.gte(swapInfo.orderInfo.safePrice)
: priceBN.lte(swapInfo.orderInfo.safePrice);
options?.logger?.(`Your price ${priceBN.toString()} is ${priceIsBTEMP ? 'better than or equal' : 'worse than'} market price ${swapInfo.orderInfo.safePrice}`);
@@ -246,7 +246,7 @@ export default async function swapLimit({
if (factoryAddress !== undefined) options?.logger?.(`Factory address is ${factoryAddress}. Exchange is ${firstSwapExchange}`);
}
const amountSpend = swapInfo.type === 'exactSpend'
const amountSpend = !(swapInfo?.isTradeBuy)
? swapInfo.amountIn
: new BigNumber(swapInfo.orderInfo.amount).multipliedBy(swapInfo.orderInfo.safePrice)
@@ -261,7 +261,7 @@ export default async function swapLimit({
sources: getAvailableSources('amount', assetInAddress, 'pool'),
});
const amountReceive = swapInfo.type === 'exactReceive'
const amountReceive = swapInfo?.isTradeBuy
? swapInfo.amountOut
: new BigNumber(swapInfo.orderInfo.amount).multipliedBy(swapInfo.orderInfo.safePrice)
const amountSpendBlockchainParam = normalizeNumber(

View File

@@ -37,13 +37,11 @@ type PoolSwap = {
export type Swap = AggregatorOrder | PoolSwap;
const isValidSingleSwap = (singleSwap: Omit<SingleSwap, 'factory'> & { factory: string }): singleSwap is SingleSwap => {
return isValidFactory(singleSwap.factory);
}
export default async function swapMarket({
type,
assetIn,
assetOut,
amount,
@@ -52,6 +50,7 @@ export default async function swapMarket({
signer,
unit,
options,
isTradeBuy = false,
}: SwapMarketParams): Promise<Swap> {
if (options?.developer) options.logger?.('YOU SPECIFIED A DEVELOPER OPTIONS. BE CAREFUL!');
@@ -125,7 +124,6 @@ export default async function swapMarket({
);
const swapInfo = await simpleFetch(aggregator.getSwapInfo)(
type,
assetIn,
assetOut,
amountBN.toString(),
@@ -133,6 +131,7 @@ export default async function swapMarket({
options?.poolOnly !== undefined && options.poolOnly
? 'pools'
: undefined,
isTradeBuy,
);
const { exchanges: swapExchanges, exchangeContractPath } = swapInfo;
@@ -141,11 +140,11 @@ export default async function swapMarket({
if (swapExchanges.length > 0) options?.logger?.(`Swap exchanges: ${swapExchanges.join(', ')}`);
if (swapInfo.type === 'exactReceive' && amountBN.lt(swapInfo.minAmountOut)) {
if (swapInfo?.isTradeBuy && amountBN.lt(swapInfo.minAmountOut)) {
throw new Error(`Amount is too low. Min amountOut is ${swapInfo.minAmountOut} ${assetOut}`);
}
if (swapInfo.type === 'exactSpend' && amountBN.lt(swapInfo.minAmountIn)) {
if (!(swapInfo?.isTradeBuy) && amountBN.lt(swapInfo.minAmountIn)) {
throw new Error(`Amount is too low. Min amountIn is ${swapInfo.minAmountIn} ${assetIn}`);
}
@@ -203,7 +202,7 @@ export default async function swapMarket({
.multipliedBy(new BigNumber(1).plus(percent))
.toString();
const amountSpend = swapInfo.type === 'exactSpend' ? swapInfo.amountIn : amountInWithSlippage;
const amountSpend = swapInfo?.isTradeBuy ? amountInWithSlippage : swapInfo.amountIn;
balanceGuard.registerRequirement({
reason: 'Amount spend',
@@ -216,7 +215,7 @@ export default async function swapMarket({
sources: getAvailableSources('amount', assetInAddress, 'pool'),
});
const amountReceive = swapInfo.type === 'exactReceive' ? swapInfo.amountOut : amountOutWithSlippage;
const amountReceive = swapInfo?.isTradeBuy ? amountOutWithSlippage : swapInfo.amountOut;
const amountSpendBlockchainParam = normalizeNumber(
amountSpend,
INTERNAL_PROTOCOL_PRECISION,

View File

@@ -79,6 +79,6 @@ export default class Pmm {
const contract = new ethers.Contract(this.contractAddress, orionRFQContractABI, signer);
// @ts-ignore
return contract.fillOrderRFQ(order.quotation, order.signature, BigInt(0));
return contract.fillOrderRFQ(order.order, order.signature, BigInt(0));
}
}

View File

@@ -11,7 +11,7 @@ export const pmmOrderQuotationSchema = z.object({
});
export const pmmOrderSchema = z.object({
quotation: pmmOrderQuotationSchema.default({}),
order: pmmOrderQuotationSchema.default({}),
signature: z.string().default(''),
success: z.boolean().default(false),
error: z.string().default(''),

View File

@@ -41,7 +41,10 @@ export default class Unit {
public readonly contracts: Record<string, string>;
constructor(config: KnownConfig | VerboseUnitConfig) {
public logger: ((message: string) => void) | undefined;
constructor(config: KnownConfig | VerboseUnitConfig, logger?: ((message: string) => void) | undefined) {
this.logger = logger;
if ('env' in config) {
const staticConfig = envs[config.env];
if (!staticConfig) {
@@ -118,7 +121,8 @@ export default class Unit {
this.aggregator = new Aggregator(
this.config.services.aggregator.http,
this.config.services.aggregator.ws,
this.config.basicAuth
this.config.basicAuth,
logger,
);
this.priceFeed = new PriceFeed(
this.config.services.priceFeed.api,

View File

@@ -81,7 +81,7 @@ describe('Orion', () => {
test('Init Orion testing', () => {
const orion = new Orion('testing');
expect(orion.referralSystem).toBeInstanceOf(ReferralSystem);
expect(orion.unitsArray.length).toBe(4); // eth, bsc, polygon, fantom
expect(orion.unitsArray.length).toBe(2); // eth, bsc
const unitBSC = orion.units[SupportedChainId.BSC_TESTNET];
expect(unitBSC?.chainId).toBe(SupportedChainId.BSC_TESTNET);
@@ -89,23 +89,11 @@ describe('Orion', () => {
expect(orion.getSiblingsOf(SupportedChainId.BSC_TESTNET)).toHaveLength(3);
expect(unitBSC?.networkCode).toBe('bsc');
const unitRopsten = orion.units[SupportedChainId.ROPSTEN]
expect(unitRopsten?.chainId).toBe(SupportedChainId.ROPSTEN);
const unitSepolia = orion.units[SupportedChainId.SEPOLIA]
expect(unitSepolia?.chainId).toBe(SupportedChainId.SEPOLIA);
// expect(unitRopsten?.env).toBe('testing');
expect(orion.getSiblingsOf(SupportedChainId.ROPSTEN)).toHaveLength(3);
expect(unitRopsten?.networkCode).toBe('eth');
const unitPolygon = orion.units[SupportedChainId.POLYGON_TESTNET];
expect(unitPolygon?.chainId).toBe(SupportedChainId.POLYGON_TESTNET);
// expect(unitPolygon?.env).toBe('testing');
expect(orion.getSiblingsOf(SupportedChainId.POLYGON_TESTNET)).toHaveLength(3);
expect(unitPolygon?.networkCode).toBe('polygon');
const unitFantom = orion.units[SupportedChainId.FANTOM_TESTNET];
expect(unitFantom?.chainId).toBe(SupportedChainId.FANTOM_TESTNET);
// expect(unitFantom?.env).toBe('testing');
expect(orion.getSiblingsOf(SupportedChainId.FANTOM_TESTNET)).toHaveLength(3);
expect(unitFantom?.networkCode).toBe('ftm');
expect(orion.getSiblingsOf(SupportedChainId.SEPOLIA)).toHaveLength(3);
expect(unitSepolia?.networkCode).toBe('eth');
});
test('Init Orion production', () => {

View File

@@ -51,42 +51,42 @@
"curveRegistry": ""
}
},
"3": {
"chainId": "3",
"explorer": "https://ropsten.etherscan.io/",
"label": "Ropsten",
"shortName": "ETH-Ropsten",
"11155111": {
"chainId": "11155111",
"explorer": "https://sepolia.etherscan.io/",
"label": "Sepolia",
"shortName": "ETH-Sepolia",
"code": "eth",
"rpc": "https://testing.orion.xyz/eth-ropsten/rpc",
"rpc": "https://gateway.tenderly.co/public/sepolia",
"baseCurrencyName": "ETH",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"5": {
"chainId": "5",
"explorer": "https://goerli.etherscan.io/",
"label": "Goerli",
"shortName": "ETH-Goerli",
"123420000034": {
"chainId": "123420000034",
"explorer": "https://blockscout-123420000034.raas-testnet.gelato.digital/",
"label": "Event Horizon Testnet",
"shortName": "EH-Testnet",
"code": "eth",
"rpc": "https://testing.orion.xyz/eth-goerli/rpc",
"rpc": "https://rpc-123420000034.raas-testnet.gelato.digital/",
"baseCurrencyName": "ETH",
"contracts": {
"WETH": "",
"WETH": "0x4200000000000000000000000000000000000006",
"curveRegistry": ""
}
},
"421613": {
"chainId": "421613",
"explorer": "https://goerli.arbiscan.io/",
"label": "Arbitrum Goerli",
"shortName": "Arbitrum Goerli",
"code": "arb",
"rpc": "https://goerli-rollup.arbitrum.io/rpc",
"baseCurrencyName": "ETH",
"1952959480": {
"chainId": "1952959480",
"explorer": "https://testnet-explorer.lumia.org/",
"label": "Lumia Testnet",
"shortName": "Lumia Testnet",
"code": "lumia",
"rpc": "https://testnet-rpc.lumia.org",
"baseCurrencyName": "LUMIA",
"contracts": {
"WETH": "",
"WETH": "0x1a1aF9C78704D3a0Ab9e031C92E7bd808711A582",
"curveRegistry": ""
}
},
@@ -103,19 +103,6 @@
"curveRegistry": "0x445FE580eF8d70FF569aB36e80c647af338db351"
}
},
"4002": {
"chainId": "4002",
"explorer": "https://testnet.ftmscan.com/",
"label": "Fantom Testnet",
"shortName": "FTM-Testnet",
"code": "ftm",
"rpc": "https://rpc.testnet.fantom.network/",
"baseCurrencyName": "FTM",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"250": {
"chainId": "250",
"explorer": "https://ftmscan.com/",
@@ -142,19 +129,6 @@
"curveRegistry": "0x094d12e5b541784701FD8d65F11fc0598FBC6332"
}
},
"80001": {
"chainId": "80001",
"label": "Polygon Mumbai",
"shortName": "Polygon Mumbai",
"code": "polygon",
"baseCurrencyName": "MATIC",
"rpc": "https://rpc.ankr.com/polygon_mumbai",
"explorer": "https://mumbai.polygonscan.com/",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"66": {
"chainId": "66",
"explorer": "https://www.oklink.com/okc/",
@@ -168,32 +142,6 @@
"curveRegistry": ""
}
},
"65": {
"chainId": "65",
"explorer": "https://www.oklink.com/okc-test/",
"label": "OKC Testnet",
"shortName": "OKC-Testnet",
"code": "okc",
"rpc": "https://exchaintestrpc.okex.org/",
"baseCurrencyName": "OKT",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"56303": {
"chainId": "56303",
"label": "DRIP Chain",
"shortName": "DRIP Chain",
"code": "drip",
"baseCurrencyName": "DRIP",
"rpc": "https://testnet.1d.rip/",
"explorer": "https://explorer-testnet.1d.rip/",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"2525": {
"chainId": "2525",
"label": "inEVM",
@@ -219,5 +167,31 @@
"WETH": "0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f",
"curveRegistry": ""
}
},
"43114": {
"chainId": "43114",
"label": "Avalanche Network",
"shortName": "Avax",
"code": "avax",
"baseCurrencyName": "AVAX",
"rpc": "https://api.avax.network/ext/bc/C/rpc/",
"explorer": "https://snowtrace.io/",
"contracts": {
"WETH": "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7",
"curveRegistry": ""
}
},
"8453": {
"chainId": "8453",
"label": "Base",
"shortName": "BASE",
"code": "base",
"baseCurrencyName": "ETH",
"rpc": "https://mainnet.base.org/",
"explorer": "https://basescan.org/",
"contracts": {
"WETH": "0x4200000000000000000000000000000000000006",
"curveRegistry": ""
}
}
}

View File

@@ -165,6 +165,42 @@
"http": "/orion-indexer/"
}
}
},
"43114": {
"api": "https://trade.orion.xyz/avalanche-c-chain",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"8453": {
"api": "https://trade.orion.xyz/base-mainnet",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
}
}
},
@@ -191,8 +227,8 @@
},
"liquidityMigratorAddress": "0x01b10dds12478C88A5E18e2707E729906bC25CfF6"
},
"5": {
"api": "https://testing.orion.xyz/eth-goerli",
"11155111": {
"api": "https://testing.orion.xyz/eth-sepolia",
"services": {
"aggregator": {
"http": "/backend",
@@ -209,8 +245,8 @@
}
}
},
"421613": {
"api": "https://testing.orion.xyz/arbitrum-goerli",
"123420000034": {
"api": "https://testing.orion.xyz/event-horizon-testnet",
"services": {
"aggregator": {
"http": "/backend",
@@ -227,44 +263,8 @@
}
}
},
"4002": {
"api": "https://testing.orion.xyz/ftm-testnet",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"80001": {
"api": "https://testing.orion.xyz/polygon-mumbai",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"56303": {
"api": "https://testing.orion.xyz/drip-testnet",
"1952959480": {
"api": "https://testing.orion.xyz/lumia-testnet",
"services": {
"aggregator": {
"http": "/backend",
@@ -414,7 +414,7 @@
}
},
"2525": {
"api": "https://trade.orion.xyz/inevm-mainnet",
"api": "https://staging.orion.xyz/inevm-mainnet",
"services": {
"aggregator": {
"http": "/backend",
@@ -432,7 +432,43 @@
}
},
"59144": {
"api": "https://trade.orion.xyz/linea-mainnet",
"api": "https://staging.orion.xyz/linea-mainnet",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"43114": {
"api": "https://staging.orion.xyz/avalanche-c-chain",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"8453": {
"api": "https://staging.orion.xyz/base-mainnet",
"services": {
"aggregator": {
"http": "/backend",
@@ -472,24 +508,6 @@
"http": "/orion-indexer/"
}
}
},
"3": {
"api": "https://dn-dev.orion.xyz/eth-ropsten",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
}
}
},

View File

@@ -16,7 +16,7 @@ export const pureEnvNetworksSchema = z.object({
}),
indexer: z.object({
http: z.string(),
}).optional(),
}).optional()
}),
rpc: z.string().optional(),
liquidityMigratorAddress: z.string().optional(),

View File

@@ -2,12 +2,9 @@ import { SupportedChainId } from '../types.js';
export const developmentChains = [
SupportedChainId.BSC_TESTNET,
SupportedChainId.ROPSTEN,
SupportedChainId.GOERLI,
SupportedChainId.ARBITRUM_GOERLI,
SupportedChainId.FANTOM_TESTNET,
SupportedChainId.POLYGON_TESTNET,
SupportedChainId.OKC_TESTNET,
SupportedChainId.SEPOLIA,
SupportedChainId.EVENT_HORIZON_TESTNET,
SupportedChainId.LUMIA_TESTNET,
];
export const productionChains = [
SupportedChainId.MAINNET,
@@ -19,4 +16,6 @@ export const productionChains = [
SupportedChainId.OPBNB,
SupportedChainId.INEVM,
SupportedChainId.LINEA,
SupportedChainId.AVAX,
SupportedChainId.BASE,
];

View File

@@ -1 +1 @@
export default ["UniswapV2", "UniswapV3", "Curve", "OrionV2", "OrionV3"] as const
export default ["UniswapV2", "UniswapV3", "Curve", "OrionV2", "OrionV3", "Aero"] as const

View File

@@ -1 +1 @@
export default ['ftm', 'bsc', 'eth', 'polygon', 'okc', 'arb', 'drip', 'opbnb', 'inevm', 'linea'] as const;
export default ['ftm', 'bsc', 'eth', 'polygon', 'okc', 'arb', 'opbnb', 'inevm', 'linea', 'avax', 'base', 'lumia'] as const;

View File

@@ -1 +1 @@
export default ['FTM', 'BSC', 'ETH', 'POLYGON', 'OKC', 'ARB', 'OPBNB', 'INEVM', 'LINEA'] as const;
export default ['FTM', 'BSC', 'ETH', 'POLYGON', 'OKC', 'ARB', 'OPBNB', 'INEVM', 'LINEA', 'AVAX', 'BASE', 'LUMIA'] as const;

View File

@@ -19,9 +19,10 @@ import httpToWS from '../../utils/httpToWS.js';
import { ethers } from 'ethers';
import orderSchema from './schemas/orderSchema.js';
import { fetchWithValidation } from 'simple-typed-fetch';
import hmacSHA256 from "crypto-js/hmac-sha256";
import Hex from "crypto-js/enc-hex";
import {pmmOrderSchema} from "../../Unit/Pmm/schemas/order";
import { pmmOrderSchema } from '../../Unit/Pmm/schemas/order';
// import hmacSHA256 from "crypto-js/hmac-sha256";
// import Hex from "crypto-js/enc-hex";
// const crypto = require('crypto')
class Aggregator {
private readonly apiUrl: string;
@@ -34,11 +35,16 @@ class Aggregator {
return this.apiUrl;
}
public logger: ((message: string) => void) | undefined;
constructor(
httpAPIUrl: string,
wsAPIUrl: string,
basicAuth?: BasicAuthCredentials
basicAuth?: BasicAuthCredentials,
logger?: ((message: string) => void) | undefined
) {
this.logger = logger;
// const oaUrl = new URL(apiUrl);
// const oaWsProtocol = oaUrl.protocol === 'https:' ? 'wss' : 'ws';
// const aggregatorWsUrl = `${oaWsProtocol}://${oaUrl.host + (oaUrl.pathname === '/'
@@ -46,7 +52,7 @@ class Aggregator {
// : oaUrl.pathname)}/v1`;
this.apiUrl = httpAPIUrl;
this.ws = new AggregatorWS(httpToWS(wsAPIUrl));
this.ws = new AggregatorWS(httpToWS(wsAPIUrl), undefined, logger);
this.basicAuth = basicAuth;
this.getHistoryAtomicSwaps = this.getHistoryAtomicSwaps.bind(this);
@@ -55,6 +61,7 @@ class Aggregator {
this.getPairsList = this.getPairsList.bind(this);
this.getSwapInfo = this.getSwapInfo.bind(this);
this.getTradeProfits = this.getTradeProfits.bind(this);
this.getStableCoins = this.getStableCoins.bind(this);
this.placeAtomicSwap = this.placeAtomicSwap.bind(this);
this.placeOrder = this.placeOrder.bind(this);
this.cancelOrder = this.cancelOrder.bind(this);
@@ -65,6 +72,7 @@ class Aggregator {
this.getPoolReserves = this.getPoolReserves.bind(this);
this.getVersion = this.getVersion.bind(this);
this.getPrices = this.getPrices.bind(this);
this.getIsCexLiquidityAvailable = this.getIsCexLiquidityAvailable.bind(this);
}
get basicAuthHeaders() {
@@ -255,21 +263,22 @@ class Aggregator {
);
getSwapInfo = (
type: 'exactSpend' | 'exactReceive',
assetIn: string,
assetOut: string,
amount: string,
instantSettlement?: boolean,
exchanges?: string[] | 'cex' | 'pools',
isTradeBuy?: boolean,
) => {
const url = new URL(`${this.apiUrl}/api/v1/swap`);
url.searchParams.append('assetIn', assetIn);
url.searchParams.append('assetOut', assetOut);
if (type === 'exactSpend') {
if (isTradeBuy !== true) {
url.searchParams.append('amountIn', amount);
} else {
url.searchParams.append('amountOut', amount);
}
if (exchanges !== undefined) {
if (Array.isArray(exchanges)) {
exchanges.forEach((exchange) => {
@@ -334,6 +343,16 @@ class Aggregator {
);
};
getStableCoins = () => {
const url = new URL(`${this.apiUrl}/api/v1/tokens/stable/`);
return fetchWithValidation(
url.toString(),
z.array(z.string()),
{ headers: this.basicAuthHeaders },
errorSchema,
);
};
/**
* Placing atomic swap. Placement must take place on the target chain.
* @param secretHash Secret hash
@@ -373,24 +392,38 @@ class Aggregator {
return fetchWithValidation(url.toString(), atomicSwapHistorySchema, { headers: this.basicAuthHeaders });
};
getIsCexLiquidityAvailable = (
assetIn: string,
assetOut: string,
) => {
const url = new URL(`${this.apiUrl}/api/v1/pairs/cex/liquidity/${assetIn}/${assetOut}`);
private encode_utf8(s : string) {
return unescape(encodeURIComponent(s));
return fetchWithValidation(
url.toString(),
z.boolean(),
{ headers: this.basicAuthHeaders },
errorSchema,
);
};
// private encode_utf8(s: string) {
// return unescape(encodeURIComponent(s));
// }
// @ts-expect-error: TODO: please remove this line!
private sign(message: string, key: string) {
// return crypto.createHmac('sha256', this.encode_utf8(key))
// .update(this.encode_utf8(message))
// .digest('hex');
return '';
}
private sign(message : string, key: string) {
return hmacSHA256(
this.encode_utf8(message),
this.encode_utf8(key)
).toString(Hex);
}
private generateHeaders(body : any, method : string, path : string, timestamp : number, apiKey : string, secretKey : string) {
private generateHeaders(body: any, method: string, path: string, timestamp: number, apiKey: string, secretKey: string) {
const sortedBody = Object.keys(body)
.sort()
.map((key) => (
`${key}=${body[key]}`
)).join('&');
.sort()
.map((key) => (
`${key}=${body[key]}`
)).join('&');
const payload = timestamp + method.toUpperCase() + path + sortedBody;
@@ -407,40 +440,38 @@ class Aggregator {
}
public async RFQOrder(
tokenFrom: string,
tokenTo: string,
fromTokenAmount: string,
apiKey: string, //
secretKey: string,
wallet: string
) : Promise<z.infer<typeof pmmOrderSchema>> {
tokenFrom: string,
tokenTo: string,
fromTokenAmount: string,
apiKey: string, //
secretKey: string,
wallet: string
): Promise<z.infer<typeof pmmOrderSchema>> {
// Making the order structure
const
path = '/rfq'
, url = `${this.apiUrl}/api/v1/integration/pmm`+path
, headers = {
'Content-Type': 'application/json',
}
, data = {
"baseToken":tokenFrom, // USDT
"quoteToken":tokenTo, // ORN
"amount": fromTokenAmount, // 100
"taker": wallet,
"feeBps": 0
}
, method = 'POST'
, timestamp = Date.now()
, signatureHeaders = this.generateHeaders(data, method, path, timestamp, apiKey, secretKey)
, compiledHeaders = {...headers, ...signatureHeaders.headers, }
, body = JSON.stringify(data)
path = '/rfq';
const url = `${this.apiUrl}/api/v1/integration/pmm` + path;
const headers = {
'Content-Type': 'application/json',
};
const data = {
baseToken: tokenFrom, // USDT
quoteToken: tokenTo, // ORN
amount: fromTokenAmount, // 100
taker: wallet,
feeBps: 0
};
const method = 'POST';
const timestamp = Date.now();
const signatureHeaders = this.generateHeaders(data, method, path, timestamp, apiKey, secretKey);
const compiledHeaders = { ...headers, ...signatureHeaders.headers, };
const body = JSON.stringify(data)
;
let res = pmmOrderSchema.parse({});
const res = pmmOrderSchema.parse({});
try {
const result = await fetch(url,{
const result = await fetch(url, {
headers: compiledHeaders,
method,
body
@@ -449,25 +480,23 @@ class Aggregator {
const json = await result.json();
const parseResult = pmmOrderSchema.safeParse(json);
if(!parseResult.success) {
if (!parseResult.success) {
// Try to parse error answer
const errorSchema = z.object({error: z.object({code: z.number(), reason: z.string()})});
const errorSchema = z.object({ error: z.object({ code: z.number(), reason: z.string() }) });
const errorParseResult = errorSchema.safeParse(json);
if(!errorParseResult.success)
throw Error(`Unrecognized answer from aggregator: ${json}`);
if (!errorParseResult.success) { throw Error(`Unrecognized answer from aggregator: ${json}`); }
throw Error(errorParseResult.data.error.reason);
}
res.quotation = parseResult.data.quotation;
res.order = parseResult.data.order;
res.signature = parseResult.data.signature;
res.error = '';
res.success = true;
// return result;
}
catch(err) {
} catch (err) {
res.error = `${err}`;
}
return res;

View File

@@ -12,6 +12,8 @@ const exchangeContractStep = z.object({
assetIn: z.string(),
assetOut: z.string(),
factory: z.string(),
assetAddressIn: z.string(),
assetAddressOut: z.string(),
});
const swapInfoBase = z.object({
@@ -49,6 +51,7 @@ const swapInfoBase = z.object({
mi: z.number().optional(), // market amount in, USD
d: z.string().optional(), // difference in available amount in/out (USD) and market amount out/in (USD) in percentage
}).optional(),
autoSlippage: z.number().optional(),
});
const swapInfoByAmountIn = swapInfoBase.extend({
@@ -58,7 +61,7 @@ const swapInfoByAmountIn = swapInfoBase.extend({
marketAmountIn: z.null(),
}).transform((val) => ({
...val,
type: 'exactSpend' as const,
isTradeBuy: false as const,
}));
const swapInfoByAmountOut = swapInfoBase.extend({
@@ -68,7 +71,7 @@ const swapInfoByAmountOut = swapInfoBase.extend({
marketAmountIn: z.number().nullable(),
}).transform((val) => ({
...val,
type: 'exactReceive' as const,
isTradeBuy: true as const,
}));
const swapInfoSchema = swapInfoByAmountIn.or(swapInfoByAmountOut);

View File

@@ -66,10 +66,11 @@ type PairConfigSubscription = {
type AggregatedOrderbookSubscription = {
payload: string
dc?: number
callback: (
asks: OrderbookItem[],
bids: OrderbookItem[],
pair: string
pair: string,
) => void
errorCb?: (message: string) => void
}
@@ -195,9 +196,10 @@ class AggregatorWS {
readonly basicAuth?: BasicAuthCredentials | undefined;
constructor(wsUrl: string, basicAuth?: BasicAuthCredentials) {
constructor(wsUrl: string, basicAuth?: BasicAuthCredentials, logger?: ((message: string) => void) | undefined) {
this.wsUrl = wsUrl;
this.basicAuth = basicAuth;
this.logger = logger;
}
private messageQueue: BufferLike[] = [];
@@ -252,7 +254,7 @@ class AggregatorWS {
subscription: Subscription[T],
prevSubscriptionId?: string
) {
const id = type === 'aobus'
const id = type === SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE
? ((subscription as any).payload as string) // TODO: Refactor!!!
: uuidv4();
@@ -262,6 +264,12 @@ class AggregatorWS {
subRequest['T'] = type;
subRequest['id'] = id;
if ('dc' in subscription) {
if (typeof subscription.dc === 'number') {
subRequest['dc'] = subscription.dc;
}
}
if ('payload' in subscription) {
if (typeof subscription.payload === 'string') {
subRequest['S'] = subscription.payload;
@@ -369,11 +377,11 @@ class AggregatorWS {
delete this.subscriptions[SubscriptionType.ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE]?.[newestSubId];
// !!! swap info subscription is uuid that contains hyphen
} else if (isOrderBooksSubscription(newestSubId)) { // is pair name(AGGREGATED_ORDER_BOOK_UPDATE)
const aobSubscriptions = this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE];
if (aobSubscriptions) {
const targetAobSub = Object.entries(aobSubscriptions).find(([, value]) => value?.payload === newestSubId);
if (targetAobSub) {
const [key] = targetAobSub;
const aobusSubscriptions = this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE];
if (aobusSubscriptions) {
const targetAobusSub = Object.entries(aobusSubscriptions).find(([, value]) => value?.payload === newestSubId);
if (targetAobusSub) {
const [key] = targetAobusSub;
delete this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE]?.[key];
}
}
@@ -506,6 +514,9 @@ class AggregatorWS {
assetIn: path.ai,
assetOut: path.ao,
factory: path.f,
assetAddressIn: path.aai,
assetAddressOut: path.aao,
fee: path.fee,
})),
poolOptimal: json.po,
...(json.oi) && {
@@ -533,21 +544,22 @@ class AggregatorWS {
marketAmountIn: json.usd.mi,
difference: json.usd.d,
},
autoSlippage: json.sl,
};
switch (json.k) { // kind
case 'exactSpend':
switch (json.tb) { // isTradeBuy
case false:
this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE]?.[json.S]?.callback({
kind: json.k,
isTradeBuy: false,
marketAmountOut: json.mo,
availableAmountIn: json.aa,
...baseSwapInfo,
});
break;
case 'exactReceive':
case true:
this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE]?.[json.S]?.callback({
kind: json.k,
isTradeBuy: true,
...baseSwapInfo,
marketAmountIn: json.mi,
availableAmountOut: json.aao,

View File

@@ -40,6 +40,9 @@ const swapInfoSchemaBase = baseMessageSchema.extend({
ai: z.string().toUpperCase(), // asset in
ao: z.string().toUpperCase(), // asset out
f: factorySchema, // factory
aai: z.string(), // asset address in
aao: z.string(), // asset address out
fee: z.number().optional(), // fee
})),
usd: z.object({ // USD info of this swap, nullable
aa: z.number().optional(), // available amount in, USD
@@ -48,6 +51,7 @@ const swapInfoSchemaBase = baseMessageSchema.extend({
mi: z.number().optional(), // market amount in, USD
d: z.string().optional(), // difference in available amount in/out (USD) and market amount out/in (USD) in percentage
}).optional(),
sl: z.number().optional(),
});
const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({
@@ -55,7 +59,7 @@ const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({
aa: z.number(), // available amount in
}).transform((content) => ({
...content,
k: 'exactSpend' as const,
tb: false as const, // isTradeBuy
}));
const swapInfoSchemaByAmountOut = swapInfoSchemaBase.extend({
@@ -63,7 +67,7 @@ const swapInfoSchemaByAmountOut = swapInfoSchemaBase.extend({
aao: z.number(), // available amount out
}).transform((content) => ({
...content,
k: 'exactReceive' as const,
tb: true as const, // isTradeBuy
}));
const swapInfoSchema = z.union([

View File

@@ -114,6 +114,7 @@ class BlockchainService {
this.getRedeemOrderBySecretHash = this.getRedeemOrderBySecretHash.bind(this);
this.claimOrder = this.claimOrder.bind(this);
this.getGasLimits = this.getGasLimits.bind(this);
this.getExchangeContractWalletBalance = this.getExchangeContractWalletBalance.bind(this);
}
get basicAuthHeaders() {
@@ -495,6 +496,12 @@ class BlockchainService {
z.record(z.number()),
{ headers: this.basicAuthHeaders }
);
getExchangeContractWalletBalance = (exchangeContractAddress: string) => fetchWithValidation(
`${this.apiUrl}/api/broker/getWalletBalance/${exchangeContractAddress}`,
z.record(z.string()),
{ headers: this.basicAuthHeaders }
);
}
export * as schemas from './schemas/index.js';

View File

@@ -1,5 +1,5 @@
import { z } from 'zod';
import { makePartial } from '../../../utils/index.js';
import { makePartial } from '../../../utils';
const internalFeeAssetSchema = z.object({
type: z.enum(['percent', 'plain']),
@@ -10,8 +10,10 @@ const internalFeeAssetSchema = z.object({
const infoSchema = z.object({
chainId: z.number(),
chainName: z.string(),
exchangeContractAddress: z.string(),
swapExecutorContractAddress: z.string(),
libValidatorContractAddress: z.string().optional(),
exchangeContractAddress: z.string(),
spvContractAddress: z.string(),
oracleContractAddress: z.string(),
matcherAddress: z.string(),
orderFeePercent: z.number(),

View File

@@ -0,0 +1,13 @@
import { z } from 'zod';
import { SupportedChainId } from '../../../types';
export const tickerSchema = z.object({
pair: z.string(),
volume24: z.number(),
change24: z.number(),
lastPrice: z.number(),
pricePrecision: z.number(),
networks: z.array(z.nativeEnum(SupportedChainId)),
});
export const tickersSchema = z.array(tickerSchema);

View File

@@ -1,5 +1,6 @@
import {
environmentResponseSchema,
getPointsAtResponseSchema,
getPoolResponseSchema,
listAmountResponseSchema,
listNFTOrderResponseSchema,
@@ -51,6 +52,12 @@ type VeORNInfoPayload = BasePayload & {
params: [string]
};
type GetPointsAtPayload = BasePayload & {
model: 'veORN'
method: 'pointsInfo'
params: [number, number]
};
type ListAmountPayload = BasePayload & {
model: string
method: 'listAmount'
@@ -68,6 +75,7 @@ type Payload =
| GetPoolInfoPayload
| ListPoolPayload
| VeORNInfoPayload
| GetPointsAtPayload
| ListAmountPayload
| GetAmountByORNPayload;
@@ -92,6 +100,7 @@ class IndexerService {
this.poolV2Info = this.poolV2Info.bind(this);
this.listPoolV3 = this.listPoolV3.bind(this);
this.veORNInfo = this.veORNInfo.bind(this);
this.getPointsAt = this.getPointsAt.bind(this);
this.listAmount = this.listAmount.bind(this);
this.getAmountByORN = this.getAmountByORN.bind(this);
this.getAmountAt = this.getAmountAt.bind(this);
@@ -118,6 +127,21 @@ class IndexerService {
});
};
/**
* @param {number} page - current page
* @param {number} [pageSize] - amount of items on one page
*/
readonly getPointsAt = (page = 1, pageSize = 1000) => {
return fetchWithValidation(this.apiUrl, getPointsAtResponseSchema, {
method: 'POST',
body: this.makeRPCPayload({
model: 'veORN',
method: 'pointsAt',
params: [page, pageSize],
}),
});
};
/**
* @param {number} amount - amount
* @param {number} [timestamp = Date.now()] - timestamp, defaults to current time

View File

@@ -0,0 +1,15 @@
import { z } from 'zod';
import infoSchema from './info-schema.js';
const getPointsAtResultSchema = z.object({
pointsObject: z.record(z.string(), z.number()),
currentPage: z.number(),
totalElements: z.number(),
});
const getPointsAtSchema = z.object({
result: getPointsAtResultSchema,
info: infoSchema,
}).nullable();
export default getPointsAtSchema;

View File

@@ -9,3 +9,4 @@ export { default as veORNInfoResponseSchema } from './veORN-info-schema';
export { default as listAmountResponseSchema } from './list-amount-schema';
export { default as votingInfoResponseSchema } from './voting-info-schema';
export { default as testIncrementorSchema } from './test-incrementor-schema';
export { default as getPointsAtResponseSchema } from './get-points-at-schema';

View File

@@ -12,12 +12,15 @@ const veORNResultSchema = z.object({
weeklyReward: z.number(),
userAPR: z.number(),
userVeORN: z.number(),
userVeORNBalance: z.number(),
userORNLocked: z.number(),
userLockEndDate: z.number(),
userReward: z.number(),
userWeeklyReward: z.number(),
userMinLockPeriod: z.number(),
});
dropLock: z.boolean().optional(),
pointsReward: z.number().optional(),
}).passthrough();
const veORNInfoSchema = z.object({
result: veORNResultSchema,

View File

@@ -4,3 +4,4 @@ export * as priceFeed from './PriceFeed/index.js';
export * as referralSystem from './ReferralSystem/index.js';
export * as frontage from './Frontage';
export * as indexer from './Indexer/index.js';
export * as frontage from './Frontage/index.js';

View File

@@ -3,8 +3,9 @@ import type factories from './constants/factories.js';
import type { BigNumber } from 'bignumber.js';
import type subOrderStatuses from './constants/subOrderStatuses.js';
import type positionStatuses from './constants/positionStatuses.js';
import type { knownEnvs } from './config/schemas/index.js';
import type { knownEnvs } from './config/schemas';
import type getHistory from './Orion/bridge/getHistory.js';
import type { networkCodes } from './constants';
export type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
@@ -81,23 +82,21 @@ export type Pair = {
export enum SupportedChainId {
MAINNET = '1',
ROPSTEN = '3',
GOERLI = '5',
ARBITRUM = '42161',
FANTOM_OPERA = '250',
POLYGON = '137',
BSC = '56',
OKC = '66',
POLYGON = '137',
OPBNB = '204',
FANTOM_OPERA = '250',
INEVM = '2525',
BASE = '8453',
ARBITRUM = '42161',
AVAX = '43114',
LINEA = '59144',
POLYGON_TESTNET = '80001',
FANTOM_TESTNET = '4002',
BSC = '56',
BSC_TESTNET = '97',
OKC_TESTNET = '65',
DRIP_TESTNET = '56303',
ARBITRUM_GOERLI = '421613',
SEPOLIA = '11155111',
EVENT_HORIZON_TESTNET = '123420000034',
LUMIA_TESTNET = '1952959480',
// For testing and debug purpose
// BROKEN = '0',
@@ -175,6 +174,9 @@ export type SingleSwap = {
assetIn: string
assetOut: string
factory: Factory
assetAddressIn: string
assetAddressOut: string
fee?: number | undefined
}
export type SwapInfoBase = {
@@ -208,16 +210,17 @@ export type SwapInfoBase = {
marketAmountIn: number | undefined
difference: string | undefined
} | undefined
autoSlippage: number | undefined
}
export type SwapInfoByAmountIn = SwapInfoBase & {
kind: 'exactSpend'
isTradeBuy: false
availableAmountIn?: number | undefined
marketAmountOut?: number | undefined
}
export type SwapInfoByAmountOut = SwapInfoBase & {
kind: 'exactReceive'
isTradeBuy: true
marketAmountIn?: number | undefined
availableAmountOut?: number | undefined
}
@@ -283,22 +286,22 @@ export type EnvConfig = {
referralAPI: string
frontageAPI: string
networks: Partial<
Record<
SupportedChainId,
VerboseUnitConfig
Record<
SupportedChainId,
VerboseUnitConfig
>
>
>
}
export type AggregatedAssets = Partial<
Record<
string,
Partial<
Record<SupportedChainId, {
address: string
}>
Record<
string,
Partial<
Record<SupportedChainId, {
address: string
}>
>
>
>
>;
>;
export type RedeemOrder = {
sender: string
@@ -438,9 +441,9 @@ type BridgeHistory = Awaited<ReturnType<typeof getHistory>>;
type BridgeHistoryItem = NonNullable<BridgeHistory[string]>;
export type AtomicSwap = Partial<
Omit<BridgeHistoryItem, 'creationDate' | 'expiration' | 'secret'>
Omit<BridgeHistoryItem, 'creationDate' | 'expiration' | 'secret'>
> & Partial<
Omit<AtomicSwapLocal, 'creationDate' | 'expiration' | 'secret'>
Omit<AtomicSwapLocal, 'creationDate' | 'expiration' | 'secret'>
> & {
sourceChainId: SupportedChainId
targetChainId: SupportedChainId
@@ -459,3 +462,21 @@ export type AtomicSwap = Partial<
}
export type OrderSource = 'TERMINAL_MARKET' | 'TERMINAL_LIMIT' | 'SWAP_UI' | 'WIDGET';
// Frontage
export type NetworkCode = typeof networkCodes[number];
export type TickersCategories = 'FAVORITES' | 'USD' | 'ORN' | 'NATIVE' | 'ALTS';
export type TickersSortBy = 'PRICE' | 'CHANGE' | 'VOLUME';
export type TickersSortType = 'ASCENDING' | 'DESCENDING';
export type TickersBaseSearchParams = {
currentNetwork?: NetworkCode
targetNetwork?: NetworkCode
sortBy?: TickersSortBy
sortType?: TickersSortType
offset?: number
limit?: number
}

View File

@@ -195,5 +195,5 @@ export async function getTotalBalance(
walletBalance,
exchangeBalance,
totalBalance: walletBalance + exchangeBalance
}
}
}

View File

@@ -8,7 +8,6 @@ const swapThroughOrionPoolSchema = z.object({
z.bigint(), // amount_spend
z.bigint(), // amount_receive
z.string().refine(ethers.isAddress).array().nonempty(), // path
z.boolean(), // is_exact_spend
]),
}).transform((data) => ({
name: data.name,
@@ -16,7 +15,6 @@ const swapThroughOrionPoolSchema = z.object({
amount_spend: data.args[0],
amount_receive: data.args[1],
path: data.args[2],
is_exact_spend: data.args[3],
},
}));