mirror of
https://github.com/orionprotocol/sdk.git
synced 2026-03-24 22:58:01 +03:00
Strictest / tests / minor improvements
This commit is contained in:
@@ -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,
|
||||
|
||||
101
src/services/OrionAggregator/schemas/orderSchema.ts
Normal file
101
src/services/OrionAggregator/schemas/orderSchema.ts
Normal 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;
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user