2022-02-09 08:28:57 -08:00
|
|
|
import { I80F48 } from '@blockworks-foundation/mango-client'
|
2021-09-02 22:32:27 -07:00
|
|
|
import { TOKEN_MINTS } from '@project-serum/serum'
|
|
|
|
import { PublicKey } from '@solana/web3.js'
|
2021-03-30 15:47:08 -07:00
|
|
|
import BN from 'bn.js'
|
2021-10-01 11:51:19 -07:00
|
|
|
import { TRIGGER_ORDER_TYPES } from '../components/trade_form/AdvancedTradeForm'
|
2021-09-02 22:32:27 -07:00
|
|
|
import { Orderbook } from '../stores/useMangoStore'
|
2021-12-02 18:38:44 -08:00
|
|
|
import { MarketKind } from '@blockworks-foundation/mango-client'
|
2022-03-20 18:30:12 -07:00
|
|
|
import BigNumber from 'bignumber.js'
|
2021-03-30 15:47:08 -07:00
|
|
|
|
|
|
|
export async function sleep(ms) {
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
|
|
}
|
|
|
|
|
|
|
|
export const percentFormat = new Intl.NumberFormat(undefined, {
|
|
|
|
style: 'percent',
|
2022-02-19 17:11:27 -08:00
|
|
|
minimumFractionDigits: 1,
|
|
|
|
maximumFractionDigits: 3,
|
2021-03-30 15:47:08 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
export function floorToDecimal(
|
|
|
|
value: number,
|
|
|
|
decimals: number | undefined | null
|
|
|
|
) {
|
|
|
|
return decimals
|
|
|
|
? Math.floor(value * 10 ** decimals) / 10 ** decimals
|
|
|
|
: Math.floor(value)
|
|
|
|
}
|
|
|
|
|
2021-04-29 14:27:13 -07:00
|
|
|
export function ceilToDecimal(
|
|
|
|
value: number,
|
|
|
|
decimals: number | undefined | null
|
|
|
|
) {
|
|
|
|
return decimals
|
|
|
|
? Math.ceil(value * 10 ** decimals) / 10 ** decimals
|
|
|
|
: Math.ceil(value)
|
|
|
|
}
|
|
|
|
|
2021-03-30 15:47:08 -07:00
|
|
|
export function roundToDecimal(
|
|
|
|
value: number,
|
|
|
|
decimals: number | undefined | null
|
|
|
|
) {
|
|
|
|
return decimals ? Math.round(value * 10 ** decimals) / 10 ** decimals : value
|
|
|
|
}
|
|
|
|
|
2022-02-19 16:47:16 -08:00
|
|
|
export function getPrecisionDigits(x: number): number {
|
|
|
|
return -Math.round(Math.log10(x))
|
|
|
|
}
|
2022-03-20 01:07:03 -07:00
|
|
|
|
2021-03-30 15:47:08 -07:00
|
|
|
export function getDecimalCount(value): number {
|
|
|
|
if (
|
|
|
|
!isNaN(value) &&
|
|
|
|
Math.floor(value) !== value &&
|
|
|
|
value.toString().includes('.')
|
|
|
|
)
|
|
|
|
return value.toString().split('.')[1].length || 0
|
|
|
|
if (
|
|
|
|
!isNaN(value) &&
|
|
|
|
Math.floor(value) !== value &&
|
|
|
|
value.toString().includes('e')
|
|
|
|
)
|
|
|
|
return parseInt(value.toString().split('e-')[1] || '0')
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getTokenMultiplierFromDecimals(decimals: number): BN {
|
|
|
|
return new BN(10).pow(new BN(decimals))
|
|
|
|
}
|
|
|
|
|
2021-04-09 17:01:00 -07:00
|
|
|
export function abbreviateAddress(address: PublicKey, size = 5) {
|
2021-03-30 15:47:08 -07:00
|
|
|
const base58 = address.toBase58()
|
|
|
|
return base58.slice(0, size) + '…' + base58.slice(-size)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isEqual(obj1, obj2, keys) {
|
|
|
|
if (!keys && Object.keys(obj1).length !== Object.keys(obj2).length) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
keys = keys || Object.keys(obj1)
|
|
|
|
for (const k of keys) {
|
|
|
|
if (obj1[k] !== obj2[k]) {
|
|
|
|
// shallow comparison
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
export function groupBy(list, keyGetter) {
|
|
|
|
const map = new Map()
|
|
|
|
list.forEach((item) => {
|
|
|
|
const key = keyGetter(item)
|
|
|
|
const collection = map.get(key)
|
|
|
|
if (!collection) {
|
|
|
|
map.set(key, [item])
|
|
|
|
} else {
|
|
|
|
collection.push(item)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return map
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isDefined<T>(argument: T | undefined): argument is T {
|
|
|
|
return argument !== undefined
|
|
|
|
}
|
2021-04-02 11:26:21 -07:00
|
|
|
|
2021-08-18 13:15:17 -07:00
|
|
|
export function calculateTradePrice(
|
2021-12-02 18:38:44 -08:00
|
|
|
kind: MarketKind,
|
2021-08-18 13:15:17 -07:00
|
|
|
tradeType: string,
|
|
|
|
orderBook: Orderbook,
|
|
|
|
baseSize: number,
|
|
|
|
side: 'buy' | 'sell',
|
2021-09-21 11:04:23 -07:00
|
|
|
price: string | number,
|
|
|
|
triggerPrice?: string | number
|
2022-03-30 04:08:05 -07:00
|
|
|
): number | undefined {
|
2021-12-02 18:38:44 -08:00
|
|
|
if (tradeType === 'Market' && kind === 'spot') {
|
2021-08-18 13:15:17 -07:00
|
|
|
return calculateMarketPrice(orderBook, baseSize, side)
|
2021-10-01 11:51:19 -07:00
|
|
|
} else if (TRIGGER_ORDER_TYPES.includes(tradeType)) {
|
2021-10-26 08:57:54 -07:00
|
|
|
if (tradeType === 'Take Profit Limit' || tradeType === 'Stop Limit') {
|
2021-10-14 11:23:42 -07:00
|
|
|
return Number(price)
|
|
|
|
} else {
|
|
|
|
return Number(triggerPrice)
|
|
|
|
}
|
2021-08-18 13:15:17 -07:00
|
|
|
}
|
|
|
|
return Number(price)
|
|
|
|
}
|
|
|
|
|
2021-04-02 11:26:21 -07:00
|
|
|
export const calculateMarketPrice = (
|
2021-06-17 17:32:45 -07:00
|
|
|
orderBook: Orderbook,
|
2021-04-02 11:26:21 -07:00
|
|
|
size: number,
|
2021-06-17 17:32:45 -07:00
|
|
|
side: 'buy' | 'sell'
|
2022-03-30 04:08:05 -07:00
|
|
|
): number | undefined => {
|
2021-06-17 17:32:45 -07:00
|
|
|
const orders = side === 'buy' ? orderBook.asks : orderBook.bids
|
2021-04-02 11:26:21 -07:00
|
|
|
let acc = 0
|
|
|
|
let selectedOrder
|
2021-06-17 17:32:45 -07:00
|
|
|
for (const order of orders) {
|
2021-04-02 11:26:21 -07:00
|
|
|
acc += order[1]
|
|
|
|
if (acc >= size) {
|
|
|
|
selectedOrder = order
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-18 13:15:17 -07:00
|
|
|
if (!selectedOrder) {
|
|
|
|
console.error('Orderbook empty no market price available')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-02 11:26:21 -07:00
|
|
|
if (side === 'buy') {
|
|
|
|
return selectedOrder[0] * 1.05
|
|
|
|
} else {
|
|
|
|
return selectedOrder[0] * 0.95
|
|
|
|
}
|
|
|
|
}
|
2021-04-09 07:27:49 -07:00
|
|
|
|
|
|
|
// Precision for our mango group token
|
|
|
|
export const tokenPrecision = {
|
|
|
|
BTC: 4,
|
|
|
|
ETH: 3,
|
2021-08-20 06:17:02 -07:00
|
|
|
MNGO: 2,
|
2021-05-20 04:20:39 -07:00
|
|
|
SOL: 2,
|
|
|
|
SRM: 2,
|
2021-11-02 02:28:18 -07:00
|
|
|
RAY: 3,
|
|
|
|
COPE: 2,
|
|
|
|
FTT: 3,
|
|
|
|
ADA: 2,
|
2021-12-20 09:32:09 -08:00
|
|
|
MSOL: 2,
|
|
|
|
BNB: 3,
|
|
|
|
AVAX: 2,
|
2021-11-02 02:28:18 -07:00
|
|
|
USDC: 2,
|
|
|
|
USDT: 2,
|
|
|
|
}
|
|
|
|
|
2021-11-23 09:52:27 -08:00
|
|
|
// Precision for 1 perp contract. -log10(baseLotSize) + baseDecimals
|
|
|
|
export const perpContractPrecision = {
|
|
|
|
BTC: 4,
|
|
|
|
ETH: 3,
|
|
|
|
MNGO: 0,
|
|
|
|
SOL: 2,
|
|
|
|
SRM: 1,
|
|
|
|
RAY: 1,
|
|
|
|
FTT: 1,
|
|
|
|
ADA: 0,
|
2021-12-20 09:32:09 -08:00
|
|
|
BNB: 3,
|
|
|
|
AVAX: 2,
|
2022-01-04 13:34:10 -08:00
|
|
|
LUNA: 2,
|
2021-11-23 09:52:27 -08:00
|
|
|
}
|
|
|
|
|
2021-11-02 02:28:18 -07:00
|
|
|
const tokenPricePrecision = {
|
|
|
|
BTC: 1,
|
|
|
|
ETH: 1,
|
|
|
|
MNGO: 4,
|
|
|
|
SOL: 2,
|
|
|
|
SRM: 3,
|
|
|
|
RAY: 3,
|
|
|
|
COPE: 3,
|
|
|
|
FTT: 3,
|
|
|
|
ADA: 4,
|
2021-12-20 09:32:09 -08:00
|
|
|
MSOL: 2,
|
|
|
|
BNB: 1,
|
|
|
|
AVAX: 2,
|
2021-04-09 07:27:49 -07:00
|
|
|
USDC: 2,
|
|
|
|
USDT: 2,
|
|
|
|
}
|
2021-04-09 17:01:00 -07:00
|
|
|
|
|
|
|
export const getSymbolForTokenMintAddress = (address: string): string => {
|
2021-04-13 16:41:04 -07:00
|
|
|
if (address && address.length) {
|
2021-04-26 09:59:01 -07:00
|
|
|
return TOKEN_MINTS.find((m) => m.address.toString() === address)?.name || ''
|
2021-04-13 16:41:04 -07:00
|
|
|
} else {
|
|
|
|
return ''
|
|
|
|
}
|
2021-04-09 17:01:00 -07:00
|
|
|
}
|
2021-04-20 14:00:45 -07:00
|
|
|
|
|
|
|
export const capitalize = (s) => {
|
|
|
|
if (typeof s !== 'string') return ''
|
|
|
|
return s.charAt(0).toUpperCase() + s.slice(1)
|
|
|
|
}
|
2021-05-01 07:02:21 -07:00
|
|
|
|
2021-06-18 20:07:57 -07:00
|
|
|
export function* chunks(arr, n) {
|
|
|
|
for (let i = 0; i < arr.length; i += n) {
|
|
|
|
yield arr.slice(i, i + n)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-18 21:26:47 -07:00
|
|
|
export function zipDict<K extends string | number | symbol, V>(
|
|
|
|
keys: K[],
|
|
|
|
values: V[]
|
|
|
|
) {
|
|
|
|
const result: Partial<Record<K, V>> = {}
|
2021-06-18 20:07:57 -07:00
|
|
|
keys.forEach((key, index) => {
|
|
|
|
result[key] = values[index]
|
|
|
|
})
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-05-01 07:02:21 -07:00
|
|
|
export const copyToClipboard = (copyThis) => {
|
|
|
|
const el = document.createElement('textarea')
|
|
|
|
el.value = copyThis.toString()
|
|
|
|
document.body.appendChild(el)
|
|
|
|
el.select()
|
|
|
|
document.execCommand('copy')
|
|
|
|
document.body.removeChild(el)
|
|
|
|
}
|
User account page (#22)
* layout, overview, start on assets, borrows and open orders
* trade history, sortable data hook for tables, borrow page
* handle deposit and withdraw buttons
* borrow modal ui and integration + settle borrow for individual assets
* in orders balance to asset table and totals, responsive css, new connected wallet button + small tweaks
* account switch/creation flow
* accounts modal, update to usebalances hook
* handle settle, deposit before settle, save last account
* disable borrow/withdraw button when no account
2021-06-05 07:11:44 -07:00
|
|
|
|
|
|
|
// Truncate decimals without rounding
|
|
|
|
export const trimDecimals = (n, digits) => {
|
2021-06-05 09:14:34 -07:00
|
|
|
const step = Math.pow(10, digits || 0)
|
|
|
|
const temp = Math.trunc(step * n)
|
User account page (#22)
* layout, overview, start on assets, borrows and open orders
* trade history, sortable data hook for tables, borrow page
* handle deposit and withdraw buttons
* borrow modal ui and integration + settle borrow for individual assets
* in orders balance to asset table and totals, responsive css, new connected wallet button + small tweaks
* account switch/creation flow
* accounts modal, update to usebalances hook
* handle settle, deposit before settle, save last account
* disable borrow/withdraw button when no account
2021-06-05 07:11:44 -07:00
|
|
|
|
|
|
|
return temp / step
|
|
|
|
}
|
2021-06-17 11:38:53 -07:00
|
|
|
|
|
|
|
export const i80f48ToPercent = (value: I80F48) =>
|
|
|
|
value.mul(I80F48.fromNumber(100))
|
2021-06-18 13:46:20 -07:00
|
|
|
|
2021-09-02 22:32:27 -07:00
|
|
|
export const usdFormatter = (value, decimals = 2, currency = true) => {
|
2021-08-23 23:44:58 -07:00
|
|
|
if (decimals === 0) {
|
|
|
|
value = Math.abs(value)
|
|
|
|
}
|
2021-09-02 22:32:27 -07:00
|
|
|
const config = currency ? { style: 'currency', currency: 'USD' } : {}
|
2021-08-23 23:44:58 -07:00
|
|
|
return new Intl.NumberFormat('en-US', {
|
2021-09-02 22:32:27 -07:00
|
|
|
...config,
|
2021-08-15 06:31:59 -07:00
|
|
|
minimumFractionDigits: decimals,
|
|
|
|
maximumFractionDigits: decimals,
|
|
|
|
}).format(value)
|
2021-08-23 23:44:58 -07:00
|
|
|
}
|
2021-08-15 06:31:59 -07:00
|
|
|
|
2021-11-02 02:28:18 -07:00
|
|
|
export const formatUsdValue = (value: number, symbol?: string) => {
|
|
|
|
const precision = symbol
|
|
|
|
? tokenPricePrecision[symbol]
|
|
|
|
: value >= 1 || value <= -1
|
|
|
|
? 2
|
|
|
|
: value === 0 ||
|
|
|
|
(value > 0 && value < 0.001) ||
|
|
|
|
(value < 0 && value > -0.001)
|
|
|
|
? 0
|
|
|
|
: 4
|
2021-08-15 06:31:59 -07:00
|
|
|
return usdFormatter(value, precision)
|
|
|
|
}
|
2021-08-24 03:09:42 -07:00
|
|
|
|
|
|
|
export const countLeadingZeros = (x) => {
|
|
|
|
if (x % 1 == 0) {
|
|
|
|
return 0
|
|
|
|
} else {
|
|
|
|
return -1 - Math.floor(Math.log10(x % 1))
|
|
|
|
}
|
|
|
|
}
|
2021-09-09 17:23:02 -07:00
|
|
|
|
|
|
|
export function getBrowserVisibilityProp() {
|
|
|
|
if (typeof document.hidden !== 'undefined') {
|
|
|
|
// Opera 12.10 and Firefox 18 and later support
|
|
|
|
return 'visibilitychange'
|
|
|
|
// @ts-ignore
|
|
|
|
} else if (typeof document.msHidden !== 'undefined') {
|
|
|
|
return 'msvisibilitychange'
|
|
|
|
// @ts-ignore
|
|
|
|
} else if (typeof document.webkitHidden !== 'undefined') {
|
|
|
|
return 'webkitvisibilitychange'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getBrowserDocumentHiddenProp() {
|
|
|
|
if (typeof document.hidden !== 'undefined') {
|
|
|
|
return 'hidden'
|
|
|
|
// @ts-ignore
|
|
|
|
} else if (typeof document.msHidden !== 'undefined') {
|
|
|
|
return 'msHidden'
|
|
|
|
// @ts-ignore
|
|
|
|
} else if (typeof document.webkitHidden !== 'undefined') {
|
|
|
|
return 'webkitHidden'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getIsDocumentHidden() {
|
2022-03-30 04:08:05 -07:00
|
|
|
const index = getBrowserDocumentHiddenProp()
|
|
|
|
if (typeof index === 'number') {
|
|
|
|
return !document[index]
|
|
|
|
}
|
2021-09-09 17:23:02 -07:00
|
|
|
}
|
2021-10-07 04:34:26 -07:00
|
|
|
|
|
|
|
export const numberCompactFormatter = Intl.NumberFormat('en', {
|
|
|
|
notation: 'compact',
|
|
|
|
})
|
2022-01-24 04:46:06 -08:00
|
|
|
|
|
|
|
export function patchInternalMarketName(marketName: string) {
|
|
|
|
if (marketName.includes('/USDC')) {
|
|
|
|
marketName = marketName.replace('/USDC', '-SPOT')
|
|
|
|
}
|
|
|
|
return marketName
|
|
|
|
}
|
2022-03-15 07:12:46 -07:00
|
|
|
|
|
|
|
export function roundPerpSize(size: number, symbol: string) {
|
2022-03-21 05:52:41 -07:00
|
|
|
return new BigNumber(size).abs().toFormat(perpContractPrecision[symbol])
|
2022-03-15 07:12:46 -07:00
|
|
|
}
|