Merge remote-tracking branch origin/main into OP-5070-visible-assets

This commit is contained in:
Kirill Litvinov
2024-04-23 17:57:31 +03:00
33 changed files with 1025 additions and 64 deletions

View File

@@ -19,6 +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 { 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;
@@ -31,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 === '/'
@@ -43,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);
@@ -52,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);
@@ -331,6 +341,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
@@ -369,6 +389,102 @@ class Aggregator {
url.searchParams.append('limit', limit.toString());
return fetchWithValidation(url.toString(), atomicSwapHistorySchema, { headers: this.basicAuthHeaders });
};
// 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 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('&');
const payload = timestamp + method.toUpperCase() + path + sortedBody;
const signature = this.sign(payload, secretKey);
const httpOptions = {
headers: {
'API-KEY': apiKey,
'ACCESS-TIMESTAMP': timestamp.toString(),
'ACCESS-SIGN': signature
}
};
return httpOptions;
}
public async RFQOrder(
tokenFrom: string,
tokenTo: string,
fromTokenAmount: string,
apiKey: string, //
secretKey: string,
wallet: string
): Promise<z.infer<typeof pmmOrderSchema>> {
// Making the order structure
const
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)
;
const res = pmmOrderSchema.parse({});
try {
const result = await fetch(url, {
headers: compiledHeaders,
method,
body
});
const json = await result.json();
const parseResult = pmmOrderSchema.safeParse(json);
if (!parseResult.success) {
// Try to parse error answer
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}`); }
throw Error(errorParseResult.data.error.reason);
}
res.order = parseResult.data.order;
res.signature = parseResult.data.signature;
res.error = '';
res.success = true;
// return result;
} catch (err) {
res.error = `${err}`;
}
return res;
}
}
export * as schemas from './schemas/index.js';
export * as ws from './ws/index.js';

View File

@@ -49,6 +49,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({

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
}
@@ -199,9 +200,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[] = [];
@@ -256,7 +258,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();
@@ -266,6 +268,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') {
console.log('subscription.payload === string', subscription.payload);
@@ -394,11 +402,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];
}
}
@@ -558,6 +566,7 @@ class AggregatorWS {
marketAmountIn: json.usd.mi,
difference: json.usd.d,
},
autoSlippage: json.sl,
};
switch (json.k) { // kind

View File

@@ -48,6 +48,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({

View File

@@ -12,6 +12,7 @@ import {
pairStatusSchema,
pricesWithQuoteAssetSchema,
referralDataSchema,
pmmSchema
} from './schemas/index.js';
import type redeemOrderSchema from '../Aggregator/schemas/redeemOrderSchema.js';
import { sourceAtomicHistorySchema, targetAtomicHistorySchema } from './schemas/atomicHistorySchema.js';
@@ -82,6 +83,7 @@ class BlockchainService {
this.getAuthToken = this.getAuthToken.bind(this);
this.getCirculatingSupply = this.getCirculatingSupply.bind(this);
this.getInfo = this.getInfo.bind(this);
this.getPmmInfo = this.getPmmInfo.bind(this);
this.getPoolsConfig = this.getPoolsConfig.bind(this);
this.getPoolsInfo = this.getPoolsInfo.bind(this);
this.getPoolsLpAndStaked = this.getPoolsLpAndStaked.bind(this);
@@ -111,6 +113,8 @@ class BlockchainService {
this.getBlockNumber = this.getBlockNumber.bind(this);
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() {
@@ -175,6 +179,8 @@ class BlockchainService {
getInfo = () => fetchWithValidation(`${this.apiUrl}/api/info`, infoSchema);
getPmmInfo = () => fetchWithValidation(`${this.apiUrl}/api/pmm-info`, pmmSchema);
getPoolsConfig = () => fetchWithValidation(
`${this.apiUrl}/api/pools/config`,
poolsConfigSchema,
@@ -484,6 +490,18 @@ class BlockchainService {
body: JSON.stringify(secretHashes),
},
);
getGasLimits = () => fetchWithValidation(
`${this.apiUrl}/api/baseLimits`,
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

@@ -13,5 +13,6 @@ export { default as poolsLpAndStakedSchema } from './poolsLpAndStakedSchema.js';
export { default as userVotesSchema } from './userVotesSchema.js';
export { default as userEarnedSchema } from './userEarnedSchema.js';
export { default as poolsV3InfoSchema } from './poolsV3InfoSchema.js';
export { default as pmmSchema } from './pmmSchema.js';
export { pricesWithQuoteAssetSchema } from './pricesWithQuoteAssetSchema.js';
export { referralDataSchema } from './referralDataSchema.js';

View File

@@ -0,0 +1,7 @@
import { z } from 'zod';
const pmmSchema = z.object({
orionPMMRouterContractAddress: z.string()
});
export default pmmSchema

View File

@@ -1,6 +1,6 @@
import { z } from "zod";
import { z } from 'zod';
export const referralDataSchema = z.object({
referer: z.string(),
referer: z.string().nullable(),
isReferral: z.boolean(),
});

View File

@@ -9,7 +9,7 @@ import {
PoolV2InfoResponseSchema,
testIncrementorSchema,
veORNInfoResponseSchema,
votingInfoResponseSchema
votingInfoResponseSchema,
} from './schemas';
import { fetchWithValidation } from 'simple-typed-fetch';
import { BigNumber } from 'bignumber.js';
@@ -94,6 +94,7 @@ class IndexerService {
this.veORNInfo = this.veORNInfo.bind(this);
this.listAmount = this.listAmount.bind(this);
this.getAmountByORN = this.getAmountByORN.bind(this);
this.getAmountAt = this.getAmountAt.bind(this);
this.getAmountAtCurrent = this.getAmountAtCurrent.bind(this);
this.getVotingInfo = this.getVotingInfo.bind(this);
}
@@ -117,6 +118,23 @@ class IndexerService {
});
};
/**
* @param {number} amount - amount
* @param {number} [timestamp = Date.now()] - timestamp, defaults to current time
*/
readonly getAmountAt = (
amount: number,
timestamp = Date.now()
): BigNumber => {
const finalTimestamp = timestamp / 1000;
// sqrt
return BigNumber(amount).dividedBy(this.getK(finalTimestamp));
};
/**
* @deprecated since version 69 in favor of getAmountAt
*/
readonly getAmountAtCurrent = (amount: number): BigNumber => {
const timestamp = Date.now() / 1000;
@@ -134,8 +152,7 @@ class IndexerService {
const multSQRT = deltaDaysBN.dividedBy(WEEK_DAYS).sqrt();
const multCUBE = deltaDaysBN.dividedBy(alpha).pow(3);
return BigNumber(amountToken)
.multipliedBy(multSQRT.plus(multCUBE));
return BigNumber(amountToken).multipliedBy(multSQRT.plus(multCUBE));
};
readonly getVotingInfo = (userAddress?: string) => {
@@ -208,7 +225,11 @@ class IndexerService {
});
};
readonly poolV2Info = (token0: string, token1: string, address: string | undefined) => {
readonly poolV2Info = (
token0: string,
token1: string,
address: string | undefined
) => {
return fetchWithValidation(this.apiUrl, PoolV2InfoResponseSchema, {
method: 'POST',
body: this.makeRPCPayload({

View File

@@ -2,7 +2,7 @@ import WebSocket from 'isomorphic-ws';
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';
import priceFeedSubscriptions from './priceFeedSubscriptions.js';
import { tickerInfoSchema, candleSchema } from './schemas/index.js';
import { tickerInfoSchema, candleSchema, cexPricesSchema } from './schemas/index.js';
import priceSchema from './schemas/priceSchema.js';
import type { Json } from '../../../types.js';
import allTickersSchema from './schemas/allTickersSchema.js';
@@ -24,6 +24,10 @@ export const subscriptions = {
schema: candleSchema,
payload: true as const,
},
[priceFeedSubscriptions.CEX]: {
schema: cexPricesSchema,
payload: false as const,
},
};
export type SubscriptionType = keyof typeof subscriptions;

View File

@@ -3,6 +3,7 @@ const priceFeedSubscriptions = {
ALL_TICKERS: 'allTickers',
LAST_PRICE: 'lastPrice',
CANDLE: 'candle',
CEX: 'cexPrices'
} as const;
export default priceFeedSubscriptions;

View File

@@ -0,0 +1,31 @@
import { z } from 'zod';
const cexPriceTickerInfoSchema = z.tuple([
z.string(), // pair name
z.number(), // lastPrice
]).transform(([pairName, lastPrice]) => ({
pairName:pairName.toUpperCase(),
lastPrice,
}));
type CEXPriceTickerInfo = z.infer<typeof cexPriceTickerInfoSchema>
const cexPricesSchema = z.unknown().array()
.transform((tickers) => {
const data = [...tickers];
data.shift();
const parsedData = cexPriceTickerInfoSchema.array().parse(data);
return parsedData.reduce<
Partial<
Record<
string,
CEXPriceTickerInfo
>
>
>((prev, pairData) => ({
...prev,
[pairData.pairName]: pairData,
}), {});
});
export default cexPricesSchema;

View File

@@ -2,3 +2,4 @@ export { default as tickerInfoSchema } from './tickerInfoSchema.js';
export { default as candleSchema } from './candleSchema.js';
export { default as priceSchema } from './priceSchema.js';
export { default as allTickersSchema } from './allTickersSchema.js';
export { default as cexPricesSchema } from './cexPricesSchema.js';