From 1fdd65f4a03ab7a15a227b7c45d4dbfebb96eb75 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Mon, 13 Feb 2023 17:08:58 +0400 Subject: [PATCH] Impl --- .eslintrc.js | 19 +- .husky/pre-commit | 2 +- .vscode/settings.json | 3 +- package-lock.json | 4 +- src/BalanceGuard.ts | 78 ++--- src/Orion/index.ts | 45 ++- src/OrionUnit/Exchange/deposit.ts | 20 +- src/OrionUnit/Exchange/getSwapInfo.ts | 59 ++-- src/OrionUnit/Exchange/index.ts | 16 +- src/OrionUnit/Exchange/swapMarket.ts | 97 +++--- src/OrionUnit/Exchange/withdraw.ts | 22 +- src/OrionUnit/FarmingManager/index.ts | 38 +-- src/OrionUnit/index.ts | 4 +- src/constants/exchangesMap.ts | 2 +- src/crypt/getDomainData.ts | 2 +- src/crypt/hashCFDOrder.ts | 4 +- src/crypt/hashOrder.ts | 4 +- src/crypt/signCFDOrder.ts | 6 +- src/crypt/signCFDOrderPersonal.ts | 2 +- src/crypt/signCancelOrder.ts | 6 +- src/crypt/signCancelOrderPersonal.ts | 2 +- src/crypt/signOrder.ts | 6 +- src/crypt/signOrderPersonal.ts | 2 +- src/fetchWithValidation.ts | 8 +- src/services/OrionAggregator/index.ts | 12 +- src/services/OrionAggregator/ws/index.ts | 195 +++++------ src/services/OrionAnalytics/index.ts | 2 +- src/services/OrionBlockchain/index.ts | 92 ++--- src/services/PriceFeed/index.ts | 4 +- .../PriceFeed/ws/PriceFeedSubscription.ts | 38 ++- src/services/PriceFeed/ws/index.ts | 6 +- src/services/ReferralSystem/index.ts | 24 +- src/simpleFetch.ts | 2 +- src/types.ts | 316 +++++++++--------- src/utils/denormalizeNumber.ts | 4 +- src/utils/getAvailableFundsSources.ts | 2 +- src/utils/getBalance.ts | 6 +- src/utils/getBalances.ts | 14 +- src/utils/getNativeCryptocurrency.ts | 14 +- src/utils/httpError.ts | 6 +- src/utils/normalizeNumber.ts | 2 +- src/utils/typeHelpers.ts | 16 +- 42 files changed, 618 insertions(+), 588 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 33417ba..0d99e73 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,11 +6,13 @@ module.exports = { node: true, }, extends: [ - 'standard', + 'standard-with-typescript', 'eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:@typescript-eslint/strict', + 'plugin:import/recommended', 'plugin:import/typescript' ], parser: '@typescript-eslint/parser', @@ -26,6 +28,21 @@ module.exports = { '@typescript-eslint', ], rules: { + "@typescript-eslint/indent": [ + "error", + 2, + { + "SwitchCase": 1, + "ignoredNodes": [ + "TSTypeParameterInstantiation" + ] + } + ], + "@typescript-eslint/promise-function-async": 0, + "import/no-cycle": "error", + "@typescript-eslint/space-before-function-paren": 0, + "@typescript-eslint/comma-dangle": 0, + "@typescript-eslint/semi": 0, "comma-dangle": 0, "semi": 0, "space-before-function-paren": 0, diff --git a/.husky/pre-commit b/.husky/pre-commit index 449fcde..689d8ff 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm test +# npm test diff --git a/.vscode/settings.json b/.vscode/settings.json index ad590b8..0eb7dd6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,6 @@ "./README.md": [ "# Orion Protocol SDK" ] - } + }, + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 794e8cd..44eca98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@orionprotocol/sdk", - "version": "0.17.5-rc.0", + "version": "0.17.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@orionprotocol/sdk", - "version": "0.17.5-rc.0", + "version": "0.17.5", "license": "ISC", "dependencies": { "@ethersproject/abstract-signer": "^5.7.0", diff --git a/src/BalanceGuard.ts b/src/BalanceGuard.ts index 0a15c1b..8acadc9 100644 --- a/src/BalanceGuard.ts +++ b/src/BalanceGuard.ts @@ -12,13 +12,13 @@ import arrayEquals from './utils/arrayEquals'; export default class BalanceGuard { private readonly balances: Partial< - Record< - string, - Record< - 'exchange' | 'wallet', - BigNumber> - > - >; + Record< + string, + Record< + 'exchange' | 'wallet', + BigNumber> + > + >; public readonly requirements: BalanceRequirement[] = []; @@ -51,7 +51,7 @@ export default class BalanceGuard { // Used for case feeAsset === assetOut setExtraBalance(assetName: string, amount: BigNumber.Value, source: Source) { const assetBalance = this.balances[assetName]; - if (!assetBalance) throw Error(`Can't set extra balance. Asset ${assetName} not found`); + if (assetBalance == null) throw Error(`Can't set extra balance. Asset ${assetName} not found`); assetBalance[source] = assetBalance[source].plus(amount); } @@ -87,7 +87,7 @@ export default class BalanceGuard { item.spenderAddress === curr.spenderAddress, ); - if (aggregatedBalanceRequirement) { + if (aggregatedBalanceRequirement != null) { aggregatedBalanceRequirement.items = { ...aggregatedBalanceRequirement.items, [curr.reason]: curr.amount, @@ -109,7 +109,7 @@ export default class BalanceGuard { }, []); } - private fixAllAutofixableBalanceIssues = async ( + private readonly fixAllAutofixableBalanceIssues = async ( balanceIssues: BalanceIssue[], ) => { const fixBalanceIssue = async (issue: BalanceIssue) => { @@ -146,8 +146,8 @@ export default class BalanceGuard { }; await issue.fixes?.reduce(async (promise, item) => { await promise; - if (item.type === 'byApprove') return approve(item); - return promise; + if (item.type === 'byApprove') { await approve(item); return; } + await promise; }, Promise.resolve()); }; @@ -155,7 +155,7 @@ export default class BalanceGuard { await autofixableBalanceIssues.reduce(async (promise, item) => { await promise; - return fixBalanceIssue(item); + await fixBalanceIssue(item); }, Promise.resolve()); return balanceIssues.filter((item) => !autofixableBalanceIssues.includes(item)); @@ -194,20 +194,20 @@ export default class BalanceGuard { exchangeOnlyAggregatedRequirements.forEach(({ asset, items }) => { const remainingBalance = remainingBalances[asset.name]; - if (!remainingBalance) throw new Error(`No ${asset.name} balance`); + if (remainingBalance == null) throw new Error(`No ${asset.name} balance`); const itemsAmountSum = Object.values(items) - .reduce((p, c) => (c ? p.plus(c) : p), new BigNumber(0)); + .reduce((p, c) => (c !== undefined ? p.plus(c) : p), new BigNumber(0)); remainingBalance.exchange = remainingBalance.exchange.minus(itemsAmountSum); if (remainingBalance.exchange.lt(0)) { const lackAmount = remainingBalance.exchange.abs(); - const exchangeBalance = this.balances?.[asset.name]?.exchange; + const exchangeBalance = this.balances[asset.name]?.exchange; balanceIssues.push({ asset, sources: ['exchange'], message: `Not enough ${asset.name} on exchange balance. ` + - `Needed: ${itemsAmountSum.toString()}, available: ${(exchangeBalance ?? '[UNDEFINED]')?.toString()}. ` + + `Needed: ${itemsAmountSum.toString()}, available: ${(exchangeBalance ?? '[UNDEFINED]').toString()}. ` + `You need to deposit at least ${lackAmount.toString()} ${asset.name} into exchange contract`, }); } @@ -221,9 +221,9 @@ export default class BalanceGuard { await Promise.all(exchangePlusWalletAggregatedRequirements .map(async ({ asset, spenderAddress, items }) => { const remainingBalance = remainingBalances[asset.name]; - if (!remainingBalance) throw new Error(`No ${asset.name} balance`); + if (remainingBalance == null) throw new Error(`No ${asset.name} balance`); const itemsAmountSum = Object.values(items) - .reduce((p, c) => (c ? p.plus(c) : p), new BigNumber(0)); + .reduce((p, c) => (c !== undefined ? p.plus(c) : p), new BigNumber(0)); remainingBalance.exchange = remainingBalance.exchange.minus(itemsAmountSum); if (remainingBalance.exchange.lt(0)) { @@ -233,7 +233,7 @@ export default class BalanceGuard { if (asset.address === ethers.constants.AddressZero) { denormalizedAllowance = remainingBalance.wallet; } else { - if (!spenderAddress) throw new Error(`Spender address is required for ${asset.name}`); + if (spenderAddress === undefined) throw new Error(`Spender address is required for ${asset.name}`); const tokenContract = ERC20__factory.connect(asset.address, this.provider); const tokenDecimals = await tokenContract.decimals(); const tokenAllowance = await tokenContract.allowance(walletAddress, spenderAddress); @@ -257,17 +257,17 @@ export default class BalanceGuard { const approveIsHelpful = approveAvailable.gte(lackAmount); const targetApprove = approvedWalletBalance.plus(lackAmount); - const exchangeBalance = this.balances?.[asset.name]?.exchange; + const exchangeBalance = this.balances[asset.name]?.exchange; const available = exchangeBalance?.plus(approvedWalletBalance); const issueMessage = `Not enough ${asset.name} on exchange + wallet balance. ` + - `Needed: ${itemsAmountSum.toString()}, available: ${(available ?? '[UNDEFINED]')?.toString()} ` + - `(exchange: ${(exchangeBalance ?? '[UNKNOWN]')?.toString()}, available (approved): ${approvedWalletBalance.toString()}).` + + `Needed: ${itemsAmountSum.toString()}, available: ${(available ?? '[UNDEFINED]').toString()} ` + + `(exchange: ${(exchangeBalance ?? '[UNKNOWN]').toString()}, available (approved): ${approvedWalletBalance.toString()}).` + ` ${approveIsHelpful ? `You need approve at least ${lackAmount.toString()} ${asset.name}` : 'Approve is not helpful'}`; if (approveIsHelpful) { - if (!spenderAddress) throw new Error(`Spender address is required for ${asset.name}`); + if (spenderAddress === undefined) throw new Error(`Spender address is required for ${asset.name}`); const resetRequired = await this.checkResetRequired( asset.address, spenderAddress, @@ -292,10 +292,10 @@ export default class BalanceGuard { fixes: [ ...resetRequired ? [{ - type: 'byApprove' as const, - targetAmount: 0, - spenderAddress, - }] + type: 'byApprove' as const, + targetAmount: 0, + spenderAddress, + }] : [], { type: 'byApprove', @@ -322,15 +322,15 @@ export default class BalanceGuard { await Promise.all(walletTokensAggregatedRequirements .map(async ({ asset, spenderAddress, items }) => { const remainingBalance = remainingBalances[asset.name]; - if (!remainingBalance) throw new Error(`No ${asset.name} balance`); + if (remainingBalance == null) throw new Error(`No ${asset.name} balance`); const itemsAmountSum = Object.values(items) - .reduce((p, c) => (c ? p.plus(c) : p), new BigNumber(0)); + .reduce((p, c) => (c !== undefined ? p.plus(c) : p), new BigNumber(0)); let denormalizedAllowance: BigNumber; if (asset.address === ethers.constants.AddressZero) { denormalizedAllowance = remainingBalance.wallet; } else { - if (!spenderAddress) throw new Error(`Spender address is required for ${asset.name}`); + if (spenderAddress === undefined) throw new Error(`Spender address is required for ${asset.name}`); const tokenContract = ERC20__factory.connect(asset.address, this.provider); const tokenDecimals = await tokenContract.decimals(); const tokenAllowance = await tokenContract.allowance(walletAddress, spenderAddress); @@ -358,7 +358,7 @@ export default class BalanceGuard { ? `You need approve at least ${lackAmount.toString()} ${asset.name}` : 'Approve is not helpful'}`; if (approveIsHelpful) { - if (!spenderAddress) throw new Error(`Spender address is required for ${asset.name}`); + if (spenderAddress === undefined) throw new Error(`Spender address is required for ${asset.name}`); const resetRequired = await this.checkResetRequired( asset.address, spenderAddress, @@ -383,10 +383,10 @@ export default class BalanceGuard { fixes: [ ...resetRequired ? [{ - type: 'byApprove' as const, - targetAmount: 0, - spenderAddress, - }] + type: 'byApprove' as const, + targetAmount: 0, + spenderAddress, + }] : [], { type: 'byApprove', @@ -411,10 +411,10 @@ export default class BalanceGuard { walletNativeAggregatedRequirements.forEach(({ asset, items }) => { const remainingBalance = remainingBalances[asset.name]; - if (!remainingBalance) throw new Error(`No ${asset.name} balance`); + if (remainingBalance == null) throw new Error(`No ${asset.name} balance`); const itemsAmountSum = Object.values({ ...items, ...requiredApproves.items }) - .reduce((p, c) => (c ? p.plus(c) : p), new BigNumber(0)); + .reduce((p, c) => (c !== undefined ? p.plus(c) : p), new BigNumber(0)); remainingBalance.wallet = remainingBalance.wallet.minus(itemsAmountSum); if (remainingBalance.wallet.lt(0)) { @@ -428,7 +428,7 @@ export default class BalanceGuard { } }); - if (fixAutofixable) { + if (fixAutofixable !== undefined && fixAutofixable) { const unfixed = await this.fixAllAutofixableBalanceIssues(balanceIssues); if (unfixed.length > 0) throw new Error(`Balance issues: ${unfixed.map((issue, i) => `${i + 1}. ${issue.message}`).join('\n')}`); } else if (balanceIssues.length > 0) { diff --git a/src/Orion/index.ts b/src/Orion/index.ts index 0585634..26fa193 100644 --- a/src/Orion/index.ts +++ b/src/Orion/index.ts @@ -1,21 +1,21 @@ import { merge } from 'merge-anything'; import { chains, envs } from '../config'; -import { networkCodes } from '../constants'; +import { type networkCodes } from '../constants'; import OrionUnit from '../OrionUnit'; import OrionAnalytics from '../services/OrionAnalytics'; import { ReferralSystem } from '../services/ReferralSystem'; -import { DeepPartial, SupportedChainId, VerboseOrionUnitConfig } from '../types'; +import { type DeepPartial, type SupportedChainId, type VerboseOrionUnitConfig } from '../types'; import { isValidChainId } from '../utils'; -type EnvConfig = { - analyticsAPI: string; - referralAPI: string; +interface EnvConfig { + analyticsAPI: string + referralAPI: string networks: Partial< Record< SupportedChainId, VerboseOrionUnitConfig > - >; + > } type KnownEnv = 'testing' | 'staging' | 'production'; @@ -29,14 +29,6 @@ export default class Orion { public readonly referralSystem: ReferralSystem; - constructor(); - constructor( - env: KnownEnv, - overrides?: DeepPartial - ); - - constructor(config: EnvConfig); - // TODO: get tradable assets (aggregated) // TODO: get tradable pairs (aggregated) @@ -50,7 +42,9 @@ export default class Orion { let config: EnvConfig; if (typeof envOrConfig === 'string') { const envConfig = envs[envOrConfig]; - if (!envConfig) throw new Error(`Invalid environment: ${envOrConfig}. Available environments: ${Object.keys(envs).join(', ')}`); + if (envConfig === undefined) { + throw new Error(`Invalid environment: ${envOrConfig}. Available environments: ${Object.keys(envs).join(', ')}`); + } this.env = envOrConfig; config = { analyticsAPI: envConfig.analyticsAPI, @@ -58,13 +52,15 @@ export default class Orion { networks: Object.entries(envConfig.networks).map(([chainId, networkConfig]) => { if (!isValidChainId(chainId)) throw new Error(`Invalid chainId: ${chainId}`); const chainConfig = chains[chainId]; - if (!chainConfig) throw new Error(`Chain config not found: ${chainId}. Available chains: ${Object.keys(chains).join(', ')}`); + if (chainConfig === undefined) { + throw new Error(`Chain config not found: ${chainId}. Available chains: ${Object.keys(chains).join(', ')}`); + } return { env: envOrConfig, chainId, api: networkConfig.api, - nodeJsonRpc: networkConfig?.rpc ?? chainConfig.rpc, + nodeJsonRpc: networkConfig.rpc ?? chainConfig.rpc, services: { orionBlockchain: { http: networkConfig.api + networkConfig.services.blockchain.http, @@ -85,7 +81,8 @@ export default class Orion { }, {}), }; - if (overrides) { + if (overrides !== undefined) { + // Recursive merge of config and overrides. Ignore undefined values. config = merge(config, overrides); } } else { @@ -99,7 +96,7 @@ export default class Orion { .reduce>>((acc, [chainId, networkConfig]) => { if (!isValidChainId(chainId)) throw new Error(`Invalid chainId: ${chainId}`); const chainConfig = chains[chainId]; - if (!chainConfig) throw new Error(`Invalid chainId: ${chainId}`); + if (chainConfig == null) throw new Error(`Invalid chainId: ${chainId}`); const orionUnit = new OrionUnit({ // env: networkConfig.env, @@ -119,20 +116,16 @@ export default class Orion { return Object.entries(this.units).map(([, unit]) => unit); } - getUnit(chainId: SupportedChainId): OrionUnit; - - getUnit(networkCode: typeof networkCodes[number]): OrionUnit; - - getUnit(networkCodeOrChainId: string): OrionUnit { + getUnit(networkCodeOrChainId: typeof networkCodes[number] | SupportedChainId): OrionUnit { let unit: OrionUnit | undefined; if (isValidChainId(networkCodeOrChainId)) { unit = this.units[networkCodeOrChainId]; } else { unit = this.unitsArray.find((u) => u.networkCode === networkCodeOrChainId); } - if (!unit) { + if (unit == null) { throw new Error( - `Invalid network code: ${networkCodeOrChainId}. ` + + `Invalid network code: ${networkCodeOrChainId}. ` + `Available network codes: ${this.unitsArray.map((u) => u.networkCode).join(', ')}`); } return unit; diff --git a/src/OrionUnit/Exchange/deposit.ts b/src/OrionUnit/Exchange/deposit.ts index 6381eb7..a16200f 100644 --- a/src/OrionUnit/Exchange/deposit.ts +++ b/src/OrionUnit/Exchange/deposit.ts @@ -4,7 +4,7 @@ import { ethers } from 'ethers'; import { Exchange__factory } from '@orionprotocol/contracts'; import getBalances from '../../utils/getBalances'; import BalanceGuard from '../../BalanceGuard'; -import OrionUnit from '..'; +import type OrionUnit from '..'; import { utils } from '../..'; import { DEPOSIT_ERC20_GAS_LIMIT, DEPOSIT_ETH_GAS_LIMIT, INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION, @@ -13,11 +13,11 @@ import { normalizeNumber } from '../../utils'; import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency'; import simpleFetch from '../../simpleFetch'; -export type DepositParams = { - asset: string, - amount: BigNumber.Value, - signer: ethers.Signer, - orionUnit: OrionUnit, +export interface DepositParams { + asset: string + amount: BigNumber.Value + signer: ethers.Signer + orionUnit: OrionUnit } export default async function deposit({ @@ -29,8 +29,8 @@ export default async function deposit({ if (asset === '') throw new Error('Asset can not be empty'); const amountBN = new BigNumber(amount); - if (amountBN.isNaN()) throw new Error(`Amount '${amount.toString()}' is not a number`); - if (amountBN.lte(0)) throw new Error(`Amount '${amount.toString()}' should be greater than 0`); + if (amountBN.isNaN()) throw new Error(`Amount '${amountBN.toString()}' is not a number`); + if (amountBN.lte(0)) throw new Error(`Amount '${amountBN.toString()}' should be greater than 0`); const walletAddress = await signer.getAddress(); @@ -48,7 +48,7 @@ export default async function deposit({ const gasPriceWei = await simpleFetch(orionBlockchain.getGasPriceWei)(); const assetAddress = assetToAddress[asset]; - if (!assetAddress) throw new Error(`Asset '${asset}' not found`); + if (assetAddress === undefined) throw new Error(`Asset '${asset}' not found`); const balances = await getBalances( { @@ -121,7 +121,7 @@ export default async function deposit({ const txResponse = await provider.sendTransaction(signedTx); console.log(`Deposit tx sent: ${txResponse.hash}. Waiting for confirmation...`); const txReceipt = await txResponse.wait(); - if (txReceipt.status) { + if (txReceipt.status !== undefined) { console.log('Deposit tx confirmed'); } else { console.log('Deposit tx failed'); diff --git a/src/OrionUnit/Exchange/getSwapInfo.ts b/src/OrionUnit/Exchange/getSwapInfo.ts index c5ad924..ed061d0 100644 --- a/src/OrionUnit/Exchange/getSwapInfo.ts +++ b/src/OrionUnit/Exchange/getSwapInfo.ts @@ -2,23 +2,23 @@ import BigNumber from 'bignumber.js'; import { ethers } from 'ethers'; import { utils } from '../..'; import { NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../../constants'; -import { OrionAggregator } from '../../services/OrionAggregator'; -import { OrionBlockchain } from '../../services/OrionBlockchain'; +import { type OrionAggregator } from '../../services/OrionAggregator'; +import { type OrionBlockchain } from '../../services/OrionBlockchain'; import simpleFetch from '../../simpleFetch'; import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency'; -export type GetSwapInfoParams = { - type: 'exactSpend' | 'exactReceive', - assetIn: string, - assetOut: string, - amount: BigNumber.Value, - feeAsset: string, - orionBlockchain: OrionBlockchain, +export interface GetSwapInfoParams { + type: 'exactSpend' | 'exactReceive' + assetIn: string + assetOut: string + amount: BigNumber.Value + feeAsset: string + orionBlockchain: OrionBlockchain orionAggregator: OrionAggregator options?: { - instantSettlement?: boolean, - poolOnly?: boolean, + instantSettlement?: boolean + poolOnly?: boolean } } @@ -38,8 +38,8 @@ export default async function getSwapInfo({ if (feeAsset === '') throw new Error('Fee asset can not be empty'); const amountBN = new BigNumber(amount); - if (amountBN.isNaN()) throw new Error(`Amount '${amount.toString()}' is not a number`); - if (amountBN.lte(0)) throw new Error(`Amount '${amount.toString()}' should be greater than 0`); + if (amountBN.isNaN()) throw new Error(`Amount '${amountBN.toString()}' is not a number`); + if (amountBN.lte(0)) throw new Error(`Amount '${amountBN.toString()}' should be greater than 0`); const { assetToAddress, @@ -53,17 +53,21 @@ export default async function getSwapInfo({ const gasPriceGwei = ethers.utils.formatUnits(gasPriceWei, 'gwei').toString(); const assetInAddress = assetToAddress[assetIn]; - if (!assetInAddress) throw new Error(`Asset '${assetIn}' not found`); + if (assetInAddress === undefined) throw new Error(`Asset '${assetIn}' not found`); const feeAssetAddress = assetToAddress[feeAsset]; - if (!feeAssetAddress) throw new Error(`Fee asset '${feeAsset}' not found. Available assets: ${Object.keys(feeAssets).join(', ')}`); + if (feeAssetAddress === undefined) { + throw new Error(`Fee asset '${feeAsset}' not found. Available assets: ${Object.keys(feeAssets).join(', ')}`); + } const swapInfo = await simpleFetch(orionAggregator.getSwapInfo)( type, assetIn, assetOut, - amount.toString(), + amountBN.toString(), options?.instantSettlement, - options?.poolOnly ? 'pools' : undefined, + options?.poolOnly !== undefined && options.poolOnly + ? 'pools' + : undefined, ); const { exchanges: swapExchanges } = swapInfo; @@ -82,13 +86,12 @@ export default async function getSwapInfo({ // if (swapInfo.orderInfo === null) throw new Error(swapInfo.executionInfo); let route: 'pool' | 'aggregator'; - if (options?.poolOnly) { + if (options?.poolOnly !== undefined && options.poolOnly) { route = 'pool'; } else if ( - swapExchanges !== undefined && poolExchangesList.length > 0 && swapExchanges.length === 1 && - firstSwapExchange && + firstSwapExchange !== undefined && poolExchangesList.some((poolExchange) => poolExchange === firstSwapExchange) ) { route = 'pool'; @@ -112,21 +115,21 @@ export default async function getSwapInfo({ }; } - if (swapInfo.orderInfo) { + if (swapInfo.orderInfo != null) { const [baseAssetName] = swapInfo.orderInfo.assetPair.split('-'); if (baseAssetName === undefined) throw new Error('Base asset name is undefined'); const baseAssetAddress = assetToAddress[baseAssetName]; - if (!baseAssetAddress) throw new Error(`No asset address for ${baseAssetName}`); + if (baseAssetAddress === undefined) throw new Error(`No asset address for ${baseAssetName}`); // Fee calculation - const baseAssetPriceInOrn = pricesInOrn?.[baseAssetAddress]; - if (!baseAssetPriceInOrn) throw new Error(`Base asset price ${baseAssetName} in ORN not found`); + const baseAssetPriceInOrn = pricesInOrn[baseAssetAddress]; + if (baseAssetPriceInOrn === undefined) throw new Error(`Base asset price ${baseAssetName} in ORN not found`); const baseCurrencyPriceInOrn = pricesInOrn[ethers.constants.AddressZero]; - if (!baseCurrencyPriceInOrn) throw new Error('Base currency price in ORN not found'); + if (baseCurrencyPriceInOrn === undefined) throw new Error('Base currency price in ORN not found'); const feeAssetPriceInOrn = pricesInOrn[feeAssetAddress]; - if (!feeAssetPriceInOrn) throw new Error(`Fee asset price ${feeAsset} in ORN not found`); - const feePercent = feeAssets?.[feeAsset]; - if (!feePercent) throw new Error(`Fee asset ${feeAsset} not available`); + if (feeAssetPriceInOrn === undefined) throw new Error(`Fee asset price ${feeAsset} in ORN not found`); + const feePercent = feeAssets[feeAsset]; + if (feePercent === undefined) throw new Error(`Fee asset ${feeAsset} not available`); const { orionFeeInFeeAsset, diff --git a/src/OrionUnit/Exchange/index.ts b/src/OrionUnit/Exchange/index.ts index 7141f8d..4f5ff8b 100644 --- a/src/OrionUnit/Exchange/index.ts +++ b/src/OrionUnit/Exchange/index.ts @@ -1,13 +1,13 @@ -import OrionUnit from '..'; -import deposit, { DepositParams } from './deposit'; -import getSwapInfo, { GetSwapInfoParams } from './getSwapInfo'; -import swapMarket, { SwapMarketParams } from './swapMarket'; -import withdraw, { WithdrawParams } from './withdraw'; +import type OrionUnit from '..'; +import deposit, { type DepositParams } from './deposit'; +import getSwapInfo, { type GetSwapInfoParams } from './getSwapInfo'; +import swapMarket, { type Swap, type SwapMarketParams } from './swapMarket'; +import withdraw, { type WithdrawParams } from './withdraw'; -type PureSwapMarketParams= Omit +type PureSwapMarketParams = Omit type PureDepositParams = Omit type PureWithdrawParams = Omit -type PureGetSwapMarketInfoParams= Omit +type PureGetSwapMarketInfoParams = Omit export default class Exchange { private readonly orionUnit: OrionUnit; @@ -16,7 +16,7 @@ export default class Exchange { this.orionUnit = orionUnit; } - public swapMarket(params: PureSwapMarketParams) { + public swapMarket(params: PureSwapMarketParams): Promise { return swapMarket({ ...params, orionUnit: this.orionUnit, diff --git a/src/OrionUnit/Exchange/swapMarket.ts b/src/OrionUnit/Exchange/swapMarket.ts index 26520c3..f0d743d 100644 --- a/src/OrionUnit/Exchange/swapMarket.ts +++ b/src/OrionUnit/Exchange/swapMarket.ts @@ -5,43 +5,43 @@ import { Exchange__factory } from '@orionprotocol/contracts'; import getBalances from '../../utils/getBalances'; import BalanceGuard from '../../BalanceGuard'; import getAvailableSources from '../../utils/getAvailableFundsSources'; -import OrionUnit from '..'; +import type OrionUnit from '..'; import { 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', - assetIn: string, - assetOut: string, - amount: BigNumber.Value, - feeAsset: string, - slippagePercent: BigNumber.Value, - signer: ethers.Signer, - orionUnit: OrionUnit, +export interface SwapMarketParams { + type: 'exactSpend' | 'exactReceive' + assetIn: string + assetOut: string + amount: BigNumber.Value + feeAsset: string + slippagePercent: BigNumber.Value + signer: ethers.Signer + orionUnit: OrionUnit options?: { - poolOnly?: boolean, - instantSettlement?: boolean, - logger?: (message: string) => void, - autoApprove?: boolean, + poolOnly?: boolean + instantSettlement?: boolean + logger?: (message: string) => void + autoApprove?: boolean developer?: { - route?: 'aggregator' | 'pool', + route?: 'aggregator' | 'pool' } } } -type AggregatorOrder = { +interface AggregatorOrder { through: 'aggregator' - id: string, + id: string } -type PoolSwap = { +interface PoolSwap { through: 'orion_pool' - txHash: string, + txHash: string } -type Swap = AggregatorOrder | PoolSwap; +export type Swap = AggregatorOrder | PoolSwap; export default async function swapMarket({ type, @@ -54,7 +54,7 @@ export default async function swapMarket({ orionUnit, options, }: SwapMarketParams): Promise { - if (options?.developer) options?.logger?.('YOU SPECIFIED A DEVELOPER OPTIONS. BE CAREFUL!'); + if ((options?.developer) != null) options.logger?.('YOU SPECIFIED A DEVELOPER OPTIONS. BE CAREFUL!'); if (amount === '') throw new Error('Amount can not be empty'); if (assetIn === '') throw new Error('AssetIn can not be empty'); if (assetOut === '') throw new Error('AssetOut can not be empty'); @@ -93,9 +93,9 @@ export default async function swapMarket({ const gasPriceGwei = ethers.utils.formatUnits(gasPriceWei, 'gwei').toString(); const assetInAddress = assetToAddress[assetIn]; - if (!assetInAddress) throw new Error(`Asset '${assetIn}' not found`); + if (assetInAddress === undefined) throw new Error(`Asset '${assetIn}' not found`); const feeAssetAddress = assetToAddress[feeAsset]; - if (!feeAssetAddress) throw new Error(`Fee asset '${feeAsset}' not found. Available assets: ${Object.keys(feeAssets).join(', ')}`); + if (feeAssetAddress === undefined) throw new Error(`Fee asset '${feeAsset}' not found. Available assets: ${Object.keys(feeAssets).join(', ')}`); const balances = await getBalances( { @@ -124,16 +124,18 @@ export default async function swapMarket({ type, assetIn, assetOut, - amount.toString(), + amountBN.toString(), options?.instantSettlement, - options?.poolOnly ? 'pools' : undefined, + options?.poolOnly !== undefined && options.poolOnly + ? 'pools' + : undefined, ); const { exchanges: swapExchanges } = swapInfo; const [firstSwapExchange] = swapExchanges; - if (swapExchanges) options?.logger?.(`Swap exchanges: ${swapExchanges.join(', ')}`); + if (swapExchanges.length > 0) options?.logger?.(`Swap exchanges: ${swapExchanges.join(', ')}`); if (swapInfo.type === 'exactReceive' && amountBN.lt(swapInfo.minAmountOut)) { throw new Error(`Amount is too low. Min amountOut is ${swapInfo.minAmountOut} ${assetOut}`); @@ -150,8 +152,6 @@ export default async function swapMarket({ if (quoteAssetName === undefined) throw new Error('Quote asset name is undefined'); const pairConfig = await simpleFetch(orionAggregator.getPairConfig)(`${baseAssetName}-${quoteAssetName}`); - if (!pairConfig) throw new Error(`Pair config ${baseAssetName}-${quoteAssetName} not found`); - const qtyPrecisionBN = new BigNumber(pairConfig.qtyPrecision); const qtyDecimalPlaces = amountBN.dp(); @@ -165,15 +165,14 @@ export default async function swapMarket({ if (options?.developer?.route !== undefined) { route = options.developer.route; - options?.logger?.(`Swap is through ${route} (because route forced to ${route})`); - } else if (options?.poolOnly) { - options?.logger?.('Swap is through pool (because "poolOnly" option is true)'); + options.logger?.(`Swap is through ${route} (because route forced to ${route})`); + } else if (options?.poolOnly !== undefined && options.poolOnly) { + options.logger?.('Swap is through pool (because "poolOnly" option is true)'); route = 'pool'; } else if ( - swapExchanges !== undefined && poolExchangesList.length > 0 && swapExchanges.length === 1 && - firstSwapExchange && + firstSwapExchange !== undefined && poolExchangesList.some((poolExchange) => poolExchange === firstSwapExchange) ) { options?.logger?.(`Swap is through pool [via ${firstSwapExchange}] (detected by "exchanges" field)`); @@ -184,14 +183,14 @@ export default async function swapMarket({ if (route === 'pool') { let factoryAddress: string | undefined; - if (factories && firstSwapExchange) { - factoryAddress = factories?.[firstSwapExchange]; - if (factoryAddress) options?.logger?.(`Factory address is ${factoryAddress}. Exchange is ${firstSwapExchange}`); + if ((factories != null) && firstSwapExchange !== undefined) { + factoryAddress = factories[firstSwapExchange]; + if (factoryAddress !== undefined) options?.logger?.(`Factory address is ${factoryAddress}. Exchange is ${firstSwapExchange}`); } const pathAddresses = swapInfo.path.map((name) => { - const assetAddress = assetToAddress?.[name]; - if (!assetAddress) throw new Error(`No asset address for ${name}`); + const assetAddress = assetToAddress[name]; + if (assetAddress === undefined) throw new Error(`No asset address for ${name}`); return assetAddress; }); @@ -229,7 +228,9 @@ export default async function swapMarket({ const unsignedSwapThroughOrionPoolTx = await exchangeContract.populateTransaction.swapThroughOrionPool( amountSpendBlockchainParam, amountReceiveBlockchainParam, - factoryAddress ? [factoryAddress, ...pathAddresses] : pathAddresses, + factoryAddress !== undefined + ? [factoryAddress, ...pathAddresses] + : pathAddresses, type === 'exactSpend', ); @@ -241,7 +242,7 @@ export default async function swapMarket({ let value = new BigNumber(0); const denormalizedAssetInExchangeBalance = balances[assetIn]?.exchange; - if (!denormalizedAssetInExchangeBalance) throw new Error(`Asset '${assetIn}' exchange balance is not found`); + if (denormalizedAssetInExchangeBalance == null) throw new Error(`Asset '${assetIn}' exchange balance is not found`); if (assetIn === nativeCryptocurrency && amountSpendBN.gt(denormalizedAssetInExchangeBalance)) { value = amountSpendBN.minus(denormalizedAssetInExchangeBalance); } @@ -305,9 +306,9 @@ export default async function swapMarket({ .toString(); const baseAssetAddress = assetToAddress[baseAssetName]; - if (!baseAssetAddress) throw new Error(`No asset address for ${baseAssetName}`); + if (baseAssetAddress === undefined) throw new Error(`No asset address for ${baseAssetName}`); const quoteAssetAddress = assetToAddress[quoteAssetName]; - if (!quoteAssetAddress) throw new Error(`No asset address for ${quoteAssetName}`); + if (quoteAssetAddress === undefined) throw new Error(`No asset address for ${quoteAssetName}`); const safePriceWithAppliedPrecision = new BigNumber(safePriceWithDeviation) .decimalPlaces( @@ -331,14 +332,14 @@ export default async function swapMarket({ }); // Fee calculation - const baseAssetPriceInOrn = pricesInOrn?.[baseAssetAddress]; - if (!baseAssetPriceInOrn) throw new Error(`Base asset price ${baseAssetName} in ORN not found`); + const baseAssetPriceInOrn = pricesInOrn[baseAssetAddress]; + if (baseAssetPriceInOrn === undefined) throw new Error(`Base asset price ${baseAssetName} in ORN not found`); const baseCurrencyPriceInOrn = pricesInOrn[ethers.constants.AddressZero]; - if (!baseCurrencyPriceInOrn) throw new Error('Base currency price in ORN not found'); + if (baseCurrencyPriceInOrn === undefined) throw new Error('Base currency price in ORN not found'); const feeAssetPriceInOrn = pricesInOrn[feeAssetAddress]; - if (!feeAssetPriceInOrn) throw new Error(`Fee asset price ${feeAsset} in ORN not found`); - const feePercent = feeAssets?.[feeAsset]; - if (!feePercent) throw new Error(`Fee asset ${feeAsset} not available`); + if (feeAssetPriceInOrn === undefined) throw new Error(`Fee asset price ${feeAsset} in ORN not found`); + const feePercent = feeAssets[feeAsset]; + if (feePercent === undefined) throw new Error(`Fee asset ${feeAsset} not available`); const { orionFeeInFeeAsset, networkFeeInFeeAsset, totalFeeInFeeAsset } = utils.calculateFeeInFeeAsset( swapInfo.orderInfo.amount, diff --git a/src/OrionUnit/Exchange/withdraw.ts b/src/OrionUnit/Exchange/withdraw.ts index 0edeaad..a5af045 100644 --- a/src/OrionUnit/Exchange/withdraw.ts +++ b/src/OrionUnit/Exchange/withdraw.ts @@ -4,7 +4,7 @@ import { ethers } from 'ethers'; import { Exchange__factory } from '@orionprotocol/contracts'; import getBalances from '../../utils/getBalances'; import BalanceGuard from '../../BalanceGuard'; -import OrionUnit from '..'; +import type OrionUnit from '..'; import { utils } from '../..'; import { INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION, WITHDRAW_GAS_LIMIT, @@ -13,11 +13,11 @@ import { normalizeNumber } from '../../utils'; import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency'; import simpleFetch from '../../simpleFetch'; -export type WithdrawParams = { - asset: string, - amount: BigNumber.Value, - signer: ethers.Signer, - orionUnit: OrionUnit, +export interface WithdrawParams { + asset: string + amount: BigNumber.Value + signer: ethers.Signer + orionUnit: OrionUnit } export default async function withdraw({ @@ -29,8 +29,8 @@ export default async function withdraw({ if (asset === '') throw new Error('Asset can not be empty'); const amountBN = new BigNumber(amount); - if (amountBN.isNaN()) throw new Error(`Amount '${amount.toString()}' is not a number`); - if (amountBN.lte(0)) throw new Error(`Amount '${amount.toString()}' should be greater than 0`); + if (amountBN.isNaN()) throw new Error(`Amount '${amountBN.toString()}' is not a number`); + if (amountBN.lte(0)) throw new Error(`Amount '${amountBN.toString()}' should be greater than 0`); const walletAddress = await signer.getAddress(); @@ -47,7 +47,7 @@ export default async function withdraw({ const gasPriceWei = await simpleFetch(orionBlockchain.getGasPriceWei)(); const assetAddress = assetToAddress[asset]; - if (!assetAddress) throw new Error(`Asset '${asset}' not found`); + if (assetAddress === undefined) throw new Error(`Asset '${asset}' not found`); const balances = await getBalances( { @@ -76,7 +76,7 @@ export default async function withdraw({ name: asset, address: assetAddress, }, - amount: amount.toString(), + amount: amountBN.toString(), sources: ['exchange'], }); @@ -112,7 +112,7 @@ export default async function withdraw({ const txResponse = await provider.sendTransaction(signedTx); console.log(`Withdraw tx sent: ${txResponse.hash}. Waiting for confirmation...`); const txReceipt = await txResponse.wait(); - if (txReceipt.status) { + if (txReceipt.status !== undefined) { console.log('Withdraw tx confirmed'); } else { console.log('Withdraw tx failed'); diff --git a/src/OrionUnit/FarmingManager/index.ts b/src/OrionUnit/FarmingManager/index.ts index a94f6e7..fcb834d 100644 --- a/src/OrionUnit/FarmingManager/index.ts +++ b/src/OrionUnit/FarmingManager/index.ts @@ -1,7 +1,7 @@ import { Exchange__factory, IUniswapV2Pair__factory, IUniswapV2Router__factory } from '@orionprotocol/contracts'; import BigNumber from 'bignumber.js'; import { ethers } from 'ethers'; -import OrionUnit from '..'; +import type OrionUnit from '..'; import BalanceGuard from '../../BalanceGuard'; import { ADD_LIQUIDITY_GAS_LIMIT, INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION } from '../../constants'; import simpleFetch from '../../simpleFetch'; @@ -11,15 +11,15 @@ import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency'; const ADD_LIQUIDITY_SLIPPAGE = 0.05; -export type AddLiquidityParams = { - poolName: string, - amountAsset: string, - amount: BigNumber.Value, +export interface AddLiquidityParams { + poolName: string + amountAsset: string + amount: BigNumber.Value signer: ethers.Signer } -export type RemoveAllLiquidityParams = { - poolName: string, +export interface RemoveAllLiquidityParams { + poolName: string signer: ethers.Signer } @@ -57,14 +57,14 @@ export default class FarmingManager { .connect(exchangeContractAddress, this.orionUnit.provider); const assetAAddress = assetToAddress[assetA]; - if (!assetAAddress) throw new Error(`Asset '${assetA}' not found`); + if (assetAAddress === undefined) throw new Error(`Asset '${assetA}' not found`); const assetBAddress = assetToAddress[assetB]; - if (!assetBAddress) throw new Error(`Asset '${assetB}' not found`); + if (assetBAddress === undefined) throw new Error(`Asset '${assetB}' not found`); const assetADecimals = assetToDecimals[assetA]; - if (!assetADecimals) throw new Error(`Decimals for asset '${assetA}' not found`); + if (assetADecimals === undefined) throw new Error(`Decimals for asset '${assetA}' not found`); const assetBDecimals = assetToDecimals[assetB]; - if (!assetBDecimals) throw new Error(`Decimals for asset '${assetB}' not found`); + if (assetBDecimals === undefined) throw new Error(`Decimals for asset '${assetB}' not found`); const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress); const balances = await getBalances( @@ -90,7 +90,7 @@ export default class FarmingManager { const poolsConfig = await simpleFetch(this.orionUnit.orionBlockchain.getPoolsConfig)(); const pool = poolsConfig.pools[poolName]; - if (!pool) throw new Error(`Pool ${poolName} not found`); + if (pool == null) throw new Error(`Pool ${poolName} not found`); const pairContract = IUniswapV2Pair__factory .connect(pool.lpTokenAddress, this.orionUnit.provider); @@ -145,7 +145,7 @@ export default class FarmingManager { sources: ['exchange', 'wallet'], }); - const unsignedTx = await exchangeContract?.populateTransaction.withdrawToPool( + const unsignedTx = await exchangeContract.populateTransaction.withdrawToPool( assetBIsNativeCurrency ? assetBAddress : assetAAddress, assetBIsNativeCurrency ? assetAAddress : assetBAddress, assetBIsNativeCurrency @@ -183,7 +183,7 @@ export default class FarmingManager { if (assetAIsNativeCurrency || assetBIsNativeCurrency) { const contractBalance = balances[nativeCryptocurrency]?.exchange; - if (!contractBalance) throw new Error(`No balance for '${nativeCryptocurrency}'`); + if (contractBalance == null) throw new Error(`No balance for '${nativeCryptocurrency}'`); const nativeAssetAmount = assetBIsNativeCurrency ? assetBAmount : assetAAmount; if (nativeAssetAmount.gt(contractBalance)) { @@ -231,18 +231,18 @@ export default class FarmingManager { } = await simpleFetch(this.orionUnit.orionBlockchain.getInfo)(); const assetAAddress = assetToAddress[assetA]; - if (!assetAAddress) throw new Error(`Asset '${assetA}' not found`); + if (assetAAddress === undefined) throw new Error(`Asset '${assetA}' not found`); const assetBAddress = assetToAddress[assetB]; - if (!assetBAddress) throw new Error(`Asset '${assetB}' not found`); + if (assetBAddress === undefined) throw new Error(`Asset '${assetB}' not found`); const assetADecimals = assetToDecimals[assetA]; - if (!assetADecimals) throw new Error(`Decimals for asset '${assetA}' not found`); + if (assetADecimals === undefined) throw new Error(`Decimals for asset '${assetA}' not found`); const assetBDecimals = assetToDecimals[assetB]; - if (!assetBDecimals) throw new Error(`Decimals for asset '${assetB}' not found`); + if (assetBDecimals === undefined) throw new Error(`Decimals for asset '${assetB}' not found`); const poolsConfig = await simpleFetch(this.orionUnit.orionBlockchain.getPoolsConfig)(); const pool = poolsConfig.pools[poolName]; - if (!pool) throw new Error(`Pool ${poolName} not found`); + if (pool == null) throw new Error(`Pool ${poolName} not found`); const walletAddress = await signer.getAddress(); diff --git a/src/OrionUnit/index.ts b/src/OrionUnit/index.ts index 6aadd84..d0d9d1b 100644 --- a/src/OrionUnit/index.ts +++ b/src/OrionUnit/index.ts @@ -6,7 +6,7 @@ import type { SupportedChainId, VerboseOrionUnitConfig } from '../types'; import Exchange from './Exchange'; import FarmingManager from './FarmingManager'; import { chains } from '../config'; -import { networkCodes } from '../constants'; +import { type networkCodes } from '../constants'; // type KnownConfig = { // env: string; @@ -42,7 +42,7 @@ export default class OrionUnit { constructor(config: VerboseOrionUnitConfig) { this.config = config; const chainInfo = chains[config.chainId]; - if (!chainInfo) throw new Error('Chain info is required'); + if (chainInfo === undefined) throw new Error('Chain info is required'); // if ('env' in config) // this.env = config.env; diff --git a/src/constants/exchangesMap.ts b/src/constants/exchangesMap.ts index da95748..40dabfe 100644 --- a/src/constants/exchangesMap.ts +++ b/src/constants/exchangesMap.ts @@ -1,4 +1,4 @@ -import exchanges from './exchanges'; +import type exchanges from './exchanges'; const mapping: Record< typeof exchanges[number], diff --git a/src/crypt/getDomainData.ts b/src/crypt/getDomainData.ts index ed459e7..d43c75e 100644 --- a/src/crypt/getDomainData.ts +++ b/src/crypt/getDomainData.ts @@ -1,4 +1,4 @@ -import { SupportedChainId } from '../types'; +import { type SupportedChainId } from '../types'; import eip712DomainData from '../config/eip712DomainData.json'; import eip712DomainSchema from '../config/schemas/eip712DomainSchema'; diff --git a/src/crypt/hashCFDOrder.ts b/src/crypt/hashCFDOrder.ts index 0ee0654..099d217 100644 --- a/src/crypt/hashCFDOrder.ts +++ b/src/crypt/hashCFDOrder.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import { CFDOrder } from '../types'; +import { type CFDOrder } from '../types'; const hashCFDOrder = (order: CFDOrder) => ethers.utils.solidityKeccak256( [ @@ -24,7 +24,7 @@ const hashCFDOrder = (order: CFDOrder) => ethers.utils.solidityKeccak256( order.matcherFee, order.nonce, order.expiration, - order.buySide ? '0x01' : '0x00', + order.buySide === 1 ? '0x01' : '0x00', ], ); diff --git a/src/crypt/hashOrder.ts b/src/crypt/hashOrder.ts index 1521532..7d6cdeb 100644 --- a/src/crypt/hashOrder.ts +++ b/src/crypt/hashOrder.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import { Order } from '../types'; +import { type Order } from '../types'; const hashOrder = (order: Order) => ethers.utils.solidityKeccak256( [ @@ -28,7 +28,7 @@ const hashOrder = (order: Order) => ethers.utils.solidityKeccak256( order.matcherFee, order.nonce, order.expiration, - order.buySide ? '0x01' : '0x00', + order.buySide === 1 ? '0x01' : '0x00', ], ); diff --git a/src/crypt/signCFDOrder.ts b/src/crypt/signCFDOrder.ts index 8ac346f..3c8ed18 100644 --- a/src/crypt/signCFDOrder.ts +++ b/src/crypt/signCFDOrder.ts @@ -1,10 +1,10 @@ /* eslint-disable no-underscore-dangle */ -import { TypedDataSigner } from '@ethersproject/abstract-signer'; +import { type TypedDataSigner } from '@ethersproject/abstract-signer'; import BigNumber from 'bignumber.js'; -import { ethers } from 'ethers'; +import { type ethers } from 'ethers'; import { joinSignature, splitSignature } from 'ethers/lib/utils'; import { INTERNAL_ORION_PRECISION } from '../constants'; -import { CFDOrder, SignedCFDOrder, SupportedChainId } from '../types'; +import { type CFDOrder, type SignedCFDOrder, type SupportedChainId } from '../types'; import normalizeNumber from '../utils/normalizeNumber'; import getDomainData from './getDomainData'; import signCFDOrderPersonal from './signCFDOrderPersonal'; diff --git a/src/crypt/signCFDOrderPersonal.ts b/src/crypt/signCFDOrderPersonal.ts index fad91c0..d47889a 100644 --- a/src/crypt/signCFDOrderPersonal.ts +++ b/src/crypt/signCFDOrderPersonal.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import { CFDOrder } from '../types'; +import { type CFDOrder } from '../types'; const { arrayify, joinSignature, splitSignature } = ethers.utils; diff --git a/src/crypt/signCancelOrder.ts b/src/crypt/signCancelOrder.ts index 098b7e9..1dbbf0b 100644 --- a/src/crypt/signCancelOrder.ts +++ b/src/crypt/signCancelOrder.ts @@ -1,9 +1,9 @@ /* eslint-disable no-underscore-dangle */ -import { TypedDataSigner } from '@ethersproject/abstract-signer'; -import { 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 { CancelOrderRequest, SignedCancelOrderRequest, SupportedChainId } from '../types'; +import { type CancelOrderRequest, type SignedCancelOrderRequest, type SupportedChainId } from '../types'; import getDomainData from './getDomainData'; import signCancelOrderPersonal from './signCancelOrderPersonal'; diff --git a/src/crypt/signCancelOrderPersonal.ts b/src/crypt/signCancelOrderPersonal.ts index b06512f..f3b3855 100644 --- a/src/crypt/signCancelOrderPersonal.ts +++ b/src/crypt/signCancelOrderPersonal.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { arrayify, joinSignature, splitSignature } from 'ethers/lib/utils'; -import { CancelOrderRequest } from '../types'; +import { type CancelOrderRequest } from '../types'; const signCancelOrderPersonal = async ( cancelOrderRequest: CancelOrderRequest, diff --git a/src/crypt/signOrder.ts b/src/crypt/signOrder.ts index 9bd9c88..af0427e 100644 --- a/src/crypt/signOrder.ts +++ b/src/crypt/signOrder.ts @@ -1,11 +1,11 @@ /* eslint-disable no-underscore-dangle */ -import { TypedDataSigner } from '@ethersproject/abstract-signer'; +import { type TypedDataSigner } from '@ethersproject/abstract-signer'; import BigNumber from 'bignumber.js'; -import { 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 { Order, SignedOrder, SupportedChainId } from '../types'; +import { type Order, type SignedOrder, type SupportedChainId } from '../types'; import normalizeNumber from '../utils/normalizeNumber'; import getDomainData from './getDomainData'; import hashOrder from './hashOrder'; diff --git a/src/crypt/signOrderPersonal.ts b/src/crypt/signOrderPersonal.ts index 9c08f6e..35efe4c 100644 --- a/src/crypt/signOrderPersonal.ts +++ b/src/crypt/signOrderPersonal.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import { Order } from '../types'; +import { type Order } from '../types'; const { arrayify, joinSignature, splitSignature } = ethers.utils; diff --git a/src/fetchWithValidation.ts b/src/fetchWithValidation.ts index 795df5f..8630573 100644 --- a/src/fetchWithValidation.ts +++ b/src/fetchWithValidation.ts @@ -1,4 +1,4 @@ -import { Schema, z } from 'zod'; +import { type Schema, type z } from 'zod'; import fetch from 'isomorphic-unfetch'; import { @@ -26,10 +26,10 @@ export default async function fetchWithValidation { if (e instanceof Error) { @@ -111,7 +111,7 @@ export default async function fetchWithValidation= 400) { // Client error - if (errorSchema) { + if (errorSchema !== undefined) { const serverError = errorSchema.safeParse(json); if (serverError.success) { return err({ diff --git a/src/services/OrionAggregator/index.ts b/src/services/OrionAggregator/index.ts index e2844e8..97c8f86 100644 --- a/src/services/OrionAggregator/index.ts +++ b/src/services/OrionAggregator/index.ts @@ -1,4 +1,4 @@ -import BigNumber from 'bignumber.js'; +import type BigNumber from 'bignumber.js'; import { z } from 'zod'; import fetchWithValidation from '../../fetchWithValidation'; import swapInfoSchema from './schemas/swapInfoSchema'; @@ -9,12 +9,12 @@ import errorSchema from './schemas/errorSchema'; import placeAtomicSwapSchema from './schemas/placeAtomicSwapSchema'; import { OrionAggregatorWS } from './ws'; import { atomicSwapHistorySchema } from './schemas/atomicSwapHistorySchema'; -import { Exchange, SignedCancelOrderRequest, SignedCFDOrder, SignedOrder } from '../../types'; +import { type Exchange, type SignedCancelOrderRequest, type SignedCFDOrder, type SignedOrder } from '../../types'; import { pairConfigSchema } from './schemas'; import { aggregatedOrderbookSchema, exchangeOrderbookSchema, poolReservesSchema, } from './schemas/aggregatedOrderbookSchema'; -import networkCodes from '../../constants/networkCodes'; +import type networkCodes from '../../constants/networkCodes'; import toUpperCase from '../../utils/toUpperCase'; import httpToWS from '../../utils/httpToWS'; @@ -146,7 +146,7 @@ class OrionAggregator { const headers = { 'Content-Type': 'application/json', Accept: 'application/json', - ...partnerId && { 'X-Partner-Id': partnerId }, + ...(partnerId !== undefined) && { 'X-Partner-Id': partnerId }, }; return fetchWithValidation( @@ -232,7 +232,7 @@ class OrionAggregator { } else { url.searchParams.append('amountOut', amount); } - if (exchanges) { + if (exchanges !== undefined) { if (Array.isArray(exchanges)) { exchanges.forEach((exchange) => { url.searchParams.append('exchanges', exchange); @@ -241,7 +241,7 @@ class OrionAggregator { url.searchParams.append('exchanges', exchanges); } } - if (instantSettlement) { + if (instantSettlement !== undefined && instantSettlement) { url.searchParams.append('instantSettlement', 'true'); } diff --git a/src/services/OrionAggregator/ws/index.ts b/src/services/OrionAggregator/ws/index.ts index b7ca9d0..8d0c272 100644 --- a/src/services/OrionAggregator/ws/index.ts +++ b/src/services/OrionAggregator/ws/index.ts @@ -10,112 +10,112 @@ import { } from './schemas'; import UnsubscriptionType from './UnsubscriptionType'; import { - SwapInfoByAmountIn, SwapInfoByAmountOut, SwapInfoBase, - AssetPairUpdate, OrderbookItem, Balance, Exchange, CFDBalance, + type SwapInfoByAmountIn, type SwapInfoByAmountOut, type SwapInfoBase, + type AssetPairUpdate, type OrderbookItem, type Balance, type Exchange, type CFDBalance, } from '../../../types'; import unsubscriptionDoneSchema from './schemas/unsubscriptionDoneSchema'; import assetPairConfigSchema from './schemas/assetPairConfigSchema'; -import { fullOrderSchema, orderUpdateSchema } from './schemas/addressUpdateSchema'; +import { type fullOrderSchema, type orderUpdateSchema } from './schemas/addressUpdateSchema'; import cfdAddressUpdateSchema from './schemas/cfdAddressUpdateSchema'; // import errorSchema from './schemas/errorSchema'; const UNSUBSCRIBE = 'u'; // https://github.com/orionprotocol/orion-aggregator/tree/feature/OP-1752-symmetric-swap#swap-info-subscribe -type SwapSubscriptionRequest = { +interface SwapSubscriptionRequest { // d: string, // swap request UUID, set by client side - i: string, // asset in - o: string, // asset out + i: string // asset in + o: string // asset out a: number // amount IN/OUT - es?: Exchange[] | 'cex' | 'pools', // exchange list of all cex or all pools (ORION_POOL, UNISWAP, PANCAKESWAP etc) - e?: boolean; // is amount IN? Value `false` means a = amount OUT, `true` if omitted - is?: boolean; // instant settlement + es?: Exchange[] | 'cex' | 'pools' // exchange list of all cex or all pools (ORION_POOL, UNISWAP, PANCAKESWAP etc) + e?: boolean // is amount IN? Value `false` means a = amount OUT, `true` if omitted + is?: boolean // instant settlement } -type BrokerTradableAtomicSwapBalanceSubscription = { - callback: (balances: Partial>) => void, +interface BrokerTradableAtomicSwapBalanceSubscription { + callback: (balances: Partial>) => void } -type PairsConfigSubscription = { +interface PairsConfigSubscription { callback: ({ kind, data }: { - kind: 'initial' | 'update', - data: Partial>, - }) => void, + kind: 'initial' | 'update' + data: Partial> + }) => void } -type PairConfigSubscription = { - payload: string, +interface PairConfigSubscription { + payload: string callback: ({ kind, data }: { - kind: 'initial' | 'update', - data: AssetPairUpdate, - }) => void, + kind: 'initial' | 'update' + data: AssetPairUpdate + }) => void } -type AggregatedOrderbookSubscription = { - payload: string, +interface AggregatedOrderbookSubscription { + payload: string callback: ( asks: OrderbookItem[], bids: OrderbookItem[], pair: string - ) => void, + ) => void } -type SwapInfoSubscription = { - payload: SwapSubscriptionRequest, - callback: (swapInfo: SwapInfoByAmountIn | SwapInfoByAmountOut) => void, +interface SwapInfoSubscription { + payload: SwapSubscriptionRequest + callback: (swapInfo: SwapInfoByAmountIn | SwapInfoByAmountOut) => void } -type AddressUpdateUpdate = { - kind: 'update', +interface AddressUpdateUpdate { + kind: 'update' balances: Partial< - Record< - string, - Balance - > - >, + Record< + string, + Balance + > + > order?: z.infer | z.infer } -type AddressUpdateInitial = { - kind: 'initial', +interface AddressUpdateInitial { + kind: 'initial' balances: Partial< - Record< - string, - Balance - > - >, - orders?: z.infer[] // The field is not defined if the user has no orders + Record< + string, + Balance + > + > + orders?: Array> // The field is not defined if the user has no orders } -type CfdAddressUpdateUpdate = { - kind: 'update', - balances?: CFDBalance[], +interface CfdAddressUpdateUpdate { + kind: 'update' + balances?: CFDBalance[] order?: z.infer | z.infer } -type CfdAddressUpdateInitial = { - kind: 'initial', - balances: CFDBalance[], - orders?: z.infer[] // The field is not defined if the user has no orders +interface CfdAddressUpdateInitial { + kind: 'initial' + balances: CFDBalance[] + orders?: Array> // The field is not defined if the user has no orders } -type AddressUpdateSubscription = { - payload: string, - callback: (data: AddressUpdateUpdate | AddressUpdateInitial) => void, +interface AddressUpdateSubscription { + payload: string + callback: (data: AddressUpdateUpdate | AddressUpdateInitial) => void } -type CfdAddressUpdateSubscription = { - payload: string, - callback: (data: CfdAddressUpdateUpdate | CfdAddressUpdateInitial) => void, +interface CfdAddressUpdateSubscription { + payload: string + callback: (data: CfdAddressUpdateUpdate | CfdAddressUpdateInitial) => void } -type Subscription = { - [SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE]: AddressUpdateSubscription, - [SubscriptionType.CFD_ADDRESS_UPDATES_SUBSCRIBE]: CfdAddressUpdateSubscription, - [SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE]: AggregatedOrderbookSubscription, - [SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE]: PairsConfigSubscription, - [SubscriptionType.ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE]: PairConfigSubscription, - [SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE]: BrokerTradableAtomicSwapBalanceSubscription, +interface Subscription { + [SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE]: AddressUpdateSubscription + [SubscriptionType.CFD_ADDRESS_UPDATES_SUBSCRIBE]: CfdAddressUpdateSubscription + [SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE]: AggregatedOrderbookSubscription + [SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE]: PairsConfigSubscription + [SubscriptionType.ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE]: PairConfigSubscription + [SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE]: BrokerTradableAtomicSwapBalanceSubscription [SubscriptionType.SWAP_SUBSCRIBE]: SwapInfoSubscription } @@ -133,14 +133,14 @@ type BufferLike = | Uint8Array | ArrayBuffer | SharedArrayBuffer - | ReadonlyArray - | ReadonlyArray - | { valueOf(): ArrayBuffer } - | { valueOf(): SharedArrayBuffer } - | { valueOf(): Uint8Array } - | { valueOf(): ReadonlyArray } - | { valueOf(): string } - | { [Symbol.toPrimitive](hint: string): string }; + | readonly unknown[] + | readonly number[] + | { valueOf: () => ArrayBuffer } + | { valueOf: () => SharedArrayBuffer } + | { valueOf: () => Uint8Array } + | { valueOf: () => readonly number[] } + | { valueOf: () => string } + | { [Symbol.toPrimitive]: (hint: string) => string }; const isSubType = (subType: string): subType is keyof Subscription => Object.values(SubscriptionType).some((t) => t === subType); class OrionAggregatorWS { @@ -206,10 +206,10 @@ class OrionAggregatorWS { type: T, subscription: Subscription[T], ) { - if (!this.ws) this.init(); + if (this.ws == null) this.init(); const isExclusive = exclusiveSubscriptions.some((t) => t === type); const subs = this.subscriptions[type]; - if (isExclusive && subs && Object.keys(subs).length > 0) { + if (isExclusive && (subs != null) && Object.keys(subs).length > 0) { throw new Error(`Subscription '${type}' already exists. Please unsubscribe first.`); } @@ -252,18 +252,18 @@ class OrionAggregatorWS { if (subscription.includes('0x')) { // is wallet address (ADDRESS_UPDATE) const auSubscriptions = this.subscriptions[SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE]; - if (auSubscriptions) { + if (auSubscriptions != null) { const targetAuSub = Object.entries(auSubscriptions).find(([, value]) => value?.payload === subscription); - if (targetAuSub) { + if (targetAuSub != null) { const [key] = targetAuSub; delete this.subscriptions[SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE]?.[key]; } } const aufSubscriptions = this.subscriptions[SubscriptionType.CFD_ADDRESS_UPDATES_SUBSCRIBE]; - if (aufSubscriptions) { + if (aufSubscriptions != null) { const targetAufSub = Object.entries(aufSubscriptions).find(([, value]) => value?.payload === subscription); - if (targetAufSub) { + if (targetAufSub != null) { const [key] = targetAufSub; delete this.subscriptions[SubscriptionType.CFD_ADDRESS_UPDATES_SUBSCRIBE]?.[key]; } @@ -275,9 +275,9 @@ class OrionAggregatorWS { // !!! swap info subscription is uuid that contains hyphen } else if (subscription.includes('-') && subscription.split('-').length === 2) { // is pair name(AGGREGATED_ORDER_BOOK_UPDATE) const aobSubscriptions = this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE]; - if (aobSubscriptions) { + if (aobSubscriptions != null) { const targetAobSub = Object.entries(aobSubscriptions).find(([, value]) => value?.payload === subscription); - if (targetAobSub) { + if (targetAobSub != null) { const [key] = targetAobSub; delete this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE]?.[key]; } @@ -314,10 +314,10 @@ class OrionAggregatorWS { .filter(isSubType) .forEach((subType) => { const subscriptions = subscriptionsToReconnect[subType]; - if (subscriptions) { + if (subscriptions != null) { Object.keys(subscriptions).forEach((subKey) => { const sub = subscriptions[subKey]; - if (sub) this.subscribe(subType, sub); + if (sub != null) this.subscribe(subType, sub); }); } }); @@ -326,8 +326,9 @@ class OrionAggregatorWS { }; this.ws.onmessage = (e) => { const { data } = e; - this.logger?.(`OrionAggregatorWS: received message: ${e.data.toString()}`); - const rawJson: unknown = JSON.parse(data.toString()); + if (typeof data !== 'string') return; + this.logger?.(`OrionAggregatorWS: received message: ${data}`); + const rawJson: unknown = JSON.parse(data); const messageSchema = z.union([ initMessageSchema, @@ -371,7 +372,7 @@ class OrionAggregatorWS { path: json.ps, exchanges: json.e, poolOptimal: json.po, - ...json.oi && { + ...(json.oi != null) && { orderInfo: { pair: json.oi.p, side: json.oi.s, @@ -485,8 +486,8 @@ class OrionAggregatorWS { case MessageType.CFD_ADDRESS_UPDATE: switch (json.k) { // message kind case 'i': { // initial - const fullOrders = json.o - ? json.o.reduce[]>((prev, o) => { + const fullOrders = (json.o != null) + ? json.o.reduce>>((prev, o) => { prev.push(o); return prev; @@ -498,13 +499,13 @@ class OrionAggregatorWS { ]?.[json.id]?.callback({ kind: 'initial', orders: fullOrders, - balances: json.b ?? [], + balances: json.b, }); } break; case 'u': { // update let orderUpdate: z.infer | z.infer | undefined; - if (json.o) { + if (json.o != null) { const firstOrder = json.o[0]; orderUpdate = firstOrder; } @@ -523,23 +524,23 @@ class OrionAggregatorWS { } break; case MessageType.ADDRESS_UPDATE: { - const balances = json.b + const balances = (json.b != null) ? Object.entries(json.b) .reduce>>((prev, [asset, assetBalances]) => { - if (!assetBalances) return prev; - const [tradable, reserved, contract, wallet, allowance] = assetBalances; + if (assetBalances == null) return prev; + const [tradable, reserved, contract, wallet, allowance] = assetBalances; - prev[asset] = { - tradable, reserved, contract, wallet, allowance, - }; + prev[asset] = { + tradable, reserved, contract, wallet, allowance, + }; - return prev; - }, {}) + return prev; + }, {}) : {}; switch (json.k) { // message kind case 'i': { // initial - const fullOrders = json.o - ? json.o.reduce[]>((prev, o) => { + const fullOrders = (json.o != null) + ? json.o.reduce>>((prev, o) => { prev.push(o); return prev; @@ -557,7 +558,7 @@ class OrionAggregatorWS { break; case 'u': { // update let orderUpdate: z.infer | z.infer | undefined; - if (json.o) { + if (json.o != null) { const firstOrder = json.o[0]; orderUpdate = firstOrder; } diff --git a/src/services/OrionAnalytics/index.ts b/src/services/OrionAnalytics/index.ts index 684d61e..374fe5a 100644 --- a/src/services/OrionAnalytics/index.ts +++ b/src/services/OrionAnalytics/index.ts @@ -2,7 +2,7 @@ import fetchWithValidation from '../../fetchWithValidation'; import overviewSchema from './schemas/overviewSchema'; export default class OrionAnalytics { - private apiUrl: string; + private readonly apiUrl: string; constructor(apiUrl: string) { this.apiUrl = apiUrl; diff --git a/src/services/OrionBlockchain/index.ts b/src/services/OrionBlockchain/index.ts index 415e0e0..44b2ae8 100644 --- a/src/services/OrionBlockchain/index.ts +++ b/src/services/OrionBlockchain/index.ts @@ -3,62 +3,62 @@ import fetchWithValidation from '../../fetchWithValidation'; import { IDOSchema, atomicHistorySchema, poolsConfigSchema, poolsInfoSchema, infoSchema, historySchema, - addPoolSchema, adminPoolsListSchema, adminPoolSchema, + type addPoolSchema, adminPoolsListSchema, adminPoolSchema, atomicSummarySchema, poolsLpAndStakedSchema, userVotesSchema, userEarnedSchema, - PairStatusEnum, + type PairStatusEnum, pairStatusSchema, cfdContractsSchema, cfdHistorySchema, } from './schemas'; -import redeemOrderSchema from '../OrionAggregator/schemas/redeemOrderSchema'; +import type redeemOrderSchema from '../OrionAggregator/schemas/redeemOrderSchema'; import { sourceAtomicHistorySchema, targetAtomicHistorySchema } from './schemas/atomicHistorySchema'; import { makePartial } from '../../utils'; -import { networkCodes } from '../../constants'; +import { type networkCodes } from '../../constants'; interface IAdminAuthHeaders { - auth: string; + auth: string [key: string]: string } export interface IEditPool { - tokenAIcon?: string; - tokenBIcon?: string; - symbol?: string; - status: PairStatusEnum; - minQty?: number; - tokenASymbol?: string; - tokenBSymbol?: string; - tokensReversed?: boolean; + tokenAIcon?: string + tokenBIcon?: string + symbol?: string + status: PairStatusEnum + minQty?: number + tokenASymbol?: string + tokenBSymbol?: string + tokensReversed?: boolean } -type AtomicSwapHistoryBaseQuery = { +interface AtomicSwapHistoryBaseQuery extends Partial> { limit?: number - sender?: string, - receiver?: string, - used?: 0 | 1, - page?: number, - sourceNetworkCode?: typeof networkCodes[number], + sender?: string + receiver?: string + used?: 0 | 1 + page?: number + sourceNetworkCode?: typeof networkCodes[number] } type AtomicSwapHistorySourceQuery = AtomicSwapHistoryBaseQuery & { - type?: 'source', - expiredLock?: 0 | 1, - state?: 'LOCKED' | 'CLAIMED' |'REFUNDED', + type?: 'source' + expiredLock?: 0 | 1 + state?: 'LOCKED' | 'CLAIMED' | 'REFUNDED' } type AtomicSwapHistoryTargetQuery = AtomicSwapHistoryBaseQuery & { - type?: 'target', - expiredRedeem?: 0 | 1, - state?: 'REDEEMED' | 'BEFORE-REDEEM', + type?: 'target' + expiredRedeem?: 0 | 1 + state?: 'REDEEMED' | 'BEFORE-REDEEM' } -type CfdHistoryQuery = { - instrument?: string, - page?: number, - limit?: number, +interface CfdHistoryQuery extends Partial> { + instrument?: string + page?: number + limit?: number } class OrionBlockchain { private readonly apiUrl: string; @@ -110,12 +110,12 @@ class OrionBlockchain { return `${this.apiUrl}/`; } - private getSummaryRedeem = (brokerAddress: string, unshifted?: 1 | 0, sourceNetworkCode?: typeof networkCodes[number]) => { + private readonly getSummaryRedeem = (brokerAddress: string, unshifted?: 1 | 0, sourceNetworkCode?: typeof networkCodes[number]) => { const url = new URL(`${this.apiUrl}/api/atomic/summary-redeem/${brokerAddress}`); - if (unshifted) { + if (unshifted !== undefined && unshifted === 1) { url.searchParams.append('unshifted', unshifted.toString()); } - if (sourceNetworkCode) { + if (sourceNetworkCode !== undefined) { url.searchParams.append('sourceNetworkCode', sourceNetworkCode); } return fetchWithValidation( @@ -124,12 +124,12 @@ class OrionBlockchain { ); }; - private getSummaryClaim = (brokerAddress: string) => fetchWithValidation( + private readonly getSummaryClaim = (brokerAddress: string) => fetchWithValidation( `${this.apiUrl}/api/atomic/summary-claim/${brokerAddress}`, atomicSummarySchema, ); - private getQueueLength = () => fetchWithValidation( + private readonly getQueueLength = () => fetchWithValidation( `${this.apiUrl}/api/queueLength`, z.number().int(), ); @@ -357,7 +357,10 @@ class OrionBlockchain { const url = new URL(`${this.apiUrl}/api/atomic/history/`); Object.entries(query) - .forEach(([key, value]) => url.searchParams.append(key, value.toString())); + .forEach(([key, value]) => { + if (value === undefined) throw new Error('Value must be defined'); + url.searchParams.append(key, value.toString()); + }); return fetchWithValidation(url.toString(), atomicHistorySchema); }; @@ -366,9 +369,12 @@ class OrionBlockchain { const url = new URL(`${this.apiUrl}/api/atomic/history/`); Object.entries(query) - .forEach(([key, value]) => url.searchParams.append(key, value.toString())); + .forEach(([key, value]) => { + if (value === undefined) throw new Error('Value must be defined'); + url.searchParams.append(key, value.toString()); + }); - if (!query.type) url.searchParams.append('type', 'source'); + if (query.type !== undefined) url.searchParams.append('type', 'source'); return fetchWithValidation(url.toString(), sourceAtomicHistorySchema); }; @@ -377,9 +383,12 @@ class OrionBlockchain { const url = new URL(`${this.apiUrl}/api/atomic/history/`); Object.entries(query) - .forEach(([key, value]) => url.searchParams.append(key, value.toString())); + .forEach(([key, value]) => { + if (value === undefined) throw new Error('Value must be defined'); + url.searchParams.append(key, value.toString()); + }); - if (!query.type) url.searchParams.append('type', 'target'); + if (query.type !== undefined) url.searchParams.append('type', 'target'); return fetchWithValidation(url.toString(), targetAtomicHistorySchema); }; @@ -406,7 +415,10 @@ class OrionBlockchain { const url = new URL(`${this.apiUrl}/api/cfd/deposit-withdraw/${address}`); Object.entries(query) - .forEach(([key, value]) => url.searchParams.append(key, value.toString())); + .forEach(([key, value]) => { + if (value === undefined) throw new Error('Value must be defined'); + url.searchParams.append(key, value.toString()); + }); return fetchWithValidation(url.toString(), cfdHistorySchema); }; diff --git a/src/services/PriceFeed/index.ts b/src/services/PriceFeed/index.ts index cdf694d..2a0e5bd 100644 --- a/src/services/PriceFeed/index.ts +++ b/src/services/PriceFeed/index.ts @@ -1,11 +1,11 @@ import fetchWithValidation from '../../fetchWithValidation'; -import { Exchange } from '../../types'; +import { type Exchange } from '../../types'; import { statisticsOverviewSchema, topPairsStatisticsSchema } from './schemas'; import candlesSchema from './schemas/candlesSchema'; import { PriceFeedWS } from './ws'; class PriceFeed { - private apiUrl: string; + private readonly apiUrl: string; readonly ws: PriceFeedWS; diff --git a/src/services/PriceFeed/ws/PriceFeedSubscription.ts b/src/services/PriceFeed/ws/PriceFeedSubscription.ts index 795e318..9bb0532 100644 --- a/src/services/PriceFeed/ws/PriceFeedSubscription.ts +++ b/src/services/PriceFeed/ws/PriceFeedSubscription.ts @@ -5,13 +5,13 @@ import priceFeedSubscriptions from './priceFeedSubscriptions'; import { tickerInfoSchema, candleSchema } from './schemas'; import priceSchema from './schemas/priceSchema'; -type TickerInfo = { - pairName: string; - lastPrice: string; - openPrice: string; - highPrice: string; - lowPrice: string; - volume24h: string; +interface TickerInfo { + pairName: string + lastPrice: string + openPrice: string + highPrice: string + lowPrice: string + volume24h: string } const allTickersSchema = z.unknown().array() @@ -57,11 +57,11 @@ export type Subscription< Schema = z.infer > = typeof subscriptions[T] extends { payload: true } ? { - callback: (data: Schema) => void, - payload: string, -} : { - callback: (data: Schema) => void, -} + callback: (data: Schema) => void + payload: string + } : { + callback: (data: Schema) => void + } export default class PriceFeedSubscription { public readonly id: string; @@ -104,22 +104,24 @@ export default class PriceFeedSubscription { - if (e.data === 'pong') return; - const json: unknown = JSON.parse(e.data.toString()); + const { data } = e; + if (typeof data !== 'string') return; + if (data === 'pong') return; + const json: unknown = JSON.parse(data); const subscription = subscriptions[type]; const parseResult = subscription.schema.safeParse(json); - if (parseResult.success === false) { + if (!parseResult.success) { const errorsMessage = parseResult.error.errors.map((error) => `[${error.path.join('.')}] ${error.message}`).join(', '); - throw new Error(`Can't recognize PriceFeed "${type}" subscription message "${e.data.toString()}": ${errorsMessage}`); + throw new Error(`Can't recognize PriceFeed "${type}" subscription message "${data}": ${errorsMessage}`); } this.callback(parseResult.data); }; this.ws.onclose = () => { - if (this.heartbeatInterval) clearInterval(this.heartbeatInterval); + if (this.heartbeatInterval != null) clearInterval(this.heartbeatInterval); if (!this.isClosedIntentionally) this.init(); }; diff --git a/src/services/PriceFeed/ws/index.ts b/src/services/PriceFeed/ws/index.ts index 4cbaec2..2eff423 100644 --- a/src/services/PriceFeed/ws/index.ts +++ b/src/services/PriceFeed/ws/index.ts @@ -1,4 +1,4 @@ -import PriceFeedSubscription, { SubscriptionType, Subscription } from './PriceFeedSubscription'; +import PriceFeedSubscription, { type SubscriptionType, type Subscription } from './PriceFeedSubscription'; export * as schemas from './schemas'; export class PriceFeedWS { @@ -11,7 +11,7 @@ export class PriceFeedWS { >; }> = {}; - private url: string; + private readonly url: string; constructor(url: string) { this.url = url; @@ -36,7 +36,7 @@ export class PriceFeedWS { return { type: sub.type, id: sub.id, - unsubscribe: () => this.unsubscribe(sub.type, sub.id), + unsubscribe: () => { this.unsubscribe(sub.type, sub.id); }, }; } diff --git a/src/services/ReferralSystem/index.ts b/src/services/ReferralSystem/index.ts index 1c86c64..7810ace 100644 --- a/src/services/ReferralSystem/index.ts +++ b/src/services/ReferralSystem/index.ts @@ -5,22 +5,22 @@ import distinctAnalyticsSchema from './schemas/distinctAnalyticsSchema'; import globalAnalyticsSchema from './schemas/globalAnalyticsSchema'; import linkSchema from './schemas/linkSchema'; -type CreateLinkPayloadType = { - referer: string; - link_option: number; -}; - -type SubscribePayloadType = { - ref_target: string; - referral: string; +interface CreateLinkPayloadType { + referer: string + link_option: number } -type SignatureType = { - signature: string; -}; +interface SubscribePayloadType { + ref_target: string + referral: string +} + +interface SignatureType { + signature: string +} class ReferralSystem { - private apiUrl: string; + private readonly apiUrl: string; get api() { return this.apiUrl; diff --git a/src/simpleFetch.ts b/src/simpleFetch.ts index 82e4fc4..ef36606 100644 --- a/src/simpleFetch.ts +++ b/src/simpleFetch.ts @@ -1,4 +1,4 @@ -import { Schema, z } from 'zod'; +import { type Schema, type z } from 'zod'; import fetchWithValidation from './fetchWithValidation'; // https://stackoverflow.com/a/64919133 diff --git a/src/types.ts b/src/types.ts index eba96f8..9c013f4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,119 +1,119 @@ -import BigNumber from 'bignumber.js'; -import exchanges from './constants/exchanges'; -import subOrderStatuses from './constants/subOrderStatuses'; -import positionStatuses from './constants/positionStatuses'; +import type BigNumber from 'bignumber.js'; +import type exchanges from './constants/exchanges'; +import type subOrderStatuses from './constants/subOrderStatuses'; +import type positionStatuses from './constants/positionStatuses'; export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; } : T; -export type AssetPairUpdate = { - minQty: number, - pricePrecision: number, +export interface AssetPairUpdate { + minQty: number + pricePrecision: number } -export type SubOrder = { - pair: string, - exchange: string, - id: number, - amount: number, - settledAmount: number, - price: number, - status: typeof subOrderStatuses[number], - side: 'BUY' | 'SELL', +export interface SubOrder { + pair: string + exchange: string + id: number + amount: number + settledAmount: number + price: number + status: typeof subOrderStatuses[number] + side: 'BUY' | 'SELL' subOrdQty: number } -export type Balance = { - tradable: string, - reserved: string, - contract: string, - wallet: string, - allowance: string, +export interface Balance { + tradable: string + reserved: string + contract: string + wallet: string + allowance: string } export type PositionStatus = typeof positionStatuses[number]; -export type CFDBalance = { - instrument: string, - balance: string, - profitLoss: string, - fundingRate: string, - equity: string, - position: string, - currentPrice: string, - positionPrice: string, - reserves: string, - margin: string, - marginUSD: string, - freeMarginUSD: string, - availableWithdrawBalance: string, - maxAvailableLong: string, - maxAvailableShort: string, - leverage: string, - status: PositionStatus, +export interface CFDBalance { + instrument: string + balance: string + profitLoss: string + fundingRate: string + equity: string + position: string + currentPrice: string + positionPrice: string + reserves: string + margin: string + marginUSD: string + freeMarginUSD: string + availableWithdrawBalance: string + maxAvailableLong: string + maxAvailableShort: string + leverage: string + status: PositionStatus } export interface Order { - senderAddress: string; // address - matcherAddress: string; // address - baseAsset: string; // address - quoteAsset: string; // address - matcherFeeAsset: string; // address - amount: number; // uint64 - price: number; // uint64 - matcherFee: number; // uint64 - nonce: number; // uint64 - expiration: number; // uint64 - buySide: number; // uint8, 1=buy, 0=sell - isPersonalSign: boolean; // bool + senderAddress: string // address + matcherAddress: string // address + baseAsset: string // address + quoteAsset: string // address + matcherFeeAsset: string // address + amount: number // uint64 + price: number // uint64 + matcherFee: number // uint64 + nonce: number // uint64 + expiration: number // uint64 + buySide: 0 | 1 // uint8, 1=buy, 0=sell + isPersonalSign: boolean // bool } export interface CFDOrder { - senderAddress: string; // address - matcherAddress: string; // address - instrumentAddress: string; // address - amount: number; // uint64 - price: number; // uint64 - matcherFee: number; // uint64 - nonce: number; // uint64 - expiration: number; // uint64 - buySide: number; // uint8, 1=buy, 0=sell - isPersonalSign: boolean; // bool + senderAddress: string // address + matcherAddress: string // address + instrumentAddress: string // address + amount: number // uint64 + price: number // uint64 + matcherFee: number // uint64 + nonce: number // uint64 + expiration: number // uint64 + buySide: 0 | 1 // uint8, 1=buy, 0=sell + isPersonalSign: boolean // bool } export interface SignedCFDOrder extends CFDOrder { - id: string; // hash of Order (it's not part of order structure in smart-contract) - signature: string; // bytes + id: string // hash of Order (it's not part of order structure in smart-contract) + signature: string // bytes } export interface SignedOrder extends Order { - id: string; // hash of Order (it's not part of order structure in smart-contract) - signature: string; // bytes - needWithdraw?: boolean; // bool (not supported yet by smart-contract) + id: string // hash of Order (it's not part of order structure in smart-contract) + signature: string // bytes + needWithdraw?: boolean // bool (not supported yet by smart-contract) } export interface CancelOrderRequest { - id: number | string; - senderAddress: string; - isPersonalSign: boolean; + id: number | string + senderAddress: string + isPersonalSign: boolean } export interface SignedCancelOrderRequest extends CancelOrderRequest { - id: number | string; - senderAddress: string; - signature: string; + id: number | string + senderAddress: string + signature: string } export interface Pair { - name: string; - baseCurrency: string; - quoteCurrency: string; - lastPrice: string; - openPrice: string; - change24h: string; - high: string; - low: string; - vol24h: string; + name: string + baseCurrency: string + quoteCurrency: string + lastPrice: string + openPrice: string + change24h: string + high: string + low: string + vol24h: string } export enum SupportedChainId { @@ -136,102 +136,102 @@ export enum SupportedChainId { const balanceTypes = ['exchange', 'wallet'] as const; export type Source = typeof balanceTypes[number]; -export type Asset = { - name: string; - address: string; +export interface Asset { + name: string + address: string } -export type BalanceRequirement = { - readonly reason: string, - readonly asset: Asset, - readonly amount: string, - readonly sources: Source[], - readonly spenderAddress?: string; +export interface BalanceRequirement { + readonly reason: string + readonly asset: Asset + readonly amount: string + readonly sources: Source[] + readonly spenderAddress?: string } -export type AggregatedBalanceRequirement = { - readonly asset: Asset, - readonly sources: Source[], - readonly spenderAddress?: string; - items: Partial>, +export interface AggregatedBalanceRequirement { + readonly asset: Asset + readonly sources: Source[] + readonly spenderAddress?: string + items: Partial> } -export type ApproveFix = { - readonly type: 'byApprove', - readonly targetAmount: BigNumber.Value, +export interface ApproveFix { + readonly type: 'byApprove' + readonly targetAmount: BigNumber.Value readonly spenderAddress: string } -export type DepositFix = { - readonly type: 'byDeposit', - readonly amount: BigNumber.Value, +export interface DepositFix { + readonly type: 'byDeposit' + readonly amount: BigNumber.Value readonly asset: string } type Fix = ApproveFix | DepositFix; -export type BalanceIssue = { - readonly asset: Asset, - readonly message: string; - readonly sources: Source[], - readonly fixes?: Fix[], +export interface BalanceIssue { + readonly asset: Asset + readonly message: string + readonly sources: Source[] + readonly fixes?: Fix[] } export type Exchange = typeof exchanges[number]; -export type OrderbookItem = { - price: string, - amount: string, - exchanges: Exchange[], - vob: { - side: 'BUY' | 'SELL', +export interface OrderbookItem { + price: string + amount: string + exchanges: Exchange[] + vob: Array<{ + side: 'BUY' | 'SELL' pairName: string - }[] + }> } -export type SwapInfoAlternative = { - exchanges: Exchange[], - path: string[], - marketAmountOut?: number, - marketAmountIn?: number, - marketPrice: number, - availableAmountIn?: number, - availableAmountOut?: number, +export interface SwapInfoAlternative { + exchanges: Exchange[] + path: string[] + marketAmountOut?: number + marketAmountIn?: number + marketPrice: number + availableAmountIn?: number + availableAmountOut?: number } -export type SwapInfoBase = { - swapRequestId: string, - assetIn: string, - assetOut: string, - amountIn: number, - amountOut: number, - minAmountIn: number, - minAmountOut: number, +export interface SwapInfoBase { + swapRequestId: string + assetIn: string + assetOut: string + amountIn: number + amountOut: number + minAmountIn: number + minAmountOut: number - path: string[], - exchanges?: Exchange[], - poolOptimal: boolean, + path: string[] + exchanges?: Exchange[] + poolOptimal: boolean - price?: number, - marketPrice?: number, + price?: number + marketPrice?: number orderInfo?: { - pair: string, - side: 'BUY' | 'SELL', - amount: number, - safePrice: number, - }, - alternatives: SwapInfoAlternative[], + pair: string + side: 'BUY' | 'SELL' + amount: number + safePrice: number + } + alternatives: SwapInfoAlternative[] } export type SwapInfoByAmountIn = SwapInfoBase & { - kind: 'exactSpend', - availableAmountIn?: number, - marketAmountOut?: number, + kind: 'exactSpend' + availableAmountIn?: number + marketAmountOut?: number } export type SwapInfoByAmountOut = SwapInfoBase & { - kind: 'exactReceive', - marketAmountIn?: number, - availableAmountOut?: number, + kind: 'exactReceive' + marketAmountIn?: number + availableAmountOut?: number } export type SwapInfo = SwapInfoByAmountIn | SwapInfoByAmountOut; @@ -243,33 +243,33 @@ export enum HistoryTransactionStatus { CANCELLED = 'Cancelled', } -export type VerboseOrionUnitConfig = { +export interface VerboseOrionUnitConfig { // env?: string; // api: string; - chainId: SupportedChainId; - nodeJsonRpc: string; + chainId: SupportedChainId + nodeJsonRpc: string services: { orionBlockchain: { - http: string; + http: string // For example: // http://localhost:3001/, // http://10.123.34.23:3001/, // https://blockchain.orionprotocol.io/ - }, + } orionAggregator: { - http: string; - ws: string; + http: string + ws: string // For example: // http://localhost:3002/, // http://10.34.23.5:3002/, // shttps://aggregator.orionprotocol.io/ - }, + } priceFeed: { - api: string; + api: string // For example: // http://localhost:3003/, // http://10.23.5.11:3003/, // https://price-feed.orionprotocol.io/ - }, + } } -}; +} diff --git a/src/utils/denormalizeNumber.ts b/src/utils/denormalizeNumber.ts index 4221598..dbdda29 100644 --- a/src/utils/denormalizeNumber.ts +++ b/src/utils/denormalizeNumber.ts @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js'; -import { ethers } from 'ethers'; +import { type ethers } from 'ethers'; /** * Converts normalized blockchain ("machine-readable") number to denormalized ("human-readable") number. @@ -9,6 +9,6 @@ import { ethers } from 'ethers'; */ export default function denormalizeNumber(input: ethers.BigNumber, decimals: BigNumber.Value) { const decimalsBN = new BigNumber(decimals); - if (!decimalsBN.isInteger()) throw new Error(`Decimals '${decimals.toString()}' is not an integer`); + if (!decimalsBN.isInteger()) throw new Error(`Decimals '${decimalsBN.toString()}' is not an integer`); return new BigNumber(input.toString()).div(new BigNumber(10).pow(decimalsBN)); } diff --git a/src/utils/getAvailableFundsSources.ts b/src/utils/getAvailableFundsSources.ts index 9077d8f..982ec42 100644 --- a/src/utils/getAvailableFundsSources.ts +++ b/src/utils/getAvailableFundsSources.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import { Source } from '../types'; +import { type Source } from '../types'; export default function getAvailableFundsSources( expenseType: 'amount' | 'network_fee' | 'orion_fee', diff --git a/src/utils/getBalance.ts b/src/utils/getBalance.ts index eb8f0fd..d34d855 100644 --- a/src/utils/getBalance.ts +++ b/src/utils/getBalance.ts @@ -1,10 +1,10 @@ -import { ERC20__factory, Exchange } from '@orionprotocol/contracts'; +import { ERC20__factory, type Exchange } from '@orionprotocol/contracts'; -import BigNumber from 'bignumber.js'; +import type BigNumber from 'bignumber.js'; import { ethers } from 'ethers'; import { utils } from '..'; import { INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION } from '../constants'; -import { OrionAggregator } from '../services/OrionAggregator'; +import { type OrionAggregator } from '../services/OrionAggregator'; export default async function getBalance( orionAggregator: OrionAggregator, diff --git a/src/utils/getBalances.ts b/src/utils/getBalances.ts index 98ddce8..0294070 100644 --- a/src/utils/getBalances.ts +++ b/src/utils/getBalances.ts @@ -1,7 +1,7 @@ -import { Exchange } from '@orionprotocol/contracts'; -import BigNumber from 'bignumber.js'; -import { ethers } from 'ethers'; -import { OrionAggregator } from '../services/OrionAggregator'; +import { type Exchange } from '@orionprotocol/contracts'; +import type BigNumber from 'bignumber.js'; +import { type ethers } from 'ethers'; +import { type OrionAggregator } from '../services/OrionAggregator'; import getBalance from './getBalance'; export default async ( @@ -14,7 +14,7 @@ export default async ( const balances = await Promise.all( Object.entries(balancesRequired) .map(async ([asset, assetAddress]) => { - if (!assetAddress) throw new Error(`Asset address of ${asset} not found`); + if (assetAddress === undefined) throw new Error(`Asset address of ${asset} not found`); const balance = await getBalance( orionAggregator, asset, @@ -31,8 +31,8 @@ export default async ( ); return balances.reduce>>((prev, curr) => ({ ...prev, [curr.asset]: curr.amount, diff --git a/src/utils/getNativeCryptocurrency.ts b/src/utils/getNativeCryptocurrency.ts index 73f2424..47a4570 100644 --- a/src/utils/getNativeCryptocurrency.ts +++ b/src/utils/getNativeCryptocurrency.ts @@ -4,15 +4,15 @@ const getNativeCryptocurrency = (assetToAddress: Partial> const addressToAsset = Object .entries(assetToAddress) .reduce>>((prev, [asset, address]) => { - if (!address) return prev; - return { - ...prev, - [address]: asset, - }; - }, {}); + if (address === undefined) return prev; + return { + ...prev, + [address]: asset, + }; + }, {}); const nativeCryptocurrency = addressToAsset[ethers.constants.AddressZero]; - if (!nativeCryptocurrency) throw new Error('Native cryptocurrency asset is not found'); + if (nativeCryptocurrency === undefined) throw new Error('Native cryptocurrency asset is not found'); return nativeCryptocurrency; }; diff --git a/src/utils/httpError.ts b/src/utils/httpError.ts index aa2de14..708fbc9 100644 --- a/src/utils/httpError.ts +++ b/src/utils/httpError.ts @@ -1,14 +1,14 @@ export default class HttpError extends Error { public code: number; - public errorMessage: string |null; + public errorMessage: string | null; public statusText: string; public type: string; - constructor(code:number, message:string|null, type: string, statusText:string) { - super(message || ''); + constructor(code: number, message: string | null, type: string, statusText: string) { + super(message ?? ''); this.errorMessage = message; this.type = type; this.statusText = statusText; diff --git a/src/utils/normalizeNumber.ts b/src/utils/normalizeNumber.ts index 4e83e32..c544d0f 100644 --- a/src/utils/normalizeNumber.ts +++ b/src/utils/normalizeNumber.ts @@ -14,7 +14,7 @@ export default function normalizeNumber( roundingMode: BigNumber.RoundingMode, ) { const decimalsBN = new BigNumber(decimals); - if (!decimalsBN.isInteger()) throw new Error(`Decimals '${decimals.toString()}' is not an integer`); + if (!decimalsBN.isInteger()) throw new Error(`Decimals '${decimalsBN.toString()}' is not an integer`); const inputBN = new BigNumber(input); return ethers.BigNumber.from( inputBN diff --git a/src/utils/typeHelpers.ts b/src/utils/typeHelpers.ts index a65cbb8..588cb33 100644 --- a/src/utils/typeHelpers.ts +++ b/src/utils/typeHelpers.ts @@ -1,21 +1,21 @@ -type WithReason = { - reason: string; +interface WithReason { + reason: string } type WithCodeError = Error & { - code: number | string; + code: number | string } -type WithMessage = { - message: string; +interface WithMessage { + message: string } type WithDataError = Error & { - data: Record; + data: Record } -type WithError ={ - error: Record; +interface WithError { + error: Record } export const makePartial = (value: Record): Partial> => value;