├── src ├── services │ ├── Frontage │ │ ├── schemas │ │ │ ├── index.ts │ │ │ └── tickers-schema.ts │ │ └── index.ts │ ├── Indexer │ │ ├── constants.ts │ │ └── schemas │ │ │ ├── list-amount-schema.ts │ │ │ ├── test-incrementor-schema.ts │ │ │ ├── list-nft-order-response-schema.ts │ │ │ ├── util-schemas.ts │ │ │ ├── list-pool-schema.ts │ │ │ ├── get-points-at-schema.ts │ │ │ ├── info-schema.ts │ │ │ ├── get-pool-response-schema.ts │ │ │ ├── environment-response-schema.ts │ │ │ ├── veORN-info-schema.ts │ │ │ ├── voting-info-schema.ts │ │ │ ├── index.ts │ │ │ ├── basic-pool-info-schema.ts │ │ │ ├── list-pool-v3-response-schema.ts │ │ │ ├── pool-schema.ts │ │ │ ├── list-pool-v2-response-schema.ts │ │ │ └── pool-v2-info-schema.ts │ ├── BlockchainService │ │ └── schemas │ │ │ ├── atomicSummarySchema.ts │ │ │ ├── userEarnedSchema.ts │ │ │ ├── userVotesSchema.ts │ │ │ ├── pmmSchema.ts │ │ │ ├── referralDataSchema.ts │ │ │ ├── adminPoolsListSchema.ts │ │ │ ├── poolsV3InfoSchema.ts │ │ │ ├── checkRedeemOrderSchema.ts │ │ │ ├── poolsLpAndStakedSchema.ts │ │ │ ├── IDOSchema.ts │ │ │ ├── pricesWithQuoteAssetSchema.ts │ │ │ ├── addPoolSchema.ts │ │ │ ├── poolsInfoSchema.ts │ │ │ ├── infoSchema.ts │ │ │ ├── adminPoolSchema.ts │ │ │ ├── historySchema.ts │ │ │ ├── poolsConfigSchema.ts │ │ │ ├── index.ts │ │ │ └── atomicHistorySchema.ts │ ├── ReferralSystem │ │ └── schemas │ │ │ ├── errorSchema.ts │ │ │ ├── linkSchema.ts │ │ │ ├── rewardsClaimedSchema.ts │ │ │ ├── contractsAddressesSchema.ts │ │ │ ├── allTimeLeadersSchema.ts │ │ │ ├── miniStatsSchema.ts │ │ │ ├── globalAnalyticsSchema.ts │ │ │ ├── inviteCodeLinkSchema.ts │ │ │ ├── aggregatedHistorySchema.ts │ │ │ ├── claimInfoSchema.ts │ │ │ ├── index.ts │ │ │ ├── distinctAnalyticsSchema.ts │ │ │ ├── rewardsMappingSchema.ts │ │ │ └── ratingSchema.ts │ ├── Aggregator │ │ ├── schemas │ │ │ ├── exchangeInfoSchema.ts │ │ │ ├── orderBenefitsSchema.ts │ │ │ ├── errorSchema.ts │ │ │ ├── redeemOrderSchema.ts │ │ │ ├── cancelOrderSchema.ts │ │ │ ├── placeAtomicSwapSchema.ts │ │ │ ├── pairConfigSchema.ts │ │ │ ├── atomicSwapHistorySchema.ts │ │ │ ├── index.ts │ │ │ ├── aggregatedOrderbookSchema.ts │ │ │ ├── swapInfoSchema.ts │ │ │ └── orderSchema.ts │ │ └── ws │ │ │ ├── UnsubscriptionType.ts │ │ │ ├── schemas │ │ │ ├── baseMessageSchema.ts │ │ │ ├── pingPongMessageSchema.ts │ │ │ ├── initMessageSchema.ts │ │ │ ├── unsubscriptionDoneSchema.ts │ │ │ ├── errorSchema.ts │ │ │ ├── balancesSchema.ts │ │ │ ├── brokerMessageSchema.ts │ │ │ ├── assetPairConfigSchema.ts │ │ │ ├── assetPairsConfigSchema.ts │ │ │ ├── index.ts │ │ │ ├── orderBookSchema.ts │ │ │ ├── swapInfoSchema.ts │ │ │ └── addressUpdateSchema.ts │ │ │ ├── SubscriptionType.ts │ │ │ └── MessageType.ts │ ├── PriceFeed │ │ ├── schemas │ │ │ ├── index.ts │ │ │ ├── allTickersSchema.ts │ │ │ ├── candlesSchema.ts │ │ │ └── statisticsSchema.ts │ │ ├── ws │ │ │ ├── priceFeedSubscriptions.ts │ │ │ ├── schemas │ │ │ │ ├── priceSchema.ts │ │ │ │ ├── index.ts │ │ │ │ ├── tickerInfoSchema.ts │ │ │ │ ├── allTickersSchema.ts │ │ │ │ ├── candleSchema.ts │ │ │ │ └── cexPricesSchema.ts │ │ │ ├── index.ts │ │ │ └── PriceFeedSubscription.ts │ │ └── index.ts │ └── index.ts ├── constants │ ├── factories.ts │ ├── timings.ts │ ├── precisions.ts │ ├── executionTypes.ts │ ├── networkCodes.ts │ ├── uppercasedNetworkCodes.ts │ ├── positionStatuses.ts │ ├── cancelOrderTypes.ts │ ├── orderStatuses.ts │ ├── exchanges.ts │ ├── orderTypes.ts │ ├── subOrderStatuses.ts │ ├── index.ts │ ├── chains.ts │ ├── exchangesMap.ts │ └── gasLimits.ts ├── crypt │ ├── index.ts │ ├── hashOrder.ts │ ├── getDomainData.ts │ ├── signCancelOrder.ts │ └── signOrder.ts ├── config │ ├── eip712DomainData.json │ ├── schemas │ │ ├── index.ts │ │ ├── eip712DomainSchema.ts │ │ ├── pureChainSchema.ts │ │ └── pureEnvSchema.ts │ └── index.ts ├── utils │ ├── arrayEquals.ts │ ├── toLowerCase.ts │ ├── toUpperCase.ts │ ├── isValidFactory.ts │ ├── isKnownEnv.ts │ ├── assertError.ts │ ├── addressLikeToString.ts │ ├── isValidChainId.ts │ ├── isUppercasedNetworkCode.ts │ ├── removeFieldsFromObject.ts │ ├── httpError.ts │ ├── invariant.ts │ ├── httpToWS.ts │ ├── laconic-parse.ts │ ├── calculateNetworkFee.ts │ ├── calculateServiceFeeInFeeAsset.ts │ ├── denormalizeNumber.ts │ ├── isNetworkCodeInEnvironment.ts │ ├── calculateNetworkFeeInFeeAsset.ts │ ├── getNativeCryptocurrencyName.ts │ ├── getValidArrayItems.ts │ ├── checkIsToken.ts │ ├── normalizeNumber.ts │ ├── getAvailableFundsSources.ts │ ├── objectKeys.ts │ ├── convertPrice.ts │ ├── getBalances.ts │ ├── generateSecret.ts │ ├── index.ts │ ├── calculateFeeInFeeAsset.ts │ ├── safeGetters.ts │ ├── typeHelpers.ts │ └── parseExchangeTradeTransaction.ts ├── addressSchema.ts ├── index.ts ├── Unit │ ├── Pmm │ │ ├── schemas │ │ │ └── order.ts │ │ ├── abi │ │ │ └── OrionRFQ.ts │ │ └── index.ts │ ├── Exchange │ │ ├── callGenerators │ │ │ ├── feePayment.ts │ │ │ ├── weth.ts │ │ │ ├── erc20.ts │ │ │ ├── aero.ts │ │ │ ├── curve.ts │ │ │ ├── uniswapV2.ts │ │ │ ├── uniswapV3.ts │ │ │ └── utils.ts │ │ ├── index.ts │ │ ├── withdraw.ts │ │ ├── deposit.ts │ │ └── getSwapInfo.ts │ └── index.ts └── __tests__ │ ├── bridge.test.ts │ ├── exchangeContract.test.ts │ ├── pools.test.ts │ ├── priceFeed.test.ts │ └── trading.test.ts ├── .husky └── pre-commit ├── .vscode └── settings.json ├── patches └── unfetch+5.0.0.patch ├── .github ├── dependabot.yml └── workflows │ ├── release-package.yml │ └── prerelease-package.yml ├── tsup.config.ts ├── jest.config.js ├── tsconfig.json ├── ADVANCED.md ├── .gitignore ├── package.json └── .eslintrc.json /src/services/Frontage/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tickers-schema'; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # npm test 5 | -------------------------------------------------------------------------------- /src/services/Indexer/constants.ts: -------------------------------------------------------------------------------- 1 | export const LOCK_START_TIME = 1690848000;// Aug 01 2023 00:00:00 UTC 2 | -------------------------------------------------------------------------------- /src/constants/factories.ts: -------------------------------------------------------------------------------- 1 | export default ["UniswapV2", "UniswapV3", "Curve", "OrionV2", "OrionV3", "Aero"] as const 2 | -------------------------------------------------------------------------------- /src/constants/timings.ts: -------------------------------------------------------------------------------- 1 | export const DAY = 86400 2 | export const WEEK_DAYS = 7; 3 | export const YEAR = 365 * DAY 4 | -------------------------------------------------------------------------------- /src/constants/precisions.ts: -------------------------------------------------------------------------------- 1 | export const INTERNAL_PROTOCOL_PRECISION = 8; 2 | export const NATIVE_CURRENCY_PRECISION = 18; 3 | -------------------------------------------------------------------------------- /src/constants/executionTypes.ts: -------------------------------------------------------------------------------- 1 | const executionTypes = ['LIMIT', 'STOP_LIMIT'] as const; 2 | 3 | export default executionTypes; 4 | -------------------------------------------------------------------------------- /src/crypt/index.ts: -------------------------------------------------------------------------------- 1 | export { default as signCancelOrder } from './signCancelOrder.js'; 2 | export { default as signOrder } from './signOrder.js'; 3 | -------------------------------------------------------------------------------- /src/constants/networkCodes.ts: -------------------------------------------------------------------------------- 1 | export default ['ftm', 'bsc', 'eth', 'polygon', 'okc', 'arb', 'opbnb', 'inevm', 'linea', 'avax', 'base', 'lumia'] as const; 2 | -------------------------------------------------------------------------------- /src/constants/uppercasedNetworkCodes.ts: -------------------------------------------------------------------------------- 1 | export default ['FTM', 'BSC', 'ETH', 'POLYGON', 'OKC', 'ARB', 'OPBNB', 'INEVM', 'LINEA', 'AVAX', 'BASE', 'LUMIA'] as const; 2 | -------------------------------------------------------------------------------- /src/config/eip712DomainData.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Orion Exchange", 3 | "version": "1", 4 | "salt": "0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a557" 5 | } -------------------------------------------------------------------------------- /src/config/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export { default as eip712DomainSchema } from './eip712DomainSchema.js'; 2 | export * from './pureEnvSchema.js'; 3 | export * from './pureChainSchema.js'; 4 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/atomicSummarySchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export default z.object({ 4 | amount: z.number(), 5 | count: z.number(), 6 | }); 7 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/userEarnedSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const userEarnedSchema = z.record( 4 | z.string(), 5 | ); 6 | 7 | export default userEarnedSchema; 8 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/userVotesSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const userVotesSchema = z.record( 4 | z.string(), 5 | ); 6 | 7 | export default userVotesSchema; 8 | -------------------------------------------------------------------------------- /src/utils/arrayEquals.ts: -------------------------------------------------------------------------------- 1 | const arrayEquals = (a: unknown[], b: unknown[]) => a.length === b.length && 2 | a.every((value, index) => value === b[index]); 3 | 4 | export default arrayEquals; 5 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/pmmSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const pmmSchema = z.object({ 4 | orionPMMRouterContractAddress: z.string() 5 | }); 6 | 7 | export default pmmSchema -------------------------------------------------------------------------------- /src/constants/positionStatuses.ts: -------------------------------------------------------------------------------- 1 | const positionStatuses = [ 2 | 'SHORT', 3 | 'LONG', 4 | 'CLOSING', 5 | 'LIQUIDATION', 6 | 'ZERO', 7 | ] as const; 8 | 9 | export default positionStatuses; 10 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/errorSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const errorSchema = z.object({ 4 | status: z.string(), 5 | message: z.string(), 6 | }); 7 | 8 | export default errorSchema; 9 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/linkSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const linkSchema = z.object({ 4 | status: z.string(), 5 | referer: z.string(), 6 | }); 7 | 8 | export default linkSchema; 9 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/referralDataSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const referralDataSchema = z.object({ 4 | referer: z.string().nullable(), 5 | isReferral: z.boolean(), 6 | }); 7 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/adminPoolsListSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import { poolOnVerificationSchema } from './adminPoolSchema.js'; 4 | 5 | export default z.array(poolOnVerificationSchema); 6 | -------------------------------------------------------------------------------- /src/constants/cancelOrderTypes.ts: -------------------------------------------------------------------------------- 1 | const CANCEL_ORDER_TYPES = { 2 | DeleteOrder: [ 3 | { name: 'senderAddress', type: 'address' }, 4 | { name: 'id', type: 'string' }, 5 | ], 6 | }; 7 | 8 | export default CANCEL_ORDER_TYPES; 9 | -------------------------------------------------------------------------------- /src/utils/toLowerCase.ts: -------------------------------------------------------------------------------- 1 | export default function toLowerCase(str: T): Lowercase { 2 | // eslint-disable-next-line @typescript-eslint/consistent-type-assertions 3 | return str.toLowerCase() as Lowercase; 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/toUpperCase.ts: -------------------------------------------------------------------------------- 1 | export default function toUpperCase(str: T): Uppercase { 2 | // eslint-disable-next-line @typescript-eslint/consistent-type-assertions 3 | return str.toUpperCase() as Uppercase; 4 | } 5 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/exchangeInfoSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import pairConfigSchema from './pairConfigSchema.js'; 3 | 4 | const exchangeInfoSchema = z.array(pairConfigSchema); 5 | 6 | export default exchangeInfoSchema; 7 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/orderBenefitsSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const orderBenefitsSchema = z.record(z.object({ 4 | benefitBtc: z.string(), 5 | benefitPct: z.string(), 6 | })); 7 | 8 | export default orderBenefitsSchema; 9 | -------------------------------------------------------------------------------- /src/addressSchema.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import { z } from 'zod'; 3 | 4 | const addressSchema = z.string().refine(ethers.isAddress, (value) => ({ 5 | message: `Should be an address, got ${value}`, 6 | })); 7 | 8 | export default addressSchema; 9 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/UnsubscriptionType.ts: -------------------------------------------------------------------------------- 1 | const UnsubscriptionType = { 2 | ASSET_PAIRS_CONFIG_UPDATES_UNSUBSCRIBE: 'apcu', 3 | BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_UNSUBSCRIBE: 'btasabu', 4 | } as const; 5 | export default UnsubscriptionType; 6 | -------------------------------------------------------------------------------- /src/services/PriceFeed/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export { default as candlesSchema } from './candlesSchema.js'; 2 | export { 3 | statisticsOverviewSchema, 4 | topPairsStatisticsSchema, 5 | } from './statisticsSchema.js'; 6 | export { allTickersSchema } from './allTickersSchema.js'; 7 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/rewardsClaimedSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const rewardsClaimedSchema = z.object({ 4 | referer: z.string(), 5 | amount: z.string(), 6 | signature: z.string(), 7 | }); 8 | 9 | export default rewardsClaimedSchema; 10 | -------------------------------------------------------------------------------- /src/constants/orderStatuses.ts: -------------------------------------------------------------------------------- 1 | import subOrderStatuses from './subOrderStatuses.js'; 2 | 3 | const orderStatuses = [ 4 | ...subOrderStatuses, 5 | 'ROUTING', // order got sub orders, but not all of them have status ACCEPTED 6 | ] as const; 7 | 8 | export default orderStatuses; 9 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/errorSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const errorSchema = z.object({ 4 | error: z.object({ 5 | code: z.number(), 6 | reason: z.string(), 7 | }), 8 | timestamp: z.string(), 9 | }); 10 | 11 | export default errorSchema; 12 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/list-amount-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import infoSchema from './info-schema.js'; 3 | 4 | const listAmountSchema = z.object({ 5 | result: z.record(z.number()), 6 | info: infoSchema, 7 | }); 8 | 9 | export default listAmountSchema; 10 | -------------------------------------------------------------------------------- /src/utils/isValidFactory.ts: -------------------------------------------------------------------------------- 1 | import { type Factory } from '../types.js'; 2 | import { factories } from '../index.js'; 3 | 4 | const isValidFactory = (factory: string): factory is Factory => { 5 | return factories.some((f) => f === factory); 6 | }; 7 | 8 | export default isValidFactory; 9 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/baseMessageSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | 4 | const baseMessageSchema = z.object({ 5 | T: z.nativeEnum(MessageType), 6 | _: z.number(), 7 | }); 8 | 9 | export default baseMessageSchema; 10 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/poolsV3InfoSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const poolsV3InfoSchema = z.object({ 4 | OrionV3Factory: z.string(), 5 | OrionV3Pool: z.string(), 6 | OrionV3NFTManager: z.string(), 7 | }); 8 | 9 | export default poolsV3InfoSchema; 10 | -------------------------------------------------------------------------------- /src/services/PriceFeed/ws/priceFeedSubscriptions.ts: -------------------------------------------------------------------------------- 1 | const priceFeedSubscriptions = { 2 | TICKER: 'ticker', 3 | ALL_TICKERS: 'allTickers', 4 | LAST_PRICE: 'lastPrice', 5 | CANDLE: 'candle', 6 | CEX: 'cexPrices' 7 | } as const; 8 | 9 | export default priceFeedSubscriptions; 10 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/test-incrementor-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import infoSchema from './info-schema.js'; 3 | 4 | const testIncrementorSchema = z.object({ 5 | result: z.number().int(), 6 | info: infoSchema, 7 | }); 8 | 9 | export default testIncrementorSchema; 10 | -------------------------------------------------------------------------------- /src/utils/isKnownEnv.ts: -------------------------------------------------------------------------------- 1 | import { knownEnvs } from '../config/schemas/index.js'; 2 | import type { KnownEnv, } from '../types.js'; 3 | 4 | const isKnownEnv = (env: string): env is KnownEnv => { 5 | return knownEnvs.some((knownEnv) => knownEnv === env); 6 | } 7 | 8 | export default isKnownEnv; 9 | -------------------------------------------------------------------------------- /src/utils/assertError.ts: -------------------------------------------------------------------------------- 1 | 2 | export default function assertError(errorCandidate: unknown): asserts errorCandidate is Error { 3 | if (!(errorCandidate instanceof Error)) { 4 | throw Error(`Assertion failed: errorCandidate is not an Error. Content: ${JSON.stringify(errorCandidate)}`); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/checkRedeemOrderSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const checkRedeemOrderSchema = z.object({ 4 | redeemTxHash: z.string(), 5 | secret: z.string().nullable(), 6 | secretHash: z.string(), 7 | }); 8 | 9 | export default checkRedeemOrderSchema; 10 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/poolsLpAndStakedSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const poolsLpAndStakedSchema = z.record( 4 | z.object({ 5 | unstakedLPBalance: z.string(), 6 | stakedLPBalance: z.string(), 7 | }), 8 | ); 9 | 10 | export default poolsLpAndStakedSchema; 11 | -------------------------------------------------------------------------------- /src/services/PriceFeed/schemas/allTickersSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | 3 | const tickerSchema = z.object({ 4 | pair: z.string(), 5 | volume24: z.number(), 6 | change24: z.number(), 7 | lastPrice: z.number(), 8 | }); 9 | 10 | export const allTickersSchema = tickerSchema.array(); 11 | -------------------------------------------------------------------------------- /src/services/PriceFeed/ws/schemas/priceSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const priceSchema = z.tuple([ 4 | z.number(), // unix timestamp 5 | z.string(), // pair 6 | z.number(), // price 7 | ]).transform(([, pair, price]) => ({ pair, price })); 8 | 9 | export default priceSchema; 10 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/IDOSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const IDOSchema = z.object({ 4 | amount: z.number().or(z.null()), 5 | amountInWei: z.number().or(z.null()), 6 | amountInUSDT: z.number().or(z.null()), 7 | address: z.string(), 8 | }); 9 | 10 | export default IDOSchema; 11 | -------------------------------------------------------------------------------- /src/utils/addressLikeToString.ts: -------------------------------------------------------------------------------- 1 | import type { AddressLike } from "ethers"; 2 | 3 | export async function addressLikeToString(address: AddressLike): Promise { 4 | address = await address 5 | if (typeof address !== 'string') { 6 | address = await address.getAddress() 7 | } 8 | return address.toLowerCase() 9 | } -------------------------------------------------------------------------------- /src/utils/isValidChainId.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { SupportedChainId } from '../types.js'; 3 | 4 | const isValidChainId = (chainId: string): chainId is SupportedChainId => { 5 | const { success } = z.nativeEnum(SupportedChainId).safeParse(chainId); 6 | return success; 7 | }; 8 | 9 | export default isValidChainId; 10 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/pricesWithQuoteAssetSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { makePartial } from '../../../utils/index.js'; 3 | 4 | export const pricesWithQuoteAssetSchema = z.object({ 5 | quoteAsset: z.string(), 6 | quoteAssetAddress: z.string(), 7 | prices: z.record(z.string()).transform(makePartial) 8 | }); 9 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/pingPongMessageSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | import baseMessageSchema from './baseMessageSchema.js'; 4 | 5 | const pingPongMessageSchema = baseMessageSchema.extend({ 6 | T: z.literal(MessageType.PING_PONG), 7 | }); 8 | 9 | export default pingPongMessageSchema; 10 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/list-nft-order-response-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import poolSchema from './pool-schema.js'; 3 | import infoSchema from './info-schema.js'; 4 | 5 | const listNFTOrderResponseSchema = z.object({ 6 | result: z.array(poolSchema), 7 | info: infoSchema, 8 | }); 9 | 10 | export default listNFTOrderResponseSchema; 11 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * as aggregator from './Aggregator/index.js'; 2 | export * as blockchainService from './BlockchainService/index.js'; 3 | export * as priceFeed from './PriceFeed/index.js'; 4 | export * as referralSystem from './ReferralSystem/index.js'; 5 | export * as indexer from './Indexer/index.js'; 6 | export * as frontage from './Frontage/index.js'; 7 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/addPoolSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const addPoolSchema = z.object({ 4 | poolAddress: z.string(), 5 | tokenAIcon: z.string().optional(), 6 | tokenAName: z.string().optional(), 7 | tokenBIcon: z.string().optional(), 8 | tokenBName: z.string().optional(), 9 | }); 10 | 11 | export default addPoolSchema; 12 | -------------------------------------------------------------------------------- /src/utils/isUppercasedNetworkCode.ts: -------------------------------------------------------------------------------- 1 | import { networkCodes } from '../constants/index.js'; 2 | import toUpperCase from './toUpperCase.js'; 3 | 4 | const isUppercasedNetworkCode = (value: string): value is Uppercase => networkCodes 5 | .map(toUpperCase).some((networkCode) => networkCode === value); 6 | 7 | export default isUppercasedNetworkCode; 8 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/initMessageSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | import baseMessageSchema from './baseMessageSchema.js'; 4 | 5 | const initMessageSchema = baseMessageSchema.extend({ 6 | T: z.literal(MessageType.INITIALIZATION), 7 | i: z.string(), 8 | }); 9 | 10 | export default initMessageSchema; 11 | -------------------------------------------------------------------------------- /src/services/PriceFeed/ws/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export { default as tickerInfoSchema } from './tickerInfoSchema.js'; 2 | export { default as candleSchema } from './candleSchema.js'; 3 | export { default as priceSchema } from './priceSchema.js'; 4 | export { default as allTickersSchema } from './allTickersSchema.js'; 5 | export { default as cexPricesSchema } from './cexPricesSchema.js'; 6 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/contractsAddressesSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { SupportedChainId } from '../../../types.js'; 3 | import { ethers } from 'ethers'; 4 | 5 | const contractsAddressesSchema = z.record( 6 | z.nativeEnum(SupportedChainId), 7 | z.string().refine(ethers.isAddress) 8 | ); 9 | 10 | export default contractsAddressesSchema; 11 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/allTimeLeadersSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const allTimeLeadersSchema = z.array(z.object({ 4 | wallet: z.string(), 5 | total_earnings_fmt: z.number(), 6 | referrals_count_fmt: z.number(), 7 | total_trades_fmt: z.number(), 8 | weekly_earnings_fmt: z.number(), 9 | })); 10 | 11 | export default allTimeLeadersSchema; 12 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/unsubscriptionDoneSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | import baseMessageSchema from './baseMessageSchema.js'; 4 | 5 | const unsubscriptionDoneSchema = baseMessageSchema.extend({ 6 | id: z.string(), 7 | T: z.literal(MessageType.UNSUBSCRIPTION_DONE), 8 | }); 9 | 10 | export default unsubscriptionDoneSchema; 11 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/miniStatsSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const miniStatsSchema = z.object({ 4 | earned_on_referrals_token: z.number(), 5 | earned_on_referrals_usd: z.number(), 6 | token_usd: z.number(), 7 | registered_via_link_count: z.number(), 8 | earned_in_a_week_token: z.number(), 9 | earned_in_a_week_usd: z.number(), 10 | }); 11 | 12 | export default miniStatsSchema; 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "ASCENDEX", 4 | "bignumber", 5 | "denormalized", 6 | "KUCOIN", 7 | "orderbook", 8 | "orionprotocol" 9 | ], 10 | "markdown.extension.toc.omittedFromToc": { 11 | "./README.md": [ 12 | "# Orion Protocol SDK" 13 | ] 14 | }, 15 | "typescript.tsdk": "node_modules/typescript/lib" 16 | } -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/redeemOrderSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const redeemOrderSchema = z.object({ 4 | asset: z.string().toUpperCase(), 5 | amount: z.number(), 6 | secretHash: z.string(), 7 | sender: z.string(), 8 | receiver: z.string(), 9 | expiration: z.number(), 10 | signature: z.string(), 11 | claimReceiver: z.string(), 12 | }); 13 | 14 | export default redeemOrderSchema; 15 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/cancelOrderSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const cancelOrderSchema = z.object({ 4 | orderId: z.union([z.number(), z.string()]), 5 | cancellationRequests: z.array(z.object({ 6 | amount: z.number(), 7 | brokerAddress: z.string(), 8 | exchange: z.string(), 9 | })).optional(), 10 | remainingAmount: z.number().optional(), 11 | }); 12 | 13 | export default cancelOrderSchema; 14 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/util-schemas.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import { z } from 'zod'; 3 | 4 | export const evmAddressSchema = z 5 | .string() 6 | .refine(ethers.isAddress, (v) => ({ 7 | message: `${v} is not a valid address`, 8 | })); 9 | 10 | export const hexStringSchema = z 11 | .string() 12 | .refine(ethers.isHexString, (v) => ({ 13 | message: `${v} is not a valid hex string`, 14 | })); 15 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/SubscriptionType.ts: -------------------------------------------------------------------------------- 1 | const SubscriptionType = { 2 | ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE: 'apcus', 3 | ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE: 'apius', 4 | AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE: 'aobus', 5 | ADDRESS_UPDATES_SUBSCRIBE: 'aus', 6 | BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE: 'btasabus', 7 | SWAP_SUBSCRIBE: 'ss', 8 | } as const; 9 | 10 | export default SubscriptionType; 11 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import jsonChains from './chains.json' assert { type: 'json' }; 2 | import jsonEnvs from './envs.json' assert { type: 'json' }; 3 | import { pureEnvSchema, pureChainInfoSchema } from './schemas/index.js'; 4 | 5 | const chains = pureChainInfoSchema.parse(jsonChains); 6 | const envs = pureEnvSchema.parse(jsonEnvs); 7 | 8 | export { 9 | chains, 10 | envs, 11 | }; 12 | 13 | export * as schemas from './schemas/index.js'; 14 | -------------------------------------------------------------------------------- /src/services/Frontage/schemas/tickers-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { SupportedChainId } from '../../../types'; 3 | 4 | export const tickerSchema = z.object({ 5 | pair: z.string(), 6 | volume24: z.number(), 7 | change24: z.number(), 8 | lastPrice: z.number(), 9 | pricePrecision: z.number(), 10 | networks: z.array(z.nativeEnum(SupportedChainId)), 11 | }); 12 | 13 | export const tickersSchema = z.array(tickerSchema); 14 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/globalAnalyticsSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const globalAnalyticsSchema = z.object({ 4 | ref_to_rewards: z.record(z.string(), z.number()), 5 | total_earned_by_refs: z.number(), 6 | total_sent_to_governance: z.number(), 7 | reward_dist_count_in_general: z.record(z.string(), z.number()), 8 | total_ref_system_actors: z.number(), 9 | }); 10 | 11 | export default globalAnalyticsSchema; 12 | -------------------------------------------------------------------------------- /src/config/schemas/eip712DomainSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const eip712DomainSchema = z.object({ 4 | name: z.string(), 5 | version: z.string(), 6 | chainId: z.string(), 7 | verifyingContract: z.string(), 8 | salt: z.string(), 9 | }) 10 | .partial() 11 | .refine( 12 | (data) => Object.keys(data).length > 0, 13 | 'At least one property should be filled in.', 14 | ); 15 | 16 | export default eip712DomainSchema; 17 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/errorSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | import baseMessageSchema from './baseMessageSchema.js'; 4 | 5 | const errorSchema = baseMessageSchema.extend({ 6 | T: z.literal(MessageType.ERROR), 7 | c: z.number().int(), // code 8 | id: z.string().optional(), // subscription id 9 | m: z.string(), // error message, 10 | }); 11 | 12 | export default errorSchema; 13 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/list-pool-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import infoSchema from './info-schema'; 3 | import { listPoolV2Schema } from './list-pool-v2-response-schema'; 4 | import { listPoolV3Schema } from './list-pool-v3-response-schema'; 5 | 6 | const listPoolResponseSchema = z.object({ 7 | result: z.array(listPoolV2Schema.or(listPoolV3Schema)), 8 | info: infoSchema, 9 | }); 10 | 11 | export default listPoolResponseSchema; 12 | -------------------------------------------------------------------------------- /src/utils/removeFieldsFromObject.ts: -------------------------------------------------------------------------------- 1 | const removeFieldsFromObject = < 2 | T extends Record, 3 | K extends keyof T 4 | >( 5 | obj: T, 6 | fields: K[] 7 | ): Omit => { 8 | const result = { ...obj }; 9 | for (const field of fields) { 10 | // eslint-disable-next-line @typescript-eslint/no-dynamic-delete 11 | delete result[field]; 12 | } 13 | return result; 14 | }; 15 | 16 | export default removeFieldsFromObject; 17 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/MessageType.ts: -------------------------------------------------------------------------------- 1 | const MessageType = { 2 | ERROR: 'e', 3 | PING_PONG: 'pp', 4 | SWAP_INFO: 'si', 5 | INITIALIZATION: 'i', 6 | AGGREGATED_ORDER_BOOK_UPDATE: 'aobu', 7 | ASSET_PAIRS_CONFIG_UPDATE: 'apcu', 8 | ASSET_PAIR_CONFIG_UPDATE: 'apiu', 9 | ADDRESS_UPDATE: 'au', 10 | BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATE: 'btasabu', 11 | UNSUBSCRIPTION_DONE: 'ud', 12 | } as const; 13 | 14 | export default MessageType; 15 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/get-points-at-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import infoSchema from './info-schema.js'; 3 | 4 | const getPointsAtResultSchema = z.object({ 5 | pointsObject: z.record(z.string(), z.number()), 6 | currentPage: z.number(), 7 | totalElements: z.number(), 8 | }); 9 | 10 | const getPointsAtSchema = z.object({ 11 | result: getPointsAtResultSchema, 12 | info: infoSchema, 13 | }).nullable(); 14 | 15 | export default getPointsAtSchema; 16 | -------------------------------------------------------------------------------- /src/services/PriceFeed/schemas/candlesSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const candlesSchema = z.object({ 4 | candles: z.array(z.object({ 5 | close: z.string(), 6 | high: z.string(), 7 | low: z.string(), 8 | open: z.string(), 9 | time: z.number(), 10 | timeEnd: z.number(), 11 | timeStart: z.number(), 12 | volume: z.string(), 13 | })), 14 | timeStart: z.number(), 15 | timeEnd: z.number(), 16 | }); 17 | 18 | export default candlesSchema; 19 | -------------------------------------------------------------------------------- /src/utils/httpError.ts: -------------------------------------------------------------------------------- 1 | export default class HttpError extends Error { 2 | public code: number; 3 | 4 | public errorMessage: string | null; 5 | 6 | public statusText: string; 7 | 8 | public type: string; 9 | 10 | constructor(code: number, message: string | null, type: string, statusText: string) { 11 | super(message ?? ''); 12 | this.errorMessage = message; 13 | this.type = type; 14 | this.statusText = statusText; 15 | this.code = code; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/invariant.ts: -------------------------------------------------------------------------------- 1 | export function invariant( 2 | condition: T, 3 | errorMessage?: ((condition: T) => string) | string, 4 | ): asserts condition { 5 | if (condition) { 6 | return; 7 | } 8 | 9 | if (typeof errorMessage === 'undefined') { 10 | throw new Error('Invariant failed'); 11 | } 12 | 13 | if (typeof errorMessage === 'string') { 14 | throw new Error(errorMessage); 15 | } 16 | 17 | throw new Error(errorMessage(condition)); 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/httpToWS.ts: -------------------------------------------------------------------------------- 1 | const httpToWS = (url: string) => { 2 | let parsedUrl: URL; 3 | try { 4 | parsedUrl = new URL(url); 5 | } catch (e) { 6 | console.error(`httpToWS: Invalid URL ${url}`); 7 | throw e; 8 | } 9 | if (parsedUrl.protocol === 'https:') { 10 | parsedUrl.protocol = 'wss:'; 11 | } else if (parsedUrl.protocol === 'http:') { 12 | parsedUrl.protocol = 'ws:'; 13 | } 14 | return parsedUrl.toString(); 15 | }; 16 | 17 | export default httpToWS; 18 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/balancesSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { makePartial } from '../../../../utils/index.js'; 3 | 4 | const balancesSchema = z.record( // changed balances in format 5 | z.string().toUpperCase(), // asset 6 | z.tuple([ 7 | z.string(), // tradable balance 8 | z.string(), // reserved balance 9 | z.string(), // contract balance 10 | z.string(), // wallet balance 11 | z.string(), // allowance 12 | ]), 13 | ).transform(makePartial); 14 | export default balancesSchema; 15 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/brokerMessageSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | import baseMessageSchema from './baseMessageSchema.js'; 4 | 5 | const brokerMessageSchema = baseMessageSchema.extend({ 6 | T: z.literal(MessageType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATE), 7 | bb: z.array( 8 | z.tuple([ 9 | z.string().toUpperCase(), // Asset name 10 | z.number(), // limit 11 | ]), 12 | ), 13 | }); 14 | 15 | export default brokerMessageSchema; 16 | -------------------------------------------------------------------------------- /src/services/PriceFeed/schemas/statisticsSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const statisticsOverview = z.object({ 4 | volume24h: z.number(), 5 | volume7d: z.number(), 6 | }); 7 | 8 | export const statisticsOverviewSchema = z.object({ 9 | time: z.number(), 10 | statisticsOverview, 11 | }); 12 | 13 | export const topPairsStatisticsSchema = z.object({ 14 | time: z.number(), 15 | topPairs: z.array( 16 | z.object({ 17 | assetPair: z.string(), 18 | statisticsOverview, 19 | }), 20 | ), 21 | }); 22 | -------------------------------------------------------------------------------- /src/utils/laconic-parse.ts: -------------------------------------------------------------------------------- 1 | import type { ZodTypeDef, Schema } from 'zod'; 2 | 3 | const laconicParse = (data: DataIn, schema: Schema) => { 4 | const payload = schema.safeParse(data); 5 | if (!payload.success) { 6 | const issuesMessages = payload.error.issues.map(issue => `[${issue.path.join('.')}] ${issue.message}`).join(', '); 7 | throw new Error(`Can't recognize payload: ${issuesMessages}`); 8 | } 9 | return payload.data; 10 | } 11 | 12 | export default laconicParse; 13 | -------------------------------------------------------------------------------- /patches/unfetch+5.0.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/unfetch/package.json b/node_modules/unfetch/package.json 2 | index ec47dc2..33954f7 100644 3 | --- a/node_modules/unfetch/package.json 4 | +++ b/node_modules/unfetch/package.json 5 | @@ -15,8 +15,8 @@ 6 | }, 7 | "exports": { 8 | ".": { 9 | - "import": "./index.mjs", 10 | - "default": "./index.js" 11 | + "import": "./dist/unfetch.mjs", 12 | + "default": "./dist/unfetch.js" 13 | }, 14 | "./polyfill": { 15 | "default": "./polyfill/index.js" 16 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/placeAtomicSwapSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const placeAtomicSwapSchema = z.object({ 4 | redeemOrder: z.object({ 5 | amount: z.number(), 6 | asset: z.string().toUpperCase(), 7 | expiration: z.number(), 8 | receiver: z.string(), 9 | secretHash: z.string(), 10 | sender: z.string(), 11 | signature: z.string(), 12 | claimReceiver: z.string(), 13 | }), 14 | secretHash: z.string(), 15 | sender: z.string(), 16 | }); 17 | 18 | export default placeAtomicSwapSchema; 19 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/info-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { ethers } from 'ethers'; 3 | 4 | const infoSchema = z.object({ 5 | blockNumber: z.number().int().nonnegative(), 6 | blockHash: z.string().refine((v) => v.length === 0 || ethers.isHexString(v), { 7 | message: 'blockHash must be a valid hex string or empty', 8 | }), 9 | timeRequest: z.number().int().nonnegative(), 10 | timeAnswer: z.number().int().nonnegative(), 11 | changes: z.number().int().nonnegative(), 12 | }); 13 | 14 | export default infoSchema; 15 | -------------------------------------------------------------------------------- /src/services/PriceFeed/ws/schemas/tickerInfoSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const tickerInfoSchema = z.tuple([ 4 | z.string(), // pair name 5 | z.string(), // lastPrice 6 | z.string(), // openPrice 7 | z.string(), // high price 8 | z.string(), // low price 9 | z.string(), // volume 24h 10 | ]).transform(([pairName, lastPrice, openPrice, highPrice, lowPrice, volume24h]) => ({ 11 | pairName, 12 | lastPrice, 13 | openPrice, 14 | highPrice, 15 | lowPrice, 16 | volume24h, 17 | })); 18 | 19 | export default tickerInfoSchema; 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/assetPairConfigSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | import baseMessageSchema from './baseMessageSchema.js'; 4 | 5 | const assetPairConfigSchema = baseMessageSchema.extend({ 6 | id: z.string(), 7 | T: z.literal(MessageType.ASSET_PAIR_CONFIG_UPDATE), 8 | k: z.enum(['i', 'u']), 9 | u: z.tuple([ 10 | z.string().toUpperCase(), // pairName 11 | z.number(), // minQty 12 | z.number().int(), // pricePrecision 13 | ]), 14 | }); 15 | 16 | export default assetPairConfigSchema; 17 | -------------------------------------------------------------------------------- /src/utils/calculateNetworkFee.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { ethers } from 'ethers'; 3 | import { NATIVE_CURRENCY_PRECISION } from '../constants/precisions.js'; 4 | 5 | export default function calculateNetworkFee( 6 | gasPriceGwei: BigNumber.Value, 7 | gasLimit: BigNumber.Value, 8 | ) { 9 | const networkFeeGwei = new BigNumber(gasPriceGwei).multipliedBy(gasLimit); 10 | 11 | const bn = new BigNumber(ethers.parseUnits(networkFeeGwei.toString(), 'gwei').toString()); 12 | return bn.div(new BigNumber(10).pow(NATIVE_CURRENCY_PRECISION)).toString(); 13 | } 14 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/inviteCodeLinkSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const inviteCodeLinkSchema = z.object({ 4 | link: z 5 | .object({ 6 | referer: z.string(), 7 | ref_link: z.string(), 8 | }) 9 | .nullable(), 10 | invite: z 11 | .object({ 12 | code: z.string(), 13 | data: z.null(), 14 | limits: z.object({ 15 | tag: z.string(), 16 | max_invites: z.number(), 17 | max_ref_depth: z.number(), 18 | }), 19 | }) 20 | .nullable(), 21 | }); 22 | 23 | export default inviteCodeLinkSchema; 24 | -------------------------------------------------------------------------------- /src/constants/exchanges.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | // CEXes 3 | 'ASCENDEX', 4 | 'OKX', 5 | 'BINANCE', 6 | 'KUCOIN', 7 | 'ORION', // Internal 8 | 'INTERNAL_ORDER_BOOK', 9 | 10 | // DEXes 11 | 'SPOOKYSWAP', 12 | 'PANCAKESWAP', 13 | 'UNISWAP', 14 | 'QUICKSWAP', 15 | 'ORION_POOL', 16 | 'INTERNAL_POOL_V2', 17 | 'INTERNAL_POOL_V3', 18 | 'INTERNAL_POOL_V3_0_01', 19 | 'INTERNAL_POOL_V3_0_05', 20 | 'INTERNAL_POOL_V3_0_3', 21 | 'INTERNAL_POOL_V3_1_0', 22 | 'CHERRYSWAP', 23 | 'OKXSWAP', 24 | 'CURVE', 25 | 'CURVE_FACTORY', 26 | 'THENA_ALGEBRA_V1', 27 | ] as const; 28 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/assetPairsConfigSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | import baseMessageSchema from './baseMessageSchema.js'; 4 | 5 | const assetPairsConfigSchema = baseMessageSchema.extend({ 6 | id: z.string(), 7 | T: z.literal(MessageType.ASSET_PAIRS_CONFIG_UPDATE), 8 | k: z.enum(['i', 'u']), 9 | u: z.array( 10 | z.tuple([ 11 | z.string().toUpperCase(), // pairName 12 | z.number(), // minQty 13 | z.number().int(), // pricePrecision 14 | ]), 15 | ), 16 | }); 17 | 18 | export default assetPairsConfigSchema; 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | BigNumber.config({ EXPONENTIAL_AT: 1e+9 }); 3 | 4 | export * as config from './config/index.js'; 5 | export { default as Unit } from './Unit/index.js'; 6 | export { default as Orion } from './Orion/index.js'; 7 | export {generateSwapCalldata} from './Unit/Exchange/generateSwapCalldata.js'; 8 | export { default as factories} from './constants/factories.js'; 9 | export * as utils from './utils/index.js'; 10 | export * as services from './services/index.js'; 11 | export * as crypt from './crypt/index.js'; 12 | export * from './constants/index.js'; 13 | export * from './types.js'; 14 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/pairConfigSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const pairConfigSchema = z.object({ 4 | // baseAssetPrecision: z.number().int(), // Deprecated. DO NOT USE 5 | // executableOnBrokersPriceDeviation: z.number().nullable(), // Deprecated. DO NOT USE 6 | maxPrice: z.number(), 7 | maxQty: z.number(), 8 | minPrice: z.number(), 9 | minQty: z.number(), 10 | name: z.string().toUpperCase(), 11 | pricePrecision: z.number().int(), 12 | qtyPrecision: z.number().int(), 13 | // quoteAssetPrecision: z.number().int(), // Deprecated. DO NOT USE 14 | }); 15 | 16 | export default pairConfigSchema; 17 | -------------------------------------------------------------------------------- /src/constants/orderTypes.ts: -------------------------------------------------------------------------------- 1 | const ORDER_TYPES = { 2 | Order: [ 3 | { name: 'senderAddress', type: 'address' }, 4 | { name: 'matcherAddress', type: 'address' }, 5 | { name: 'baseAsset', type: 'address' }, 6 | { name: 'quoteAsset', type: 'address' }, 7 | { name: 'matcherFeeAsset', type: 'address' }, 8 | { name: 'amount', type: 'uint64' }, 9 | { name: 'price', type: 'uint64' }, 10 | { name: 'matcherFee', type: 'uint64' }, 11 | { name: 'nonce', type: 'uint64' }, 12 | { name: 'expiration', type: 'uint64' }, 13 | { name: 'buySide', type: 'uint8' }, 14 | ], 15 | }; 16 | 17 | export default ORDER_TYPES; 18 | -------------------------------------------------------------------------------- /src/utils/calculateServiceFeeInFeeAsset.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import convertPrice from './convertPrice.js'; 3 | 4 | export default function calculateServiceFeeInFeeAsset( 5 | amount: BigNumber.Value, 6 | baseAssetName: string, 7 | feeAssetName: string, 8 | feePercent: BigNumber.Value, 9 | prices: Partial> 10 | ) { 11 | const feeAmount = new BigNumber(amount).multipliedBy(new BigNumber(feePercent).div(100)); 12 | 13 | const feeAssetAmount = convertPrice( 14 | feeAmount, 15 | baseAssetName, 16 | feeAssetName, 17 | prices 18 | ); 19 | 20 | return feeAssetAmount; 21 | } 22 | -------------------------------------------------------------------------------- /src/Unit/Pmm/schemas/order.ts: -------------------------------------------------------------------------------- 1 | import {z} from "zod"; 2 | 3 | export const pmmOrderQuotationSchema = z.object({ 4 | info: z.string().default(''), 5 | makerAsset: z.string().default(''), 6 | takerAsset: z.string().default(''), 7 | maker: z.string().default(''), 8 | allowedSender: z.string().default(''), 9 | makingAmount: z.string().default(''), 10 | takingAmount: z.string().default(''), 11 | }); 12 | 13 | export const pmmOrderSchema = z.object({ 14 | order: pmmOrderQuotationSchema.default({}), 15 | signature: z.string().default(''), 16 | success: z.boolean().default(false), 17 | error: z.string().default(''), 18 | }); -------------------------------------------------------------------------------- /src/config/schemas/pureChainSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { networkCodes } from '../../constants/index.js'; 3 | import { SupportedChainId } from '../../types.js'; 4 | 5 | export const pureChainInfoPayloadSchema = z.object({ 6 | chainId: z.nativeEnum(SupportedChainId), 7 | label: z.string(), 8 | shortName: z.string(), 9 | code: z.enum(networkCodes), 10 | explorer: z.string(), 11 | rpc: z.string(), 12 | baseCurrencyName: z.string(), 13 | contracts: z.record(z.string(), z.string()) 14 | }); 15 | 16 | export const pureChainInfoSchema = z.record( 17 | z.nativeEnum(SupportedChainId), 18 | pureChainInfoPayloadSchema, 19 | ); 20 | -------------------------------------------------------------------------------- /src/constants/subOrderStatuses.ts: -------------------------------------------------------------------------------- 1 | const subOrderStatuses = [ 2 | 'NEW', // created, wasn't added to IOB or wasn't accepted by the broker 3 | 'ACCEPTED', // added to IOB or accepted by the broker 4 | 'PARTIALLY_FILLED', // partially filled 5 | 'FILLED', // fully filled 6 | 'TX_PENDING', // sub order was filled and at least one of its trades is pending 7 | 'CANCELED', // canceled by user or by expiration 8 | 'REJECTED', // rejected by broker 9 | 'FAILED', // at least one trade failed 10 | 'SETTLED', // all trades successfully settled 11 | 'NOT_FOUND', // broker not processed sub order yet 12 | ] as const; 13 | 14 | export default subOrderStatuses; 15 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { default as cancelOrderTypes } from './cancelOrderTypes.js'; 2 | export { default as orderStatuses } from './orderStatuses.js'; 3 | export { default as orderTypes } from './orderTypes.js'; 4 | export { default as subOrderStatuses } from './subOrderStatuses.js'; 5 | export { default as networkCodes } from './networkCodes.js'; 6 | export { default as exchanges } from './exchanges.js'; 7 | export { default as exchangesMap } from './exchangesMap.js'; 8 | 9 | export * from './chains.js'; 10 | export * from './precisions.js'; 11 | export * from './gasLimits.js'; 12 | export * from './timings.js'; 13 | 14 | export const SERVICE_TOKEN = 'ORN'; 15 | -------------------------------------------------------------------------------- /src/utils/denormalizeNumber.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | 3 | /** 4 | * Converts normalized blockchain ("machine-readable") number to denormalized ("human-readable") number. 5 | * @param input Any blockchain-normalized numeric value 6 | * @param decimals Blockchain asset precision 7 | * @returns BigNumber 8 | */ 9 | export default function denormalizeNumber(input: bigint, decimals: bigint) { 10 | const decimalsBN = new BigNumber(decimals.toString()); 11 | if (!decimalsBN.isInteger()) throw new Error(`Decimals '${decimalsBN.toString()}' is not an integer`); 12 | return new BigNumber(input.toString()).div(new BigNumber(10).pow(decimalsBN)); 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/isNetworkCodeInEnvironment.ts: -------------------------------------------------------------------------------- 1 | import { chains, envs } from '../config/index.js'; 2 | 3 | export default function isNetworkCodeInEnvironment(networkCode: string, env: string) { 4 | if (!(env in envs)) { 5 | throw new Error(`Env ${env} is not supported. Available environments is: ${Object.keys(envs).join(', ')}`); 6 | } 7 | const envInfo = envs[env]; 8 | const envNetworks = envInfo?.networks; 9 | if (!envNetworks) throw new Error('Env networks is undefined (isNetworkCodeInEnvironment)'); 10 | 11 | return Object.values(chains) 12 | .some((chain) => chain.code.toLowerCase() === networkCode.toLowerCase() && 13 | chain.chainId in envNetworks); 14 | } 15 | -------------------------------------------------------------------------------- /src/constants/chains.ts: -------------------------------------------------------------------------------- 1 | import { SupportedChainId } from '../types.js'; 2 | 3 | export const developmentChains = [ 4 | SupportedChainId.BSC_TESTNET, 5 | SupportedChainId.SEPOLIA, 6 | SupportedChainId.EVENT_HORIZON_TESTNET, 7 | SupportedChainId.LUMIA_TESTNET, 8 | ]; 9 | export const productionChains = [ 10 | SupportedChainId.MAINNET, 11 | SupportedChainId.BSC, 12 | SupportedChainId.FANTOM_OPERA, 13 | SupportedChainId.POLYGON, 14 | SupportedChainId.OKC, 15 | SupportedChainId.ARBITRUM, 16 | SupportedChainId.OPBNB, 17 | SupportedChainId.INEVM, 18 | SupportedChainId.LINEA, 19 | SupportedChainId.AVAX, 20 | SupportedChainId.BASE, 21 | SupportedChainId.LUMIA, 22 | ]; 23 | -------------------------------------------------------------------------------- /src/Unit/Exchange/callGenerators/feePayment.ts: -------------------------------------------------------------------------------- 1 | import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js" 2 | import type { BigNumberish, AddressLike } from "ethers" 3 | import { type CallParams, addCallParams } from "./utils.js" 4 | 5 | 6 | export function generateFeePaymentCall( 7 | matcher: AddressLike, 8 | token: AddressLike, 9 | amount: BigNumberish, 10 | callParams?: CallParams 11 | ) { 12 | 13 | const executorInterface = SwapExecutor__factory.createInterface() 14 | const calldata = executorInterface.encodeFunctionData('payFeeToMatcher', [ 15 | matcher, 16 | token, 17 | amount 18 | ]) 19 | 20 | return addCallParams(calldata, callParams) 21 | } -------------------------------------------------------------------------------- /src/utils/calculateNetworkFeeInFeeAsset.ts: -------------------------------------------------------------------------------- 1 | import type { BigNumber } from 'bignumber.js'; 2 | import calculateNetworkFee from './calculateNetworkFee.js'; 3 | import convertPrice from './convertPrice.js'; 4 | 5 | const calculateNetworkFeeInFeeAsset = ( 6 | gasPriceGwei: BigNumber.Value, 7 | gasLimit: BigNumber.Value, 8 | baseCurrencyName: string, 9 | feeAssetName: string, 10 | prices: Partial> 11 | ) => { 12 | const networkFee = calculateNetworkFee(gasPriceGwei, gasLimit); 13 | 14 | return convertPrice( 15 | networkFee, 16 | baseCurrencyName, // from 17 | feeAssetName, // to 18 | prices 19 | ); 20 | }; 21 | 22 | export default calculateNetworkFeeInFeeAsset; 23 | -------------------------------------------------------------------------------- /src/constants/exchangesMap.ts: -------------------------------------------------------------------------------- 1 | const mapping: Record = { 2 | // CEXes 3 | ASCENDEX: 'AscendEx', 4 | OKX: 'OKX', 5 | BINANCE: 'Binance', 6 | KUCOIN: 'KuCoin', 7 | ORION: 'Orion', // Internal 8 | INTERNAL_ORDER_BOOK: 'Internal', 9 | 10 | // DEXes 11 | SPOOKYSWAP: 'SpookySwap', 12 | PANCAKESWAP: 'PancakeSwap', 13 | UNISWAP: 'Uniswap', 14 | QUICKSWAP: 'QuickSwap', 15 | ORION_POOL: 'Internal Pool', 16 | INTERNAL_POOL_V2: 'Internal Pool V2', 17 | INTERNAL_POOL_V3: 'Internal Pool V3', 18 | CHERRYSWAP: 'CherrySwap', 19 | OKXSWAP: 'OKXSwap', 20 | CURVE: 'Curve', 21 | CURVE_FACTORY: 'Curve Factory', 22 | THENA_ALGEBRA_V1: 'Thena', 23 | }; 24 | 25 | export default mapping; 26 | -------------------------------------------------------------------------------- /src/services/PriceFeed/ws/schemas/allTickersSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import tickerInfoSchema from './tickerInfoSchema.js'; 3 | 4 | type TickerInfo = z.infer 5 | 6 | const allTickersSchema = z.unknown().array() 7 | .transform((tickers) => { 8 | const data = [...tickers]; 9 | data.shift(); 10 | const parsedData = tickerInfoSchema.array().parse(data); 11 | return parsedData.reduce< 12 | Partial< 13 | Record< 14 | string, 15 | TickerInfo 16 | > 17 | > 18 | >((prev, pairData) => ({ 19 | ...prev, 20 | [pairData.pairName]: pairData, 21 | }), {}); 22 | }); 23 | 24 | export default allTickersSchema; 25 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/get-pool-response-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { evmAddressSchema } from './util-schemas.js'; 3 | import basicPoolInfo from './basic-pool-info-schema.js'; 4 | import infoSchema from './info-schema.js'; 5 | 6 | const getPoolResponseSchema = z.object({ 7 | result: z.object({ 8 | token0: z.string().nonempty(), 9 | token1: z.string().nonempty(), 10 | token0Address: evmAddressSchema, 11 | token1Address: evmAddressSchema, 12 | 13 | totalLiquidity: z.number().nonnegative(), 14 | WETH9: evmAddressSchema, 15 | pools: z.record(z.number(), basicPoolInfo.nullable()), 16 | }), 17 | info: infoSchema, 18 | }); 19 | 20 | export default getPoolResponseSchema; 21 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export { default as addressUpdateSchema } from './addressUpdateSchema.js'; 2 | export { default as assetPairsConfigSchema } from './assetPairsConfigSchema.js'; 3 | export { default as baseMessageSchema } from './baseMessageSchema.js'; 4 | export { default as brokerMessageSchema } from './brokerMessageSchema.js'; 5 | export { default as errorSchema } from './errorSchema.js'; 6 | export { default as initMessageSchema } from './initMessageSchema.js'; 7 | export { default as pingPongMessageSchema } from './pingPongMessageSchema.js'; 8 | export { default as swapInfoSchema } from './swapInfoSchema.js'; 9 | export { default as balancesSchema } from './balancesSchema.js'; 10 | 11 | export * from './orderBookSchema.js'; 12 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/orderBookSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | import baseMessageSchema from './baseMessageSchema.js'; 4 | 5 | export const orderBookItemSchema = z.tuple([ 6 | z.string(), // price 7 | z.string(), // size 8 | z.array( 9 | z.string(), 10 | ), // exchanges 11 | z.array(z.tuple([ 12 | z.enum(['SELL', 'BUY']), // side 13 | z.string().toUpperCase(), // pairname 14 | ])), 15 | ]); 16 | 17 | export const orderBookSchema = baseMessageSchema.extend({ 18 | // id: z.string(), 19 | T: z.literal(MessageType.AGGREGATED_ORDER_BOOK_UPDATE), 20 | S: z.string(), 21 | ob: z.object({ 22 | a: z.array(orderBookItemSchema), 23 | b: z.array(orderBookItemSchema), 24 | }), 25 | }); 26 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/aggregatedHistorySchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const aggregatedHistorySchema = z.object({ 4 | data: z.array(z.object({ 5 | history_type: z.string(), 6 | chain_type: z.string(), 7 | chain_comp: z.string(), 8 | chain_id: z.number(), 9 | date_unix: z.number(), 10 | date_time_local: z.string(), 11 | date_time_utc: z.string(), 12 | amount_token: z.string(), 13 | amount_token_fmt: z.number(), 14 | amount_usd: z.string(), 15 | amount_usd_fmt: z.number(), 16 | token_price: z.string(), 17 | token_price_fmt: z.number() 18 | })), 19 | pagination_info: z.object({ 20 | c_page: z.number(), 21 | t_pages: z.number() 22 | }) 23 | }) 24 | 25 | export default aggregatedHistorySchema; 26 | -------------------------------------------------------------------------------- /src/utils/getNativeCryptocurrencyName.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | 3 | const getNativeCryptocurrencyName = (assetToAddress: Partial>) => { 4 | const addressToAssetName = Object 5 | .entries(assetToAddress) 6 | .reduce>>((prev, [assetName, address]) => { 7 | if (address === undefined) return prev; 8 | return { 9 | ...prev, 10 | [address]: assetName, 11 | }; 12 | }, {}); 13 | 14 | const nativeCryptocurrencyName = addressToAssetName[ethers.ZeroAddress]; 15 | if (nativeCryptocurrencyName === undefined) { 16 | throw new Error('Native cryptocurrency asset name is not found'); 17 | } 18 | return nativeCryptocurrencyName; 19 | }; 20 | 21 | export default getNativeCryptocurrencyName; 22 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/poolsInfoSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const poolsInfoSchema = z.object({ 4 | governance: z.object({ 5 | apr: z.string(), 6 | rewardRate: z.string(), 7 | totalBalance: z.string(), 8 | }), 9 | totalRewardRatePerWeek: z.string(), 10 | pools: z.record( 11 | z.object({ 12 | currentAPR: z.string(), 13 | isUser: z.boolean().optional(), 14 | price: z.string(), 15 | reserves: z.record(z.string()), 16 | totalLiquidityInDollars: z.string(), 17 | totalRewardRatePerWeek: z.string(), 18 | totalStakedAmountInDollars: z.string(), 19 | totalSupply: z.string(), 20 | totalVoted: z.string(), 21 | weight: z.string(), 22 | }), 23 | ), 24 | }); 25 | 26 | export default poolsInfoSchema; 27 | -------------------------------------------------------------------------------- /src/services/PriceFeed/ws/schemas/candleSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const candleSchema = z 4 | .tuple([ 5 | z.string(), // interval [FIVE, FIFTEEN, THIRTY, HOUR, HOUR4, DAY, WEEK] 6 | z.string(), // pair ["btc-usdt"] 7 | z.number(), // timeStart [timestamp] 8 | z.number(), // timeEnd [timestamp] 9 | z.string(), // close 10 | z.string(), // open 11 | z.string(), // high 12 | z.string(), // low 13 | z.string(), // volume 14 | ]) 15 | .transform( 16 | ([interval, pair, timeStart, timeEnd, close, open, high, low, volume]) => ({ 17 | interval, 18 | pair, 19 | timeStart, 20 | timeEnd, 21 | close, 22 | open, 23 | high, 24 | low, 25 | volume, 26 | }), 27 | ); 28 | 29 | export default candleSchema; 30 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/atomicSwapHistorySchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import uppercasedNetworkCodes from '../../../constants/uppercasedNetworkCodes.js'; 3 | import redeemOrderSchema from './redeemOrderSchema.js'; 4 | 5 | export const atomicSwapHistorySchema = z.array(z.object({ 6 | id: z.string(), 7 | sender: z.string(), 8 | lockOrder: z.object({ 9 | sender: z.string(), 10 | asset: z.string().toUpperCase(), 11 | amount: z.number(), 12 | expiration: z.number(), 13 | secretHash: z.string(), 14 | used: z.boolean(), 15 | sourceNetworkCode: z.enum(uppercasedNetworkCodes), 16 | }), 17 | redeemOrder: redeemOrderSchema, 18 | status: z.enum(['SETTLED', 'EXPIRED', 'ACTIVE']), 19 | creationTime: z.number(), 20 | })); 21 | 22 | export default atomicSwapHistorySchema; 23 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export { default as atomicSwapHistorySchema } from './atomicSwapHistorySchema.js'; 2 | export { default as cancelOrderSchema } from './cancelOrderSchema.js'; 3 | export { default as exchangeInfoSchema } from './exchangeInfoSchema.js'; 4 | export { default as orderBenefitsSchema } from './orderBenefitsSchema.js'; 5 | export { default as pairConfigSchema } from './pairConfigSchema.js'; 6 | export { default as placeAtomicSwapSchema } from './placeAtomicSwapSchema.js'; 7 | export { default as redeemOrderSchema } from './redeemOrderSchema.js'; 8 | export { default as swapInfoSchema } from './swapInfoSchema.js'; 9 | export { default as orderSchema } from './orderSchema.js'; 10 | export * from './aggregatedOrderbookSchema.js' 11 | export { default as errorSchema } from './errorSchema.js'; 12 | -------------------------------------------------------------------------------- /src/utils/getValidArrayItems.ts: -------------------------------------------------------------------------------- 1 | import type { Schema, ZodTypeDef } from 'zod'; 2 | import { z } from 'zod' 3 | 4 | export default function getValidArrayItemsSchema ( 5 | elemSchema: Schema, 6 | ) { 7 | return z.array(z.unknown()).transform((items) => { 8 | const validItems: Array> = []; 9 | for (let i = 0; i < items.length; i++) { 10 | const item = items[i]; 11 | const parsedItem = elemSchema.safeParse(item); 12 | if (parsedItem.success) { 13 | validItems.push(parsedItem.data); 14 | } else { 15 | console.log(`Array item with index ${i} is invalid. Error: ${parsedItem.error.message}. Data: ${JSON.stringify(item)}.`) 16 | } 17 | } 18 | return validItems; 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/checkIsToken.ts: -------------------------------------------------------------------------------- 1 | import { ERC20__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js'; 2 | import { ethers } from 'ethers'; 3 | import invariant from 'tiny-invariant'; 4 | 5 | const checkIsToken = async (address: string, provider?: ethers.Provider) => { 6 | invariant(provider, 'No provider for token checking'); 7 | const tokenContract = ERC20__factory.connect(address, provider); 8 | try { 9 | const results = await Promise.all( 10 | [ 11 | tokenContract.name(), 12 | tokenContract.symbol(), 13 | tokenContract.decimals(), 14 | tokenContract.totalSupply(), 15 | tokenContract.balanceOf(ethers.ZeroAddress), 16 | ], 17 | ); 18 | 19 | return Boolean(results); 20 | } catch (err) { 21 | return false; 22 | } 23 | }; 24 | 25 | export default checkIsToken; 26 | -------------------------------------------------------------------------------- /src/crypt/hashOrder.ts: -------------------------------------------------------------------------------- 1 | import { ethers, keccak256 } from 'ethers'; 2 | import type { Order } from '../types.js'; 3 | 4 | const ORDER_TYPEHASH = "0xb5132db62dfceb466f2f8aee7a039db36a99772e5a9771d28388a5f9baad7c54" 5 | 6 | export default function getOrderHash(order: Order) { 7 | const abiCoder = ethers.AbiCoder.defaultAbiCoder() 8 | 9 | const { senderAddress, matcherAddress, baseAsset, quoteAsset, matcherFeeAsset, amount, price, matcherFee, nonce, expiration, buySide } = order 10 | const orderBytes = abiCoder.encode( 11 | ["bytes32", "address", "address", "address", "address", "address", "uint64", "uint64", "uint64", "uint64", "uint64", "uint8"], 12 | [ORDER_TYPEHASH, senderAddress, matcherAddress, baseAsset, quoteAsset, matcherFeeAsset, amount, price, matcherFee, nonce, expiration, buySide] 13 | ) 14 | 15 | return keccak256(orderBytes) 16 | } -------------------------------------------------------------------------------- /src/services/PriceFeed/ws/schemas/cexPricesSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const cexPriceTickerInfoSchema = z.tuple([ 4 | z.string(), // pair name 5 | z.number(), // lastPrice 6 | ]).transform(([pairName, lastPrice]) => ({ 7 | pairName:pairName.toUpperCase(), 8 | lastPrice, 9 | })); 10 | 11 | type CEXPriceTickerInfo = z.infer 12 | 13 | const cexPricesSchema = z.unknown().array() 14 | .transform((tickers) => { 15 | const data = [...tickers]; 16 | data.shift(); 17 | const parsedData = cexPriceTickerInfoSchema.array().parse(data); 18 | return parsedData.reduce< 19 | Partial< 20 | Record< 21 | string, 22 | CEXPriceTickerInfo 23 | > 24 | > 25 | >((prev, pairData) => ({ 26 | ...prev, 27 | [pairData.pairName]: pairData, 28 | }), {}); 29 | }); 30 | 31 | export default cexPricesSchema; -------------------------------------------------------------------------------- /src/__tests__/bridge.test.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from 'ethers'; 2 | import Orion from '../Orion/index.js'; 3 | import { SupportedChainId } from '../types.js'; 4 | import { SERVICE_TOKEN } from '../index.js'; 5 | 6 | const privateKey = process.env['PRIVATE_KEY'] 7 | if (privateKey === undefined) throw new Error('Private key is required'); 8 | 9 | jest.setTimeout(30000); 10 | 11 | describe('Bridge', () => { 12 | test('Execution', async () => { 13 | const orion = new Orion('testing'); 14 | const wallet = new Wallet(privateKey); 15 | 16 | await orion.bridge.swap( 17 | SERVICE_TOKEN, 18 | 0.12345678, 19 | SupportedChainId.FANTOM_TESTNET, 20 | SupportedChainId.BSC_TESTNET, 21 | wallet, 22 | { 23 | autoApprove: true, 24 | logger: console.log, 25 | withdrawToWallet: true, 26 | }, 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/utils/normalizeNumber.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | 3 | /** 4 | * Converts denormalized ("human-readable") number to normalized ("machine-readable") number. 5 | * @param input Any numeric value 6 | * @param decimals Blockchain asset precision 7 | * @param roundingMode Rounding mode 8 | * @returns bigint 9 | */ 10 | export default function normalizeNumber( 11 | input: BigNumber.Value, 12 | decimals: BigNumber.Value, 13 | roundingMode: BigNumber.RoundingMode 14 | ) { 15 | const decimalsBN = new BigNumber(decimals); 16 | if (!decimalsBN.isInteger()) 17 | throw new Error(`Decimals '${decimalsBN.toString()}' is not an integer`); 18 | const inputBN = new BigNumber(input); 19 | return BigInt( 20 | inputBN 21 | .multipliedBy(new BigNumber(10).pow(decimals)) 22 | .integerValue(roundingMode) 23 | .toString() 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/release-package.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build-and-publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: '18.x' 15 | - run: npm ci 16 | - run: npm run build 17 | # - run: npm test 18 | - name: Set package registry 19 | run: npm config set registry https://npm.pkg.github.com 20 | - name: Github package registry authentication 21 | run: npm set //npm.pkg.github.com/:_authToken ${{ secrets.GITHUB_TOKEN }} 22 | - name: Npm registry authentication 23 | run: npm set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }} 24 | - name: Publish the package to Github and Npm package registries 25 | run: npm publish 26 | -------------------------------------------------------------------------------- /src/crypt/getDomainData.ts: -------------------------------------------------------------------------------- 1 | import type { SupportedChainId } from '../types.js'; 2 | import eip712DomainData from '../config/eip712DomainData.json' assert { type: 'json' }; 3 | import eip712DomainSchema from '../config/schemas/eip712DomainSchema.js'; 4 | 5 | const EIP712Domain = eip712DomainSchema.parse(eip712DomainData); 6 | 7 | function removeUndefined(obj: Record) { 8 | const newObj: Partial> = {}; 9 | for (const [key, value] of Object.entries(obj)) { 10 | if (value !== undefined) { 11 | newObj[key] = value; 12 | } 13 | } 14 | return newObj; 15 | } 16 | 17 | /** 18 | * See {@link https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator} 19 | */ 20 | const getDomainData = (chainId: SupportedChainId) => ({ 21 | ...removeUndefined(EIP712Domain), 22 | chainId, 23 | }); 24 | 25 | export default getDomainData; 26 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/environment-response-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { evmAddressSchema } from './util-schemas.js'; 3 | import infoSchema from './info-schema.js'; 4 | 5 | const environmentResponseSchema = z.object({ 6 | result: z.object({ 7 | chainId: z.number().int().nonnegative(), 8 | nativeToken: z.string(), 9 | ORN: evmAddressSchema, 10 | WETH9: evmAddressSchema, 11 | OrionV3Factory: evmAddressSchema.optional(), 12 | OrionV2Factory: evmAddressSchema.optional(), 13 | OrionV3NFTManager: evmAddressSchema.optional(), 14 | SwapRouterV3: evmAddressSchema.optional(), 15 | OrionFarmV3: evmAddressSchema.optional(), 16 | OrionFarmV2: evmAddressSchema.optional(), 17 | OrionVoting: evmAddressSchema.optional(), 18 | veORN: evmAddressSchema.optional(), 19 | }), 20 | info: infoSchema, 21 | }); 22 | 23 | export default environmentResponseSchema; 24 | -------------------------------------------------------------------------------- /src/utils/getAvailableFundsSources.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import type { Source } from '../types.js'; 3 | 4 | export default function getAvailableFundsSources( 5 | expenseType: 'amount' | 'network_fee' | 'service_fee', 6 | assetAddress: string, 7 | route: 'aggregator' | 'pool', 8 | ): Source[] { 9 | switch (route) { 10 | case 'aggregator': 11 | if (assetAddress === ethers.ZeroAddress) return ['exchange']; // We can't take native crypto from wallet 12 | return ['exchange', 'wallet']; // We can take any token amount from exchange + wallet. Order is important! 13 | case 'pool': 14 | if (expenseType === 'network_fee') return ['wallet']; // Network fee is always taken from wallet 15 | return ['exchange', 'wallet']; // We can take any token amount from exchange + wallet (specify 'value' for 'pool'). Order is important! 16 | default: 17 | throw new Error('Unknown route item'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/objectKeys.ts: -------------------------------------------------------------------------------- 1 | export type ObjectKeys = `${Exclude}`; 2 | 3 | /** 4 | A strongly-typed version of `Object.keys()`. 5 | This is useful since `Object.keys()` always returns an array of strings. This function returns a strongly-typed array of the keys of the given object. 6 | - [Explanation](https://stackoverflow.com/questions/55012174/why-doesnt-object-keys-return-a-keyof-type-in-typescript) 7 | - [TypeScript issues about this](https://github.com/microsoft/TypeScript/issues/45390) 8 | @example 9 | ``` 10 | const stronglyTypedItems = objectKeys({a: 1, b: 2, c: 3}); // => Array<'a' | 'b' | 'c'> 11 | const untypedItems = Object.keys(items); // => Array 12 | ``` 13 | @category Improved builtin 14 | @category Type guard 15 | */ 16 | // eslint-disable-next-line @typescript-eslint/consistent-type-assertions 17 | export const objectKeys = Object.keys as (value: Type) => Array>; 18 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/veORN-info-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { evmAddressSchema } from './util-schemas.js'; 3 | import infoSchema from './info-schema.js'; 4 | 5 | const veORNResultSchema = z.object({ 6 | avgAPR: z.number(), 7 | minAPR: z.number(), 8 | maxAPR: z.number(), 9 | veTokenAddress: evmAddressSchema, 10 | totalORNLocked: z.number(), 11 | totalVeORN: z.number(), 12 | weeklyReward: z.number(), 13 | userAPR: z.number(), 14 | userVeORN: z.number(), 15 | userVeORNBalance: z.number(), 16 | userORNLocked: z.number(), 17 | userLockEndDate: z.number(), 18 | userReward: z.number(), 19 | userWeeklyReward: z.number(), 20 | userMinLockPeriod: z.number(), 21 | dropLock: z.boolean().optional(), 22 | pointsReward: z.number().optional(), 23 | }).passthrough(); 24 | 25 | const veORNInfoSchema = z.object({ 26 | result: veORNResultSchema, 27 | info: infoSchema, 28 | }); 29 | 30 | export default veORNInfoSchema; 31 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/aggregatedOrderbookSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const orderbookElementSchema = z.object({ 4 | price: z.number(), 5 | amount: z.number(), 6 | path: z.array(z.object({ 7 | assetPair: z.string().toUpperCase(), 8 | action: z.enum(['BUY', 'SELL']), 9 | })), 10 | }); 11 | 12 | const aggregatedOrderbookElementSchema = orderbookElementSchema 13 | .extend({ 14 | exchanges: z.string().array(), 15 | }); 16 | 17 | export const aggregatedOrderbookSchema = z.object({ 18 | asks: z.array(aggregatedOrderbookElementSchema), 19 | bids: z.array(aggregatedOrderbookElementSchema), 20 | }); 21 | 22 | export const exchangeOrderbookSchema = z.object({ 23 | asks: z.array(orderbookElementSchema), 24 | bids: z.array(orderbookElementSchema), 25 | }); 26 | 27 | export const poolReservesSchema = z.object({ 28 | a: z.number(), // amount asset 29 | p: z.number(), // price asset 30 | indicativePrice: z.number(), 31 | }); 32 | -------------------------------------------------------------------------------- /.github/workflows/prerelease-package.yml: -------------------------------------------------------------------------------- 1 | name: Prerelease 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'main' 7 | 8 | jobs: 9 | build-and-publish: 10 | if: github.actor != 'dependabot[bot]' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: '18.x' 17 | - run: npm ci 18 | - run: npm run build 19 | # - run: npm test 20 | - name: Set package registry 21 | run: npm config set registry https://npm.pkg.github.com 22 | - name: Github package registry authentication 23 | run: npm set //npm.pkg.github.com/:_authToken ${{ secrets.GITHUB_TOKEN }} 24 | - name: Npm registry authentication 25 | run: npm set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }} 26 | - name: Publish the package to Github and Npm package registries 27 | run: npm publish --tag next 28 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/voting-info-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import infoSchema from './info-schema.js'; 3 | 4 | const poolSchema = z.object({ 5 | allVote: z.number(), 6 | name: z.string(), 7 | poolAddress: z.string(), 8 | type: z.string(), 9 | userVote: z.number(), 10 | token0: z.string(), // deprecated 11 | token1: z.string(), // deprecated 12 | name0: z.string(), 13 | name1: z.string(), 14 | poolFee: z.number(), 15 | userWeight: z.number(), 16 | weight: z.number(), 17 | }); 18 | 19 | const votingResultSchema = z.object({ 20 | absoluteVeTokenInVoting: z.number(), 21 | pools: z.array(poolSchema), 22 | userVeTokenBalance: z.number(), 23 | userVeTokenInVoting: z.number(), 24 | veTokenAddress: z.string(), 25 | votingAddress: z.string(), 26 | weeklyReward: z.number(), 27 | }); 28 | 29 | const votingInfoSchema = z.object({ 30 | result: votingResultSchema, 31 | info: infoSchema, 32 | }); 33 | 34 | export default votingInfoSchema; 35 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/claimInfoSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const claimInfoSchema = z.object({ 4 | global: z.object({ 5 | total_non_accrued: z.number(), 6 | total_non_accrued_token: z.number(), 7 | total_non_accrued_usd: z.number() 8 | }), 9 | chain_to_reward_info: z.record( 10 | z.string(), 11 | z.object({ 12 | total_accrued: z.number(), 13 | total_accrued_token: z.number(), 14 | total_accrued_usd: z.number(), 15 | total_non_accrued: z.number(), 16 | total_non_accrued_token: z.number(), 17 | total_non_accrued_usd: z.number(), 18 | total_earned: z.number() 19 | }) 20 | ), 21 | mini_stats: z.object({ 22 | earned_on_referrals_token: z.number(), 23 | earned_on_referrals_usd: z.number(), 24 | token_usd: z.number(), 25 | registered_via_link_count: z.number(), 26 | earned_in_a_week_token: z.number(), 27 | earned_in_a_week_usd: z.number() 28 | }), 29 | }); 30 | 31 | export default claimInfoSchema; 32 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export { default as environmentResponseSchema } from './environment-response-schema'; 2 | export { default as listNFTOrderResponseSchema } from './list-nft-order-response-schema'; 3 | export { default as getPoolResponseSchema } from './get-pool-response-schema'; 4 | export { default as listPoolResponseSchema } from './list-pool-schema'; 5 | export { default as listPoolV2ResponseSchema } from './list-pool-v2-response-schema'; 6 | export { default as PoolV2InfoResponseSchema } from './pool-v2-info-schema'; 7 | export { default as listPoolV3ResponseSchema } from './list-pool-v3-response-schema'; 8 | export { default as veORNInfoResponseSchema } from './veORN-info-schema'; 9 | export { default as listAmountResponseSchema } from './list-amount-schema'; 10 | export { default as votingInfoResponseSchema } from './voting-info-schema'; 11 | export { default as testIncrementorSchema } from './test-incrementor-schema'; 12 | export { default as getPointsAtResponseSchema } from './get-points-at-schema'; 13 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/infoSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { makePartial } from '../../../utils'; 3 | 4 | const internalFeeAssetSchema = z.object({ 5 | type: z.enum(['percent', 'plain']), 6 | value: z.number(), 7 | asset: z.string(), 8 | }); 9 | 10 | const infoSchema = z.object({ 11 | chainId: z.number(), 12 | chainName: z.string(), 13 | swapExecutorContractAddress: z.string(), 14 | libValidatorContractAddress: z.string().optional(), 15 | exchangeContractAddress: z.string(), 16 | spvContractAddress: z.string(), 17 | oracleContractAddress: z.string(), 18 | matcherAddress: z.string(), 19 | orderFeePercent: z.number(), 20 | assetToAddress: z.record(z.string()).transform(makePartial), 21 | assetToDecimals: z.record(z.number()).transform(makePartial), 22 | assetToIcons: z.record(z.string()).transform(makePartial).optional(), 23 | cexTokens: z.string().array(), 24 | internalFeeAssets: internalFeeAssetSchema.array().optional(), 25 | }); 26 | 27 | export default infoSchema; 28 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export { default as linkSchema } from './linkSchema.js'; 2 | export { default as distinctAnalyticsSchema } from './distinctAnalyticsSchema.js'; 3 | export { default as errorSchema } from './errorSchema.js'; 4 | export { default as miniStatsSchema } from './miniStatsSchema.js'; 5 | export { default as rewardsMappingSchema } from './rewardsMappingSchema.js'; 6 | export { default as rewardsClaimedSchema } from './rewardsClaimedSchema.js'; 7 | export { default as globalAnalyticsSchema } from './globalAnalyticsSchema.js'; 8 | export { default as ratingSchema } from './ratingSchema.js'; 9 | export { default as claimInfoSchema } from './claimInfoSchema.js'; 10 | export { default as aggregatedHistorySchema } from './aggregatedHistorySchema.js'; 11 | export { default as contractsAddressesSchema } from './contractsAddressesSchema.js'; 12 | export { default as inviteCodeLinkSchema } from './inviteCodeLinkSchema.js'; 13 | export { default as allTimeLeadersSchema } from './allTimeLeadersSchema.js'; 14 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/distinctAnalyticsSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const distinctAnalyticsSchema = z.object({ 4 | referer: z.string(), 5 | refs_info: z.record( 6 | z.string(), 7 | z.object({ 8 | referral_address: z.string(), 9 | referral_earned_fees: z.number(), 10 | referer_earned_fees: z.number(), 11 | relative_ref_level: z.number(), 12 | reward_record_hash: z.string(), 13 | timestamp: z.number(), 14 | latest_timestamp: z.number(), 15 | latest_block: z.number(), 16 | }), 17 | ), 18 | total_sent_to_governance: z.number(), 19 | total_earned: z.number(), 20 | total_volume: z.number(), 21 | total_trades: z.number(), 22 | all_time_earnings_boost_only: z.number(), 23 | all_time_earnings_boost_only_usd: z.number(), 24 | all_time_earnings: z.number(), 25 | all_time_earnings_usd: z.number(), 26 | all_weekly_earnings: z.number(), 27 | all_weekly_earnings_usd: z.number(), 28 | }); 29 | 30 | export default distinctAnalyticsSchema; 31 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/adminPoolSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export enum PairStatusEnum { 4 | DOESNT_EXIST = -1, 5 | REVIEW = 0, 6 | ACCEPTED = 1, 7 | REJECTED = 2, 8 | } 9 | 10 | export const pairStatusSchema = z.nativeEnum(PairStatusEnum); 11 | 12 | const tokenSchema = z.object({ 13 | symbol: z.string(), 14 | icon: z.string().optional(), 15 | address: z.string(), 16 | decimals: z.number().optional(), 17 | isUser: z.boolean().optional(), 18 | }); 19 | 20 | export const poolOnVerificationSchema = z.object({ 21 | tokenA: tokenSchema, 22 | tokenB: tokenSchema, 23 | _id: z.string().optional(), 24 | address: z.string(), 25 | symbol: z.string(), 26 | isUser: z.boolean(), 27 | minQty: z.number().optional(), 28 | tokensReversed: z.boolean(), 29 | status: pairStatusSchema, 30 | updatedAt: z.number(), 31 | createdAt: z.number(), 32 | }); 33 | 34 | export type adminPoolType = z.infer; 35 | 36 | export const adminPoolSchema = poolOnVerificationSchema; 37 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/historySchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { HistoryTransactionStatus } from '../../../types.js'; 3 | 4 | const historySchema = z.array(z.object( 5 | { 6 | amount: z.string(), 7 | amountNumber: z.string(), 8 | asset: z.string(), 9 | assetAddress: z.string(), 10 | contractBalance: z.string().nullable().optional(), 11 | createdAt: z.number(), 12 | transactionHash: z.string(), 13 | type: z.enum(['deposit', 'withdrawal']), 14 | user: z.string(), 15 | walletBalance: z.string().nullable().optional(), 16 | }, 17 | )).transform((response) => { 18 | return response.map((item) => { 19 | const { 20 | type, createdAt, transactionHash, user, 21 | } = item; 22 | return { 23 | type, 24 | date: createdAt * 1000, 25 | token: item.asset, 26 | amount: item.amountNumber, 27 | status: HistoryTransactionStatus.DONE, 28 | transactionHash, 29 | user, 30 | }; 31 | }); 32 | }); 33 | 34 | export default historySchema; 35 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/basic-pool-info-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { evmAddressSchema } from './util-schemas.js'; 3 | 4 | const basicPoolInfo = z.object({ 5 | poolAddress: evmAddressSchema, 6 | isInitialized: z.boolean(), 7 | liquidity: z.number().nonnegative(), 8 | liquidityInUSD: z.number().nonnegative(), 9 | liquidityShare: z.number().nonnegative(), 10 | isFarming: z.boolean(), 11 | rewardsTotal: z.number().nonnegative(), 12 | rewardsPerPeriod: z.number().nonnegative(), 13 | rewardsShare: z.number().nonnegative(), 14 | feePerPeriod: z.number().nonnegative(), 15 | feeTotal: z.number().nonnegative(), 16 | feeShare: z.number().nonnegative(), 17 | tickMultiplier: z.number().nonnegative(), 18 | MAX_TICK: z.number().nonnegative().int(), 19 | minAPR: z.number().nonnegative(), 20 | maxAPR: z.number().nonnegative(), 21 | avgAPR: z.number().nonnegative(), 22 | maxBoost: z.number().nonnegative().int(), 23 | feeRate: z.array(z.number().nonnegative()), 24 | }); 25 | 26 | export default basicPoolInfo; 27 | -------------------------------------------------------------------------------- /src/Unit/Exchange/callGenerators/weth.ts: -------------------------------------------------------------------------------- 1 | import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js" 2 | import type { BigNumberish } from "ethers" 3 | import { type CallParams, addCallParams } from "./utils.js" 4 | import type { AddressLike } from "ethers" 5 | 6 | export function generateWrapAndTransferCall( 7 | target: AddressLike, 8 | callParams?: CallParams 9 | ) { 10 | 11 | const executorInterface = SwapExecutor__factory.createInterface() 12 | const calldata = executorInterface.encodeFunctionData('wrapAndTransfer', [ 13 | target, 14 | ]) 15 | 16 | return addCallParams(calldata, callParams) 17 | } 18 | 19 | export function generateUnwrapAndTransferCall( 20 | target: AddressLike, 21 | amount: BigNumberish, 22 | callParams?: CallParams 23 | ) { 24 | 25 | const executorInterface = SwapExecutor__factory.createInterface() 26 | const calldata = executorInterface.encodeFunctionData('unwrapAndTransfer', [ 27 | target, 28 | amount 29 | ]) 30 | 31 | return addCallParams(calldata, callParams) 32 | } -------------------------------------------------------------------------------- /src/services/Indexer/schemas/list-pool-v3-response-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { evmAddressSchema } from './util-schemas.js'; 3 | import basicPoolInfo from './basic-pool-info-schema.js'; 4 | import infoSchema from './info-schema.js'; 5 | 6 | export const listPoolV3Schema = z.object({ 7 | token0: z.string().nonempty(), 8 | token1: z.string().nonempty(), 9 | name: z.string(), 10 | name0: z.string(), 11 | name1: z.string(), 12 | token0Address: evmAddressSchema, 13 | token1Address: evmAddressSchema, 14 | token0Decimals: z.number().int().nonnegative().max(18), 15 | token1Decimals: z.number().int().nonnegative().max(18), 16 | WETH9: evmAddressSchema, 17 | poolFee: z.number(), 18 | weeklyReward: z.number(), 19 | weight: z.number(), 20 | totalLPStakeInUSD: z.number(), 21 | 22 | ...basicPoolInfo.shape, 23 | 24 | type: z.literal('v3'), 25 | }); 26 | 27 | const listPoolV3ResponseSchema = z.object({ 28 | result: z.array(listPoolV3Schema), 29 | info: infoSchema, 30 | }); 31 | 32 | export default listPoolV3ResponseSchema; 33 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/rewardsMappingSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const rewardsMappingSchema = z.object({ 4 | data: z.array( 5 | z.object({ 6 | distribution: z.object({ 7 | dist: z.object({ 8 | underlying_token: z.number(), 9 | referers_list: z.array(z.number()), 10 | }), 11 | address_to_reward_mapping: z.record(z.string(), z.number()), 12 | ref_offset_to_rewarded_actors: z.record(z.string(), z.string()), 13 | governance_reward_only: z.number(), 14 | total_reward: z.number(), 15 | trade_initiator: z.string(), 16 | }), 17 | timestamp_ms: z.number(), 18 | block_height: z.number(), 19 | tx_hash: z.string(), 20 | price_feed_meta_info: z 21 | .record(z.string(), z.record(z.string(), z.number())) 22 | .nullable(), 23 | }) 24 | ), 25 | pagination_info: z.object({ 26 | c_page: z.number().int().nonnegative(), 27 | t_pages: z.number().int().nonnegative(), 28 | }), 29 | }); 30 | 31 | export default rewardsMappingSchema; 32 | -------------------------------------------------------------------------------- /src/Unit/Exchange/callGenerators/erc20.ts: -------------------------------------------------------------------------------- 1 | import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js" 2 | import type { BigNumberish, AddressLike } from "ethers" 3 | import { type CallParams, addCallParams } from "./utils.js" 4 | 5 | export function generateTransferCall( 6 | token: AddressLike, 7 | target: AddressLike, 8 | amount: BigNumberish, 9 | callParams?: CallParams 10 | ) { 11 | 12 | const executorInterface = SwapExecutor__factory.createInterface() 13 | const calldata = executorInterface.encodeFunctionData('safeTransfer', [ 14 | token, 15 | target, 16 | amount 17 | ]) 18 | 19 | return addCallParams(calldata, callParams) 20 | } 21 | 22 | export function generateApproveCall( 23 | token: AddressLike, 24 | target: AddressLike, 25 | amount: BigNumberish, 26 | callParams?: CallParams 27 | ) { 28 | const executorInterface = SwapExecutor__factory.createInterface() 29 | const calldata = executorInterface.encodeFunctionData('safeApprove', [ 30 | token, 31 | target, 32 | amount 33 | ]) 34 | 35 | return addCallParams(calldata, callParams) 36 | } -------------------------------------------------------------------------------- /src/Unit/Exchange/callGenerators/aero.ts: -------------------------------------------------------------------------------- 1 | import { SwapExecutor__factory, AeroPool__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js" 2 | import { type BigNumberish, JsonRpcProvider } from "ethers" 3 | import { SafeArray } from "../../../utils/safeGetters.js" 4 | import { addCallParams } from "./utils.js" 5 | import type { SingleSwap } from "../../../types.js" 6 | 7 | export async function generateAeroCalls( 8 | path: SafeArray, 9 | amount: BigNumberish, 10 | recipient: string, 11 | provider: JsonRpcProvider 12 | ) { 13 | const pools: string[] = []; 14 | const direct: boolean[] = []; 15 | for (const swap of path) { 16 | pools.push(swap.pool); 17 | 18 | const token0 = await AeroPool__factory.connect(swap.pool, provider).token0(); 19 | direct.push(swap.assetIn.toLowerCase() === token0.toLowerCase()); 20 | } 21 | 22 | const executorInterface = SwapExecutor__factory.createInterface() 23 | let calldata = executorInterface.encodeFunctionData('swapAeroMulti', [pools, direct, amount, recipient]); 24 | calldata = addCallParams(calldata) 25 | 26 | return [calldata] 27 | } 28 | -------------------------------------------------------------------------------- /src/config/schemas/pureEnvSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { SupportedChainId } from '../../types.js'; 3 | 4 | export const pureEnvNetworksSchema = z.object({ 5 | api: z.string(), 6 | services: z.object({ 7 | blockchain: z.object({ 8 | http: z.string(), 9 | }), 10 | aggregator: z.object({ 11 | http: z.string(), 12 | ws: z.string(), 13 | }), 14 | priceFeed: z.object({ 15 | all: z.string(), 16 | }), 17 | indexer: z.object({ 18 | http: z.string(), 19 | }).optional() 20 | }), 21 | rpc: z.string().optional(), 22 | liquidityMigratorAddress: z.string().optional(), 23 | }); 24 | 25 | export const pureEnvPayloadSchema = z.object({ 26 | analyticsAPI: z.string().url().optional(), 27 | referralAPI: z.string().url(), 28 | frontageAPI: z.string().url(), 29 | networks: z.record( 30 | z.nativeEnum(SupportedChainId), 31 | pureEnvNetworksSchema 32 | ), 33 | }); 34 | 35 | export const knownEnvs = ['production', 'staging', 'testing'] as const; 36 | 37 | export const pureEnvSchema = z.record( 38 | z.enum(knownEnvs).or(z.string()), 39 | pureEnvPayloadSchema, 40 | ); 41 | -------------------------------------------------------------------------------- /src/__tests__/exchangeContract.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import Orion from '../Orion/index.js'; 3 | import { SERVICE_TOKEN } from '../index.js'; 4 | 5 | const privateKey = process.env['PRIVATE_KEY']; 6 | if (privateKey === undefined) throw new Error('Private key is required'); 7 | 8 | jest.setTimeout(30000); 9 | 10 | describe('Transfers', () => { 11 | test(`Deposit ${SERVICE_TOKEN}`, async () => { 12 | const orion = new Orion('testing'); 13 | const bscUnit = orion.getUnit('bsc'); 14 | const wallet = new ethers.Wallet( 15 | privateKey, 16 | bscUnit.provider 17 | ); 18 | 19 | await bscUnit.exchange.deposit({ 20 | asset: SERVICE_TOKEN, 21 | amount: 20, 22 | signer: wallet, 23 | }); 24 | }); 25 | 26 | test(`Withdraw ${SERVICE_TOKEN}`, async () => { 27 | const orion = new Orion('testing'); 28 | const bscUnit = orion.getUnit('bsc'); 29 | const wallet = new ethers.Wallet( 30 | privateKey, 31 | bscUnit.provider 32 | ); 33 | 34 | await bscUnit.exchange.withdraw({ 35 | asset: SERVICE_TOKEN, 36 | amount: 20, 37 | signer: wallet, 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig([ 4 | { 5 | entry: ['src/index.ts'], 6 | sourcemap: true, 7 | platform: 'neutral', 8 | minify: true, 9 | outDir: 'lib', 10 | format: ['esm', 'cjs'], 11 | dts: true, 12 | clean: true, 13 | shims: true, 14 | }, 15 | { 16 | entry: ['src/index.ts'], 17 | globalName: 'orion', 18 | sourcemap: true, 19 | platform: 'browser', 20 | minify: true, 21 | outDir: 'lib', 22 | format: 'iife', 23 | dts: true, 24 | clean: true, 25 | shims: true, 26 | 27 | // Suppress all 'node:' imports 28 | esbuildPlugins: [ 29 | { 30 | name: 'resolve-node-polyfill', 31 | setup(build) { 32 | build.onResolve({ filter: /^node:/ }, (args) => { 33 | return { 34 | path: args.path, 35 | namespace: 'node-polyfill', 36 | } 37 | }) 38 | build.onLoad({ filter: /.*/, namespace: 'node-polyfill' }, (args) => { 39 | return { 40 | contents: 'undefined', 41 | } 42 | }) 43 | }, 44 | }, 45 | ], 46 | } 47 | ]) -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/poolsConfigSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import addressSchema from '../../../addressSchema.js'; 3 | import { makePartial } from '../../../utils/index.js'; 4 | 5 | const poolsConfigSchema = z.object({ 6 | WETHAddress: addressSchema.optional(), 7 | factoryAddress: addressSchema, 8 | governanceAddress: addressSchema.optional(), 9 | routerAddress: addressSchema, 10 | votingAddress: addressSchema.optional(), 11 | factories: z.record( 12 | z.string(), 13 | addressSchema, 14 | ) 15 | .transform(makePartial) 16 | .optional(), 17 | pools: z.record( 18 | z.string(), 19 | z.object({ 20 | lpTokenAddress: addressSchema, 21 | minQty: z.number().optional(), 22 | reverted: z.boolean().optional(), 23 | rewardToken: z.string().nullable().optional(), 24 | state: z.number().int().optional(), 25 | rewardTokenDecimals: z.number().int().optional(), 26 | stakingRewardFinish: z.number().optional(), 27 | stakingRewardAddress: addressSchema, 28 | vote_rewards_disabled: z.boolean().optional(), 29 | }), 30 | ).transform(makePartial), 31 | }); 32 | 33 | export default poolsConfigSchema; 34 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/pool-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { evmAddressSchema } from './util-schemas.js'; 3 | 4 | const poolSchema = z.object({ 5 | tokenId: evmAddressSchema, 6 | 7 | token0: z.string().nonempty(), 8 | token1: z.string().nonempty(), 9 | token0Address: evmAddressSchema, 10 | token1Address: evmAddressSchema, 11 | token0Decimals: z.number().int().nonnegative().max(18), 12 | token1Decimals: z.number().int().nonnegative().max(18), 13 | 14 | amount: z.number().nonnegative(), 15 | amount0: z.number().nonnegative(), 16 | amount1: z.number().nonnegative(), 17 | from: z.number().nonnegative(), 18 | to: z.number().nonnegative(), 19 | fee: z.number().nonnegative(), 20 | collectFee: z.number().nonnegative(), 21 | reward: z.number().nonnegative(), 22 | apr: z.number().nonnegative(), 23 | boost: z.number().int().nonnegative(), 24 | isStaked: z.boolean(), 25 | poolFee: z.number().nonnegative(), 26 | poolAddress: evmAddressSchema, 27 | veOrnForMaxBoost: z.number().nonnegative(), 28 | veOrnMaxBoost: z.number().nonnegative(), 29 | veORNCurrent: z.number().nonnegative(), 30 | time: z.number().int().nonnegative(), // tim 31 | }); 32 | 33 | export default poolSchema; 34 | -------------------------------------------------------------------------------- /src/__tests__/pools.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import Orion from '../Orion/index.js'; 3 | import { SERVICE_TOKEN } from '../index.js'; 4 | 5 | const privateKey = process.env['PRIVATE_KEY'] 6 | if (privateKey === undefined) throw new Error('Private key is required'); 7 | 8 | jest.setTimeout(30000); 9 | 10 | describe('Pools', () => { 11 | test(`Add liquidity ${SERVICE_TOKEN}`, async () => { 12 | const orion = new Orion('testing'); 13 | const bscUnit = orion.getUnit('bsc'); 14 | const wallet = new ethers.Wallet( 15 | privateKey, 16 | bscUnit.provider 17 | ); 18 | 19 | await bscUnit.farmingManager.addLiquidity({ 20 | amountAsset: SERVICE_TOKEN, 21 | poolName: `${SERVICE_TOKEN}-USDT`, 22 | amount: 20, 23 | signer: wallet, 24 | }); 25 | }); 26 | 27 | test(`Remove liquidity ${SERVICE_TOKEN}`, async () => { 28 | const orion = new Orion('testing'); 29 | const bscUnit = orion.getUnit('bsc'); 30 | const wallet = new ethers.Wallet( 31 | privateKey, 32 | bscUnit.provider 33 | ); 34 | 35 | await bscUnit.farmingManager.removeAllLiquidity({ 36 | poolName: `${SERVICE_TOKEN}-USDT`, 37 | signer: wallet, 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/utils/convertPrice.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | 3 | export default function convertPrice( 4 | amount: BigNumber.Value, 5 | assetInName: string, 6 | assetOutName: string, 7 | prices: Partial> // quoted in quoteAsset. [name]: priceQuotedInQuoteAsset 8 | ) { 9 | const assetInPrice = prices[assetInName]; 10 | if (assetInPrice === undefined) throw Error(`Price conversion: AssetIn (${assetInName}) price is undefined. Available prices: ${JSON.stringify(prices)}`); 11 | 12 | const assetOutPrice = prices[assetOutName]; 13 | if (assetOutPrice === undefined) throw Error(`Price conversion: AssetOut (${assetOutName}) price is undefined. Available prices: ${JSON.stringify(prices)}`); 14 | 15 | const assetInPriceBN = new BigNumber(assetInPrice); 16 | const assetOutPriceBN = new BigNumber(assetOutPrice); 17 | 18 | const assetInAmountBN = new BigNumber(amount); 19 | 20 | const assetInAmountInQuoteAsset = assetInAmountBN.multipliedBy(assetInPriceBN); 21 | const assetInAmountInQuoteAssetBN = new BigNumber(assetInAmountInQuoteAsset); 22 | 23 | const assetOutAmountInQuoteAsset = assetInAmountInQuoteAssetBN.dividedBy(assetOutPriceBN); 24 | 25 | return assetOutAmountInQuoteAsset; 26 | } 27 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | import preset from 'ts-jest/presets/index.js' 2 | 3 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 4 | export default { 5 | ...preset.defaultsESM, 6 | moduleNameMapper: { 7 | '^(\\.{1,2}/.*)\\.js$': '$1', 8 | }, 9 | modulePathIgnorePatterns: ['lib', 'node_modules'], 10 | testMatch: ['**/__tests__/**/*.test.ts'], 11 | transform: { 12 | '^.+\\.tsx?$': [ 13 | 'ts-jest', 14 | { 15 | isolatedModules: true, 16 | // tsconfig: 'tsconfig.json', 17 | useESM: true, 18 | }, 19 | ], 20 | }, 21 | } 22 | 23 | // const config: JestConfigWithTsJest = { 24 | // extensionsToTreatAsEsm: ['.ts'], 25 | // moduleNameMapper: { 26 | // '^(\\.{1,2}/.*)\\.js$': '$1', 27 | // }, 28 | // preset: 'ts-jest/presets/default-esm', 29 | // testEnvironment: 'node', 30 | // testMatch: ['**/__tests__/**/*.test.ts'], 31 | // modulePathIgnorePatterns: ['lib', 'node_modules'], 32 | // transform: { 33 | // // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest` 34 | // // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest` 35 | // '^.+\\.tsx?$': [ 36 | // 'ts-jest', 37 | // { 38 | // useESM: true, 39 | // }, 40 | // ], 41 | // }, 42 | // }; 43 | // export default config; 44 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export * from './adminPoolSchema.js'; 2 | export { default as adminPoolsListSchema } from './adminPoolsListSchema.js'; 3 | export { default as addPoolSchema } from './addPoolSchema.js'; 4 | export { default as atomicHistorySchema } from './atomicHistorySchema.js'; 5 | export { default as checkRedeemOrderSchema } from './checkRedeemOrderSchema.js'; 6 | export { default as historySchema } from './historySchema.js'; 7 | export { default as IDOSchema } from './IDOSchema.js'; 8 | export { default as infoSchema } from './infoSchema.js'; 9 | export { default as poolsConfigSchema } from './poolsConfigSchema.js'; 10 | export { default as poolsInfoSchema } from './poolsInfoSchema.js'; 11 | export { default as atomicSummarySchema } from './atomicSummarySchema.js'; 12 | export { default as poolsLpAndStakedSchema } from './poolsLpAndStakedSchema.js'; 13 | export { default as userVotesSchema } from './userVotesSchema.js'; 14 | export { default as userEarnedSchema } from './userEarnedSchema.js'; 15 | export { default as poolsV3InfoSchema } from './poolsV3InfoSchema.js'; 16 | export { default as pmmSchema } from './pmmSchema.js'; 17 | export { pricesWithQuoteAssetSchema } from './pricesWithQuoteAssetSchema.js'; 18 | export { referralDataSchema } from './referralDataSchema.js'; -------------------------------------------------------------------------------- /src/constants/gasLimits.ts: -------------------------------------------------------------------------------- 1 | export const DEPOSIT_ETH_GAS_LIMIT = 70000; 2 | export const DEPOSIT_ERC20_GAS_LIMIT = 150000; 3 | export const WITHDRAW_GAS_LIMIT = DEPOSIT_ERC20_GAS_LIMIT; 4 | export const APPROVE_ERC20_GAS_LIMIT = 80000; 5 | export const STAKE_ERC20_GAS_LIMIT = 150000; 6 | export const VOTE_ERC20_GAS_LIMIT = 150000; 7 | export const FILL_ORDERS_GAS_LIMIT = 220000; 8 | export const SWAP_THROUGH_ORION_POOL_GAS_LIMIT = 600000; 9 | export const ADD_LIQUIDITY_GAS_LIMIT = 600000; 10 | export const FARMING_STAKE_GAS_LIMIT = 350000; 11 | export const FARMING_CLAIM_GAS_LIMIT = 350000; 12 | export const FARMING_EXIT_GAS_LIMIT = 500000; 13 | export const FARMING_WITHDRAW_GAS_LIMIT = 350000; 14 | export const GOVERNANCE_GET_REWARD_GAS_LIMIT = 250000; 15 | export const GOVERNANCE_STAKE_GAS_LIMIT = 300000; 16 | export const GOVERNANCE_UNSTAKE_GAS_LIMIT = 250000; 17 | export const GOVERNANCE_VOTE_GAS_LIMIT = 200000; 18 | export const MIGRATE_GAS_LIMIT = 800000; 19 | export const LOCKATOMIC_GAS_LIMIT = 200000; 20 | export const REDEEMATOMIC_GAS_LIMIT = 200000; 21 | export const LIQUIDITY_MIGRATE_GAS_LIMIT = 600000; 22 | 23 | export const DEFAULT_GAS_LIMIT = 700000; 24 | 25 | export const TOKEN_EXCEPTIONS: Record> = { 26 | CUMMIES: { 27 | deposit: 300000, 28 | withdraw: 300000, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@tsconfig/strictest/tsconfig.json", 4 | "@tsconfig/esm/tsconfig.json" 5 | ], 6 | "files": ["./src/index.ts"], 7 | "include": ["./src/**/*.ts"], 8 | "exclude": [ 9 | "node_modules", 10 | // "**/__tests__/*", 11 | "lib" 12 | ], 13 | "compilerOptions": { 14 | "moduleResolution": "node", 15 | "target": "esnext", 16 | "module": "ESNext", 17 | "esModuleInterop": true, 18 | "resolveJsonModule": true /* Enable importing .json files */, 19 | "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, 20 | "sourceMap": true /* Create source map files for emitted JavaScript files. */, 21 | "outDir": "./lib/esm" /* Specify an output folder for all emitted files. */, 22 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 23 | "noUnusedLocals": true /* Enable error reporting when a local variables aren't read. */, 24 | "noUnusedParameters": true /* Raise an error when a function parameter isn't read */, 25 | "noUncheckedIndexedAccess": true, 26 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 27 | }, 28 | "ts-node": { 29 | // Tell ts-node CLI to install the --loader automatically, explained below 30 | "esm": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/getBalances.ts: -------------------------------------------------------------------------------- 1 | import type { Exchange } from '@orionprotocol/contracts/lib/ethers-v6/index.js'; 2 | import type { BigNumber } from 'bignumber.js'; 3 | import type { ethers } from 'ethers'; 4 | import type { Aggregator } from '../services/Aggregator/index.js'; 5 | import getBalance from './getBalance.js'; 6 | 7 | export default async ( 8 | balancesRequired: Partial>, 9 | aggregator: Aggregator, 10 | walletAddress: string, 11 | exchangeContract: Exchange, 12 | provider: ethers.Provider, 13 | ) => { 14 | const balances = await Promise.all( 15 | Object.entries(balancesRequired) 16 | .map(async ([assetName, assetAddress]) => { 17 | if (assetAddress === undefined) throw new Error(`Asset address of ${assetName} not found`); 18 | const balance = await getBalance( 19 | aggregator, 20 | assetName, 21 | assetAddress, 22 | walletAddress, 23 | exchangeContract, 24 | provider, 25 | ); 26 | return { 27 | assetName, 28 | amount: balance, 29 | }; 30 | }), 31 | ); 32 | 33 | return balances.reduce>>((prev, curr) => ({ 37 | ...prev, 38 | [curr.assetName]: curr.amount, 39 | }), {}); 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/generateSecret.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | class XorShift128Plus { 3 | private x: number; 4 | private y: number; 5 | 6 | constructor(seed: number) { 7 | this.x = seed; 8 | this.y = seed ^ 0x6d2b79f5; // 0x6d2b79f5 is the golden ratio 9 | } 10 | 11 | public nextInt32(): number { 12 | let x = this.x; 13 | const y = this.y; 14 | 15 | this.x = y; 16 | x ^= x << 23; 17 | x ^= x >> 17; 18 | x ^= y ^ (y >> 26); 19 | 20 | this.y = x; 21 | return x + y; 22 | } 23 | } 24 | 25 | function generateSeed(): number { 26 | return Math.floor(Date.now() * Math.random()); 27 | } 28 | 29 | function generateRandomBytes(size: number, rng: XorShift128Plus): Uint8Array { 30 | const buffer = new Uint8Array(size); 31 | 32 | for (let i = 0; i < size; i++) { 33 | buffer[i] = rng.nextInt32() & 0xff; 34 | } 35 | 36 | return buffer; 37 | } 38 | 39 | function isomorphicCryptoRandomBytes(size: number): Uint8Array { 40 | const seed = generateSeed(); 41 | const rng = new XorShift128Plus(seed); 42 | return generateRandomBytes(size, rng); 43 | } 44 | 45 | const generateSecret = () => { 46 | const RANDOM_BITS = 256; 47 | const rand = isomorphicCryptoRandomBytes(RANDOM_BITS); 48 | const secret = ethers.keccak256(rand); 49 | return secret; 50 | }; 51 | 52 | export default generateSecret; 53 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as calculateFeeInFeeAsset } from './calculateFeeInFeeAsset.js'; 2 | export { default as calculateNetworkFee } from './calculateNetworkFee.js'; 3 | export { default as calculateNetworkFeeInFeeAsset } from './calculateNetworkFeeInFeeAsset.js'; 4 | export { default as calculateServiceFeeInFeeAsset } from './calculateServiceFeeInFeeAsset.js'; 5 | export { default as checkIsToken } from './checkIsToken.js'; 6 | export { default as generateSecret } from './generateSecret.js'; 7 | export { default as denormalizeNumber } from './denormalizeNumber.js'; 8 | export { default as normalizeNumber } from './normalizeNumber.js'; 9 | export { default as isNetworkCodeInEnvironment } from './isNetworkCodeInEnvironment.js'; 10 | export { default as parseExchangeTradeTransaction } from './parseExchangeTradeTransaction.js'; 11 | export { default as toUpperCase } from './toUpperCase.js'; 12 | export { default as toLowerCase } from './toLowerCase.js'; 13 | export { default as isUppercasedNetworkCode } from './isUppercasedNetworkCode.js'; 14 | export { default as getNativeCryptocurrencyName } from './getNativeCryptocurrencyName.js'; 15 | 16 | export { default as laconicParse } from './laconic-parse.js'; 17 | export { default as isValidChainId } from './isValidChainId.js'; 18 | export { default as isKnownEnv } from './isKnownEnv.js'; 19 | export { default as removeFieldsFromObject } from './removeFieldsFromObject.js'; 20 | // export { default as HttpError } from './httpError'; 21 | 22 | export * from './typeHelpers.js'; 23 | -------------------------------------------------------------------------------- /src/crypt/signCancelOrder.ts: -------------------------------------------------------------------------------- 1 | import type { TypedDataSigner } from '@ethersproject/abstract-signer'; 2 | import { ethers } from 'ethers'; 3 | import CANCEL_ORDER_TYPES from '../constants/cancelOrderTypes.js'; 4 | import type { CancelOrderRequest, SignedCancelOrderRequest, SupportedChainId } from '../types.js'; 5 | import getDomainData from './getDomainData.js'; 6 | 7 | type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner; 8 | 9 | const signCancelOrder = async ( 10 | senderAddress: string, 11 | id: string, 12 | signer: ethers.Signer, 13 | chainId: SupportedChainId, 14 | ) => { 15 | const cancelOrderRequest: CancelOrderRequest = { 16 | id, 17 | senderAddress, 18 | }; 19 | // eslint-disable-next-line @typescript-eslint/consistent-type-assertions 20 | const typedDataSigner = signer as SignerWithTypedDataSign; 21 | 22 | const signature = await typedDataSigner.signTypedData( 23 | getDomainData(chainId), 24 | CANCEL_ORDER_TYPES, 25 | cancelOrderRequest, 26 | ); 27 | 28 | // https://github.com/poap-xyz/poap-fun/pull/62#issue-928290265 29 | // "Signature's v was always send as 27 or 28, but from Ledger was 0 or 1" 30 | const fixedSignature = ethers.Signature.from(signature).serialized; 31 | 32 | // if (!fixedSignature) throw new Error("Can't sign order cancel"); 33 | 34 | const signedCancelOrderReqeust: SignedCancelOrderRequest = { 35 | ...cancelOrderRequest, 36 | signature: fixedSignature, 37 | }; 38 | return signedCancelOrderReqeust; 39 | }; 40 | 41 | export default signCancelOrder; 42 | -------------------------------------------------------------------------------- /src/__tests__/priceFeed.test.ts: -------------------------------------------------------------------------------- 1 | import Orion from '../Orion/index.js'; 2 | import { SERVICE_TOKEN } from '../index.js'; 3 | 4 | describe('Price Feed', () => { 5 | test('Ticker', async () => { 6 | const { unitsArray } = new Orion('testing'); 7 | for (const unit of unitsArray) { 8 | const ticker = `${SERVICE_TOKEN}-USDT`; 9 | await new Promise((resolve, reject) => { 10 | const timeout = setTimeout(() => { 11 | reject(new Error('Timeout')); 12 | }, 10000); 13 | console.log('Subscribing to ticker: ', ticker, ' on network: ', unit.networkCode); 14 | const { unsubscribe } = unit.priceFeed.ws.subscribe('ticker', { 15 | payload: ticker, 16 | callback: () => { 17 | clearTimeout(timeout); 18 | unsubscribe() 19 | resolve(true); 20 | }, 21 | }); 22 | }); 23 | } 24 | }); 25 | 26 | test('Handle error', async () => { 27 | const orion = new Orion('testing'); 28 | const bscUnit = orion.getUnit('bsc') 29 | 30 | await new Promise((resolve, reject) => { 31 | const timeout = setTimeout(() => { 32 | reject(new Error('Timeout')); 33 | }, 10000); 34 | const { unsubscribe } = bscUnit.priceFeed.ws.subscribe('ticker', { 35 | payload: 'SGERGEWRGWERG', 36 | callback: () => null, 37 | errorCallback: (error) => { 38 | expect(error.message).toContain('Can\'t recognize PriceFeed "ticker" subscription message "{"message":"Wrong pair"}"') 39 | clearTimeout(timeout); 40 | unsubscribe() 41 | resolve(true); 42 | } 43 | }) 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /ADVANCED.md: -------------------------------------------------------------------------------- 1 | # Orion Verbose configuration 2 | 3 | ```ts 4 | const orion = new Orion({ 5 | analyticsAPI: "https://analytics-api.orionprotocol.io", 6 | referralAPI: "https://referral-api.orionprotocol.io", 7 | networks: { 8 | 1: { 9 | chainId: SupportedChainId.MAINNET, 10 | nodeJsonRpc: "https://cloudflare-eth.com/", 11 | services: { 12 | blockchainService: { 13 | http: "http://localhost:3000", 14 | }, 15 | aggregator: { 16 | http: "http://localhost:3001/backend", 17 | ws: "http://localhost:3001/v1", 18 | }, 19 | priceFeed: { 20 | api: "http://localhost:3002/price-feed", 21 | }, 22 | }, 23 | }, 24 | }, 25 | }); 26 | 27 | // Also you can set some config as default and override it for some params 28 | const orion = new Orion("testing", { 29 | analyticsAPI: "https://analytics-api.orionprotocol.io", 30 | networks: { 31 | [SupportedChainId.BSC_TESTNET]: { 32 | nodeJsonRpc: "https://data-seed-prebsc-1-s1.binance.org:8545/", 33 | }, 34 | }, 35 | }); 36 | 37 | // Orion unit init 38 | const unit = orion.getUnit("bsc"); 39 | // OR 40 | const unit = orion.getUnit(SupportedChainId.BSC); 41 | // OR 42 | const unit = new Unit({ 43 | chainId: SupportedChainId.BSC, 44 | nodeJsonRpc: "https://bsc-dataseed.binance.org/", 45 | services: { 46 | blockchainService: { 47 | http: "https://orion-bsc-api.orionprotocol.io", 48 | }, 49 | aggregator: { 50 | http: "https://orion-bsc-api.orionprotocol.io/backend", 51 | ws: "https://orion-bsc-api.orionprotocol.io/v1", 52 | }, 53 | priceFeed: { 54 | api: "https://orion-bsc-api.orionprotocol.io/price-feed", 55 | }, 56 | }, 57 | }); 58 | ``` 59 | -------------------------------------------------------------------------------- /src/services/ReferralSystem/schemas/ratingSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const ratingSchema = z.object({ 4 | info: z.object({ 5 | weekly_boost_budget: z.string(), 6 | weekly_boost_budget_fmt: z.number(), 7 | monthly_boost_budget: z.string(), 8 | monthly_boost_budget_fmt: z.number(), 9 | displayed_boost_budget_fmt: z.number(), 10 | time_left_for_the_reward: z.number(), 11 | time_left_for_the_reward_local: z.string(), 12 | time_left_for_the_reward_utc: z.string(), 13 | personal_info: z.object({ 14 | rank_id: z.number(), 15 | wallet: z.string(), 16 | staked_ve_token: z.string(), 17 | staked_ve_token_fmt: z.number(), 18 | staked_ve_token_weight: z.string(), 19 | staked_ve_token_weight_fmt: z.number(), 20 | weighted_volume: z.string(), 21 | weighted_volume_fmt: z.number(), 22 | total_weight: z.string(), 23 | total_weight_fmt: z.number(), 24 | reward: z.string(), 25 | reward_fmt: z.number() 26 | }).nullable(), 27 | }), 28 | list: z.array(z.object({ 29 | rank_id: z.number(), 30 | wallet: z.string(), 31 | staked_ve_token: z.string(), 32 | staked_ve_token_fmt: z.number(), 33 | staked_ve_token_weight: z.string(), 34 | staked_ve_token_weight_fmt: z.number(), 35 | weighted_volume: z.string(), 36 | weighted_volume_fmt: z.number(), 37 | total_weight: z.string(), 38 | total_weight_fmt: z.number(), 39 | total_volume_fmt: z.number(), 40 | weekly_earnings_fmt: z.number(), 41 | total_earnings_fmt: z.number(), 42 | referrals_count_fmt: z.number(), 43 | total_trades_fmt: z.number(), 44 | reward: z.string(), 45 | reward_fmt: z.number() 46 | })), 47 | }); 48 | 49 | export default ratingSchema; 50 | -------------------------------------------------------------------------------- /src/Unit/Exchange/callGenerators/curve.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SwapExecutor__factory, 3 | CurveRegistry__factory, 4 | ERC20__factory, 5 | } from "@orionprotocol/contracts/lib/ethers-v6/index.js"; 6 | import { MaxUint256, type BigNumberish, type JsonRpcProvider } from "ethers"; 7 | import { addCallParams, pathCallWithBalance } from "./utils.js"; 8 | import type { SingleSwap } from "../../../types.js"; 9 | import { generateApproveCall } from "./erc20.js"; 10 | import type { BytesLike } from "ethers"; 11 | 12 | export async function generateCurveStableSwapCall( 13 | amount: BigNumberish, 14 | to: string, 15 | swap: SingleSwap, 16 | provider: JsonRpcProvider, 17 | swapExecutorContractAddress: string, 18 | curveRegistry: string, 19 | pathWithBalance = false 20 | ) { 21 | const executorInterface = SwapExecutor__factory.createInterface(); 22 | const registry = CurveRegistry__factory.connect(curveRegistry, provider); 23 | const { pool, assetIn, assetOut } = swap; 24 | const firstToken = ERC20__factory.connect(assetIn, provider) 25 | const executorAllowance = await firstToken.allowance(swapExecutorContractAddress, pool) 26 | 27 | const calls: BytesLike[] = [] 28 | if (executorAllowance <= BigInt(amount)) { 29 | const approveCall = await generateApproveCall( 30 | assetIn, 31 | pool, 32 | MaxUint256 33 | ); 34 | calls.push(approveCall); 35 | } 36 | 37 | const [i, j] = await registry.get_coin_indices(pool, assetIn, assetOut); 38 | let calldata = executorInterface.encodeFunctionData( 39 | "curveSwapStableAmountIn", 40 | [pool, assetOut, i, j, to, amount] 41 | ); 42 | calldata = addCallParams(calldata) 43 | if (pathWithBalance) { 44 | calldata = pathCallWithBalance(calldata, swap.assetIn) 45 | } 46 | calls.push(calldata) 47 | 48 | return calls 49 | } 50 | -------------------------------------------------------------------------------- /src/utils/calculateFeeInFeeAsset.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { FILL_ORDERS_GAS_LIMIT } from '../constants/index.js'; 3 | import calculateNetworkFeeInFeeAsset from './calculateNetworkFeeInFeeAsset.js'; 4 | import calculateServiceFeeInFeeAsset from './calculateServiceFeeInFeeAsset.js'; 5 | 6 | const calculateFeeInFeeAsset = ( 7 | amount: BigNumber.Value, 8 | gasPriceGwei: BigNumber.Value, 9 | feePercent: BigNumber.Value, 10 | baseAssetName: string, 11 | baseCurrencyName: string, 12 | feeAssetName: string, 13 | prices: Partial>, 14 | ) => { 15 | const feeAssetPrice = prices[feeAssetName]; 16 | if (feeAssetPrice === undefined) throw Error(`Fee asset price not found. Available prices: ${Object.keys(prices).join(', ')}`); 17 | const baseAssetPrice = prices[baseAssetName]; 18 | if (baseAssetPrice === undefined) throw Error(`Base asset price not found. Available prices: ${Object.keys(prices).join(', ')}`); 19 | const baseCurrencyPrice = prices[baseCurrencyName]; // ETH, BNB, MATIC, etc. 20 | if (baseCurrencyPrice === undefined) throw Error(`Base currency price not found. Available prices: ${Object.keys(prices).join(', ')}`); 21 | 22 | const serviceFeeInFeeAsset = calculateServiceFeeInFeeAsset( 23 | amount, 24 | baseAssetName, 25 | feeAssetName, 26 | feePercent, 27 | prices, 28 | ); 29 | const networkFeeInFeeAsset = calculateNetworkFeeInFeeAsset( 30 | gasPriceGwei, 31 | FILL_ORDERS_GAS_LIMIT, 32 | baseCurrencyName, 33 | feeAssetName, 34 | prices, 35 | ); 36 | 37 | return { 38 | serviceFeeInFeeAsset, 39 | networkFeeInFeeAsset, 40 | totalFeeInFeeAsset: new BigNumber(serviceFeeInFeeAsset) 41 | .plus(networkFeeInFeeAsset) 42 | .toString(), 43 | }; 44 | }; 45 | 46 | export default calculateFeeInFeeAsset; 47 | -------------------------------------------------------------------------------- /src/services/PriceFeed/ws/index.ts: -------------------------------------------------------------------------------- 1 | import type WebSocket from 'ws'; 2 | import PriceFeedSubscription, { type SubscriptionType, type Subscription } from './PriceFeedSubscription.js'; 3 | import type { BasicAuthCredentials } from '../../../types.js'; 4 | 5 | export * as schemas from './schemas/index.js'; 6 | export class PriceFeedWS { 7 | private subscriptions: Partial<{ 8 | [K in SubscriptionType]: Partial< 9 | Record< 10 | string, 11 | PriceFeedSubscription 12 | > 13 | >; 14 | }> = {}; 15 | 16 | private readonly url: string; 17 | 18 | readonly basicAuth?: BasicAuthCredentials | undefined; 19 | 20 | constructor(url: string, basicAuth?: BasicAuthCredentials) { 21 | this.url = url; 22 | this.basicAuth = basicAuth; 23 | } 24 | 25 | get api() { 26 | const url = new URL(this.url); 27 | 28 | if (this.basicAuth) { 29 | url.username = this.basicAuth.username; 30 | url.password = this.basicAuth.password; 31 | } 32 | 33 | return url.toString(); 34 | } 35 | 36 | subscribe( 37 | type: S, 38 | params: Subscription, 39 | onOpen?: (event: WebSocket.Event) => void, 40 | ) { 41 | const sub = new PriceFeedSubscription( 42 | type, 43 | this.api, 44 | params, 45 | onOpen 46 | ); 47 | this.subscriptions = { 48 | ...this.subscriptions, 49 | [type]: { 50 | ...this.subscriptions[type], 51 | [sub.id]: sub, 52 | }, 53 | }; 54 | return { 55 | type: sub.type, 56 | id: sub.id, 57 | unsubscribe: () => { this.unsubscribe(sub.type, sub.id); }, 58 | }; 59 | } 60 | 61 | unsubscribe(subType: SubscriptionType, subId: string) { 62 | this.subscriptions[subType]?.[subId]?.kill(); 63 | delete this.subscriptions[subType]?.[subId]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/list-pool-v2-response-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { evmAddressSchema } from './util-schemas.js'; 3 | import basicPoolInfo from './basic-pool-info-schema.js'; 4 | import infoSchema from './info-schema.js'; 5 | 6 | export const listPoolV2Schema = z.object({ 7 | pair: z.string(), 8 | token0: z.string().nonempty(), 9 | token1: z.string().nonempty(), 10 | name: z.string(), 11 | name0: z.string(), 12 | name1: z.string(), 13 | token0Address: evmAddressSchema, 14 | token1Address: evmAddressSchema, 15 | token0Decimals: z.number().int().nonnegative().max(18), 16 | token1Decimals: z.number().int().nonnegative().max(18), 17 | WETH9: evmAddressSchema, 18 | farmAddress: z.string().optional(), 19 | weight: z.number(), 20 | liquidity0: z.number(), 21 | liquidity1: z.number(), 22 | token0Price: z.number(), 23 | token1Price: z.number(), 24 | totalLPSupply: z.number(), 25 | totalLPStake: z.number(), 26 | totalLPStakeInUSD: z.number(), 27 | userLPStaked: z.number(), 28 | userLPStakedInUSD: z.number(), 29 | lpPriceInUSD: z.number(), 30 | lpPriceInORN: z.number(), 31 | userReward: z.number(), 32 | weeklyReward: z.number(), 33 | userAPR: z.number(), 34 | lockMaxMultiplier: z.number(), 35 | veornMaxMultiplier: z.number(), 36 | veornBoostScaleFactor: z.number(), 37 | lockTimeForMaxMultiplier: z.number(), 38 | userBoost: z.number(), 39 | userTimeDeposit: z.number(), 40 | userLockTimeStart: z.number(), 41 | userLockTimePeriod: z.number(), 42 | userVeORN: z.number(), 43 | userORN: z.number(), 44 | userRewardToPool: z.number(), 45 | boostTotalVeORN: z.number(), 46 | boostCurrentPoolReward: z.number(), 47 | boostTotalLiquidity: z.number(), 48 | boostCurrentLiquidity: z.number(), 49 | boostCurrentVeORN: z.number(), 50 | boostTotalReward: z.number(), 51 | 52 | ...basicPoolInfo.shape, 53 | 54 | type: z.string().nonempty(), 55 | }); 56 | 57 | const listPoolV2ResponseSchema = z.object({ 58 | result: z.array(listPoolV2Schema), 59 | info: infoSchema, 60 | }); 61 | 62 | export default listPoolV2ResponseSchema; 63 | -------------------------------------------------------------------------------- /src/utils/safeGetters.ts: -------------------------------------------------------------------------------- 1 | export class SafeArray extends Array { 2 | public static override from(array: ArrayLike): SafeArray { 3 | return new SafeArray(array); 4 | } 5 | 6 | constructor(array: ArrayLike) { 7 | super(array.length); 8 | for (const index in array) { 9 | const value = array[index] 10 | if (value === undefined) { 11 | throw new Error('Array passed to constructor has undefined values') 12 | } 13 | 14 | this[index] = value; 15 | } 16 | } 17 | 18 | public toArray(): T[] { 19 | return [...this]; 20 | } 21 | 22 | public override map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: unknown): SafeArray { 23 | return new SafeArray(super.map(callbackfn, thisArg)); 24 | } 25 | 26 | public override filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: unknown): SafeArray { 27 | return new SafeArray(super.filter(callbackfn, thisArg)); 28 | } 29 | 30 | public get(this: SafeArray, index: number): T { 31 | const value = this.at(index); 32 | if (value === undefined) { 33 | throw new Error(`Element at index ${index} is undefined.`) 34 | } 35 | return value 36 | } 37 | 38 | public last(this: SafeArray): T { 39 | return this.get(this.length - 1) 40 | } 41 | 42 | public first(this: SafeArray): T { 43 | return this.get(0) 44 | } 45 | } 46 | 47 | export function safeGet(obj: Partial>, key: string, errorMessage?: string) { 48 | const value = obj[key]; 49 | if (value === undefined) throw new Error(`Key '${key.toString()}' not found in object. Available keys: ${Object.keys(obj).join(', ')}.${errorMessage ? ` ${errorMessage}` : ''}`); 50 | return value; 51 | } 52 | 53 | const prefix = 'Requirement not met'; 54 | 55 | export function must(condition: unknown, message?: string | (() => string)): asserts condition { 56 | if (condition) return; 57 | const provided = typeof message === 'function' ? message() : message; 58 | const value = provided ? `${prefix}: ${provided}` : prefix; 59 | throw new Error(value); 60 | } 61 | -------------------------------------------------------------------------------- /src/services/Indexer/schemas/pool-v2-info-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { evmAddressSchema } from './util-schemas.js'; 3 | import basicPoolInfo from './basic-pool-info-schema'; 4 | import infoSchema from './info-schema.js'; 5 | 6 | const poolInfoSchema = z.object({ 7 | pair: z.string(), 8 | name: z.string(), 9 | token0: z.string(), 10 | token1: z.string(), 11 | name0: z.string(), 12 | name1: z.string(), 13 | token0Address: evmAddressSchema, 14 | token1Address: evmAddressSchema, 15 | token0Decimals: z.number().int().nonnegative().max(18), 16 | token1Decimals: z.number().int().nonnegative().max(18), 17 | WETH9: z.string(), 18 | farmAddress: z.string().optional(), 19 | weight: z.number(), 20 | liquidity0: z.number(), 21 | liquidity1: z.number(), 22 | token0Price: z.number(), 23 | token1Price: z.number(), 24 | userLPBalance: z.number(), 25 | userLPBalanceStr: z.string(), 26 | totalLPSupply: z.number(), 27 | totalLPStake: z.number(), 28 | totalLPStakeInUSD: z.number(), 29 | userLPStaked: z.number(), 30 | userLPStakedInUSD: z.number(), 31 | lpPriceInUSD: z.number(), 32 | lpPriceInORN: z.number(), 33 | userReward: z.number(), 34 | userWeeklyReward: z.number(), 35 | userRewardToPool: z.number(), 36 | weeklyReward: z.number(), 37 | userAPR: z.number(), 38 | lockMaxMultiplier: z.number(), 39 | veornMaxMultiplier: z.number(), 40 | veornBoostScaleFactor: z.number(), 41 | lockTimeForMaxMultiplier: z.number(), 42 | userBoost: z.number(), 43 | userTimeDeposit: z.number(), 44 | userLockTimeStart: z.number(), 45 | userLockTimePeriod: z.number(), 46 | userVeORN: z.number(), 47 | userORN: z.number(), 48 | boostTotalVeORN: z.number(), 49 | boostCurrentPoolReward: z.number(), 50 | boostTotalLiquidity: z.number(), 51 | boostCurrentLiquidity: z.number(), 52 | boostCurrentVeORN: z.number(), 53 | boostTotalReward: z.number(), 54 | type: z.literal('v2'), 55 | 56 | ...basicPoolInfo.shape, 57 | }); 58 | 59 | const PoolV2InfoResponseSchema = z.object({ 60 | result: poolInfoSchema, 61 | info: infoSchema, 62 | }); 63 | 64 | export default PoolV2InfoResponseSchema; 65 | -------------------------------------------------------------------------------- /src/Unit/Exchange/index.ts: -------------------------------------------------------------------------------- 1 | import type Unit from '../index.js'; 2 | import deposit, { type DepositParams } from './deposit.js'; 3 | import getSwapInfo, { type GetSwapInfoParams } from './getSwapInfo.js'; 4 | import { generateSwapCalldataWithUnit, type GenerateSwapCalldataWithUnitParams } from './generateSwapCalldata.js'; 5 | import withdraw, { type WithdrawParams } from './withdraw.js'; 6 | import type { SwapLimitParams } from './swapLimit.js'; 7 | import swapLimit from './swapLimit.js'; 8 | import swapMarket from './swapMarket.js'; 9 | import type { SwapMarketParams } from './swapMarket.js'; 10 | 11 | type PureDepositParams = Omit 12 | type PureWithdrawParams = Omit 13 | type PureGetSwapMarketInfoParams = Omit 14 | type PureGenerateSwapCalldataParams = Omit 15 | type PureSwapLimitParams = Omit 16 | type PureSwapMarketParams = Omit 17 | 18 | export default class Exchange { 19 | private readonly unit: Unit; 20 | 21 | constructor(unit: Unit) { 22 | this.unit = unit; 23 | } 24 | 25 | public getSwapInfo(params: PureGetSwapMarketInfoParams) { 26 | return getSwapInfo({ 27 | aggregator: this.unit.aggregator, 28 | blockchainService: this.unit.blockchainService, 29 | ...params, 30 | }); 31 | } 32 | 33 | public deposit(params: PureDepositParams) { 34 | return deposit({ 35 | ...params, 36 | unit: this.unit, 37 | }); 38 | } 39 | 40 | public withdraw(params: PureWithdrawParams) { 41 | return withdraw({ 42 | ...params, 43 | unit: this.unit, 44 | }); 45 | } 46 | 47 | public generateSwapCalldata(params: PureGenerateSwapCalldataParams) { 48 | return generateSwapCalldataWithUnit({ 49 | ...params, 50 | unit: this.unit, 51 | }) 52 | } 53 | 54 | public swapLimit(params: PureSwapLimitParams) { 55 | return swapLimit({ 56 | ...params, 57 | unit: this.unit, 58 | }); 59 | } 60 | 61 | public swapMarket(params: PureSwapMarketParams) { 62 | return swapMarket({ 63 | ...params, 64 | unit: this.unit, 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/typeHelpers.ts: -------------------------------------------------------------------------------- 1 | type WithReason = { 2 | reason: string 3 | } 4 | 5 | type WithCodeError = Error & { 6 | code: number | string 7 | } 8 | 9 | type WithMessage = { 10 | message: string 11 | } 12 | 13 | type WithDataError = Error & { 14 | data: Record 15 | } 16 | 17 | type WithError = { 18 | error: Record 19 | } 20 | 21 | export const makePartial = (value: Record): Partial> => value; 22 | 23 | export function isUnknownObject(x: unknown): x is { 24 | [key in PropertyKey]: unknown 25 | } { 26 | return x !== null && typeof x === 'object'; 27 | } 28 | 29 | export function isKeyOfObject( 30 | key: string | number | symbol, 31 | obj: T, 32 | ): key is keyof T { 33 | return key in obj; 34 | } 35 | 36 | export function hasProp, K extends PropertyKey>( 37 | obj: T, 38 | key: K, 39 | ): obj is T & Record { 40 | return key in obj; 41 | } 42 | 43 | export function isWithCode(candidate: unknown): candidate is WithCodeError { 44 | if (!isUnknownObject(candidate) || !hasProp(candidate, 'code')) return false; 45 | const type = typeof candidate.code; 46 | return type === 'number' || type === 'string'; 47 | } 48 | 49 | export function isWithReason(candidate: unknown): candidate is WithReason { 50 | if (!isUnknownObject(candidate)) return false; 51 | const hasReasonProperty = hasProp(candidate, 'reason') && typeof candidate.reason === 'string'; 52 | return hasReasonProperty; 53 | } 54 | 55 | export function isWithMessage(candidate: unknown): candidate is WithMessage { 56 | if (!isUnknownObject(candidate)) return false; 57 | const hasMessageProperty = hasProp(candidate, 'message') && typeof candidate.message === 'string'; 58 | return hasMessageProperty; 59 | } 60 | 61 | export function isWithError(candidate: unknown): candidate is WithError { 62 | if (!isUnknownObject(candidate)) return false; 63 | const hasErrorProperty = hasProp(candidate, 'error') && isUnknownObject(candidate.error); 64 | return hasErrorProperty; 65 | } 66 | 67 | export function isWithData(candidate: unknown): candidate is WithDataError { 68 | if (!isUnknownObject(candidate)) return false; 69 | const hasDataProperty = hasProp(candidate, 'data') && typeof candidate.data === 'object'; 70 | return hasDataProperty; 71 | } 72 | -------------------------------------------------------------------------------- /src/Unit/Exchange/callGenerators/uniswapV2.ts: -------------------------------------------------------------------------------- 1 | import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js" 2 | import { SafeArray } from "../../../utils/safeGetters.js" 3 | import { type BytesLike, type BigNumberish, concat, ethers, toBeHex } from "ethers" 4 | import { addCallParams } from "./utils.js" 5 | import type { SingleSwap } from "../../../types.js" 6 | import { BigNumber } from 'bignumber.js'; 7 | 8 | const BILLION = 1000000000; 9 | const TEN_THOUSANDS = 10000; 10 | 11 | function countScaledFee(fee: string) { 12 | // The count is needed for the swapUniV2Scaled function, where the denominator is one billion 13 | return new BigNumber(fee).multipliedBy(BILLION).div(TEN_THOUSANDS).toNumber(); 14 | } 15 | 16 | export async function generateUni2Calls( 17 | path: SafeArray, 18 | recipient: string 19 | ) { 20 | const executorInterface = SwapExecutor__factory.createInterface() 21 | const calls: BytesLike[] = [] 22 | if (path.length > 1) { 23 | for (let i = 0; i < path.length - 1; ++i) { 24 | const currentSwap = path.get(i) 25 | const nextSwap = path.get(i + 1) 26 | 27 | const call = await generateUni2Call( 28 | currentSwap.pool, 29 | currentSwap.assetIn, 30 | currentSwap.assetOut, 31 | nextSwap.pool, 32 | currentSwap.fee 33 | ) 34 | calls.push(call) 35 | } 36 | } 37 | 38 | const lastSwap = path.last(); 39 | const fee = lastSwap.fee ?? 30; 40 | const scaledFee = countScaledFee(fee.toString()); 41 | const calldata = executorInterface.encodeFunctionData('swapUniV2Scaled', [ 42 | lastSwap.pool, 43 | lastSwap.assetIn, 44 | lastSwap.assetOut, 45 | ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(scaledFee), recipient])]), 46 | ]) 47 | calls.push(addCallParams(calldata)) 48 | 49 | return calls 50 | } 51 | 52 | export function generateUni2Call( 53 | pool: string, 54 | assetIn: string, 55 | assetOut: string, 56 | recipient: string, 57 | fee: BigNumberish = 30, 58 | ) { 59 | const executorInterface = SwapExecutor__factory.createInterface() 60 | const scaledFee = countScaledFee(fee.toString()); 61 | const calldata = executorInterface.encodeFunctionData('swapUniV2Scaled', [ 62 | pool, 63 | assetIn, 64 | assetOut, 65 | ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(scaledFee), recipient])]), 66 | ]) 67 | return addCallParams(calldata) 68 | } 69 | -------------------------------------------------------------------------------- /src/Unit/Pmm/abi/OrionRFQ.ts: -------------------------------------------------------------------------------- 1 | export const orionRFQContractABI = 2 | [ 3 | { 4 | "inputs": [ 5 | { 6 | "components": [ 7 | { 8 | "internalType": "uint256", 9 | "name": "info", 10 | "type": "uint256" 11 | }, 12 | { 13 | "internalType": "address", 14 | "name": "makerAsset", 15 | "type": "address" 16 | }, 17 | { 18 | "internalType": "address", 19 | "name": "takerAsset", 20 | "type": "address" 21 | }, 22 | { 23 | "internalType": "address", 24 | "name": "maker", 25 | "type": "address" 26 | }, 27 | { 28 | "internalType": "address", 29 | "name": "allowedSender", 30 | "type": "address" 31 | }, 32 | { 33 | "internalType": "uint256", 34 | "name": "makingAmount", 35 | "type": "uint256" 36 | }, 37 | { 38 | "internalType": "uint256", 39 | "name": "takingAmount", 40 | "type": "uint256" 41 | } 42 | ], 43 | "internalType": "struct OrderRFQLib.OrderRFQ", 44 | "name": "order", 45 | "type": "tuple" 46 | }, 47 | { 48 | "internalType": "bytes", 49 | "name": "signature", 50 | "type": "bytes" 51 | }, 52 | { 53 | "internalType": "uint256", 54 | "name": "flagsAndAmount", 55 | "type": "uint256" 56 | } 57 | ], 58 | "name": "fillOrderRFQ", 59 | "outputs": [], 60 | "stateMutability": "nonpayable", 61 | "type": "function" 62 | } 63 | ]; 64 | -------------------------------------------------------------------------------- /src/services/BlockchainService/schemas/atomicHistorySchema.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import { z } from 'zod'; 3 | import getValidArrayItemsSchema from '../../../utils/getValidArrayItems.js'; 4 | 5 | const baseAtomicHistorySchema = z.object({ 6 | success: z.boolean(), 7 | count: z.number(), 8 | total: z.number(), 9 | pagination: z.object({}), 10 | }); 11 | 12 | const baseAtomicHistoryItem = z.object({ 13 | used: z.boolean(), 14 | claimed: z.boolean(), 15 | isAggApplied: z.boolean(), 16 | _id: z.string(), 17 | __v: z.number(), 18 | asset: z.string(), 19 | sender: z.string().refine(ethers.isAddress), 20 | secretHash: z.string().refine(ethers.isHexString), 21 | receiver: z.string().refine(ethers.isAddress).optional(), 22 | secret: z.string().optional(), 23 | }); 24 | 25 | const sourceAtomicHistorySchemaItem = baseAtomicHistoryItem.extend({ 26 | type: z.literal('source'), 27 | amountToReceive: z.number().optional(), 28 | amountToSpend: z.number().optional(), 29 | timestamp: z.object({ 30 | lock: z.number().optional(), 31 | claim: z.number().optional(), 32 | refund: z.number().optional(), 33 | }).optional(), 34 | expiration: z.object({ 35 | lock: z.number().optional(), 36 | }).optional(), 37 | state: z.enum(['BEFORE-LOCK', 'LOCKED', 'REFUNDED', 'CLAIMED']).optional(), 38 | targetChainId: z.number(), 39 | transactions: z.object({ 40 | lock: z.string().optional(), 41 | claim: z.string().optional(), 42 | refund: z.string().optional(), 43 | }).optional(), 44 | }); 45 | 46 | const targetAtomicHistorySchemaItem = baseAtomicHistoryItem.extend({ 47 | type: z.literal('target'), 48 | timestamp: z.object({ 49 | redeem: z.number().optional(), 50 | }).optional(), 51 | expiration: z.object({ 52 | redeem: z.number().optional(), 53 | }).optional(), 54 | state: z.enum(['BEFORE-REDEEM', 'REDEEMED']).optional(), 55 | transactions: z.object({ 56 | redeem: z.string().optional(), 57 | }).optional(), 58 | }); 59 | 60 | export const sourceAtomicHistorySchema = baseAtomicHistorySchema.extend({ 61 | data: z.array(sourceAtomicHistorySchemaItem), 62 | }); 63 | 64 | export const targetAtomicHistorySchema = baseAtomicHistorySchema.extend({ 65 | data: z.array(targetAtomicHistorySchemaItem), 66 | }); 67 | 68 | const atomicHistorySchema = baseAtomicHistorySchema.extend({ 69 | data: getValidArrayItemsSchema( 70 | z.discriminatedUnion('type', [sourceAtomicHistorySchemaItem, targetAtomicHistorySchemaItem]), 71 | ), 72 | }); 73 | 74 | export default atomicHistorySchema; 75 | -------------------------------------------------------------------------------- /src/crypt/signOrder.ts: -------------------------------------------------------------------------------- 1 | import type { TypedDataSigner } from '@ethersproject/abstract-signer'; 2 | import { BigNumber } from 'bignumber.js'; 3 | import { ethers } from 'ethers'; 4 | import { INTERNAL_PROTOCOL_PRECISION } from '../constants/index.js'; 5 | import ORDER_TYPES from '../constants/orderTypes.js'; 6 | import type { Order, SignedOrder, SupportedChainId } from '../types.js'; 7 | import normalizeNumber from '../utils/normalizeNumber.js'; 8 | import getDomainData from './getDomainData.js'; 9 | import hashOrder from './hashOrder.js'; 10 | 11 | const DEFAULT_EXPIRATION = 29 * 24 * 60 * 60 * 1000; // 29 days 12 | 13 | type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner; 14 | 15 | export const signOrder = async ( 16 | baseAssetAddr: string, 17 | quoteAssetAddr: string, 18 | side: 'BUY' | 'SELL', 19 | price: BigNumber.Value, 20 | amount: BigNumber.Value, 21 | matcherFee: BigNumber.Value, 22 | senderAddress: string, 23 | matcherAddress: string, 24 | serviceFeeAssetAddr: string, 25 | signer: ethers.Signer, 26 | chainId: SupportedChainId, 27 | ) => { 28 | const nonce = Date.now(); 29 | const expiration = nonce + DEFAULT_EXPIRATION; 30 | 31 | const order: Order = { 32 | senderAddress, 33 | matcherAddress, 34 | baseAsset: baseAssetAddr, 35 | quoteAsset: quoteAssetAddr, 36 | matcherFeeAsset: serviceFeeAssetAddr, 37 | amount: Number(normalizeNumber( 38 | amount, 39 | INTERNAL_PROTOCOL_PRECISION, 40 | BigNumber.ROUND_FLOOR, 41 | )), 42 | price: Number(normalizeNumber( 43 | price, 44 | INTERNAL_PROTOCOL_PRECISION, 45 | BigNumber.ROUND_FLOOR, 46 | )), 47 | matcherFee: Number(normalizeNumber( 48 | matcherFee, 49 | INTERNAL_PROTOCOL_PRECISION, 50 | BigNumber.ROUND_CEIL, // ROUND_CEIL because we don't want get "not enough fee" error 51 | )), 52 | nonce, 53 | expiration, 54 | buySide: side === 'BUY' ? 1 : 0, 55 | }; 56 | 57 | // eslint-disable-next-line @typescript-eslint/consistent-type-assertions 58 | const typedDataSigner = signer as SignerWithTypedDataSign; 59 | 60 | const signature = await typedDataSigner.signTypedData( 61 | getDomainData(chainId), 62 | ORDER_TYPES, 63 | order, 64 | ); 65 | 66 | // https://github.com/poap-xyz/poap-fun/pull/62#issue-928290265 67 | // "Signature's v was always send as 27 or 28, but from Ledger was 0 or 1" 68 | const fixedSignature = ethers.Signature.from(signature).serialized; 69 | 70 | // if (!fixedSignature) throw new Error("Can't sign order"); 71 | 72 | const signedOrder: SignedOrder = { 73 | ...order, 74 | id: hashOrder(order), 75 | signature: fixedSignature, 76 | }; 77 | return signedOrder; 78 | }; 79 | 80 | export default signOrder; 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | lib 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # TernJS port file 121 | .tern-port 122 | 123 | # Stores VSCode versions used for testing VSCode extensions 124 | .vscode-test 125 | #idea service files 126 | .idea 127 | # yarn v2 128 | .yarn/cache 129 | .yarn/unplugged 130 | .yarn/build-state.yml 131 | .yarn/install-state.gz 132 | .pnp.* 133 | 134 | src/artifacts/contracts/ 135 | .DS_store -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/swapInfoSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const orderInfoSchema = z.object({ 4 | assetPair: z.string().toUpperCase(), 5 | side: z.enum(['BUY', 'SELL']), 6 | amount: z.number(), 7 | safePrice: z.number(), 8 | }).nullable(); 9 | 10 | const exchangeContractStep = z.object({ 11 | pool: z.string(), 12 | assetIn: z.string(), 13 | assetOut: z.string(), 14 | factory: z.string(), 15 | assetAddressIn: z.string(), 16 | assetAddressOut: z.string(), 17 | }); 18 | 19 | const swapInfoBase = z.object({ 20 | id: z.string(), 21 | amountIn: z.number(), 22 | amountOut: z.number(), 23 | assetIn: z.string().toUpperCase(), 24 | assetOut: z.string().toUpperCase(), 25 | path: z.array(z.string()), 26 | // isThroughPoolOptimal: z.boolean(), // deprecated 27 | executionInfo: z.string(), 28 | orderInfo: orderInfoSchema, 29 | exchanges: z.array(z.string()), 30 | price: z.number().nullable(), // spending asset price 31 | minAmountOut: z.number(), 32 | minAmountIn: z.number(), 33 | marketPrice: z.number().nullable(), // spending asset market price 34 | exchangeContractPath: z.array(exchangeContractStep), 35 | alternatives: z.object({ // execution alternatives 36 | exchanges: z.array(z.string()), 37 | path: z.array(z.string()), 38 | marketAmountOut: z.number().nullable(), 39 | marketAmountIn: z.number().nullable(), 40 | marketPrice: z.number(), 41 | availableAmountIn: z.number().nullable(), 42 | availableAmountOut: z.number().nullable(), 43 | orderInfo: orderInfoSchema, 44 | isThroughPoolOrCurve: z.boolean(), 45 | }).array(), 46 | assetNameMapping: z.record(z.string()).optional(), // address to ERC20 names 47 | usd: z.object({ // USD info of this swap, nullable 48 | aa: z.number().optional(), // available amount in, USD 49 | aao: z.number().optional(), // available amount out, USD 50 | mo: z.number().optional(), // market amount out, USD 51 | mi: z.number().optional(), // market amount in, USD 52 | d: z.string().optional(), // difference in available amount in/out (USD) and market amount out/in (USD) in percentage 53 | }).optional(), 54 | autoSlippage: z.number().optional(), 55 | }); 56 | 57 | const swapInfoByAmountIn = swapInfoBase.extend({ 58 | availableAmountOut: z.null(), 59 | availableAmountIn: z.number(), 60 | marketAmountOut: z.number().nullable(), 61 | marketAmountIn: z.null(), 62 | }).transform((val) => ({ 63 | ...val, 64 | isTradeBuy: false as const, 65 | })); 66 | 67 | const swapInfoByAmountOut = swapInfoBase.extend({ 68 | availableAmountOut: z.number(), 69 | availableAmountIn: z.null(), 70 | marketAmountOut: z.null(), 71 | marketAmountIn: z.number().nullable(), 72 | }).transform((val) => ({ 73 | ...val, 74 | isTradeBuy: true as const, 75 | })); 76 | 77 | const swapInfoSchema = swapInfoByAmountIn.or(swapInfoByAmountOut); 78 | 79 | export default swapInfoSchema; 80 | -------------------------------------------------------------------------------- /src/services/Frontage/index.ts: -------------------------------------------------------------------------------- 1 | import { fetchWithValidation } from 'simple-typed-fetch'; 2 | import { tickersSchema } from './schemas'; 3 | import type { TickersBaseSearchParams, TickersCategories } from '../../types'; 4 | 5 | export class Frontage { 6 | private readonly apiUrl: string; 7 | 8 | constructor(apiUrl: string) { 9 | this.apiUrl = apiUrl; 10 | } 11 | 12 | searchTickers = ({ 13 | searchValue, 14 | currentNetwork, 15 | targetNetwork, 16 | sortBy, 17 | sortType, 18 | offset, 19 | limit, 20 | }: { searchValue: string } & TickersBaseSearchParams) => { 21 | const url = new URL(this.apiUrl); 22 | const params = new URLSearchParams(); 23 | 24 | params.set('searchValue', encodeURIComponent(searchValue)); 25 | if (currentNetwork !== undefined) params.set('currentNetwork', encodeURIComponent(currentNetwork).toUpperCase()); 26 | if (targetNetwork !== undefined) params.set('targetNetwork', encodeURIComponent(targetNetwork).toUpperCase()); 27 | if (sortBy !== undefined) params.set('sortBy', encodeURIComponent(sortBy)); 28 | if (sortType !== undefined) params.set('sortType', encodeURIComponent(sortType)); 29 | if (offset !== undefined) params.set('offset', offset.toString()); 30 | if (limit !== undefined) params.set('limit', limit.toString()); 31 | 32 | url.pathname += '/api/v1/tickers/search'; 33 | url.search = params.toString(); 34 | 35 | return fetchWithValidation( 36 | url.toString(), 37 | tickersSchema 38 | ); 39 | }; 40 | 41 | getTickers = ({ 42 | category, 43 | currentNetwork, 44 | targetNetwork, 45 | sortBy, 46 | sortType, 47 | offset, 48 | limit, 49 | tickers, 50 | }: { category: TickersCategories, tickers?: string } & TickersBaseSearchParams) => { 51 | const url = new URL(this.apiUrl); 52 | const params = new URLSearchParams(); 53 | 54 | if (category === 'FAVORITES' && tickers !== undefined) params.set('tickers', tickers); 55 | if (category !== 'FAVORITES') params.set('category', category); 56 | if (currentNetwork !== undefined) params.set('currentNetwork', encodeURIComponent(currentNetwork).toUpperCase()); 57 | if (targetNetwork !== undefined) params.set('targetNetwork', encodeURIComponent(targetNetwork).toUpperCase()); 58 | if (sortBy !== undefined) params.set('sortBy', encodeURIComponent(sortBy)); 59 | if (sortType !== undefined) params.set('sortType', encodeURIComponent(sortType)); 60 | if (offset !== undefined) params.set('offset', offset.toString()); 61 | if (limit !== undefined) params.set('limit', limit.toString()); 62 | 63 | if (category === 'FAVORITES' && tickers !== undefined) { 64 | url.pathname += '/api/v1/tickers/get/favourites'; 65 | } else { 66 | url.pathname += '/api/v1/tickers/get/category'; 67 | } 68 | 69 | url.search = params.toString(); 70 | 71 | return fetchWithValidation( 72 | url.toString(), 73 | tickersSchema 74 | ); 75 | }; 76 | } 77 | 78 | export * as schemas from './schemas/index.js'; 79 | -------------------------------------------------------------------------------- /src/__tests__/trading.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import Orion from '../Orion/index.js'; 3 | import swapMarket from '../Unit/Exchange/swapMarket.js'; 4 | import { SERVICE_TOKEN } from '../index.js'; 5 | 6 | const privateKey = process.env['PRIVATE_KEY'] 7 | if (privateKey === undefined) throw new Error('Private key is required'); 8 | 9 | jest.setTimeout(240000); 10 | 11 | describe('Spot trading', () => { 12 | test('Sell. Simple', async () => { 13 | const orion = new Orion('testing'); 14 | const bscUnit = orion.getUnit('bsc'); 15 | const wallet = new ethers.Wallet( 16 | privateKey, 17 | bscUnit.provider 18 | ); 19 | 20 | const result = await swapMarket({ 21 | assetIn: SERVICE_TOKEN, 22 | assetOut: 'USDT', 23 | amount: 20, 24 | type: 'exactSpend', 25 | signer: wallet, 26 | feeAsset: 'USDT', 27 | unit: bscUnit, 28 | slippagePercent: 1, 29 | // options: { 30 | // logger: console.log 31 | // } 32 | }) 33 | await result.wait(); 34 | }); 35 | 36 | test('Buy. Simple', async () => { 37 | const orion = new Orion('testing'); 38 | const bscUnit = orion.getUnit('bsc'); 39 | const wallet = new ethers.Wallet( 40 | privateKey, 41 | bscUnit.provider 42 | ); 43 | 44 | const result = await bscUnit.exchange.swapMarket({ 45 | assetIn: 'USDT', 46 | assetOut: SERVICE_TOKEN, 47 | amount: 20, 48 | type: 'exactReceive', 49 | signer: wallet, 50 | feeAsset: 'USDT', 51 | slippagePercent: 1, 52 | // options: { 53 | // logger: console.log 54 | // } 55 | }) 56 | await result.wait(); 57 | }); 58 | 59 | test('Buy. Complex', async () => { 60 | const orion = new Orion('testing'); 61 | const bscUnit = orion.getUnit('bsc'); 62 | const wallet = new ethers.Wallet( 63 | privateKey, 64 | bscUnit.provider 65 | ); 66 | 67 | const resultExactSpend = await bscUnit.exchange.swapMarket({ 68 | assetIn: 'USDT', 69 | assetOut: 'BNB', 70 | amount: 40, 71 | type: 'exactSpend', 72 | signer: wallet, 73 | feeAsset: 'USDT', 74 | slippagePercent: 1, 75 | // options: { 76 | // logger: console.log 77 | // } 78 | }) 79 | await resultExactSpend.wait(); 80 | 81 | const resultExactReceive = await bscUnit.exchange.swapMarket({ 82 | assetIn: 'BNB', 83 | assetOut: 'BTC', 84 | amount: resultExactSpend.amountOut.toPrecision(3), 85 | type: 'exactSpend', 86 | signer: wallet, 87 | feeAsset: 'USDT', 88 | slippagePercent: 1, 89 | options: { 90 | logger: console.log 91 | } 92 | }); 93 | await resultExactReceive.wait(); 94 | 95 | // Return back to USDT 96 | const returnBackUsdt = await bscUnit.exchange.swapMarket({ 97 | amount: 40, 98 | assetIn: 'BTC', 99 | assetOut: 'USDT', 100 | type: 'exactReceive', 101 | signer: wallet, 102 | feeAsset: 'USDT', 103 | slippagePercent: 1, 104 | }); 105 | await returnBackUsdt.wait(); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/swapInfoSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import MessageType from '../MessageType.js'; 3 | import baseMessageSchema from './baseMessageSchema.js'; 4 | import factories from '../../../../constants/factories.js'; 5 | 6 | const alternativeSchema = z.object({ // execution alternatives 7 | e: z.string().array(), // exchanges 8 | ps: z.string().array(), // path 9 | mo: z.number().optional(), // market amount out 10 | mi: z.number().optional(), // market amount in 11 | mp: z.number(), // market price 12 | aa: z.number().optional(), // available amount in 13 | aao: z.number().optional(), // available amount out 14 | }); 15 | const factorySchema = z.enum(factories); 16 | const swapInfoSchemaBase = baseMessageSchema.extend({ 17 | T: z.literal(MessageType.SWAP_INFO), 18 | S: z.string(), // swap request id 19 | ai: z.string().toUpperCase(), // asset in, 20 | ao: z.string().toUpperCase(), // asset out 21 | a: z.number(), // amount in 22 | o: z.number(), // amount out 23 | ma: z.number(), // min amount in 24 | mao: z.number(), // min amount out 25 | ps: z.string().array(), // path 26 | po: z.boolean(), // is swap through pool optimal 27 | e: z.string().array().optional(), // Exchanges 28 | p: z.number().optional(), // price 29 | mp: z.number().optional(), // market price 30 | oi: z.object({ // info about order equivalent to this swap 31 | p: z.string().toUpperCase(), // asset pair 32 | s: z.enum(['SELL', 'BUY']), // side 33 | a: z.number(), // amount 34 | sp: z.number(), // safe price (with safe deviation but without slippage) 35 | }).optional(), 36 | as: alternativeSchema.array(), 37 | anm: z.record(z.string()).optional(), // address to ERC20 names 38 | eps: z.array(z.object({ 39 | p: z.string(), // pool address 40 | ai: z.string().toUpperCase(), // asset in 41 | ao: z.string().toUpperCase(), // asset out 42 | f: factorySchema, // factory 43 | aai: z.string(), // asset address in 44 | aao: z.string(), // asset address out 45 | fee: z.number().optional(), // fee 46 | })), 47 | usd: z.object({ // USD info of this swap, nullable 48 | aa: z.number().optional(), // available amount in, USD 49 | aao: z.number().optional(), // available amount out, USD 50 | mo: z.number().optional(), // market amount out, USD 51 | mi: z.number().optional(), // market amount in, USD 52 | d: z.string().optional(), // difference in available amount in/out (USD) and market amount out/in (USD) in percentage 53 | }).optional(), 54 | sl: z.number().optional(), 55 | }); 56 | 57 | const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({ 58 | mo: z.number().optional(), // market amount out 59 | aa: z.number(), // available amount in 60 | }).transform((content) => ({ 61 | ...content, 62 | tb: false as const, // isTradeBuy 63 | })); 64 | 65 | const swapInfoSchemaByAmountOut = swapInfoSchemaBase.extend({ 66 | mi: z.number().optional(), // market amount in 67 | aao: z.number(), // available amount out 68 | }).transform((content) => ({ 69 | ...content, 70 | tb: true as const, // isTradeBuy 71 | })); 72 | 73 | const swapInfoSchema = z.union([ 74 | swapInfoSchemaByAmountIn, 75 | swapInfoSchemaByAmountOut, 76 | ]); 77 | 78 | export default swapInfoSchema; 79 | -------------------------------------------------------------------------------- /src/Unit/Pmm/index.ts: -------------------------------------------------------------------------------- 1 | import type Unit from '../index'; 2 | import { z } from 'zod'; 3 | import {pmmOrderSchema} from "./schemas/order"; 4 | import {simpleFetch} from "simple-typed-fetch"; 5 | import {ethers, Wallet} from "ethers"; 6 | import {BigNumber} from "bignumber.js"; 7 | import { ERC20__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js'; 8 | import {orionRFQContractABI} from "./abi/OrionRFQ"; 9 | 10 | export default class Pmm { 11 | private readonly unit: Unit; 12 | private readonly provider: ethers.Provider; 13 | private contractAddress: string; 14 | 15 | constructor(unit: Unit) { 16 | this.unit = unit; 17 | this.provider = unit.provider; 18 | this.contractAddress = ''; 19 | // this.contractAddress = '0x89357522c0ed6e557d39dc75290859246077bdfc'; 20 | } 21 | 22 | private isInitialized() : boolean { 23 | return this.contractAddress !== ''; 24 | } 25 | 26 | public async init() { 27 | if(this.isInitialized()) 28 | return; 29 | const { orionPMMRouterContractAddress } = await simpleFetch(this.unit.blockchainService.getPmmInfo)(); 30 | this.contractAddress = orionPMMRouterContractAddress; 31 | } 32 | 33 | public async getContractAddress() { 34 | await this.init(); 35 | return this.contractAddress; 36 | } 37 | 38 | public async setAllowance(token: string, amount: string, signer: Wallet) { 39 | await this.init(); 40 | 41 | const bnTargetAmount = new BigNumber(amount); 42 | const walletAddress = await signer.getAddress(); 43 | 44 | const tokenContract = ERC20__factory 45 | .connect(token, this.unit.provider); 46 | 47 | const unsignedApproveTx = await tokenContract 48 | .approve.populateTransaction( 49 | this.contractAddress, 50 | bnTargetAmount.toString() 51 | ); 52 | const nonce = await this.provider.getTransactionCount(walletAddress, 'pending'); 53 | const { gasPrice, maxFeePerGas } = await this.provider.getFeeData(); 54 | const network = await this.provider.getNetwork(); 55 | 56 | if (gasPrice !== null) 57 | unsignedApproveTx.gasPrice = gasPrice; 58 | 59 | if(maxFeePerGas !== null) 60 | unsignedApproveTx.maxFeePerGas = maxFeePerGas; 61 | 62 | unsignedApproveTx.chainId = network.chainId; 63 | unsignedApproveTx.nonce = nonce; 64 | unsignedApproveTx.from = walletAddress; 65 | const gasLimit = await this.provider.estimateGas(unsignedApproveTx); 66 | unsignedApproveTx.gasLimit = gasLimit; 67 | 68 | const signedTx = await signer.signTransaction(unsignedApproveTx); 69 | const txResponse = await this.provider.broadcastTransaction(signedTx); 70 | await txResponse.wait(); 71 | } 72 | 73 | public async fillRFQOrder(order : z.infer, signer: Wallet) { 74 | await this.init(); 75 | 76 | if(!order.success) 77 | throw Error("Invalid order provided"); 78 | 79 | const contract = new ethers.Contract(this.contractAddress, orionRFQContractABI, signer); 80 | 81 | // @ts-ignore 82 | return contract.fillOrderRFQ(order.order, order.signature, BigInt(0)); 83 | } 84 | } -------------------------------------------------------------------------------- /src/services/PriceFeed/index.ts: -------------------------------------------------------------------------------- 1 | import { fetchWithValidation } from 'simple-typed-fetch'; 2 | import type { BasicAuthCredentials } from '../../types.js'; 3 | import { allTickersSchema, statisticsOverviewSchema, topPairsStatisticsSchema } from './schemas/index.js'; 4 | import candlesSchema from './schemas/candlesSchema.js'; 5 | import { PriceFeedWS } from './ws/index.js'; 6 | 7 | class PriceFeed { 8 | private readonly apiUrl: string; 9 | 10 | private readonly basicAuth?: BasicAuthCredentials | undefined; 11 | 12 | readonly ws: PriceFeedWS; 13 | 14 | get api() { 15 | return this.apiUrl; 16 | } 17 | 18 | constructor(apiUrl: string, basicAuth?: BasicAuthCredentials) { 19 | this.apiUrl = apiUrl; 20 | this.ws = new PriceFeedWS(this.wsUrl); 21 | this.basicAuth = basicAuth; 22 | 23 | this.getCandles = this.getCandles.bind(this); 24 | this.getStatisticsOverview = this.getStatisticsOverview.bind(this); 25 | this.getTopPairStatistics = this.getTopPairStatistics.bind(this); 26 | this.getAllTickers = this.getAllTickers.bind(this); 27 | } 28 | 29 | get basicAuthHeaders() { 30 | if (this.basicAuth) { 31 | return { 32 | Authorization: `Basic ${btoa(`${this.basicAuth.username}:${this.basicAuth.password}`)}`, 33 | }; 34 | } 35 | return {}; 36 | } 37 | 38 | getCandles = ( 39 | symbol: string, 40 | timeStart: number, 41 | timeEnd: number, 42 | interval: '5m' | '30m' | '1h' | '1d', 43 | exchange = 'all' 44 | ) => { 45 | const url = new URL(this.candlesUrl); 46 | url.searchParams.append('symbol', symbol); 47 | url.searchParams.append('timeStart', timeStart.toString()); 48 | url.searchParams.append('timeEnd', timeEnd.toString()); 49 | url.searchParams.append('interval', interval); 50 | url.searchParams.append('exchange', exchange); 51 | 52 | return fetchWithValidation( 53 | url.toString(), 54 | candlesSchema, 55 | { headers: this.basicAuthHeaders } 56 | ); 57 | }; 58 | 59 | getStatisticsOverview = (exchange: string | 'ALL' = 'ALL') => { 60 | const url = new URL(`${this.statisticsUrl}/overview`); 61 | url.searchParams.append('exchange', exchange); 62 | 63 | return fetchWithValidation( 64 | url.toString(), 65 | statisticsOverviewSchema, 66 | { headers: this.basicAuthHeaders } 67 | ); 68 | } 69 | 70 | getTopPairStatistics = (exchange: string | 'ALL' = 'ALL') => { 71 | const url = new URL(`${this.statisticsUrl}/top-pairs`); 72 | url.searchParams.append('exchange', exchange); 73 | 74 | return fetchWithValidation( 75 | url.toString(), 76 | topPairsStatisticsSchema, 77 | { headers: this.basicAuthHeaders } 78 | ); 79 | } 80 | 81 | getAllTickers = () => { 82 | return fetchWithValidation( 83 | `${this.tickersUrl}/all`, 84 | allTickersSchema, 85 | { headers: this.basicAuthHeaders } 86 | ); 87 | } 88 | 89 | get wsUrl() { 90 | const url = new URL(this.apiUrl); 91 | const wsProtocol = url.protocol === 'https:' ? 'wss' : 'ws'; 92 | return `${wsProtocol}://${url.host + url.pathname}/api/v1`; 93 | } 94 | 95 | get candlesUrl() { 96 | return `${this.apiUrl}/api/v1/candles`; 97 | } 98 | 99 | get statisticsUrl() { 100 | return `${this.apiUrl}/api/v1/statistics`; 101 | } 102 | 103 | get tickersUrl() { 104 | return `${this.apiUrl}/api/v1/ticker`; 105 | } 106 | } 107 | 108 | export * as schemas from './schemas/index.js'; 109 | export * as ws from './ws/index.js'; 110 | export { PriceFeed }; 111 | -------------------------------------------------------------------------------- /src/Unit/Exchange/callGenerators/uniswapV3.ts: -------------------------------------------------------------------------------- 1 | import { SwapExecutor__factory, UniswapV3Pool__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js" 2 | import { type BigNumberish , ethers, JsonRpcProvider } from "ethers" 3 | import { SafeArray } from "../../../utils/safeGetters.js" 4 | import { addCallParams } from "./utils.js" 5 | import type { SingleSwap } from "../../../types.js" 6 | 7 | export async function generateUni3Call( 8 | swap: SingleSwap, 9 | amount: BigNumberish | undefined, 10 | recipient: string, 11 | provider: JsonRpcProvider 12 | ) { 13 | if (typeof amount === 'undefined') amount = 0 14 | 15 | const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider) 16 | const executorInterface = SwapExecutor__factory.createInterface() 17 | let calldata = executorInterface.encodeFunctionData('uniswapV3SingleSwapTo', [encodedPool, recipient, amount]) 18 | 19 | return addCallParams(calldata) 20 | } 21 | 22 | export async function generateOrion3Call( 23 | swap: SingleSwap, 24 | amount: BigNumberish | undefined, 25 | recipient: string, 26 | provider: JsonRpcProvider 27 | ) { 28 | if (amount === undefined) amount = 0 29 | 30 | const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider) 31 | const executorInterface = SwapExecutor__factory.createInterface() 32 | const calldata = executorInterface.encodeFunctionData('orionV3SingleSwapTo', [encodedPool, recipient, amount]) 33 | 34 | return addCallParams(calldata) 35 | } 36 | 37 | export async function generateUni3Calls( 38 | path: SafeArray, 39 | amount: BigNumberish, 40 | recipient: string, 41 | provider: JsonRpcProvider 42 | ) { 43 | const encodedPools: BigNumberish[] = [] 44 | for (const swap of path) { 45 | const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider) 46 | encodedPools.push(encodedPool) 47 | } 48 | const executorInterface = SwapExecutor__factory.createInterface() 49 | let calldata = executorInterface.encodeFunctionData('uniswapV3SwapTo', [encodedPools, recipient, amount]) 50 | calldata = addCallParams(calldata) 51 | 52 | return [calldata] 53 | } 54 | 55 | export async function generateOrion3Calls( 56 | path: SafeArray, 57 | amount: BigNumberish, 58 | recipient: string, 59 | provider: JsonRpcProvider 60 | ) { 61 | const encodedPools: BigNumberish[] = [] 62 | for (const swap of path) { 63 | const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider) 64 | encodedPools.push(encodedPool) 65 | } 66 | const executorInterface = SwapExecutor__factory.createInterface() 67 | let calldata = executorInterface.encodeFunctionData('orionV3SwapTo', [encodedPools, recipient, amount]) 68 | calldata = addCallParams(calldata) 69 | 70 | return [calldata] 71 | } 72 | 73 | export async function encodePoolV3( 74 | poolAddress: string, 75 | assetInAddress: string, 76 | assetOutAddress: string, 77 | provider: JsonRpcProvider 78 | ) { 79 | const pool = UniswapV3Pool__factory.connect(poolAddress, provider) 80 | const token0 = await pool.token0() 81 | const zeroForOne = token0.toLowerCase() === assetInAddress.toLowerCase() 82 | const unwrapWETH = assetOutAddress === ethers.ZeroAddress 83 | 84 | let encodedPool = ethers.solidityPacked(['uint256'], [await pool.getAddress()]) 85 | encodedPool = ethers.dataSlice(encodedPool, 1) 86 | let firstByte = 0 87 | if (unwrapWETH) firstByte += 32 88 | if (!zeroForOne) firstByte += 128 89 | const encodedFirstByte = ethers.solidityPacked(['uint8'], [firstByte]) 90 | encodedPool = ethers.hexlify(ethers.concat([encodedFirstByte, encodedPool])) 91 | return encodedPool 92 | } -------------------------------------------------------------------------------- /src/utils/parseExchangeTradeTransaction.ts: -------------------------------------------------------------------------------- 1 | import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js'; 2 | import { ethers } from 'ethers'; 3 | import { z } from 'zod'; 4 | 5 | const swapThroughOrionPoolSchema = z.object({ 6 | name: z.literal('swapThroughOrionPool'), 7 | args: z.tuple([ 8 | z.bigint(), // amount_spend 9 | z.bigint(), // amount_receive 10 | z.string().refine(ethers.isAddress).array().nonempty(), // path 11 | ]), 12 | }).transform((data) => ({ 13 | name: data.name, 14 | args: { 15 | amount_spend: data.args[0], 16 | amount_receive: data.args[1], 17 | path: data.args[2], 18 | }, 19 | })); 20 | 21 | const buyOrderSchema = z.tuple([ // buy order 22 | z.string().refine(ethers.isAddress), // senderAddress 23 | z.string().refine(ethers.isAddress), // matcherAddress 24 | z.string().refine(ethers.isAddress), // baseAsset 25 | z.string().refine(ethers.isAddress), // quoteAsset 26 | z.string().refine(ethers.isAddress), // matcherFeeAsset 27 | z.bigint(), // amount 28 | z.bigint(), // price 29 | z.bigint(), // matcherFee 30 | z.bigint(), // nonce 31 | z.bigint(), // expiration 32 | z.literal(1), // buySide 33 | z.boolean(), // isPersonalSign 34 | z.string().refine(ethers.isHexString), // signature 35 | ]); 36 | const sellOrderSchema = z.tuple([ // sell orer 37 | z.string().refine(ethers.isAddress), // senderAddress 38 | z.string().refine(ethers.isAddress), // matcherAddress 39 | z.string().refine(ethers.isAddress), // baseAsset 40 | z.string().refine(ethers.isAddress), // quoteAsset 41 | z.string().refine(ethers.isAddress), // matcherFeeAsset 42 | z.bigint(), // amount 43 | z.bigint(), // price 44 | z.bigint(), // matcherFee 45 | z.bigint(), // nonce 46 | z.bigint(), // expiration 47 | z.literal(0), // buySide 48 | z.boolean(), // isPersonalSign 49 | z.string().refine(ethers.isHexString), // signature 50 | ]); 51 | 52 | const toOrder = | z.infer>(data: T) => ({ 53 | senderAddress: data[0], 54 | matcherAddress: data[1], 55 | baseAsset: data[2], 56 | quoteAsset: data[3], 57 | matcherFeeAsset: data[4], 58 | amount: data[5], 59 | price: data[6], 60 | matcherFee: data[7], 61 | nonce: data[8], 62 | expiration: data[9], 63 | buySide: data[10], 64 | isPersonalSign: data[11], 65 | signature: data[12], 66 | }); 67 | 68 | const fillThroughOrionPoolSchema = z.object({ 69 | name: z.literal('fillThroughOrionPool'), 70 | args: z.tuple([ 71 | sellOrderSchema, 72 | z.bigint(), // filled amount 73 | z.bigint(), // blockchainFee 74 | z.string().refine(ethers.isAddress).array().nonempty(), // path 75 | ]), 76 | }).transform((data) => ({ 77 | name: data.name, 78 | args: { 79 | order: toOrder(data.args[0]), 80 | filledAmount: data.args[1], 81 | blockchainFee: data.args[2], 82 | path: data.args[3], 83 | }, 84 | })); 85 | 86 | const fillOrdersSchema = z.object({ 87 | name: z.literal('fillOrders'), 88 | args: z.tuple([ 89 | buyOrderSchema, 90 | sellOrderSchema, 91 | z.bigint(), // filledPrice 92 | z.bigint(), // filledAmount 93 | ]), 94 | }).transform((data) => ({ 95 | name: data.name, 96 | args: { 97 | orders: { 98 | buyOrder: toOrder(data.args[0]), 99 | sellOrder: toOrder(data.args[1]), 100 | }, 101 | filledPrice: data.args[2], 102 | filledAmount: data.args[3], 103 | }, 104 | })); 105 | 106 | export default function parseExchangeTradeTransaction(tx: { data: string, value?: ethers.BigNumberish }) { 107 | const exchangeContractInterface = Exchange__factory.createInterface(); 108 | const txDescription = exchangeContractInterface.parseTransaction(tx); 109 | const schema = z.union([ 110 | fillOrdersSchema, 111 | swapThroughOrionPoolSchema, 112 | fillThroughOrionPoolSchema, 113 | ]); 114 | const data = schema.parse(txDescription); 115 | return data; 116 | } 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@orionprotocol/sdk", 3 | "version": "0.22.18", 4 | "description": "Orion Protocol SDK", 5 | "main": "./lib/index.cjs", 6 | "module": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "require": "./lib/index.cjs", 11 | "import": "./lib/index.js", 12 | "types": "./lib/index.d.ts" 13 | }, 14 | "./browser": { 15 | "browser": "./lib/index.global.js" 16 | } 17 | }, 18 | "engines": { 19 | "node": ">=18.0.0" 20 | }, 21 | "type": "module", 22 | "scripts": { 23 | "start": "npm run build && node lib/esm/index.js", 24 | "develop": "concurrently -i -k -p \"[{name}]\" -n \"Node,TypeScript\" -c \"yellow.bold,cyan.bold\" \"yarn watch-js\" \"yarn watch-ts\"", 25 | "clean": "rimraf lib/*", 26 | "watch-ts": "tsc -w --skipLibCheck", 27 | "watch-js": "nodemon lib/esm/index.js", 28 | "prepare": "is-ci || husky install", 29 | "build": "tsup src/index.ts", 30 | "coverage": "jest --coverage", 31 | "lint:eslint": "eslint ./src --ext .ts,.js,.tsx,.jsx", 32 | "lint:eslint:fix": "eslint ./src --ext .ts,.js,.tsx,.jsx --fix", 33 | "postinstall": "patch-package", 34 | "postpublish": "npm run publish-npm", 35 | "publish-npm": "npm publish --access public --ignore-scripts --@orionprotocol:registry='https://registry.npmjs.org'", 36 | "test": "dotenv jest", 37 | "test:coverage": "jest --coverage", 38 | "test:watch": "jest --watch" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/orionprotocol/sdk.git" 43 | }, 44 | "keywords": [ 45 | "sdk", 46 | "orion", 47 | "orionprotocol", 48 | "trading" 49 | ], 50 | "author": "", 51 | "license": "ISC", 52 | "bugs": { 53 | "url": "https://github.com/orionprotocol/sdk/issues" 54 | }, 55 | "devDependencies": { 56 | "@babel/core": "^7.21.4", 57 | "@babel/plugin-syntax-import-assertions": "^7.20.0", 58 | "@tsconfig/esm": "^1.0.4", 59 | "@tsconfig/strictest": "^2.0.1", 60 | "@types/express": "^4.17.17", 61 | "@types/jest": "^29.5.1", 62 | "@types/node": "^20.5.1", 63 | "@types/uuid": "^9.0.1", 64 | "@types/ws": "^8.5.4", 65 | "@typescript-eslint/eslint-plugin": "^6.4.0", 66 | "@typescript-eslint/parser": "^6.4.0", 67 | "babel-loader": "^9.1.2", 68 | "concurrently": "^8.1.0", 69 | "esbuild": "^0.13.4", 70 | "eslint": "^8.47.0", 71 | "eslint-config-airbnb-base": "^15.0.0", 72 | "eslint-config-standard": "^17.1.0", 73 | "eslint-config-standard-with-typescript": "^38.0.0", 74 | "eslint-plugin-import": "^2.28.1", 75 | "eslint-plugin-n": "^16.0.1", 76 | "eslint-plugin-promise": "^6.1.1", 77 | "http-terminator": "^3.2.0", 78 | "husky": "^8.0.3", 79 | "is-ci": "^3.0.1", 80 | "jest": "^29.5.0", 81 | "ts-jest": "^29.1.0", 82 | "ts-loader": "^9.4.3", 83 | "ts-node": "github:TypeStrong/ts-node#main", 84 | "tsup": "^7.2.0", 85 | "typescript": "^5.2.2" 86 | }, 87 | "dependencies": { 88 | "@babel/runtime": "^7.21.0", 89 | "@ethersproject/abstract-signer": "^5.7.0", 90 | "@ethersproject/providers": "^5.7.2", 91 | "@orionprotocol/contracts": "^1.23.9", 92 | "@types/lodash.clonedeep": "^4.5.9", 93 | "bignumber.js": "^9.1.1", 94 | "bson-objectid": "^2.0.4", 95 | "buffer": "^6.0.3", 96 | "crypto-js": "^4.2.0", 97 | "ethers": "^6.12.0", 98 | "express": "^4.18.2", 99 | "isomorphic-ws": "^5.0.0", 100 | "just-clone": "^6.2.0", 101 | "lodash.clonedeep": "^4.5.0", 102 | "merge-anything": "^5.1.7", 103 | "neverthrow": "^6.0.0", 104 | "patch-package": "^8.0.0", 105 | "simple-typed-fetch": "0.2.3", 106 | "stream-browserify": "^3.0.0", 107 | "tiny-invariant": "^1.3.1", 108 | "ts-is-present": "^1.2.2", 109 | "uuid": "^9.0.0", 110 | "ws": "^8.13.0", 111 | "zod": "3.22.2" 112 | }, 113 | "homepage": "https://github.com/orionprotocol/sdk#readme", 114 | "files": [ 115 | "lib/**/*" 116 | ], 117 | "overrides": { 118 | "tsconfig-paths": "^4.0.0" 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Unit/Exchange/callGenerators/utils.ts: -------------------------------------------------------------------------------- 1 | import { ERC20__factory, SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js" 2 | import type { AddressLike } from "ethers" 3 | import { type BytesLike, ethers, type BigNumberish } from "ethers" 4 | 5 | const EXECUTOR_SWAP_FUNCTION = 'func_70LYiww' 6 | 7 | export type CallParams = { 8 | isMandatory?: boolean, 9 | target?: string, 10 | gaslimit?: BigNumberish, 11 | value?: BigNumberish 12 | } 13 | 14 | export type PatchParams = { 15 | skipOnZeroAmount?: boolean, 16 | skipCallDataPatching?: boolean, 17 | skipValuePatching?: boolean 18 | } 19 | 20 | export function pathCallWithBalance( 21 | calldata: BytesLike, 22 | tokenAddress: AddressLike, 23 | patchParams: PatchParams = { skipCallDataPatching: false, skipValuePatching: true } 24 | ) { 25 | const executorInterface = SwapExecutor__factory.createInterface() 26 | const skipMaskAndOffset = createPatchMask(calldata, patchParams) 27 | calldata = executorInterface.encodeFunctionData("patchCallWithTokenBalance", [ 28 | calldata, 29 | skipMaskAndOffset, 30 | tokenAddress, 31 | ethers.MaxUint256]) 32 | return addCallParams(calldata) 33 | } 34 | 35 | export function addCallParams( 36 | calldata: BytesLike, 37 | callParams?: CallParams 38 | ) { 39 | let firstByte = 0 40 | if (callParams) { 41 | if (callParams.value !== undefined) { 42 | firstByte += 16 // 00010000 43 | const encodedValue = ethers.solidityPacked(['uint128'], [callParams.value]) 44 | calldata = ethers.hexlify(ethers.concat([encodedValue, calldata])) 45 | } 46 | if (callParams.target !== undefined) { 47 | firstByte += 32 // 00100000 48 | const encodedAddress = ethers.solidityPacked(['address'], [callParams.target]) 49 | calldata = ethers.hexlify(ethers.concat([encodedAddress, calldata])) 50 | } 51 | if (callParams.gaslimit !== undefined) { 52 | firstByte += 64 // 01000000 53 | const encodedGaslimit = ethers.solidityPacked(['uint32'], [callParams.gaslimit]) 54 | calldata = ethers.hexlify(ethers.concat([encodedGaslimit, calldata])) 55 | } 56 | if (callParams.isMandatory !== undefined) firstByte += 128 // 10000000 57 | } 58 | 59 | const encodedFirstByte = ethers.solidityPacked(['uint8'], [firstByte]) 60 | calldata = ethers.hexlify(ethers.concat([encodedFirstByte, calldata])) 61 | return calldata 62 | } 63 | 64 | export function createPatchMask(calldata: BytesLike, patchParams?: PatchParams) { 65 | let firstByte = 0 66 | let mask = ethers.solidityPacked(["uint256"], [(calldata.length - 4) / 2 - 32]) //finding offset of last 32 bytes slot in calldata 67 | mask = ethers.dataSlice(mask, 1) 68 | if (patchParams) { 69 | if (patchParams.skipOnZeroAmount !== undefined && patchParams.skipOnZeroAmount === false) { 70 | firstByte += 32 71 | } 72 | if (patchParams.skipCallDataPatching !== undefined && patchParams.skipCallDataPatching) { 73 | firstByte += 64 74 | } 75 | if (patchParams.skipValuePatching !== undefined && patchParams.skipValuePatching) { 76 | firstByte += 128 77 | } 78 | } 79 | const encodedFirstByte = ethers.solidityPacked(["uint8"], [firstByte]) 80 | mask = ethers.hexlify(ethers.concat([encodedFirstByte, mask])) 81 | return mask 82 | } 83 | 84 | export function generateCalls(calls: BytesLike[]) { 85 | const executorInterface = SwapExecutor__factory.createInterface() 86 | return '0x' + executorInterface.encodeFunctionData(EXECUTOR_SWAP_FUNCTION, [ethers.ZeroAddress, calls]).slice(74) 87 | } 88 | 89 | export async function exchangeToNativeDecimals(token: AddressLike, amount: BigNumberish, provider: ethers.JsonRpcProvider) { 90 | return await toNativeDecimals(token, amount, provider) / (BigInt(10) ** 8n) 91 | } 92 | 93 | export async function toNativeDecimals(token: AddressLike, amount: BigNumberish, provider: ethers.JsonRpcProvider) { 94 | token = await token 95 | if (typeof token !== "string") token = await token.getAddress() 96 | 97 | let decimals = 18n 98 | if (token !== ethers.ZeroAddress) { 99 | const contract = ERC20__factory.connect(token, provider) 100 | decimals = BigInt(await contract.decimals()) 101 | } 102 | return BigInt(amount) * (BigInt(10) ** decimals) 103 | } -------------------------------------------------------------------------------- /src/Unit/Exchange/withdraw.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { ethers } from 'ethers'; 3 | import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js'; 4 | import getBalances from '../../utils/getBalances.js'; 5 | import BalanceGuard from '../../BalanceGuard.js'; 6 | import type Unit from '../index.js'; 7 | import { 8 | INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION, WITHDRAW_GAS_LIMIT, 9 | } from '../../constants/index.js'; 10 | import { denormalizeNumber, normalizeNumber } from '../../utils/index.js'; 11 | import getNativeCryptocurrencyName from '../../utils/getNativeCryptocurrencyName.js'; 12 | import { simpleFetch } from 'simple-typed-fetch'; 13 | 14 | export type WithdrawParams = { 15 | asset: string 16 | amount: BigNumber.Value 17 | signer: ethers.Signer 18 | unit: Unit 19 | } 20 | 21 | export default async function withdraw({ 22 | asset, 23 | amount, 24 | signer, 25 | unit, 26 | }: WithdrawParams) { 27 | if (asset === '') throw new Error('Asset can not be empty'); 28 | 29 | const amountBN = new BigNumber(amount); 30 | if (amountBN.isNaN()) throw new Error(`Amount '${amountBN.toString()}' is not a number`); 31 | if (amountBN.lte(0)) throw new Error(`Amount '${amountBN.toString()}' should be greater than 0`); 32 | 33 | const walletAddress = await signer.getAddress(); 34 | 35 | const { 36 | blockchainService, aggregator, provider, chainId, 37 | } = unit; 38 | const { 39 | exchangeContractAddress, 40 | assetToAddress, 41 | } = await simpleFetch(blockchainService.getInfo)(); 42 | 43 | const nativeCryptocurrency = getNativeCryptocurrencyName(assetToAddress); 44 | const exchangeContract = Exchange__factory.connect(exchangeContractAddress, provider); 45 | const gasPriceWei = await simpleFetch(blockchainService.getGasPriceWei)(); 46 | 47 | const assetAddress = assetToAddress[asset]; 48 | if (assetAddress === undefined) throw new Error(`Asset '${asset}' not found`); 49 | 50 | const balances = await getBalances( 51 | { 52 | [asset]: assetAddress, 53 | [nativeCryptocurrency]: ethers.ZeroAddress, 54 | }, 55 | aggregator, 56 | walletAddress, 57 | exchangeContract, 58 | provider, 59 | ); 60 | 61 | const balanceGuard = new BalanceGuard( 62 | balances, 63 | { 64 | name: nativeCryptocurrency, 65 | address: ethers.ZeroAddress, 66 | }, 67 | provider, 68 | signer, 69 | ); 70 | 71 | balanceGuard.registerRequirement({ 72 | reason: 'Amount', 73 | asset: { 74 | name: asset, 75 | address: assetAddress, 76 | }, 77 | amount: amountBN.toString(), 78 | sources: ['exchange'], 79 | }); 80 | 81 | const unsignedTx = await exchangeContract.withdraw.populateTransaction( 82 | assetAddress, 83 | normalizeNumber(amount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString(), 84 | ); 85 | unsignedTx.gasLimit = BigInt(WITHDRAW_GAS_LIMIT); 86 | 87 | const transactionCost = BigInt(unsignedTx.gasLimit) * BigInt(gasPriceWei); 88 | const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION)); 89 | 90 | balanceGuard.registerRequirement({ 91 | reason: 'Network fee', 92 | asset: { 93 | name: nativeCryptocurrency, 94 | address: ethers.ZeroAddress, 95 | }, 96 | amount: denormalizedTransactionCost.toString(), 97 | sources: ['wallet'], 98 | }); 99 | 100 | unsignedTx.chainId = BigInt(chainId); 101 | unsignedTx.gasPrice = BigInt(gasPriceWei); 102 | unsignedTx.from = walletAddress; 103 | 104 | await balanceGuard.check(true); 105 | 106 | const nonce = await provider.getTransactionCount(walletAddress, 'pending'); 107 | unsignedTx.nonce = nonce; 108 | 109 | const signedTx = await signer.signTransaction(unsignedTx); 110 | const txResponse = await provider.broadcastTransaction(signedTx); 111 | console.log(`Withdraw tx sent: ${txResponse.hash}. Waiting for confirmation...`); 112 | try { 113 | const txReceipt = await txResponse.wait(); 114 | if (txReceipt?.status !== undefined) { 115 | console.log('Withdraw tx confirmed'); 116 | } else { 117 | console.log('Withdraw tx failed'); 118 | } 119 | } catch (e) { 120 | if (!(e instanceof Error)) throw new Error('e is not an Error'); 121 | console.error(`Deposit tx failed: ${e.message}`, { 122 | unsignedTx, 123 | }); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | ".eslintrc.js" 4 | ], 5 | "env": { 6 | "browser": true, 7 | "es2021": true, 8 | "node": true 9 | }, 10 | "extends": [ 11 | "standard-with-typescript", 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 16 | "plugin:@typescript-eslint/strict" 17 | // "plugin:import/recommended", 18 | // "plugin:import/typescript" 19 | ], 20 | "parser": "@typescript-eslint/parser", 21 | "parserOptions": { 22 | "tsconfigRootDir": ".", 23 | "project": [ 24 | "./tsconfig.json" 25 | ], 26 | "ecmaVersion": 2020, 27 | "sourceType": "module" 28 | }, 29 | "plugins": [ 30 | "@typescript-eslint" 31 | ], 32 | "rules": { 33 | "@typescript-eslint/consistent-type-imports": [ 34 | "error", 35 | { 36 | "fixStyle": "separate-type-imports", 37 | "disallowTypeAnnotations": true 38 | } 39 | ], 40 | "@typescript-eslint/strict-boolean-expressions": [ 41 | "error", 42 | { 43 | "allowNullableObject": true, 44 | "allowString": false, 45 | "allowNumber": false, 46 | "allowNullableBoolean": false, 47 | "allowNullableString": false, 48 | "allowNullableNumber": false, 49 | "allowAny": false, 50 | "allowNullableEnum": false 51 | } 52 | ], 53 | "eqeqeq": "error", 54 | "@typescript-eslint/consistent-type-definitions": [ 55 | "warn", 56 | "type" 57 | ], 58 | "@typescript-eslint/indent": [ 59 | "error", 60 | 2, 61 | { 62 | "SwitchCase": 1, 63 | "ignoredNodes": [ 64 | "TSTypeParameterInstantiation" 65 | ] 66 | } 67 | ], 68 | "@typescript-eslint/promise-function-async": 0, 69 | // "import/no-cycle": "error", 70 | "@typescript-eslint/space-before-function-paren": 0, 71 | "@typescript-eslint/comma-dangle": 0, 72 | "@typescript-eslint/semi": 0, 73 | "comma-dangle": 0, 74 | "semi": 0, 75 | "space-before-function-paren": 0, 76 | "@typescript-eslint/explicit-function-return-type": 0, 77 | "no-param-reassign": [ 78 | "error", 79 | { 80 | "props": true, 81 | "ignorePropertyModificationsFor": [ 82 | "acc", 83 | "prev" 84 | ] 85 | } 86 | ], 87 | "camelcase": "off", 88 | "@typescript-eslint/consistent-type-assertions": [ 89 | "error", 90 | { 91 | "assertionStyle": "never" 92 | } 93 | ], 94 | // "import/max-dependencies": [ 95 | // "error", 96 | // { 97 | // "max": 20, 98 | // "ignoreTypeImports": false 99 | // } 100 | // ], 101 | "no-unused-vars": "off", 102 | "@typescript-eslint/no-unused-vars": "error", 103 | "no-shadow": "off", 104 | "@typescript-eslint/no-shadow": [ 105 | "error" 106 | ], 107 | "max-len": [ 108 | 1, 109 | 140, 110 | 2, 111 | { 112 | "ignoreComments": true, 113 | "ignoreUrls": true, 114 | "ignoreTemplateLiterals": true 115 | } 116 | ] 117 | // "import/extensions": [ 118 | // "error", 119 | // "ignorePackages", 120 | // { 121 | // "js": "never", 122 | // "jsx": "never", 123 | // "ts": "never", 124 | // "tsx": "never" 125 | // } 126 | // ] 127 | }, 128 | "settings": { 129 | "import/resolver": { 130 | "node": { 131 | "extensions": [ 132 | ".js", 133 | ".jsx", 134 | ".ts", 135 | ".tsx" 136 | ] 137 | } 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /src/services/Aggregator/ws/schemas/addressUpdateSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import orderStatuses from '../../../../constants/orderStatuses.js'; 3 | import executionTypes from '../../../../constants/executionTypes.js'; 4 | import subOrderStatuses from '../../../../constants/subOrderStatuses.js'; 5 | import MessageType from '../MessageType.js'; 6 | import balancesSchema from './balancesSchema.js'; 7 | import baseMessageSchema from './baseMessageSchema.js'; 8 | 9 | const baseAddressUpdate = baseMessageSchema.extend({ 10 | id: z.string(), 11 | T: z.literal(MessageType.ADDRESS_UPDATE), 12 | S: z.string(), // subscription 13 | uc: z.array(z.enum(['b', 'o'])), // update content 14 | }); 15 | 16 | const subOrderSchema = z.object({ 17 | i: z.number(), // id 18 | I: z.string(), // parent order id 19 | O: z.string(), // sender (owner) 20 | P: z.string().toUpperCase(), // asset pair 21 | s: z.enum(['BUY', 'SELL']), // side 22 | a: z.number(), // amount 23 | A: z.number(), // settled amount 24 | p: z.number(), // avg weighed settlement price 25 | e: z.string(), // exchange 26 | es: z.string().array().optional(), // exchanges 27 | b: z.string(), // broker address 28 | S: z.enum(subOrderStatuses), // status 29 | o: z.boolean(), // internal only 30 | }); 31 | 32 | export const orderUpdateSchema = z.object({ 33 | I: z.string(), // id 34 | A: z.number(), // settled amount 35 | S: z.enum(orderStatuses), // status 36 | l: z.boolean().optional(), // is liquidation order 37 | t: z.number(), // update time 38 | C: z.string().optional(), // trigger condition 39 | E: z.enum(executionTypes).optional(), 40 | c: subOrderSchema.array(), 41 | }) 42 | .transform((val) => ({ 43 | ...val, 44 | k: 'update' as const, 45 | })).transform((o) => ({ 46 | kind: o.k, 47 | id: o.I, 48 | settledAmount: o.A, 49 | status: o.S, 50 | liquidated: o.l, 51 | executionType: o.E, 52 | triggerCondition: o.C, 53 | subOrders: o.c.map((so) => ({ 54 | pair: so.P, 55 | exchange: so.e, 56 | exchanges: so.es, 57 | id: so.i, 58 | amount: so.a, 59 | settledAmount: so.A, 60 | price: so.p, 61 | status: so.S, 62 | side: so.s, 63 | subOrdQty: so.A, 64 | })), 65 | })); 66 | 67 | export const fullOrderSchema = z.object({ 68 | I: z.string(), // id 69 | O: z.string(), // sender (owner) 70 | P: z.string().toUpperCase(), // asset pair 71 | s: z.enum(['BUY', 'SELL']), // side 72 | a: z.number(), // amount 73 | A: z.number(), // settled amount 74 | p: z.number(), // price 75 | F: z.string().toUpperCase(), // fee asset 76 | f: z.number(), // fee 77 | l: z.boolean().optional(), // is liquidation order 78 | L: z.number().optional(), // stop limit price, 79 | o: z.boolean(), // internal only 80 | S: z.enum(orderStatuses), // status 81 | T: z.number(), // creation time / unix timestamp 82 | t: z.number(), // update time 83 | c: subOrderSchema.array(), 84 | E: z.enum(executionTypes).optional(), 85 | C: z.string().optional(), // trigger condition 86 | ro: z.boolean().optional(), // is reversed order 87 | }).transform((val) => ({ 88 | ...val, 89 | k: 'full' as const, 90 | })).transform((o) => ({ 91 | kind: o.k, 92 | id: o.I, 93 | settledAmount: o.A, 94 | feeAsset: o.F, 95 | fee: o.f, 96 | liquidated: o.l, 97 | stopPrice: o.L, 98 | status: o.S, 99 | date: o.T, 100 | clientOrdId: o.O, 101 | type: o.s, 102 | pair: o.P, 103 | amount: o.a, 104 | price: o.p, 105 | executionType: o.E, 106 | triggerCondition: o.C, 107 | isReversedOrder: o.ro, 108 | subOrders: o.c.map((so) => ({ 109 | pair: so.P, 110 | exchange: so.e, 111 | exchanges: so.es, 112 | id: so.i, 113 | amount: so.a, 114 | settledAmount: so.A, 115 | price: so.p, 116 | status: so.S, 117 | side: so.s, 118 | subOrdQty: so.A, 119 | })), 120 | })); 121 | 122 | const updateMessageSchema = baseAddressUpdate.extend({ 123 | k: z.literal('u'), // kind of message: "u" - updates 124 | uc: z.array(z.enum(['b', 'o'])), // update content: "o" - orders updates, "b" - balance updates 125 | b: balancesSchema.optional(), 126 | o: z.tuple([fullOrderSchema.or(orderUpdateSchema)]).optional(), 127 | }); 128 | 129 | const initialMessageSchema = baseAddressUpdate.extend({ 130 | k: z.literal('i'), // kind of message: "i" - initial 131 | b: balancesSchema, 132 | o: z.array(fullOrderSchema) 133 | .optional(), // When no orders — no field 134 | }); 135 | 136 | const addressUpdateSchema = z.union([ 137 | initialMessageSchema, 138 | updateMessageSchema, 139 | ]); 140 | 141 | export default addressUpdateSchema; 142 | -------------------------------------------------------------------------------- /src/Unit/index.ts: -------------------------------------------------------------------------------- 1 | import { JsonRpcProvider } from 'ethers'; 2 | import { Aggregator } from '../services/Aggregator'; 3 | import { BlockchainService } from '../services/BlockchainService'; 4 | import { PriceFeed } from '../services/PriceFeed'; 5 | import type { 6 | KnownEnv, 7 | SupportedChainId, 8 | VerboseUnitConfig, 9 | } from '../types.js'; 10 | import Exchange from './Exchange/index.js'; 11 | import { chains, envs } from '../config'; 12 | import type { networkCodes } from '../constants/index.js'; 13 | import { IndexerService } from '../services/Indexer'; 14 | import Pmm from './Pmm'; 15 | 16 | type KnownConfig = { 17 | env: KnownEnv 18 | chainId: SupportedChainId 19 | }; 20 | 21 | export default class Unit { 22 | public readonly networkCode: (typeof networkCodes)[number]; 23 | 24 | public readonly chainId: SupportedChainId; 25 | 26 | public readonly provider: JsonRpcProvider; 27 | 28 | public readonly blockchainService: BlockchainService; 29 | 30 | public readonly indexer: IndexerService | undefined; 31 | 32 | public readonly aggregator: Aggregator; 33 | 34 | public readonly pmm: Pmm; 35 | 36 | public readonly priceFeed: PriceFeed; 37 | 38 | public readonly exchange: Exchange; 39 | 40 | public readonly config: VerboseUnitConfig; 41 | 42 | public readonly contracts: Record; 43 | 44 | public logger: ((message: string) => void) | undefined; 45 | 46 | constructor(config: KnownConfig | VerboseUnitConfig, logger?: ((message: string) => void) | undefined) { 47 | this.logger = logger; 48 | if ('env' in config) { 49 | const staticConfig = envs[config.env]; 50 | if (!staticConfig) { 51 | throw new Error( 52 | `Invalid environment: ${ 53 | config.env 54 | }. Available environments: ${Object.keys(envs).join(', ')}` 55 | ); 56 | } 57 | 58 | const chainConfig = chains[config.chainId]; 59 | if (!chainConfig) { 60 | throw new Error( 61 | `Invalid chainId: ${ 62 | config.chainId 63 | }. Available chainIds: ${Object.keys(chains).join(', ')}` 64 | ); 65 | } 66 | 67 | const networkConfig = staticConfig.networks[config.chainId]; 68 | if (!networkConfig) { 69 | throw new Error( 70 | `Invalid chainId: ${ 71 | config.chainId 72 | }. Available chainIds: ${Object.keys(staticConfig.networks).join( 73 | ', ' 74 | )}` 75 | ); 76 | } 77 | this.config = { 78 | chainId: config.chainId, 79 | nodeJsonRpc: networkConfig.rpc ?? chainConfig.rpc, 80 | services: { 81 | blockchainService: { 82 | http: networkConfig.api + networkConfig.services.blockchain.http, 83 | }, 84 | aggregator: { 85 | http: networkConfig.api + networkConfig.services.aggregator.http, 86 | ws: networkConfig.api + networkConfig.services.aggregator.ws, 87 | }, 88 | priceFeed: { 89 | api: networkConfig.api + networkConfig.services.priceFeed.all, 90 | }, 91 | indexer: { 92 | api: networkConfig.api + networkConfig.services.indexer?.http, 93 | } 94 | }, 95 | }; 96 | } else { 97 | this.config = config; 98 | } 99 | const chainInfo = chains[config.chainId]; 100 | if (!chainInfo) throw new Error('Chain info is required'); 101 | this.chainId = config.chainId; 102 | this.networkCode = chainInfo.code; 103 | this.contracts = chainInfo.contracts; 104 | const intNetwork = parseInt(this.chainId, 10); 105 | if (Number.isNaN(intNetwork)) { 106 | throw new Error('Invalid chainId (not a number)' + this.chainId); 107 | } 108 | this.provider = new JsonRpcProvider(this.config.nodeJsonRpc, intNetwork); 109 | this.provider.pollingInterval = 1000; 110 | 111 | this.blockchainService = new BlockchainService( 112 | this.config.services.blockchainService.http, 113 | this.config.basicAuth 114 | ); 115 | this.indexer = this.config.services.indexer 116 | ? new IndexerService( 117 | this.config.services.indexer.api, 118 | intNetwork 119 | ) 120 | : undefined; 121 | this.aggregator = new Aggregator( 122 | this.config.services.aggregator.http, 123 | this.config.services.aggregator.ws, 124 | this.config.basicAuth, 125 | logger, 126 | ); 127 | this.priceFeed = new PriceFeed( 128 | this.config.services.priceFeed.api, 129 | this.config.basicAuth 130 | ); 131 | this.exchange = new Exchange(this); 132 | this.pmm = new Pmm(this); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/services/Aggregator/schemas/orderSchema.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import { z } from 'zod'; 3 | import { exchanges, orderStatuses, subOrderStatuses } from '../../../constants/index.js'; 4 | 5 | const blockchainOrderSchema = z.object({ 6 | id: z.string().refine(ethers.isHexString, (value) => ({ 7 | message: `blockchainOrder.id must be a hex string, got ${value}`, 8 | })), 9 | senderAddress: z.string().refine(ethers.isAddress, (value) => ({ 10 | message: `blockchainOrder.senderAddress must be an address, got ${value}`, 11 | })), 12 | matcherAddress: z.string().refine(ethers.isAddress, (value) => ({ 13 | message: `blockchainOrder.matcherAddress must be an address, got ${value}`, 14 | })), 15 | baseAsset: z.string().refine(ethers.isAddress, (value) => ({ 16 | message: `blockchainOrder.baseAsset must be an address, got ${value}`, 17 | })), 18 | quoteAsset: z.string().refine(ethers.isAddress, (value) => ({ 19 | message: `blockchainOrder.quoteAsset must be an address, got ${value}`, 20 | })), 21 | matcherFeeAsset: z.string().refine(ethers.isAddress, (value) => ({ 22 | message: `blockchainOrder.matcherFeeAsset must be an address, got ${value}`, 23 | })), 24 | amount: z.number().int().nonnegative(), 25 | price: z.number().int().nonnegative(), 26 | matcherFee: z.number().int().nonnegative(), 27 | nonce: z.number(), 28 | expiration: z.number(), 29 | buySide: z.union([z.literal(1), z.literal(0)]), 30 | signature: z.string().refine(ethers.isHexString, (value) => ({ 31 | message: `blockchainOrder.signature must be a hex string, got ${value}`, 32 | })).nullable(), 33 | isPersonalSign: z.boolean(), 34 | needWithdraw: z.boolean(), 35 | }); 36 | 37 | const tradeInfoSchema = z.object({ 38 | tradeId: z.string().uuid(), 39 | tradeStatus: z.enum(['NEW', 'PENDING', 'OK', 'FAIL', 'TEMP_ERROR', 'REJECTED']), 40 | filledAmount: z.number().nonnegative(), 41 | price: z.number().nonnegative(), 42 | creationTime: z.number(), 43 | updateTime: z.number(), 44 | matchedBlockchainOrder: blockchainOrderSchema.optional(), 45 | matchedSubOrderId: z.number().int().nonnegative().optional(), 46 | }); 47 | 48 | const baseOrderSchema = z.object({ 49 | assetPair: z.string().toUpperCase(), 50 | side: z.enum(['BUY', 'SELL']), 51 | amount: z.number().nonnegative(), 52 | remainingAmount: z.number().nonnegative(), 53 | price: z.number().nonnegative(), 54 | sender: z.string().refine(ethers.isAddress, (value) => ({ 55 | message: `order.sender must be an address, got ${value}`, 56 | })), 57 | filledAmount: z.number().nonnegative(), 58 | internalOnly: z.boolean(), 59 | }) 60 | 61 | const selfBrokers = exchanges.map((exchange) => `SELF_BROKER_${exchange}` as const); 62 | type SelfBroker = typeof selfBrokers[number]; 63 | const isSelfBroker = (value: string): value is SelfBroker => selfBrokers.some((broker) => broker === value); 64 | 65 | const selfBrokerSchema = z.custom((value) => { 66 | if (typeof value === 'string' && isSelfBroker(value)) { 67 | return true; 68 | } 69 | return false; 70 | }); 71 | 72 | const brokerAddressSchema = z.enum([ 73 | 'INTERNAL_BROKER', 74 | 'ORION_BROKER', 75 | 'SELF_BROKER' 76 | ]) 77 | .or(selfBrokerSchema) 78 | .or(z.string().refine(ethers.isAddress, (value) => ({ 79 | message: `subOrder.subOrders.[n].brokerAddress must be an address, got ${value}`, 80 | }))); 81 | const subOrderSchema = baseOrderSchema.extend({ 82 | price: z.number(), 83 | id: z.number(), 84 | parentOrderId: z.string().refine(ethers.isHexString, (value) => ({ 85 | message: `subOrder.parentOrderId must be a hex string, got ${value}`, 86 | })), 87 | exchange: z.string(), 88 | exchanges: z.string().array().optional(), 89 | brokerAddress: brokerAddressSchema, 90 | tradesInfo: z.record( 91 | z.string().uuid(), 92 | tradeInfoSchema 93 | ), 94 | status: z.enum(subOrderStatuses), 95 | complexSwap: z.boolean(), 96 | }); 97 | 98 | const orderSchema = z.object({ 99 | orderId: z.string().refine(ethers.isHexString, (value) => ({ 100 | message: `orderId must be a hex string, got ${value}`, 101 | })), 102 | order: baseOrderSchema.extend({ 103 | id: z.string().refine(ethers.isHexString, (value) => ({ 104 | message: `order.id must be a hex string, got ${value}`, 105 | })), 106 | fee: z.number().nonnegative(), 107 | feeAsset: z.string().toUpperCase(), 108 | creationTime: z.number(), 109 | blockchainOrder: blockchainOrderSchema, 110 | subOrders: z.record(subOrderSchema), 111 | updateTime: z.number(), 112 | status: z.enum(orderStatuses), 113 | settledAmount: z.number().nonnegative(), 114 | }) 115 | }); 116 | 117 | export default orderSchema; 118 | -------------------------------------------------------------------------------- /src/Unit/Exchange/deposit.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { ethers } from 'ethers'; 3 | import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js'; 4 | import getBalances from '../../utils/getBalances.js'; 5 | import BalanceGuard from '../../BalanceGuard.js'; 6 | import type Unit from '../index.js'; 7 | import { 8 | DEPOSIT_ERC20_GAS_LIMIT, DEPOSIT_ETH_GAS_LIMIT, INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION, 9 | } from '../../constants/index.js'; 10 | import { denormalizeNumber, normalizeNumber } from '../../utils/index.js'; 11 | import getNativeCryptocurrencyName from '../../utils/getNativeCryptocurrencyName.js'; 12 | import { simpleFetch } from 'simple-typed-fetch'; 13 | 14 | export type DepositParams = { 15 | asset: string 16 | amount: BigNumber.Value 17 | signer: ethers.Signer 18 | unit: Unit 19 | } 20 | 21 | export default async function deposit({ 22 | asset, 23 | amount, 24 | signer, 25 | unit, 26 | }: DepositParams) { 27 | if (asset === '') throw new Error('Asset can not be empty'); 28 | 29 | const amountBN = new BigNumber(amount); 30 | if (amountBN.isNaN()) throw new Error(`Amount '${amountBN.toString()}' is not a number`); 31 | if (amountBN.lte(0)) throw new Error(`Amount '${amountBN.toString()}' should be greater than 0`); 32 | 33 | const walletAddress = await signer.getAddress(); 34 | 35 | const { 36 | blockchainService, aggregator, provider, chainId, 37 | } = unit; 38 | const { 39 | exchangeContractAddress, 40 | assetToAddress, 41 | } = await simpleFetch(blockchainService.getInfo)(); 42 | 43 | const nativeCryptocurrency = getNativeCryptocurrencyName(assetToAddress); 44 | 45 | const exchangeContract = Exchange__factory.connect(exchangeContractAddress, provider); 46 | const gasPriceWei = await simpleFetch(blockchainService.getGasPriceWei)(); 47 | 48 | const assetAddress = assetToAddress[asset]; 49 | if (assetAddress === undefined) throw new Error(`Asset '${asset}' not found`); 50 | 51 | const balances = await getBalances( 52 | { 53 | [asset]: assetAddress, 54 | [nativeCryptocurrency]: ethers.ZeroAddress, 55 | }, 56 | aggregator, 57 | walletAddress, 58 | exchangeContract, 59 | provider, 60 | ); 61 | 62 | const balanceGuard = new BalanceGuard( 63 | balances, 64 | { 65 | name: nativeCryptocurrency, 66 | address: ethers.ZeroAddress, 67 | }, 68 | provider, 69 | signer, 70 | ); 71 | 72 | balanceGuard.registerRequirement({ 73 | reason: 'Amount', 74 | asset: { 75 | name: asset, 76 | address: assetAddress, 77 | }, 78 | amount: amountBN.toString(), 79 | spenderAddress: exchangeContractAddress, 80 | sources: ['wallet'], 81 | }); 82 | 83 | let unsignedTx: ethers.TransactionLike; 84 | if (asset === nativeCryptocurrency) { 85 | unsignedTx = await exchangeContract.deposit.populateTransaction(); 86 | unsignedTx.value = normalizeNumber(amount, NATIVE_CURRENCY_PRECISION, BigNumber.ROUND_CEIL); 87 | unsignedTx.gasLimit = BigInt(DEPOSIT_ETH_GAS_LIMIT); 88 | } else { 89 | unsignedTx = await exchangeContract.depositAsset.populateTransaction( 90 | assetAddress, 91 | normalizeNumber(amount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_CEIL), 92 | ); 93 | unsignedTx.gasLimit = BigInt(DEPOSIT_ERC20_GAS_LIMIT); 94 | } 95 | 96 | const transactionCost = BigInt(unsignedTx.gasLimit) * BigInt(gasPriceWei); 97 | const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION)); 98 | 99 | balanceGuard.registerRequirement({ 100 | reason: 'Network fee', 101 | asset: { 102 | name: nativeCryptocurrency, 103 | address: ethers.ZeroAddress, 104 | }, 105 | amount: denormalizedTransactionCost.toString(), 106 | sources: ['wallet'], 107 | }); 108 | 109 | unsignedTx.chainId = parseInt(chainId, 10); 110 | unsignedTx.gasPrice = BigInt(gasPriceWei); 111 | unsignedTx.from = walletAddress; 112 | 113 | await balanceGuard.check(true); 114 | 115 | const nonce = await provider.getTransactionCount(walletAddress, 'pending'); 116 | unsignedTx.nonce = nonce; 117 | 118 | const signedTx = await signer.signTransaction(unsignedTx); 119 | try { 120 | const txResponse = await provider.broadcastTransaction(signedTx); 121 | console.log(`Deposit tx sent: ${txResponse.hash}. Waiting for confirmation...`); 122 | const txReceipt = await txResponse.wait(); 123 | if (txReceipt?.status !== undefined) { 124 | console.log('Deposit tx confirmed'); 125 | } else { 126 | console.log('Deposit tx failed'); 127 | } 128 | } catch (e) { 129 | if (!(e instanceof Error)) throw new Error('e is not an Error'); 130 | console.error(`Deposit tx failed: ${e.message}`, { 131 | unsignedTx, 132 | }); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/services/PriceFeed/ws/PriceFeedSubscription.ts: -------------------------------------------------------------------------------- 1 | import WebSocket from 'isomorphic-ws'; 2 | import { z } from 'zod'; 3 | import { v4 as uuidv4 } from 'uuid'; 4 | import priceFeedSubscriptions from './priceFeedSubscriptions.js'; 5 | import { tickerInfoSchema, candleSchema, cexPricesSchema } from './schemas/index.js'; 6 | import priceSchema from './schemas/priceSchema.js'; 7 | import type { Json } from '../../../types.js'; 8 | import allTickersSchema from './schemas/allTickersSchema.js'; 9 | 10 | export const subscriptions = { 11 | [priceFeedSubscriptions.ALL_TICKERS]: { 12 | schema: allTickersSchema, 13 | payload: false as const, 14 | }, 15 | [priceFeedSubscriptions.TICKER]: { 16 | schema: z.tuple([z.number(), tickerInfoSchema]).transform(([, tickerInfo]) => tickerInfo), 17 | payload: true as const, 18 | }, 19 | [priceFeedSubscriptions.LAST_PRICE]: { 20 | schema: priceSchema, 21 | payload: true as const, 22 | }, 23 | [priceFeedSubscriptions.CANDLE]: { 24 | schema: candleSchema, 25 | payload: true as const, 26 | }, 27 | [priceFeedSubscriptions.CEX]: { 28 | schema: cexPricesSchema, 29 | payload: false as const, 30 | }, 31 | }; 32 | 33 | export type SubscriptionType = keyof typeof subscriptions; 34 | export type Subscription< 35 | T extends SubscriptionType, 36 | Schema = z.infer 37 | > = typeof subscriptions[T] extends { payload: true } 38 | ? { 39 | callback: (data: Schema) => void 40 | errorCallback?: (error: Error) => void 41 | payload: string 42 | } : { 43 | callback: (data: Schema) => void 44 | errorCallback?: (error: Error) => void 45 | } 46 | 47 | export default class PriceFeedSubscription { 48 | public readonly id: string; 49 | 50 | private readonly callback: Subscription['callback']; 51 | 52 | private readonly errorCallback?: Subscription['errorCallback']; 53 | 54 | private readonly payload?: string; 55 | 56 | private ws?: WebSocket; 57 | 58 | private readonly url: string; 59 | 60 | private heartbeatInterval?: ReturnType; 61 | 62 | readonly type: T; 63 | 64 | // is used to make sure we do not need to renew ws subscription 65 | // we can not be sure that onclose event will recieve our code when we do `ws.close(4000)` 66 | // since sometimes it can be replaced with system one. 67 | // https://stackoverflow.com/questions/19304157/getting-the-reason-why-websockets-closed-with-close-code-1006 68 | private isClosedIntentionally = false; 69 | 70 | private readonly onOpen: ((event: WebSocket.Event) => void) | undefined; 71 | 72 | constructor( 73 | type: T, 74 | url: string, 75 | params: Subscription, 76 | onOpen?: (event: WebSocket.Event) => void, 77 | ) { 78 | this.id = uuidv4(); 79 | this.url = url; 80 | this.type = type; 81 | if ('payload' in params) { 82 | this.payload = params.payload; 83 | } 84 | this.callback = params.callback; 85 | this.errorCallback = params.errorCallback; 86 | this.onOpen = onOpen; 87 | this.init(); 88 | } 89 | 90 | private send(jsonObject: Json) { 91 | if (this.ws?.readyState === WebSocket.OPEN) { 92 | const jsonData = JSON.stringify(jsonObject); 93 | this.ws.send(jsonData); 94 | } else { 95 | setTimeout(() => { 96 | this.send(jsonObject); 97 | }, 50); 98 | } 99 | } 100 | 101 | init() { 102 | this.isClosedIntentionally = false; 103 | 104 | const { payload, url, type } = this; 105 | this.ws = new WebSocket(`${url}/${type}${payload !== undefined ? `/${payload.toString()}` : ''}`); 106 | 107 | if (this.onOpen !== undefined) { 108 | this.ws.onopen = this.onOpen; 109 | } 110 | this.ws.onmessage = (e) => { 111 | const { data } = e; 112 | 113 | // Convert data to string 114 | 115 | let dataString: string; 116 | if (typeof data === 'string') { 117 | dataString = data; 118 | } else if (Buffer.isBuffer(data)) { 119 | dataString = data.toString(); 120 | } else if (Array.isArray(data)) { 121 | dataString = Buffer.concat(data).toString(); 122 | } else { // ArrayBuffer 123 | dataString = Buffer.from(data).toString(); 124 | } 125 | 126 | if (dataString === 'pong') return; 127 | const json: unknown = JSON.parse(dataString); 128 | const subscription = subscriptions[type]; 129 | const parseResult = subscription.schema.safeParse(json); 130 | if (!parseResult.success) { 131 | const errorsMessage = parseResult.error.errors.map((error) => `[${error.path.join('.')}] ${error.message}`).join(', '); 132 | const error = new Error(`Can't recognize PriceFeed "${type}" subscription message "${dataString}": ${errorsMessage}`); 133 | if (this.errorCallback !== undefined) { 134 | this.errorCallback(error); 135 | } else throw error; 136 | } else { 137 | this.callback(parseResult.data); 138 | } 139 | }; 140 | 141 | this.ws.onclose = () => { 142 | if (this.heartbeatInterval) clearInterval(this.heartbeatInterval); 143 | if (!this.isClosedIntentionally) this.init(); 144 | }; 145 | 146 | this.heartbeatInterval = setInterval(() => { 147 | this.send('heartbeat'); 148 | }, 15000); 149 | } 150 | 151 | kill() { 152 | this.isClosedIntentionally = true; 153 | this.ws?.close(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Unit/Exchange/getSwapInfo.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { ethers } from 'ethers'; 3 | import { simpleFetch } from 'simple-typed-fetch'; 4 | import { NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../../constants/index.js'; 5 | import type { Aggregator } from '../../services/Aggregator/index.js'; 6 | import type { BlockchainService } from '../../services/BlockchainService/index.js'; 7 | 8 | import { calculateFeeInFeeAsset, denormalizeNumber, getNativeCryptocurrencyName } from '../../utils/index.js'; 9 | 10 | export type GetSwapInfoParams = { 11 | assetIn: string 12 | assetOut: string 13 | amount: BigNumber.Value 14 | feeAsset: string 15 | blockchainService: BlockchainService 16 | aggregator: Aggregator 17 | options?: { 18 | instantSettlement?: boolean 19 | poolOnly?: boolean 20 | } 21 | walletAddress?: string 22 | isTradeBuy?: boolean 23 | } 24 | 25 | export default async function getSwapInfo({ 26 | assetIn, 27 | assetOut, 28 | amount, 29 | feeAsset, 30 | blockchainService, 31 | aggregator, 32 | options, 33 | walletAddress, 34 | isTradeBuy = false, 35 | }: GetSwapInfoParams) { 36 | if (amount === '') throw new Error('Amount can not be empty'); 37 | if (assetIn === '') throw new Error('AssetIn can not be empty'); 38 | if (assetOut === '') throw new Error('AssetOut can not be empty'); 39 | if (feeAsset === '') throw new Error('Fee asset can not be empty'); 40 | 41 | const amountBN = new BigNumber(amount); 42 | if (amountBN.isNaN()) throw new Error(`Amount '${amountBN.toString()}' is not a number`); 43 | if (amountBN.lte(0)) throw new Error(`Amount '${amountBN.toString()}' should be greater than 0`); 44 | 45 | const { 46 | assetToAddress, 47 | } = await simpleFetch(blockchainService.getInfo)(); 48 | const nativeCryptocurrencyName = getNativeCryptocurrencyName(assetToAddress); 49 | 50 | const feeAssets = await simpleFetch(blockchainService.getPlatformFees)({ walletAddress, assetIn, assetOut }); 51 | const allPrices = await simpleFetch(blockchainService.getPricesWithQuoteAsset)(); 52 | const gasPriceWei = await simpleFetch(blockchainService.getGasPriceWei)(); 53 | 54 | const gasPriceGwei = ethers.formatUnits(gasPriceWei, 'gwei').toString(); 55 | 56 | const assetInAddress = assetToAddress[assetIn]; 57 | if (assetInAddress === undefined) throw new Error(`Asset '${assetIn}' not found`); 58 | const feeAssetAddress = assetToAddress[feeAsset]; 59 | if (feeAssetAddress === undefined) { 60 | throw new Error(`Fee asset '${feeAsset}' not found. Available assets: ${Object.keys(feeAssets).join(', ')}`); 61 | } 62 | 63 | const swapInfo = await simpleFetch(aggregator.getSwapInfo)( 64 | assetIn, 65 | assetOut, 66 | amountBN.toString(), 67 | options?.instantSettlement, 68 | options?.poolOnly !== undefined && options.poolOnly 69 | ? 'pools' 70 | : undefined, 71 | isTradeBuy, 72 | ); 73 | 74 | const { exchanges: swapExchanges } = swapInfo; 75 | const { factories } = await simpleFetch(blockchainService.getPoolsConfig)(); 76 | const poolExchangesList = factories !== undefined ? Object.keys(factories) : []; 77 | const [firstSwapExchange] = swapExchanges; 78 | 79 | let route: 'pool' | 'aggregator'; 80 | if (options?.poolOnly !== undefined && options.poolOnly) { 81 | route = 'pool'; 82 | } else if ( 83 | poolExchangesList.length > 0 && 84 | swapExchanges.length === 1 && 85 | firstSwapExchange !== undefined && 86 | poolExchangesList.some((poolExchange) => poolExchange === firstSwapExchange) 87 | ) { 88 | route = 'pool'; 89 | } else { 90 | route = 'aggregator'; 91 | } 92 | 93 | if (route === 'pool') { 94 | const transactionCost = BigInt(SWAP_THROUGH_ORION_POOL_GAS_LIMIT) * BigInt(gasPriceWei); 95 | const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION)); 96 | 97 | return { 98 | route, 99 | swapInfo, 100 | fee: { 101 | assetName: nativeCryptocurrencyName, 102 | assetAddress: ethers.ZeroAddress, 103 | networkFeeInFeeAsset: denormalizedTransactionCost.toString(), 104 | protocolFeeInFeeAsset: undefined, 105 | }, 106 | }; 107 | } 108 | 109 | if (swapInfo.orderInfo !== null) { 110 | const [baseAssetName] = swapInfo.orderInfo.assetPair.split('-'); 111 | if (baseAssetName === undefined) throw new Error('Base asset name is undefined'); 112 | const baseAssetAddress = assetToAddress[baseAssetName]; 113 | if (baseAssetAddress === undefined) throw new Error(`No asset address for ${baseAssetName}`); 114 | 115 | // Fee calculation 116 | const feePercent = feeAssets[feeAsset]; 117 | if (feePercent === undefined) throw new Error(`Fee asset ${feeAsset} not available`); 118 | 119 | const { 120 | serviceFeeInFeeAsset, 121 | networkFeeInFeeAsset, 122 | } = calculateFeeInFeeAsset( 123 | swapInfo.orderInfo.amount, 124 | gasPriceGwei, 125 | feePercent, 126 | baseAssetAddress, 127 | ethers.ZeroAddress, 128 | feeAssetAddress, 129 | allPrices.prices 130 | ); 131 | 132 | return { 133 | route, 134 | swapInfo, 135 | fee: { 136 | assetName: feeAsset, 137 | assetAddress: feeAssetAddress, 138 | networkFeeInFeeAsset, 139 | protocolFeeInFeeAsset: serviceFeeInFeeAsset, 140 | }, 141 | }; 142 | } 143 | 144 | return { 145 | route, 146 | swapInfo, 147 | fee: { 148 | assetName: feeAsset, 149 | assetAddress: feeAssetAddress, 150 | networkFeeInFeeAsset: undefined, 151 | protocolFeeInFeeAsset: undefined, 152 | }, 153 | }; 154 | } 155 | --------------------------------------------------------------------------------