Strictest / tests / minor improvements

This commit is contained in:
Aleksandr Kraiz
2023-02-17 17:31:35 +04:00
parent 11c8348ee9
commit 2c24f71453
39 changed files with 566 additions and 128 deletions

View File

@@ -28,6 +28,13 @@ module.exports = {
'@typescript-eslint',
],
rules: {
"@typescript-eslint/consistent-type-imports": [
"error",
{
"fixStyle": "separate-type-imports",
"disallowTypeAnnotations": true
}
],
"@typescript-eslint/strict-boolean-expressions": [
"error",
{

36
package-lock.json generated
View File

@@ -1,14 +1,15 @@
{
"name": "@orionprotocol/sdk",
"version": "0.17.7-rc.1",
"version": "0.17.11",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@orionprotocol/sdk",
"version": "0.17.7-rc.1",
"version": "0.17.11",
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@lukeed/csprng": "^1.0.1",
@@ -515,6 +516,22 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
"integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -11637,6 +11654,21 @@
"@babel/helper-plugin-utils": "^7.19.0"
}
},
"@babel/runtime": {
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
"integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
"requires": {
"regenerator-runtime": "^0.13.11"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
}
}
},
"@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@orionprotocol/sdk",
"version": "0.17.11",
"version": "0.17.12",
"description": "Orion Protocol SDK",
"main": "./lib/esm/index.js",
"module": "./lib/esm/index.js",
@@ -19,7 +19,7 @@
"lint:eslint:fix": "eslint ./src --ext .ts,.js,.tsx,.jsx --fix",
"postpublish": "npm run publish-npm",
"publish-npm": "npm publish --access public --ignore-scripts --@orionprotocol:registry='https://registry.npmjs.org'",
"test": "jest",
"test": "dotenv jest",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch"
},
@@ -70,6 +70,7 @@
"webpack-cli": "^5.0.1"
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@lukeed/csprng": "^1.0.1",

View File

@@ -28,7 +28,7 @@ export default class BalanceGuard {
private readonly signer: ethers.Signer;
private readonly logger?: (message: string) => void;
private readonly logger?: ((message: string) => void) | undefined
constructor(
balances: Partial<Record<string, Record<'exchange' | 'wallet', BigNumber>>>,

View File

@@ -1,11 +1,11 @@
import { merge } from 'merge-anything';
import { chains, envs } from '../config';
import { type networkCodes } from '../constants';
import type { networkCodes } from '../constants';
import OrionUnit from '../OrionUnit';
import OrionAnalytics from '../services/OrionAnalytics';
import { ReferralSystem } from '../services/ReferralSystem';
import simpleFetch from '../simpleFetch';
import { type SupportedChainId, type DeepPartial, type VerboseOrionUnitConfig } from '../types';
import type { SupportedChainId, DeepPartial, VerboseOrionUnitConfig, KnownEnv } from '../types';
import { isValidChainId } from '../utils';
type EnvConfig = {
@@ -29,8 +29,6 @@ type AggregatedAssets = Partial<
>
>;
type KnownEnv = 'testing' | 'staging' | 'production';
export default class Orion {
public readonly env?: string;

View File

@@ -116,12 +116,19 @@ export default async function deposit({
unsignedTx.nonce = nonce;
const signedTx = await signer.signTransaction(unsignedTx);
const txResponse = await provider.sendTransaction(signedTx);
console.log(`Deposit tx sent: ${txResponse.hash}. Waiting for confirmation...`);
const txReceipt = await txResponse.wait();
if (txReceipt.status !== undefined) {
console.log('Deposit tx confirmed');
} else {
console.log('Deposit tx failed');
try {
const txResponse = await provider.sendTransaction(signedTx);
console.log(`Deposit tx sent: ${txResponse.hash}. Waiting for confirmation...`);
const txReceipt = await txResponse.wait();
if (txReceipt.status !== undefined) {
console.log('Deposit tx confirmed');
} else {
console.log('Deposit tx failed');
}
} catch (e) {
if (!(e instanceof Error)) throw new Error('e is not an Error');
console.error(`Deposit tx failed: ${e.message}`, {
unsignedTx,
});
}
}

View File

@@ -1,8 +1,8 @@
import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import { NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../../constants';
import { type OrionAggregator } from '../../services/OrionAggregator';
import { type OrionBlockchain } from '../../services/OrionBlockchain';
import type { OrionAggregator } from '../../services/OrionAggregator';
import type { OrionBlockchain } from '../../services/OrionBlockchain';
import simpleFetch from '../../simpleFetch';
import { calculateFeeInFeeAsset, denormalizeNumber, getNativeCryptocurrency } from '../../utils';

View File

@@ -10,6 +10,8 @@ import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency';
import simpleFetch from '../../simpleFetch';
import { calculateFeeInFeeAsset, denormalizeNumber, normalizeNumber } from '../../utils';
import { signOrder } from '../../crypt';
import type orderSchema from '../../services/OrionAggregator/schemas/orderSchema';
import type { z } from 'zod';
export type SwapMarketParams = {
type: 'exactSpend' | 'exactReceive'
@@ -34,11 +36,13 @@ export type SwapMarketParams = {
type AggregatorOrder = {
through: 'aggregator'
id: string
wait: () => Promise<z.infer<typeof orderSchema>>
}
type PoolSwap = {
through: 'orion_pool'
txHash: string
wait: (confirmations?: number | undefined) => Promise<ethers.providers.TransactionReceipt>
}
export type Swap = AggregatorOrder | PoolSwap;
@@ -293,6 +297,7 @@ export default async function swapMarket({
const swapThroughOrionPoolTxResponse = await signer.sendTransaction(unsignedSwapThroughOrionPoolTx);
options?.logger?.(`Transaction sent. Tx hash: ${swapThroughOrionPoolTxResponse.hash}`);
return {
wait: swapThroughOrionPoolTxResponse.wait,
through: 'orion_pool',
txHash: swapThroughOrionPoolTxResponse.hash,
};
@@ -408,6 +413,26 @@ export default async function swapMarket({
options?.logger?.(`Order placed. Order id: ${orderId}`);
return {
wait: () => new Promise<z.infer<typeof orderSchema>>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout'))
}, 60000);
const interval = setInterval(() => {
simpleFetch(orionAggregator.getOrder)(orderId).then((data) => {
if (data.order.status === 'SETTLED') {
options?.logger?.(`Order ${orderId} settled`);
clearTimeout(timeout);
clearInterval(interval);
resolve(data);
} else {
options?.logger?.(`Order ${orderId} status: ${data.order.status}`);
}
}).catch((e) => {
if (!(e instanceof Error)) throw new Error('Not an error');
options?.logger?.(`Error while getting order status: ${e.message}`);
});
}, 1000);
}),
through: 'aggregator',
id: orderId,
};

View File

@@ -109,10 +109,17 @@ export default async function withdraw({
const signedTx = await signer.signTransaction(unsignedTx);
const txResponse = await provider.sendTransaction(signedTx);
console.log(`Withdraw tx sent: ${txResponse.hash}. Waiting for confirmation...`);
const txReceipt = await txResponse.wait();
if (txReceipt.status !== undefined) {
console.log('Withdraw tx confirmed');
} else {
console.log('Withdraw tx failed');
try {
const txReceipt = await txResponse.wait();
if (txReceipt.status !== undefined) {
console.log('Withdraw tx confirmed');
} else {
console.log('Withdraw tx failed');
}
} catch (e) {
if (!(e instanceof Error)) throw new Error('e is not an Error');
console.error(`Deposit tx failed: ${e.message}`, {
unsignedTx,
});
}
}

View File

@@ -2,16 +2,16 @@ import { ethers } from 'ethers';
import { OrionAggregator } from '../services/OrionAggregator';
import { OrionBlockchain } from '../services/OrionBlockchain';
import { PriceFeed } from '../services/PriceFeed';
import type { SupportedChainId, VerboseOrionUnitConfig } from '../types';
import type { KnownEnv, SupportedChainId, VerboseOrionUnitConfig } from '../types';
import Exchange from './Exchange';
import FarmingManager from './FarmingManager';
import { chains } from '../config';
import { type networkCodes } from '../constants';
import { chains, envs } from '../config';
import type { networkCodes } from '../constants';
// type KnownConfig = {
// env: string;
// chainId: SupportedChainId;
// }
type KnownConfig = {
env: KnownEnv
chainId: SupportedChainId
}
// type OrionUnitConfig = KnownConfig | VerboseOrionUnitConfig;
@@ -39,8 +39,36 @@ export default class OrionUnit {
// constructor(config: KnownConfig);
// constructor(config: VerboseConfig);
constructor(config: VerboseOrionUnitConfig) {
this.config = config;
constructor(config: KnownConfig | VerboseOrionUnitConfig) {
if ('env' in config) {
const staticConfig = envs[config.env];
if (!staticConfig) throw new Error(`Invalid environment: ${config.env}. Available environments: ${Object.keys(envs).join(', ')}`);
const chainConfig = chains[config.chainId];
if (!chainConfig) throw new Error(`Invalid chainId: ${config.chainId}. Available chainIds: ${Object.keys(chains).join(', ')}`);
const networkConfig = staticConfig.networks[config.chainId];
if (!networkConfig) throw new Error(`Invalid chainId: ${config.chainId}. Available chainIds: ${Object.keys(staticConfig.networks).join(', ')}`);
this.config = {
chainId: config.chainId,
nodeJsonRpc: networkConfig.rpc ?? chainConfig.rpc,
services: {
orionBlockchain: {
http: networkConfig.api + networkConfig.services.blockchain.http,
},
orionAggregator: {
http: networkConfig.api + networkConfig.services.aggregator.http,
ws: networkConfig.api + networkConfig.services.aggregator.ws,
},
priceFeed: {
api: networkConfig.api + networkConfig.services.priceFeed.all,
},
},
}
} else {
this.config = config;
}
const chainInfo = chains[config.chainId];
if (!chainInfo) throw new Error('Chain info is required');
@@ -48,14 +76,14 @@ export default class OrionUnit {
// this.env = config.env;
this.chainId = config.chainId;
this.networkCode = chainInfo.code;
this.provider = new ethers.providers.StaticJsonRpcProvider(config.nodeJsonRpc);
this.provider = new ethers.providers.StaticJsonRpcProvider(this.config.nodeJsonRpc);
this.orionBlockchain = new OrionBlockchain(config.services.orionBlockchain.http);
this.orionBlockchain = new OrionBlockchain(this.config.services.orionBlockchain.http);
this.orionAggregator = new OrionAggregator(
config.services.orionAggregator.http,
config.services.orionAggregator.ws,
this.config.services.orionAggregator.http,
this.config.services.orionAggregator.ws,
);
this.priceFeed = new PriceFeed(config.services.priceFeed.api);
this.priceFeed = new PriceFeed(this.config.services.priceFeed.api);
this.exchange = new Exchange(this);
this.farmingManager = new FarmingManager(this);
}

View File

@@ -1,4 +1,3 @@
// import { ethers } from 'ethers';
import Orion from '../Orion';
import OrionAnalytics from '../services/OrionAnalytics';
import { ReferralSystem } from '../services/ReferralSystem';

View File

@@ -0,0 +1,39 @@
import { ethers } from 'ethers';
import Orion from '../Orion';
const privateKey = process.env['PRIVATE_KEY'];
if (privateKey === undefined) throw new Error('Private key is required');
jest.setTimeout(30000);
describe('Transfers', () => {
test('Deposit ORN', async () => {
const orion = new Orion('testing');
const bscUnit = orion.getUnit('bsc');
const wallet = new ethers.Wallet(
privateKey,
bscUnit.provider
);
await bscUnit.exchange.deposit({
asset: 'ORN',
amount: 20,
signer: wallet,
});
});
test('Withdraw ORN', async () => {
const orion = new Orion('testing');
const bscUnit = orion.getUnit('bsc');
const wallet = new ethers.Wallet(
privateKey,
bscUnit.provider
);
await bscUnit.exchange.withdraw({
asset: 'ORN',
amount: 20,
signer: wallet,
});
});
});

View File

@@ -0,0 +1,39 @@
import { ethers } from 'ethers';
import Orion from '../Orion';
const privateKey = process.env['PRIVATE_KEY']
if (privateKey === undefined) throw new Error('Private key is required');
jest.setTimeout(30000);
describe('Pools', () => {
test('Add liquidity ORN', async () => {
const orion = new Orion('testing');
const bscUnit = orion.getUnit('bsc');
const wallet = new ethers.Wallet(
privateKey,
bscUnit.provider
);
await bscUnit.farmingManager.addLiquidity({
amountAsset: 'ORN',
poolName: 'ORN-USDT',
amount: 20,
signer: wallet,
});
});
test('Remove liquidity ORN', async () => {
const orion = new Orion('testing');
const bscUnit = orion.getUnit('bsc');
const wallet = new ethers.Wallet(
privateKey,
bscUnit.provider
);
await bscUnit.farmingManager.removeAllLiquidity({
poolName: 'ORN-USDT',
signer: wallet,
});
});
});

View File

@@ -0,0 +1,103 @@
import { ethers } from 'ethers';
import Orion from '../Orion';
import swapMarket from '../OrionUnit/Exchange/swapMarket';
const privateKey = process.env['PRIVATE_KEY']
if (privateKey === undefined) throw new Error('Private key is required');
jest.setTimeout(240000);
describe('Spot trading', () => {
test('Sell. Simple', async () => {
const orion = new Orion('testing');
const bscUnit = orion.getUnit('bsc');
const wallet = new ethers.Wallet(
privateKey,
bscUnit.provider
);
const result = await swapMarket({
assetIn: 'ORN',
assetOut: 'USDT',
amount: 20,
type: 'exactSpend',
signer: wallet,
feeAsset: 'USDT',
orionUnit: bscUnit,
slippagePercent: 1,
// options: {
// logger: console.log
// }
})
await result.wait();
});
test('Buy. Simple', async () => {
const orion = new Orion('testing');
const bscUnit = orion.getUnit('bsc');
const wallet = new ethers.Wallet(
privateKey,
bscUnit.provider
);
const result = await bscUnit.exchange.swapMarket({
assetIn: 'USDT',
assetOut: 'ORN',
amount: 20,
type: 'exactReceive',
signer: wallet,
feeAsset: 'USDT',
slippagePercent: 1,
// options: {
// logger: console.log
// }
})
await result.wait();
});
test('Buy. Complex', async () => {
const orion = new Orion('testing');
const bscUnit = orion.getUnit('bsc');
const wallet = new ethers.Wallet(
privateKey,
bscUnit.provider
);
const result = await bscUnit.exchange.swapMarket({
assetIn: 'USDT',
assetOut: 'BNB',
amount: 40,
type: 'exactSpend',
signer: wallet,
feeAsset: 'USDT',
slippagePercent: 1,
// options: {
// logger: console.log
// }
})
await result.wait();
});
test('Sell. Complex', async () => {
const orion = new Orion('testing');
const bscUnit = orion.getUnit('bsc');
const wallet = new ethers.Wallet(
privateKey,
bscUnit.provider
);
const result = await bscUnit.exchange.swapMarket({
assetIn: 'BNB',
assetOut: 'ETH',
amount: 0.01,
type: 'exactReceive',
signer: wallet,
feeAsset: 'USDT',
slippagePercent: 1,
// options: {
// logger: console.log
// }
});
await result.wait();
});
});

View File

@@ -1,3 +1,4 @@
// https://github.com/orionprotocol/orion-aggregator/blob/11a7847af958726c65ae5f7b14ee6463c75ea14b/src/main/java/io/orionprotocol/aggregator/model/Exchange.java
export default [
// CEXes
'ASCENDEX',

View File

@@ -1,14 +1,24 @@
import { type SupportedChainId } from '../types';
import type { SupportedChainId } from '../types';
import eip712DomainData from '../config/eip712DomainData.json';
import eip712DomainSchema from '../config/schemas/eip712DomainSchema';
const EIP712Domain = eip712DomainSchema.parse(eip712DomainData);
function removeUndefined<T>(obj: Record<string, T | undefined>) {
const newObj: Partial<Record<string, T>> = {};
for (const [key, value] of Object.entries(obj)) {
if (value !== undefined) {
newObj[key] = value;
}
}
return newObj;
}
/**
* See {@link https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator}
*/
const getDomainData = (chainId: SupportedChainId) => ({
...EIP712Domain,
...removeUndefined(EIP712Domain),
chainId,
});

View File

@@ -1,5 +1,5 @@
import { ethers } from 'ethers';
import { type CFDOrder } from '../types';
import type { CFDOrder } from '../types';
const hashCFDOrder = (order: CFDOrder) => ethers.utils.solidityKeccak256(
[

View File

@@ -1,5 +1,5 @@
import { ethers } from 'ethers';
import { type Order } from '../types';
import type { Order } from '../types';
const hashOrder = (order: Order) => ethers.utils.solidityKeccak256(
[

View File

@@ -1,9 +1,9 @@
import { type TypedDataSigner } from '@ethersproject/abstract-signer';
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
import BigNumber from 'bignumber.js';
import { type ethers } from 'ethers';
import type { ethers } from 'ethers';
import { joinSignature, splitSignature } from 'ethers/lib/utils';
import { INTERNAL_ORION_PRECISION } from '../constants';
import { type CFDOrder, type SignedCFDOrder, type SupportedChainId } from '../types';
import type { CFDOrder, SignedCFDOrder, SupportedChainId } from '../types';
import normalizeNumber from '../utils/normalizeNumber';
import getDomainData from './getDomainData';
import signCFDOrderPersonal from './signCFDOrderPersonal';

View File

@@ -1,5 +1,5 @@
import { ethers } from 'ethers';
import { type CFDOrder } from '../types';
import type { CFDOrder } from '../types';
const { arrayify, joinSignature, splitSignature } = ethers.utils;

View File

@@ -1,8 +1,8 @@
import { type TypedDataSigner } from '@ethersproject/abstract-signer';
import { type ethers } from 'ethers';
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
import type { ethers } from 'ethers';
import { joinSignature, splitSignature } from 'ethers/lib/utils';
import CANCEL_ORDER_TYPES from '../constants/cancelOrderTypes';
import { type CancelOrderRequest, type SignedCancelOrderRequest, type SupportedChainId } from '../types';
import type { CancelOrderRequest, SignedCancelOrderRequest, SupportedChainId } from '../types';
import getDomainData from './getDomainData';
import signCancelOrderPersonal from './signCancelOrderPersonal';

View File

@@ -1,6 +1,6 @@
import { ethers } from 'ethers';
import { arrayify, joinSignature, splitSignature } from 'ethers/lib/utils';
import { type CancelOrderRequest } from '../types';
import type { CancelOrderRequest } from '../types';
const signCancelOrderPersonal = async (
cancelOrderRequest: CancelOrderRequest,

View File

@@ -1,10 +1,10 @@
import { type TypedDataSigner } from '@ethersproject/abstract-signer';
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
import BigNumber from 'bignumber.js';
import { type ethers } from 'ethers';
import type { ethers } from 'ethers';
import { joinSignature, splitSignature } from 'ethers/lib/utils';
import { INTERNAL_ORION_PRECISION } from '../constants';
import ORDER_TYPES from '../constants/orderTypes';
import { type Order, type SignedOrder, type SupportedChainId } from '../types';
import type { Order, SignedOrder, SupportedChainId } from '../types';
import normalizeNumber from '../utils/normalizeNumber';
import getDomainData from './getDomainData';
import hashOrder from './hashOrder';

View File

@@ -1,5 +1,5 @@
import { ethers } from 'ethers';
import { type Order } from '../types';
import type { Order } from '../types';
const { arrayify, joinSignature, splitSignature } = ethers.utils;

View File

@@ -1,4 +1,4 @@
import { type Schema, type z } from 'zod';
import type { Schema, z } from 'zod';
import fetch from 'isomorphic-unfetch';
import {

View File

@@ -9,7 +9,7 @@ import errorSchema from './schemas/errorSchema';
import placeAtomicSwapSchema from './schemas/placeAtomicSwapSchema';
import { OrionAggregatorWS } from './ws';
import { atomicSwapHistorySchema } from './schemas/atomicSwapHistorySchema';
import { type Exchange, type SignedCancelOrderRequest, type SignedCFDOrder, type SignedOrder } from '../../types';
import type { Exchange, SignedCancelOrderRequest, SignedCFDOrder, SignedOrder } from '../../types';
import { pairConfigSchema } from './schemas';
import {
aggregatedOrderbookSchema, exchangeOrderbookSchema, poolReservesSchema,
@@ -17,6 +17,8 @@ import {
import type networkCodes from '../../constants/networkCodes';
import toUpperCase from '../../utils/toUpperCase';
import httpToWS from '../../utils/httpToWS';
import { ethers } from 'ethers';
import orderSchema from './schemas/orderSchema';
class OrionAggregator {
private readonly apiUrl: string;
@@ -55,6 +57,27 @@ class OrionAggregator {
this.getAggregatedOrderbook = this.getAggregatedOrderbook.bind(this);
this.getExchangeOrderbook = this.getExchangeOrderbook.bind(this);
this.getPoolReserves = this.getPoolReserves.bind(this);
this.getVersion = this.getVersion.bind(this);
}
getOrder = (orderId: string, owner?: string) => {
if (!ethers.utils.isHexString(orderId)) {
throw new Error(`Invalid order id: ${orderId}. Must be a hex string`);
}
const url = new URL(`${this.apiUrl}/api/v1/order`);
url.searchParams.append('orderId', orderId);
if (owner !== undefined) {
if (!ethers.utils.isAddress(owner)) {
throw new Error(`Invalid owner address: ${owner}`);
}
url.searchParams.append('owner', owner);
}
return fetchWithValidation(
url.toString(),
orderSchema,
undefined,
errorSchema,
);
}
getPairsList = (market: 'spot' | 'futures') => {
@@ -124,6 +147,17 @@ class OrionAggregator {
);
};
getVersion = () => fetchWithValidation(
`${this.apiUrl}/api/v1/version`,
z.object({
serviceName: z.string(),
version: z.string(),
apiVersion: z.string(),
}),
undefined,
errorSchema,
);
getPairConfig = (assetPair: string) => fetchWithValidation(
`${this.apiUrl}/api/v1/pairs/exchangeInfo/${assetPair}`,
pairConfigSchema,

View File

@@ -0,0 +1,101 @@
import { ethers } from 'ethers';
import { z } from 'zod';
import { exchanges, orderStatuses, subOrderStatuses } from '../../../constants';
const blockchainOrderSchema = z.object({
id: z.string().refine(ethers.utils.isHexString, (value) => ({
message: `blockchainOrder.id must be a hex string, got ${value}`,
})),
senderAddress: z.string().refine(ethers.utils.isAddress, (value) => ({
message: `blockchainOrder.senderAddress must be an address, got ${value}`,
})),
matcherAddress: z.string().refine(ethers.utils.isAddress, (value) => ({
message: `blockchainOrder.matcherAddress must be an address, got ${value}`,
})),
baseAsset: z.string().refine(ethers.utils.isAddress, (value) => ({
message: `blockchainOrder.baseAsset must be an address, got ${value}`,
})),
quoteAsset: z.string().refine(ethers.utils.isAddress, (value) => ({
message: `blockchainOrder.quoteAsset must be an address, got ${value}`,
})),
matcherFeeAsset: z.string().refine(ethers.utils.isAddress, (value) => ({
message: `blockchainOrder.matcherFeeAsset must be an address, got ${value}`,
})),
amount: z.number().int().nonnegative(),
price: z.number().int().nonnegative(),
matcherFee: z.number().int().nonnegative(),
nonce: z.number(),
expiration: z.number(),
buySide: z.union([z.literal(1), z.literal(0)]),
signature: z.string().refine(ethers.utils.isHexString, (value) => ({
message: `blockchainOrder.signature must be a hex string, got ${value}`,
})),
isPersonalSign: z.boolean(),
needWithdraw: z.boolean(),
});
const tradeInfoSchema = z.object({
tradeId: z.string().uuid(),
tradeStatus: z.enum(['NEW', 'PENDING', 'OK', 'FAIL', 'TEMP_ERROR', 'REJECTED']),
filledAmount: z.number().nonnegative(),
price: z.number().nonnegative(),
creationTime: z.number(),
updateTime: z.number(),
matchedBlockchainOrder: blockchainOrderSchema,
matchedSubOrderId: z.number().int().nonnegative(),
exchangeTradeInfo: z.boolean(),
poolTradeInfo: z.boolean(),
});
const baseOrderSchema = z.object({
assetPair: z.string(),
side: z.enum(['BUY', 'SELL']),
amount: z.number().nonnegative(),
remainingAmount: z.number().nonnegative(),
price: z.number().nonnegative(),
sender: z.string().refine(ethers.utils.isAddress, (value) => ({
message: `order.sender must be an address, got ${value}`,
})),
filledAmount: z.number().nonnegative(),
internalOnly: z.boolean(),
})
const subOrderSchema = baseOrderSchema.extend({
price: z.number(),
id: z.number(),
parentOrderId: z.string().refine(ethers.utils.isHexString, (value) => ({
message: `subOrder.parentOrderId must be a hex string, got ${value}`,
})),
exchange: z.enum(exchanges),
brokerAddress:
z.literal('ORION_BROKER').or(z.string().refine(ethers.utils.isAddress, (value) => ({
message: `subOrder.subOrders.[n].brokerAddress must be an address, got ${value}`,
}))),
tradesInfo: z.record(
z.string().uuid(),
tradeInfoSchema
),
status: z.enum(subOrderStatuses),
complexSwap: z.boolean(),
});
const orderSchema = z.object({
orderId: z.string().refine(ethers.utils.isHexString, (value) => ({
message: `orderId must be a hex string, got ${value}`,
})),
order: baseOrderSchema.extend({
id: z.string().refine(ethers.utils.isHexString, (value) => ({
message: `order.id must be a hex string, got ${value}`,
})),
fee: z.number().nonnegative(),
feeAsset: z.string(),
creationTime: z.number(),
blockchainOrder: blockchainOrderSchema,
subOrders: z.record(subOrderSchema),
updateTime: z.number(),
status: z.enum(orderStatuses),
settledAmount: z.number().nonnegative(),
})
});
export default orderSchema;

View File

@@ -1,6 +1,13 @@
import { z } from 'zod';
import { exchanges } from '../../../constants';
const orderInfoSchema = z.object({
assetPair: z.string(),
side: z.enum(['BUY', 'SELL']),
amount: z.number(),
safePrice: z.number(),
}).nullable();
const swapInfoBase = z.object({
id: z.string(),
amountIn: z.number(),
@@ -10,12 +17,7 @@ const swapInfoBase = z.object({
path: z.array(z.string()),
// isThroughPoolOptimal: z.boolean(), // deprecated
executionInfo: z.string(),
orderInfo: z.object({
assetPair: z.string(),
side: z.enum(['BUY', 'SELL']),
amount: z.number(),
safePrice: z.number(),
}).nullable(),
orderInfo: orderInfoSchema,
exchanges: z.array(z.enum(exchanges)),
price: z.number().nullable(), // spending asset price
minAmountOut: z.number(),
@@ -29,11 +31,13 @@ const swapInfoBase = z.object({
action: z.string(),
}).array(),
}),
marketAmountOut: z.number().optional(),
marketAmountIn: z.number().optional(),
marketAmountOut: z.number().nullable(),
marketAmountIn: z.number().nullable(),
marketPrice: z.number(),
availableAmountIn: z.number().optional(),
availableAmountOut: z.number().optional(),
availableAmountIn: z.number().nullable(),
availableAmountOut: z.number().nullable(),
orderInfo: orderInfoSchema,
isThroughPoolOrCurve: z.boolean(),
}).array(),
});

View File

@@ -9,13 +9,13 @@ import {
assetPairsConfigSchema, addressUpdateSchema, swapInfoSchema,
} from './schemas';
import UnsubscriptionType from './UnsubscriptionType';
import {
type SwapInfoBase, type AssetPairUpdate, type OrderbookItem,
type Balance, type Exchange, type CFDBalance, type FuturesTradeInfo, type SwapInfo,
import type {
SwapInfoBase, AssetPairUpdate, OrderbookItem,
Balance, Exchange, CFDBalance, FuturesTradeInfo, SwapInfo,
} from '../../../types';
import unsubscriptionDoneSchema from './schemas/unsubscriptionDoneSchema';
import assetPairConfigSchema from './schemas/assetPairConfigSchema';
import { type fullOrderSchema, type orderUpdateSchema } from './schemas/addressUpdateSchema';
import type { fullOrderSchema, orderUpdateSchema } from './schemas/addressUpdateSchema';
import cfdAddressUpdateSchema from './schemas/cfdAddressUpdateSchema';
import futuresTradeInfoSchema from './schemas/futuresTradeInfoSchema';
// import errorSchema from './schemas/errorSchema';
@@ -79,35 +79,35 @@ type FuturesTradeInfoSubscription = {
type AddressUpdateUpdate = {
kind: 'update'
balances: Partial<
Record<
string,
Balance
Record<
string,
Balance
>
>
>
order?: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema>
order?: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema> | undefined
}
type AddressUpdateInitial = {
kind: 'initial'
balances: Partial<
Record<
string,
Balance
Record<
string,
Balance
>
>
>
orders?: Array<z.infer<typeof fullOrderSchema>> // The field is not defined if the user has no orders
orders?: Array<z.infer<typeof fullOrderSchema>> | undefined // The field is not defined if the user has no orders
}
type CfdAddressUpdateUpdate = {
kind: 'update'
balances?: CFDBalance[]
order?: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema>
balances?: CFDBalance[] | undefined
order?: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema> | undefined
}
type CfdAddressUpdateInitial = {
kind: 'initial'
balances: CFDBalance[]
orders?: Array<z.infer<typeof fullOrderSchema>> // The field is not defined if the user has no orders
orders?: Array<z.infer<typeof fullOrderSchema>> | undefined // The field is not defined if the user has no orders
}
type AddressUpdateSubscription = {
@@ -156,7 +156,7 @@ type BufferLike =
const isSubType = (subType: string): subType is keyof Subscription => Object.values(SubscriptionType).some((t) => t === subType);
class OrionAggregatorWS {
private ws: WebSocket | undefined;
private ws?: WebSocket | undefined;
// is used to make sure we do not need to renew ws subscription
// we can not be sure that onclose event will recieve our code when we do `ws.close(4000)`
@@ -168,11 +168,11 @@ class OrionAggregatorWS {
[K in keyof Subscription]: Partial<Record<string, Subscription[K]>>
}> = {};
public onInit?: () => void;
public onInit: (() => void) | undefined
public onError?: (err: string) => void;
public onError: ((err: string) => void) | undefined
public logger?: (message: string) => void;
public logger: ((message: string) => void) | undefined
private readonly wsUrl: string;
@@ -229,15 +229,15 @@ class OrionAggregatorWS {
? ((subscription as any).payload as string) // TODO: Refactor!!!
: uuidv4();
const subRequest: Partial<Record<string, unknown>> = {};
subRequest.T = type;
subRequest.id = id;
subRequest['T'] = type;
subRequest['id'] = id;
// TODO Refactor this
if ('payload' in subscription) {
if (typeof subscription.payload === 'string') {
subRequest.S = subscription.payload;
subRequest['S'] = subscription.payload;
} else {
subRequest.S = {
subRequest['S'] = {
d: id,
...subscription.payload,
};
@@ -296,9 +296,9 @@ class OrionAggregatorWS {
}
}
} else if (subscription === UnsubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_UNSUBSCRIBE) {
delete this.subscriptions[SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE]?.default;
delete this.subscriptions[SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE]?.['default'];
} else if (subscription === UnsubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_UNSUBSCRIBE) {
delete this.subscriptions[SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE]?.default;
delete this.subscriptions[SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE]?.['default'];
}
}
@@ -503,7 +503,7 @@ class OrionAggregatorWS {
this.subscriptions[
SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE
]?.default?.callback({
]?.['default']?.callback({
kind: json.k === 'i' ? 'initial' : 'update',
data: priceUpdates,
});
@@ -612,7 +612,7 @@ class OrionAggregatorWS {
this.subscriptions[
SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE
]?.default?.callback(brokerBalances);
]?.['default']?.callback(brokerBalances);
}
break;
default:

View File

@@ -3,6 +3,15 @@ import exchanges from '../../../../constants/exchanges';
import MessageType from '../MessageType';
import baseMessageSchema from './baseMessageSchema';
const alternativeSchema = z.object({ // execution alternatives
e: z.enum(exchanges).array(), // exchanges
ps: z.string().array(), // path
mo: z.number().optional(), // market amount out
mi: z.number().optional(), // market amount in
mp: z.number(), // market price
aa: z.number().optional(), // available amount in
aao: z.number().optional(), // available amount out
});
const swapInfoSchemaBase = baseMessageSchema.extend({
T: z.literal(MessageType.SWAP_INFO),
S: z.string(), // swap request id
@@ -23,15 +32,7 @@ const swapInfoSchemaBase = baseMessageSchema.extend({
a: z.number(), // amount
sp: z.number(), // safe price (with safe deviation but without slippage)
}).optional(),
as: z.object({ // execution alternatives
e: z.enum(exchanges).array(), // exchanges
ps: z.string().array(), // path
mo: z.number().optional(), // market amount out
mi: z.number().optional(), // market amount in
mp: z.number(), // market price
aa: z.number().optional(), // available amount in
aao: z.number().optional(), // available amount out
}).array(),
as: alternativeSchema.array(),
});
const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({

View File

@@ -16,7 +16,7 @@ import {
import type redeemOrderSchema from '../OrionAggregator/schemas/redeemOrderSchema';
import { sourceAtomicHistorySchema, targetAtomicHistorySchema } from './schemas/atomicHistorySchema';
import { makePartial } from '../../utils';
import { type networkCodes } from '../../constants';
import type { networkCodes } from '../../constants';
type IAdminAuthHeaders = {
auth: string

View File

@@ -1,5 +1,5 @@
import fetchWithValidation from '../../fetchWithValidation';
import { type Exchange } from '../../types';
import type { Exchange } from '../../types';
import { statisticsOverviewSchema, topPairsStatisticsSchema } from './schemas';
import candlesSchema from './schemas/candlesSchema';
import { PriceFeedWS } from './ws';

View File

@@ -1,4 +1,4 @@
import { type Schema, type z } from 'zod';
import type { Schema, z } from 'zod';
import fetchWithValidation from './fetchWithValidation';
// https://stackoverflow.com/a/64919133

View File

@@ -143,13 +143,13 @@ export type BalanceRequirement = {
readonly asset: Asset
readonly amount: string
readonly sources: Source[]
readonly spenderAddress?: string
readonly spenderAddress?: string | undefined
}
export type AggregatedBalanceRequirement = {
readonly asset: Asset
readonly sources: Source[]
readonly spenderAddress?: string
readonly spenderAddress?: string | undefined
items: Partial<Record<string, string>>
}
@@ -189,11 +189,11 @@ export type OrderbookItem = {
export type SwapInfoAlternative = {
exchanges: Exchange[]
path: string[]
marketAmountOut?: number
marketAmountIn?: number
marketAmountOut?: number | undefined
marketAmountIn?: number | undefined
marketPrice: number
availableAmountIn?: number
availableAmountOut?: number
availableAmountIn?: number | undefined
availableAmountOut?: number | undefined
}
export type SwapInfoBase = {
@@ -206,30 +206,30 @@ export type SwapInfoBase = {
minAmountOut: number
path: string[]
exchanges?: Exchange[]
exchanges?: Exchange[] | undefined
poolOptimal: boolean
price?: number
marketPrice?: number
price?: number | undefined
marketPrice?: number | undefined
orderInfo?: {
pair: string
side: 'BUY' | 'SELL'
amount: number
safePrice: number
}
} | undefined
alternatives: SwapInfoAlternative[]
}
export type SwapInfoByAmountIn = SwapInfoBase & {
kind: 'exactSpend'
availableAmountIn?: number
marketAmountOut?: number
availableAmountIn?: number | undefined
marketAmountOut?: number | undefined
}
export type SwapInfoByAmountOut = SwapInfoBase & {
kind: 'exactReceive'
marketAmountIn?: number
availableAmountOut?: number
marketAmountIn?: number | undefined
availableAmountOut?: number | undefined
}
export type SwapInfo = SwapInfoByAmountIn | SwapInfoByAmountOut;
@@ -282,3 +282,5 @@ export type VerboseOrionUnitConfig = {
}
}
}
export type KnownEnv = 'testing' | 'staging' | 'production';

View File

@@ -1,5 +1,5 @@
import BigNumber from 'bignumber.js';
import { type ethers } from 'ethers';
import type { ethers } from 'ethers';
/**
* Converts normalized blockchain ("machine-readable") number to denormalized ("human-readable") number.

View File

@@ -1,5 +1,5 @@
import { ethers } from 'ethers';
import { type Source } from '../types';
import type { Source } from '../types';
export default function getAvailableFundsSources(
expenseType: 'amount' | 'network_fee' | 'orion_fee',

View File

@@ -3,7 +3,7 @@ import { ERC20__factory, type Exchange } from '@orionprotocol/contracts';
import type BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import { INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION } from '../constants';
import { type OrionAggregator } from '../services/OrionAggregator';
import type { OrionAggregator } from '../services/OrionAggregator';
import denormalizeNumber from './denormalizeNumber';
export default async function getBalance(

View File

@@ -1,7 +1,7 @@
import { type Exchange } from '@orionprotocol/contracts';
import type { Exchange } from '@orionprotocol/contracts';
import type BigNumber from 'bignumber.js';
import { type ethers } from 'ethers';
import { type OrionAggregator } from '../services/OrionAggregator';
import type { ethers } from 'ethers';
import type { OrionAggregator } from '../services/OrionAggregator';
import getBalance from './getBalance';
export default async (

View File

@@ -1,5 +1,5 @@
{
// "extends": "@tsconfig/strictest/tsconfig.json",
"extends": "@tsconfig/strictest/tsconfig.json",
"files": [
"./src/index.ts"
],