New fetchWithValidation / introducing simpleFetch

This commit is contained in:
Aleksandr Kraiz
2022-05-10 11:44:09 +04:00
parent edc91ecefe
commit 883bcf928f
16 changed files with 350 additions and 117 deletions

View File

@@ -15,11 +15,11 @@ module.exports = {
],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: "./",
tsconfigRootDir: __dirname,
project: [
"./tsconfig.json"
],
ecmaVersion: 12,
ecmaVersion: 2021,
sourceType: 'module',
},
plugins: [

View File

@@ -46,7 +46,7 @@ const chain = "0x61"; // bsc-testnet
const env = "testing";
const startApp = async (provider: BaseProvider) => {
const web3provider = new providers.Web3Provider(provider);
const web3Provider = new providers.Web3Provider(provider);
await web3Provider.ready;
const signer = web3Provider.getSigner(); // ready to go
const orionUnit = initOrionUnit(chain, env); // ready to go
@@ -128,7 +128,9 @@ orionUnit.farmingManager.removeAllLiquidity({
### Get historical price
```ts
const candles = await orionUnit.priceFeed.getCandles(
import { simpleFetch } from "@orionprotocol/sdk";
const candles = await simpleFetch(orionUnit.priceFeed.getCandles)(
"ORN-USDT",
1650287678, // interval start
1650374078, // interval end
@@ -163,13 +165,16 @@ const orionVoting = contracts.OrionVoting__factory.connect(
### Get tradable pairs
```ts
const pairsList = await orionUnit.orionAggregator.getPairsList();
import { simpleFetch } from "@orionprotocol/sdk";
const pairsList = await simpleFetch(orionUnit.orionAggregator.getPairsList)();
```
### Get swap info
```ts
const swapInfo = await orionUnit.orionAggregator.getSwapInfo(
import { simpleFetch } from '@orionprotocol/sdk';
const swapInfo = await simpleFetch(orionUnit.orionAggregator.getSwapInfo)(
// Use 'exactSpend' when 'amount' is how much you want spend. Use 'exactReceive' otherwise
type: 'exactSpend',
assetIn: 'ORN',
@@ -181,7 +186,12 @@ const swapInfo = await orionUnit.orionAggregator.getSwapInfo(
### Place order in Orion Aggregator
```ts
const { orderId } = await orionUnit.orionAggregator.placeOrder(
import { simpleFetch } from "@orionprotocol/sdk";
// You can yse simpleFetch or "default" (vebose) fetch
// Simple fetch
const { orderId } = await simpleFetch(orionUnit.orionAggregator.placeOrder)(
{
senderAddress: "0x61eed69c0d112c690fd6f44bb621357b89fbe67f",
matcherAddress: "0xfbcad2c3a90fbd94c335fbdf8e22573456da7f68",
@@ -198,6 +208,35 @@ const { orderId } = await orionUnit.orionAggregator.placeOrder(
},
false // Place in internal orderbook
);
// Default ("verbose") fetch
const placeOrderFetchResult = await simpleFetch(
orionUnit.orionAggregator.placeOrder
)();
// Same params as above
if (placeOrderFetchResult.isErr()) {
// You can handle fetching errors here
// You can access error text, statuses
const { error } = placeOrderFetchResult;
switch (error.type) {
case "fetchError": // (no network, connection refused, connection break)
console.error(error.message);
break;
case "unknownFetchError": // Instance of Error
console.error(`URL: ${error.url}, Error: ${error.message}`);
break;
case "unknownFetchThrow":
console.error("Something wrong happened furing fetching", error.error);
break;
// ... and 8 errors types more
// see src/fetchWithValidation.ts for details
}
} else {
// Succes result
const { orderId } = placeOrderFetchResult.value;
}
```
### Orion Aggregator WebSocket

11
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"ethers": "^5.6.2",
"isomorphic-ws": "^4.0.1",
"just-clone": "^5.0.1",
"neverthrow": "^4.3.1",
"node-fetch": "^2.6.7",
"socket.io-client": "2.4.0",
"stream-browserify": "^3.0.0",
@@ -9135,6 +9136,11 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"node_modules/neverthrow": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-4.3.1.tgz",
"integrity": "sha512-+vxjSaiDWjAj6kR6KKW0YDuV6O4UCNWGAO8m8ITjFKPWcTmU1GVnL+J5TAUTKpPnUAHCKDxXpOHVaERid223Ww=="
},
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -17497,6 +17503,11 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"neverthrow": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-4.3.1.tgz",
"integrity": "sha512-+vxjSaiDWjAj6kR6KKW0YDuV6O4UCNWGAO8m8ITjFKPWcTmU1GVnL+J5TAUTKpPnUAHCKDxXpOHVaERid223Ww=="
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@orionprotocol/sdk",
"version": "0.3.4",
"version": "0.4.0",
"description": "Orion Protocol SDK",
"main": "./lib/esm/index.js",
"module": "./lib/esm/index.js",
@@ -70,6 +70,7 @@
"ethers": "^5.6.2",
"isomorphic-ws": "^4.0.1",
"just-clone": "^5.0.1",
"neverthrow": "^4.3.1",
"node-fetch": "^2.6.7",
"socket.io-client": "2.4.0",
"stream-browserify": "^3.0.0",

View File

@@ -10,6 +10,7 @@ import {
} from '../../constants';
import { normalizeNumber } from '../../utils';
import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency';
import simpleFetch from '../../simpleFetch';
export type DepositParams = {
asset: string,
@@ -38,12 +39,12 @@ export default async function deposit({
const {
exchangeContractAddress,
assetToAddress,
} = await orionBlockchain.getInfo();
} = await simpleFetch(orionBlockchain.getInfo)();
const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress);
const exchangeContract = contracts.Exchange__factory.connect(exchangeContractAddress, provider);
const gasPriceWei = await orionBlockchain.getGasPriceWei();
const gasPriceWei = await simpleFetch(orionBlockchain.getGasPriceWei)();
const assetAddress = assetToAddress[asset];
if (!assetAddress) throw new Error(`Asset '${asset}' not found`);

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable max-len */
import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
@@ -8,6 +9,7 @@ import OrionUnit from '..';
import { contracts, crypt, utils } from '../..';
import { INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../../constants';
import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency';
import simpleFetch from '../../simpleFetch';
export type SwapMarketParams = {
type: 'exactSpend' | 'exactReceive',
@@ -74,13 +76,13 @@ export default async function swapMarket({
exchangeContractAddress,
matcherAddress,
assetToAddress,
} = await orionBlockchain.getInfo();
const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress);
} = await simpleFetch(orionBlockchain.getInfo)();
const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress);
const exchangeContract = contracts.Exchange__factory.connect(exchangeContractAddress, provider);
const feeAssets = await orionBlockchain.getTokensFee();
const pricesInOrn = await orionBlockchain.getPrices();
const gasPriceWei = await orionBlockchain.getGasPriceWei();
const feeAssets = await simpleFetch(orionBlockchain.getTokensFee)();
const pricesInOrn = await simpleFetch(orionBlockchain.getPrices)();
const gasPriceWei = await simpleFetch(orionBlockchain.getGasPriceWei)();
const gasPriceGwei = ethers.utils.formatUnits(gasPriceWei, 'gwei').toString();
@@ -111,7 +113,7 @@ export default async function swapMarket({
signer,
);
const swapInfo = await orionAggregator.getSwapInfo(type, assetIn, assetOut, amount.toString());
const swapInfo = await simpleFetch(orionAggregator.getSwapInfo)(type, assetIn, assetOut, amount.toString());
if (swapInfo.type === 'exactReceive' && amountBN.lt(swapInfo.minAmountOut)) {
throw new Error(`Amount is too low. Min amountOut is ${swapInfo.minAmountOut} ${assetOut}`);
@@ -240,7 +242,7 @@ export default async function swapMarket({
.toString();
const [baseAssetName, quoteAssetName] = swapInfo.orderInfo.assetPair.split('-');
const pairConfig = await orionAggregator.getPairConfig(`${baseAssetName}-${quoteAssetName}`);
const pairConfig = await simpleFetch(orionAggregator.getPairConfig)(`${baseAssetName}-${quoteAssetName}`);
if (!pairConfig) throw new Error(`Pair config ${baseAssetName}-${quoteAssetName} not found`);
const baseAssetAddress = assetToAddress[baseAssetName];
@@ -336,7 +338,7 @@ export default async function swapMarket({
const orderIsOk = await exchangeContract.validateOrder(signedOrder);
if (!orderIsOk) throw new Error('Order is not valid');
const { orderId } = await orionAggregator.placeOrder(signedOrder, false);
const { orderId } = await simpleFetch(orionAggregator.placeOrder)(signedOrder, false);
return {
through: 'aggregator',
id: orderId,

View File

@@ -10,6 +10,7 @@ import {
} from '../../constants';
import { normalizeNumber } from '../../utils';
import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency';
import simpleFetch from '../../simpleFetch';
export type WithdrawParams = {
asset: string,
@@ -38,11 +39,11 @@ export default async function withdraw({
const {
exchangeContractAddress,
assetToAddress,
} = await orionBlockchain.getInfo();
} = await simpleFetch(orionBlockchain.getInfo)();
const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress);
const exchangeContract = contracts.Exchange__factory.connect(exchangeContractAddress, provider);
const gasPriceWei = await orionBlockchain.getGasPriceWei();
const gasPriceWei = await simpleFetch(orionBlockchain.getGasPriceWei)();
const assetAddress = assetToAddress[asset];
if (!assetAddress) throw new Error(`Asset '${asset}' not found`);

View File

@@ -4,6 +4,7 @@ import OrionUnit from '..';
import { contracts } from '../..';
import BalanceGuard from '../../BalanceGuard';
import { ADD_LIQUIDITY_GAS_LIMIT, INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION } from '../../constants';
import simpleFetch from '../../simpleFetch';
import { denormalizeNumber, normalizeNumber } from '../../utils';
import getBalances from '../../utils/getBalances';
import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency';
@@ -46,7 +47,7 @@ export default class FarmingManager {
exchangeContractAddress,
assetToAddress,
assetToDecimals,
} = await this.orionUnit.orionBlockchain.getInfo();
} = await simpleFetch(this.orionUnit.orionBlockchain.getInfo)();
const walletAddress = await signer.getAddress();
@@ -85,7 +86,7 @@ export default class FarmingManager {
signer,
);
const poolsConfig = await this.orionUnit.orionBlockchain.getPoolsConfig();
const poolsConfig = await simpleFetch(this.orionUnit.orionBlockchain.getPoolsConfig)();
const pool = poolsConfig.pools[poolName];
if (!pool) throw new Error(`Pool ${poolName} not found`);
@@ -223,7 +224,7 @@ export default class FarmingManager {
assetToAddress,
assetToDecimals,
exchangeContractAddress,
} = await this.orionUnit.orionBlockchain.getInfo();
} = await simpleFetch(this.orionUnit.orionBlockchain.getInfo)();
const assetAAddress = assetToAddress[assetA];
if (!assetAAddress) throw new Error(`Asset '${assetA}' not found`);
@@ -235,7 +236,7 @@ export default class FarmingManager {
const assetBDecimals = assetToDecimals[assetB];
if (!assetBDecimals) throw new Error(`Decimals for asset '${assetB}' not found`);
const poolsConfig = await this.orionUnit.orionBlockchain.getPoolsConfig();
const poolsConfig = await simpleFetch(this.orionUnit.orionBlockchain.getPoolsConfig)();
const pool = poolsConfig.pools[poolName];
if (!pool) throw new Error(`Pool ${poolName} not found`);

View File

@@ -1,55 +1,155 @@
import { Schema, z } from 'zod';
import fetch, { RequestInit } from 'node-fetch';
import { isWithError, isWithReason, HttpError } from './utils';
import fetch, { FetchError, RequestInit } from 'node-fetch';
import {
err, fromPromise, fromThrowable, ok,
} from 'neverthrow';
export class ExtendedError extends Error {
public url: string;
public status: number | null;
constructor(url: string, status: number | null, message: string) {
super();
this.url = url;
this.status = status;
this.message = message;
}
}
export const fetchJsonWithValidation = async <DataOut, DataIn, ErrorOut, ErrorIn>(
export default async function fetchWithValidation<DataOut, DataIn, ErrorOut, ErrorIn>(
url: string,
schema: Schema<DataOut, z.ZodTypeDef, DataIn>,
options?: RequestInit,
errorSchema?: Schema<ErrorOut, z.ZodTypeDef, ErrorIn>,
) => {
const response = await fetch(url, {
) {
// Cases:
// 1. fetchError (no network, connection refused, connection break)
// 2. unknownFetchError
// 3. unknownFetchThrow
// 4. unknownGetTextError
// 5. unknownGetTextUnknownError
// 6. serverError
// 7. jsonParseError
// 8. jsonParseUnknownError
// 9. clientErrorWithResponsePayload
// 10. clientErrorPayloadParseError
// 11. clientError
// 12. payloadParseError
// 13. payload
const fetchResult = await fromPromise(fetch(url, {
...options || {},
headers: {
'Cache-Control': 'no-store, max-age=0',
...(options ? options.headers : {}),
},
});
const text = await response.text();
// The ok read-only property of the Response interface contains a Boolean
// stating whether the response was successful (status in the range 200 - 299) or not.
if (!response.ok) {
throw new HttpError(response.status, text, 'HTTP', response.statusText);
}
const payload: unknown = JSON.parse(text);
try {
const data = schema.parse(payload);
return data;
} catch (e) {
if (errorSchema) {
const errorObj = errorSchema.parse(payload);
if (isWithError(errorObj) && isWithReason(errorObj.error)) {
throw new ExtendedError(url, response.status, errorObj.error.reason);
}
}), (e) => {
if (e instanceof FetchError) {
return err({
type: 'fetchError' as const,
url,
message: `${e.message} (${e.type})`,
error: e,
});
} if (e instanceof Error) {
return err({
type: 'unknownFetchError' as const,
url,
message: e.message,
error: e,
});
}
if (e instanceof Error) throw new ExtendedError(url, response.status, e.message);
throw e;
return err({
type: 'unknownFetchThrow' as const,
url,
message: 'Unknown fetch error',
error: e,
});
});
if (fetchResult.isErr()) return fetchResult.error;
const response = fetchResult.value;
const textResult = await fromPromise(response.text(), (e) => {
if (e instanceof Error) {
return err({
type: 'unknownGetTextError' as const,
url,
message: `Can't get response content: ${e.message}`,
error: e,
});
}
return err({
type: 'unknownGetTextUnknownError' as const,
url,
message: "Can't get response content: unknown error",
error: e,
});
});
if (textResult.isErr()) return textResult.error;
const text = textResult.value;
if (response.status >= 500) { // Server error
return err({
type: 'serverError' as const,
url,
message: `Server error: ${response.status} ${response.statusText}`,
status: response.status,
text,
});
}
};
const safeParseJson = fromThrowable(JSON.parse, (e) => {
if (e instanceof Error) {
return err({
type: 'jsonParseError' as const,
url,
message: e.message,
error: e,
});
}
return err({
type: 'jsonParseUnknownError' as const,
url,
message: 'Unknown JSON parse error',
error: e,
});
});
const jsonResult = safeParseJson(text);
if (jsonResult.isErr()) return jsonResult.error;
const json: unknown = jsonResult.value;
if (response.status >= 400) { // Client error
if (errorSchema) {
const serverError = errorSchema.safeParse(json);
if (serverError.success) {
return err({
type: 'clientErrorWithResponsePayload' as const,
url,
message: `Client error: ${response.status} ${response.statusText}`,
status: response.status,
payload: serverError.data,
});
}
return err({
type: 'clientErrorPayloadParseError' as const,
message: 'Can\'t recognize error message',
status: response.status,
text,
error: serverError.error,
});
}
return err({
type: 'clientError' as const,
url,
message: `Error: ${response.status} ${response.statusText}`,
status: response.status,
text,
});
}
const payload = schema.safeParse(json);
if (!payload.success) {
return err({
type: 'payloadParseError' as const,
url,
message: 'Can\'t recognize response payload',
error: payload.error,
});
}
return ok(payload.data);
}

View File

@@ -6,7 +6,8 @@ export * as config from './config';
// export * from './entities';
export { default as OrionUnit } from './OrionUnit';
export { default as initOrionUnit } from './initOrionUnit';
export * from './fetchWithValidation';
export { default as fetchWithValidation } from './fetchWithValidation';
export { default as simpleFetch } from './simpleFetch';
export * as utils from './utils';
export * as services from './services';
export * as contracts from './artifacts/contracts';

View File

@@ -1,6 +1,6 @@
import BigNumber from 'bignumber.js';
import { z } from 'zod';
import { fetchJsonWithValidation } from '../../fetchWithValidation';
import fetchWithValidation from '../../fetchWithValidation';
import swapInfoSchema from './schemas/swapInfoSchema';
import exchangeInfoSchema from './schemas/exchangeInfoSchema';
import cancelOrderSchema from './schemas/cancelOrderSchema';
@@ -8,7 +8,7 @@ import orderBenefitsSchema from './schemas/orderBenefitsSchema';
import errorSchema from './errorSchema';
import placeAtomicSwapSchema from './schemas/placeAtomicSwapSchema';
import { OrionAggregatorWS } from './ws';
import atomicSwapHistorySchema from './schemas/atomicSwapHistorySchema';
import { atomicSwapHistorySchema } from './schemas/atomicSwapHistorySchema';
import { SignedCancelOrderRequest, SignedOrder, SupportedChainId } from '../../types';
import { pairConfigSchema } from './schemas';
@@ -20,6 +20,18 @@ class OrionAggregator {
constructor(apiUrl: string, chainId: SupportedChainId) {
this.apiUrl = apiUrl;
this.ws = new OrionAggregatorWS(this.aggregatorWSUrl, chainId);
this.getHistoryAtomicSwaps = this.getHistoryAtomicSwaps.bind(this);
this.getPairConfig = this.getPairConfig.bind(this);
this.getPairConfigs = this.getPairConfigs.bind(this);
this.getPairsList = this.getPairsList.bind(this);
this.getSwapInfo = this.getSwapInfo.bind(this);
this.getTradeProfits = this.getTradeProfits.bind(this);
this.placeAtomicSwap = this.placeAtomicSwap.bind(this);
this.placeOrder = this.placeOrder.bind(this);
this.cancelOrder = this.cancelOrder.bind(this);
this.checkWhitelisted = this.checkWhitelisted.bind(this);
this.getLockedBalance = this.getLockedBalance.bind(this);
}
get aggregatorWSUrl() { return `wss://${this.apiUrl}/v1`; }
@@ -29,14 +41,14 @@ class OrionAggregator {
}
getPairsList() {
return fetchJsonWithValidation(
return fetchWithValidation(
`${this.aggregatorUrl}/api/v1/pairs/list`,
z.array(z.string()),
);
}
getPairConfigs() {
return fetchJsonWithValidation(
return fetchWithValidation(
`${this.aggregatorUrl}/api/v1/pairs/exchangeInfo`,
exchangeInfoSchema,
undefined,
@@ -45,7 +57,7 @@ class OrionAggregator {
}
getPairConfig(assetPair: string) {
return fetchJsonWithValidation(
return fetchWithValidation(
`${this.aggregatorUrl}/api/v1/pairs/exchangeInfo/${assetPair}`,
pairConfigSchema,
undefined,
@@ -54,7 +66,7 @@ class OrionAggregator {
}
checkWhitelisted(address: string) {
return fetchJsonWithValidation(
return fetchWithValidation(
`${this.aggregatorUrl}/api/v1/whitelist/check?address=${address}`,
z.boolean(),
undefined,
@@ -73,7 +85,7 @@ class OrionAggregator {
...partnerId && { 'X-Partner-Id': partnerId },
};
return fetchJsonWithValidation(
return fetchWithValidation(
`${this.aggregatorUrl}/api/v1/order/${isCreateInternalOrder ? 'internal' : ''}`,
z.object({
orderId: z.string(),
@@ -95,7 +107,7 @@ class OrionAggregator {
}
cancelOrder(signedCancelOrderRequest: SignedCancelOrderRequest) {
return fetchJsonWithValidation(
return fetchWithValidation(
`${this.aggregatorUrl}/api/v1/order`,
cancelOrderSchema,
{
@@ -128,7 +140,7 @@ class OrionAggregator {
url.searchParams.append('amountOut', amount);
}
return fetchJsonWithValidation(
return fetchWithValidation(
url.toString(),
swapInfoSchema,
undefined,
@@ -139,7 +151,7 @@ class OrionAggregator {
getLockedBalance(address: string, currency: string) {
const url = new URL(`${this.aggregatorUrl}/api/v1/address/balance/reserved/${currency}`);
url.searchParams.append('address', address);
return fetchJsonWithValidation(
return fetchWithValidation(
url.toString(),
z.object({
[currency]: z.number(),
@@ -159,7 +171,7 @@ class OrionAggregator {
url.searchParams.append('amount', amount.toString());
url.searchParams.append('side', isBuy ? 'buy' : 'sell');
return fetchJsonWithValidation(
return fetchWithValidation(
url.toString(),
orderBenefitsSchema,
undefined,
@@ -177,7 +189,7 @@ class OrionAggregator {
secretHash: string,
sourceNetworkCode: string,
) {
return fetchJsonWithValidation(
return fetchWithValidation(
`${this.aggregatorUrl}/api/v1/atomic-swap`,
placeAtomicSwapSchema,
{
@@ -204,7 +216,7 @@ class OrionAggregator {
const url = new URL(`${this.aggregatorUrl}/api/v1/atomic-swap/history/all`);
url.searchParams.append('sender', sender);
url.searchParams.append('limit', limit.toString());
return fetchJsonWithValidation(url.toString(), atomicSwapHistorySchema);
return fetchWithValidation(url.toString(), atomicSwapHistorySchema);
}
}
export * as schemas from './schemas';

View File

@@ -1,5 +1,5 @@
import { z } from 'zod';
import { fetchJsonWithValidation } from '../../fetchWithValidation';
import fetchWithValidation from '../../fetchWithValidation';
import { PairStatusEnum, pairStatusSchema } from './schemas/adminPoolsListSchema';
import {
IDOSchema, atomicHistorySchema,
@@ -56,6 +56,32 @@ class OrionBlockchain {
constructor(apiUrl: string) {
this.apiUrl = apiUrl;
this.ws = new OrionBlockchainSocketIO(`https://${apiUrl}/`);
this.getAtomicSwapAssets = this.getAtomicSwapAssets.bind(this);
this.getAtomicSwapHistory = this.getAtomicSwapHistory.bind(this);
this.getAuthToken = this.getAuthToken.bind(this);
this.getCirculatingSupply = this.getCirculatingSupply.bind(this);
this.getInfo = this.getInfo.bind(this);
this.getPoolsConfig = this.getPoolsConfig.bind(this);
this.getPoolsInfo = this.getPoolsInfo.bind(this);
this.getHistory = this.getHistory.bind(this);
this.getPrices = this.getPrices.bind(this);
this.getTokensFee = this.getTokensFee.bind(this);
this.getGasPriceWei = this.getGasPriceWei.bind(this);
this.checkFreeRedeemAvailable = this.checkFreeRedeemAvailable.bind(this);
this.redeemAtomicSwap = this.redeemAtomicSwap.bind(this);
this.redeem2AtomicSwaps = this.redeem2AtomicSwaps.bind(this);
this.checkRedeem = this.checkRedeem.bind(this);
this.checkRedeem2Atomics = this.checkRedeem2Atomics.bind(this);
this.getIDOInfo = this.getIDOInfo.bind(this);
this.checkAuth = this.checkAuth.bind(this);
this.addPool = this.addPool.bind(this);
this.editPool = this.editPool.bind(this);
this.getPoolsList = this.getPoolsList.bind(this);
this.getSourceAtomicSwapHistory = this.getSourceAtomicSwapHistory.bind(this);
this.getTargetAtomicSwapHistory = this.getTargetAtomicSwapHistory.bind(this);
this.checkPoolInformation = this.checkPoolInformation.bind(this);
this.checkIfHashUsed = this.checkIfHashUsed.bind(this);
}
get orionBlockchainWsUrl() {
@@ -63,43 +89,43 @@ class OrionBlockchain {
}
getAuthToken() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/auth/token`, z.object({ token: z.string() }));
return fetchWithValidation(`https://${this.apiUrl}/api/auth/token`, z.object({ token: z.string() }));
}
getCirculatingSupply() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/circulating-supply`, z.number());
return fetchWithValidation(`https://${this.apiUrl}/api/circulating-supply`, z.number());
}
getInfo() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/info`, infoSchema);
return fetchWithValidation(`https://${this.apiUrl}/api/info`, infoSchema);
}
getPoolsConfig() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/pools/config`, poolsConfigSchema);
return fetchWithValidation(`https://${this.apiUrl}/api/pools/config`, poolsConfigSchema);
}
getPoolsInfo() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/pools/info`, poolsInfoSchema);
return fetchWithValidation(`https://${this.apiUrl}/api/pools/info`, poolsInfoSchema);
}
getHistory(address: string) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/history/${address}`, historySchema);
return fetchWithValidation(`https://${this.apiUrl}/api/history/${address}`, historySchema);
}
getPrices() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/prices`, z.record(z.string()).transform(utils.makePartial));
return fetchWithValidation(`https://${this.apiUrl}/api/prices`, z.record(z.string()).transform(utils.makePartial));
}
getTokensFee() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/tokensFee`, z.record(z.string()).transform(utils.makePartial));
return fetchWithValidation(`https://${this.apiUrl}/api/tokensFee`, z.record(z.string()).transform(utils.makePartial));
}
getGasPriceWei() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/gasPrice`, z.string());
return fetchWithValidation(`https://${this.apiUrl}/api/gasPrice`, z.string());
}
checkFreeRedeemAvailable(walletAddress: string) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/atomic/has-free-redeem/${walletAddress}`, z.boolean());
return fetchWithValidation(`https://${this.apiUrl}/api/atomic/has-free-redeem/${walletAddress}`, z.boolean());
}
redeemAtomicSwap(
@@ -107,7 +133,7 @@ class OrionBlockchain {
secret: string,
sourceNetwork: string,
) {
return fetchJsonWithValidation(
return fetchWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem`,
z.string(),
{
@@ -131,7 +157,7 @@ class OrionBlockchain {
secret2: string,
sourceNetwork: string,
) {
return fetchJsonWithValidation(
return fetchWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem2atomics`,
z.string(),
{
@@ -151,31 +177,31 @@ class OrionBlockchain {
}
checkRedeem(secretHash: string) {
return fetchJsonWithValidation(
return fetchWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem/${secretHash}`,
z.enum(['OK', 'FAIL']).nullable(),
);
}
checkRedeem2Atomics(firstSecretHash: string, secondSecretHash: string) {
return fetchJsonWithValidation(
return fetchWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem/${firstSecretHash}-${secondSecretHash}`,
z.enum(['OK', 'FAIL']).nullable(),
);
}
getIDOInfo() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/solarflare`, IDOSchema);
return fetchWithValidation(`https://${this.apiUrl}/api/solarflare`, IDOSchema);
}
checkAuth(headers: IAdminAuthHeaders) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/auth/check`, z.object({
return fetchWithValidation(`https://${this.apiUrl}/api/auth/check`, z.object({
auth: z.boolean(),
}), { headers });
}
getPoolsList(headers: IAdminAuthHeaders) {
return fetchJsonWithValidation(
return fetchWithValidation(
`https://${this.apiUrl}/api/pools/list`,
adminPoolsListSchema,
{ headers },
@@ -183,7 +209,7 @@ class OrionBlockchain {
}
editPool(address: string, data: IEditPool, headers: IAdminAuthHeaders) {
return fetchJsonWithValidation(
return fetchWithValidation(
`https://${this.apiUrl}/api/pools/edit/${address}`,
pairStatusSchema,
{
@@ -198,22 +224,27 @@ class OrionBlockchain {
}
addPool(data: z.infer<typeof addPoolSchema>) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/pools/add`, z.number(), {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
return fetchWithValidation(
`https://${this.apiUrl}/api/pools/add`,
z.number(),
{
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
},
});
z.string(),
);
}
checkPoolInformation(poolAddress: string) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/pools/check/${poolAddress}`, pairStatusSchema);
return fetchWithValidation(`https://${this.apiUrl}/api/pools/check/${poolAddress}`, pairStatusSchema);
}
getAtomicSwapAssets() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/atomic/swap-assets`, z.array(z.string()));
return fetchWithValidation(`https://${this.apiUrl}/api/atomic/swap-assets`, z.array(z.string()));
}
/**
@@ -226,7 +257,7 @@ class OrionBlockchain {
Object.entries(query)
.forEach(([key, value]) => url.searchParams.append(key, value.toString()));
return fetchJsonWithValidation(url.toString(), atomicHistorySchema);
return fetchWithValidation(url.toString(), atomicHistorySchema);
}
getSourceAtomicSwapHistory(query: AtomicSwapHistorySourceQuery) {
@@ -237,7 +268,7 @@ class OrionBlockchain {
if (!query.type) url.searchParams.append('type', 'source');
return fetchJsonWithValidation(url.toString(), sourceAtomicHistorySchema);
return fetchWithValidation(url.toString(), sourceAtomicHistorySchema);
}
getTargetAtomicSwapHistory(query: AtomicSwapHistoryTargetQuery) {
@@ -248,11 +279,11 @@ class OrionBlockchain {
if (!query.type) url.searchParams.append('type', 'target');
return fetchJsonWithValidation(url.toString(), targetAtomicHistorySchema);
return fetchWithValidation(url.toString(), targetAtomicHistorySchema);
}
checkIfHashUsed(secretHashes: string[]) {
return fetchJsonWithValidation(
return fetchWithValidation(
`https://${this.apiUrl}/api/atomic/is-hash-used`,
z.record(z.boolean()).transform(utils.makePartial),
{

View File

@@ -1,4 +1,4 @@
import { fetchJsonWithValidation } from '../../fetchWithValidation';
import fetchWithValidation from '../../fetchWithValidation';
import candlesSchema from './schemas/candlesSchema';
class PriceFeed {
@@ -6,6 +6,8 @@ class PriceFeed {
constructor(apiUrl: string) {
this.apiUrl = apiUrl;
this.getCandles = this.getCandles.bind(this);
}
getCandles(
@@ -22,7 +24,7 @@ class PriceFeed {
url.searchParams.append('interval', interval);
url.searchParams.append('exchange', exchange);
return fetchJsonWithValidation(
return fetchWithValidation(
url.toString(),
candlesSchema,
);

28
src/simpleFetch.ts Normal file
View File

@@ -0,0 +1,28 @@
import { Schema, z } from 'zod';
import { RequestInit } from 'node-fetch';
import fetchWithValidation from './fetchWithValidation';
// https://stackoverflow.com/a/64919133
class Wrapper<DataOut, DataIn, ErrorOut, ErrorIn> {
// eslint-disable-next-line class-methods-use-this
wrapped(
url: string,
schema: Schema<DataOut, z.ZodTypeDef, DataIn>,
options?: RequestInit,
errorSchema?: Schema<ErrorOut, z.ZodTypeDef, ErrorIn>,
) {
return fetchWithValidation<DataOut, DataIn, ErrorOut, ErrorIn>(url, schema, options, errorSchema);
}
}
type FetchWithValidationInternalType<O, I, EO, EI> = ReturnType<Wrapper<O, I, EO, EI>['wrapped']>
export default function simpleFetch<O, I, EO, EI, P extends unknown[]>(
f: (...params: P) => FetchWithValidationInternalType<O, I, EO, EI>,
) {
return async (...params: Parameters<typeof f>) => {
const result = await f(...params);
if (result.isErr()) throw new Error(result.error.message);
return result.value;
};
}

View File

@@ -30,10 +30,13 @@ export default async function getBalance(
}
const assetContractBalance = await exchangeContract.getBalance(assetAddress, walletAddress);
const denormalizedAssetInContractBalance = utils.denormalizeNumber(assetContractBalance, INTERNAL_ORION_PRECISION);
const denormalizedAssetLockedBalance = await orionAggregator.getLockedBalance(walletAddress, asset);
const denormalizedAssetLockedBalanceResult = await orionAggregator.getLockedBalance(walletAddress, asset);
if (denormalizedAssetLockedBalanceResult.isErr()) {
throw new Error(denormalizedAssetLockedBalanceResult.error.message);
}
return {
exchange: denormalizedAssetInContractBalance.minus(denormalizedAssetLockedBalance[asset] ?? 0),
exchange: denormalizedAssetInContractBalance.minus(denormalizedAssetLockedBalanceResult.value[asset] ?? 0),
wallet: denormalizedAssetInWalletBalance,
};
}

View File

@@ -10,6 +10,6 @@ export { default as normalizeNumber } from './normalizeNumber';
export { default as getSwapPair } from './getSwapPair';
export { default as getSwapSide } from './getSwapSide';
export { default as HttpError } from './httpError';
// export { default as HttpError } from './httpError';
export * from './typeHelpers';