useCallback to reduce trading view rerenders and memory leaks

This commit is contained in:
tjs 2023-02-25 14:59:28 -05:00
parent c3f3a34a63
commit ebb0d546b7
3 changed files with 520 additions and 464 deletions

View File

@ -152,6 +152,496 @@ const TradingViewChart = () => {
} }
}, [selectedMarketName, spotOrPerp]) }, [selectedMarketName, spotOrPerp])
const createStablePriceButton = useCallback(() => {
const toggleStablePrice = (button: HTMLElement) => {
toggleShowStablePrice((prevState: boolean) => !prevState)
if (button.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
button.style.color = COLORS.FGD4[theme]
} else {
button.style.color = COLORS.ACTIVE[theme]
}
}
const button = tvWidgetRef?.current?.createButton()
if (!button) {
return
}
button.textContent = 'SP'
if (showStablePriceLocalStorage) {
button.style.color = COLORS.ACTIVE[theme]
} else {
button.style.color = COLORS.FGD4[theme]
}
button.setAttribute('title', t('tv-chart:toggle-stable-price'))
button.addEventListener('click', () => toggleStablePrice(button))
}, [showStablePriceLocalStorage, theme, t])
useEffect(() => {
if (showStablePrice !== showStablePriceLocalStorage) {
toggleShowStablePriceLocalStorage(showStablePrice)
}
}, [
showStablePrice,
showStablePriceLocalStorage,
toggleShowStablePriceLocalStorage,
])
const drawStablePriceLine = useCallback(
(price: number) => {
if (!tvWidgetRef?.current?.chart()) return
const now = Date.now() / 1000
try {
const id = tvWidgetRef.current.chart().createShape(
{ time: now, price: price },
{
shape: 'horizontal_line',
overrides: {
linecolor: COLORS.FGD4[theme],
linestyle: 1,
linewidth: 1,
},
}
)
if (id) {
return id
} else {
console.warn('failed to create stable price line')
}
} catch {
console.warn('failed to create stable price line')
}
},
[theme]
)
const removeStablePrice = useCallback((id: EntityId) => {
if (!tvWidgetRef?.current?.chart()) return
const set = mangoStore.getState().set
try {
tvWidgetRef.current.chart().removeEntity(id)
} catch (error) {
console.warn('stable price could not be removed')
}
set((s) => {
s.tradingView.stablePriceLine = undefined
})
}, [])
// remove stable price line when toggling off
useEffect(() => {
if (tvWidgetRef.current && chartReady) {
if (!showStablePrice && stablePriceLine) {
removeStablePrice(stablePriceLine)
}
}
}, [showStablePrice, chartReady, removeStablePrice, stablePriceLine])
// update stable price line when toggled on
useEffect(() => {
if (tvWidgetRef.current && chartReady) {
if (showStablePrice && stablePrice) {
const set = mangoStore.getState().set
set((s) => {
s.tradingView.stablePriceLine = drawStablePriceLine(stablePrice)
})
}
}
}, [stablePrice, chartReady, showStablePrice, drawStablePriceLine])
useEffect(() => {
if (showOrderLines !== showOrderLinesLocalStorage) {
toggleShowOrderLinesLocalStorage(showOrderLines)
}
}, [
showOrderLines,
showOrderLinesLocalStorage,
toggleShowOrderLinesLocalStorage,
])
const deleteLines = useCallback(() => {
const set = mangoStore.getState().set
const orderLines = mangoStore.getState().tradingView.orderLines
if (orderLines.size > 0) {
orderLines?.forEach((value: IOrderLineAdapter, key: string | BN) => {
orderLines.get(key)?.remove()
})
set((state) => {
state.tradingView.orderLines = new Map()
})
}
}, [])
const getOrderDecimals = useCallback(() => {
const selectedMarket = mangoStore.getState().selectedMarket.current
let minOrderDecimals = 4
let tickSizeDecimals = 2
if (!selectedMarket) return [minOrderDecimals, tickSizeDecimals]
if (selectedMarket instanceof PerpMarket) {
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
tickSizeDecimals = getDecimalCount(selectedMarket.tickSize)
} else {
const group = mangoStore.getState().group
const market = group?.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
if (market) {
minOrderDecimals = getDecimalCount(market.minOrderSize)
tickSizeDecimals = getDecimalCount(market.tickSize)
}
}
return [minOrderDecimals, tickSizeDecimals]
}, [])
const findSerum3MarketPkInOpenOrders = useCallback(
(o: Order): string | undefined => {
const openOrders = mangoStore.getState().mangoAccount.openOrders
let foundedMarketPk: string | undefined = undefined
for (const [marketPk, orders] of Object.entries(openOrders)) {
for (const order of orders) {
if (order.orderId.eq(o.orderId)) {
foundedMarketPk = marketPk
break
}
}
if (foundedMarketPk) {
break
}
}
return foundedMarketPk
},
[]
)
const modifyOrder = useCallback(
async (o: PerpOrder | Order, price: number) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
const baseSize = o.size
if (!group || !mangoAccount) return
try {
let tx = ''
if (o instanceof PerpOrder) {
tx = await client.modifyPerpOrder(
group,
mangoAccount,
o.perpMarketIndex,
o.orderId,
o.side,
price,
Math.abs(baseSize),
undefined, // maxQuoteQuantity
Date.now(),
PerpOrderType.limit,
undefined,
undefined
)
} else {
const marketPk = findSerum3MarketPkInOpenOrders(o)
if (!marketPk) return
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
tx = await client.modifySerum3Order(
group,
o.orderId,
mangoAccount,
market.serumMarketExternal,
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
price,
baseSize,
Serum3SelfTradeBehavior.decrementTake,
Serum3OrderType.limit,
Date.now(),
10
)
}
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: 'Unable to modify order',
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[findSerum3MarketPkInOpenOrders]
)
const cancelSpotOrder = useCallback(
async (o: Order) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!group || !mangoAccount) return
const marketPk = findSerum3MarketPkInOpenOrders(o)
if (!marketPk) return
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
try {
const tx = await client.serum3CancelOrder(
group,
mangoAccount,
market!.serumMarketExternal,
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
o.orderId
)
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: t('trade:cancel-order-error'),
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[t, findSerum3MarketPkInOpenOrders]
)
const cancelPerpOrder = useCallback(
async (o: PerpOrder) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!group || !mangoAccount) return
try {
const tx = await client.perpCancelOrder(
group,
mangoAccount,
o.perpMarketIndex,
o.orderId
)
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: t('trade:cancel-order-error'),
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[t]
)
const drawLine = useCallback(
(order: Order | PerpOrder) => {
const side =
typeof order.side === 'string'
? t(order.side)
: 'bid' in order.side
? t('buy')
: t('sell')
const isLong = side.toLowerCase() === 'buy'
const isShort = side.toLowerCase() === 'sell'
const [minOrderDecimals, tickSizeDecimals] = getOrderDecimals()
const orderSizeUi: string = formatNumericValue(
order.size,
minOrderDecimals
)
if (!tvWidgetRef?.current?.chart()) return
return (
tvWidgetRef.current
.chart()
.createOrderLine({ disableUndo: false })
.onMove(function (this: IOrderLineAdapter) {
const currentOrderPrice = order.price
const updatedOrderPrice = this.getPrice()
const selectedMarketPrice =
mangoStore.getState().selectedMarket.markPrice
if (
(isLong && updatedOrderPrice > 1.05 * selectedMarketPrice) ||
(isShort && updatedOrderPrice < 0.95 * selectedMarketPrice)
) {
tvWidgetRef.current?.showNoticeDialog({
title: t('tv-chart:outside-range'),
body:
t('tv-chart:slippage-warning', {
updatedOrderPrice: updatedOrderPrice,
aboveBelow:
side == 'buy' || side === 'long'
? t('above')
: t('below'),
selectedMarketPrice: selectedMarketPrice,
}) +
'<p><p>' +
t('tv-chart:slippage-accept'),
callback: () => {
this.setPrice(currentOrderPrice)
},
})
} else {
tvWidgetRef.current?.showConfirmDialog({
title: t('tv-chart:modify-order'),
body: t('tv-chart:modify-order-details', {
marketName: selectedMarketName,
orderSize: orderSizeUi,
orderSide: side.toUpperCase(),
currentOrderPrice: formatNumericValue(
currentOrderPrice,
tickSizeDecimals
),
updatedOrderPrice: formatNumericValue(
updatedOrderPrice,
tickSizeDecimals
),
}),
callback: (res) => {
if (res) {
modifyOrder(order, updatedOrderPrice)
} else {
this.setPrice(currentOrderPrice)
}
},
})
}
})
.onCancel(function () {
tvWidgetRef.current?.showConfirmDialog({
title: t('tv-chart:cancel-order'),
body: t('tv-chart:cancel-order-details', {
marketName: selectedMarketName,
orderSize: orderSizeUi,
orderSide: side.toUpperCase(),
orderPrice: formatNumericValue(order.price, tickSizeDecimals),
}),
callback: (res) => {
if (res) {
if (order instanceof PerpOrder) {
cancelPerpOrder(order)
} else {
cancelSpotOrder(order)
}
}
},
})
})
.setPrice(order.price)
.setQuantity(orderSizeUi)
.setText(side.toUpperCase())
// .setTooltip(
// order.perpTrigger?.clientOrderId
// ? `${order.orderType} Order #: ${order.orderId}`
// : `Order #: ${order.orderId}`
// )
.setBodyTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setQuantityTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setCancelButtonIconColor(COLORS.FGD4[theme])
.setBodyBorderColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setQuantityBorderColor(
isLong ? COLORS.UP[theme] : COLORS.DOWN[theme]
)
.setCancelButtonBorderColor(
isLong ? COLORS.UP[theme] : COLORS.DOWN[theme]
)
.setBodyBackgroundColor(COLORS.BKG1[theme])
.setQuantityBackgroundColor(COLORS.BKG1[theme])
.setCancelButtonBackgroundColor(COLORS.BKG1[theme])
.setLineColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setLineLength(3)
.setLineWidth(1)
.setLineStyle(1)
)
},
[
cancelPerpOrder,
cancelSpotOrder,
modifyOrder,
selectedMarketName,
t,
theme,
getOrderDecimals,
]
)
const drawLinesForMarket = useCallback(
(openOrders: Record<string, Order[] | PerpOrder[]>) => {
const set = mangoStore.getState().set
const newOrderLines = new Map()
const oOrders = Object.entries(openOrders).map(([marketPk, orders]) => ({
orders,
marketPk,
}))
if (oOrders?.length) {
const selectedMarket = mangoStore.getState().selectedMarket.current
const selectedMarketPk =
selectedMarket instanceof Serum3Market
? selectedMarket?.serumMarketExternal.toString()
: selectedMarket?.publicKey.toString()
for (const { orders, marketPk } of oOrders) {
if (marketPk === selectedMarketPk) {
for (const order of orders) {
newOrderLines.set(order.orderId.toString(), drawLine(order))
}
}
}
}
set((state) => {
state.tradingView.orderLines = newOrderLines
})
},
[drawLine]
)
const toggleOrderLines = useCallback(
(el: HTMLElement) => {
toggleShowOrderLines((prevState: boolean) => !prevState)
if (el.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
deleteLines()
el.style.color = COLORS.FGD4[theme]
} else {
const openOrders = mangoStore.getState().mangoAccount.openOrders
drawLinesForMarket(openOrders)
el.style.color = COLORS.ACTIVE[theme]
}
},
[drawLinesForMarket, deleteLines, theme]
)
const createOLButton = useCallback(() => {
const button = tvWidgetRef?.current?.createButton()
if (!button) {
return
}
button.textContent = 'OL'
if (showOrderLinesLocalStorage) {
button.style.color = COLORS.ACTIVE[theme]
} else {
button.style.color = COLORS.FGD4[theme]
}
button.setAttribute('title', t('tv-chart:toggle-order-line'))
button.addEventListener('click', () => toggleOrderLines(button))
}, [t, theme, toggleOrderLines, showOrderLinesLocalStorage])
useEffect(() => { useEffect(() => {
if (window) { if (window) {
let chartStyleOverrides = { let chartStyleOverrides = {
@ -251,139 +741,31 @@ const TradingViewChart = () => {
} }
if (showStablePrice && stablePrice) { if (showStablePrice && stablePrice) {
const set = mangoStore.getState().set const set = mangoStore.getState().set
const elementId = drawStablePriceLine(stablePrice)
set((s) => { set((s) => {
s.tradingView.stablePriceLine = drawStablePriceLine(stablePrice) s.tradingView.stablePriceLine = elementId
}) })
} }
setChartReady(true) setChartReady(true)
}) })
//eslint-disable-next-line //eslint-disable-next-line
} }
}, [theme, isMobile, defaultProps, spotOrPerp]) }, [
createOLButton,
const createStablePriceButton = () => { createStablePriceButton,
const button = tvWidgetRef?.current?.createButton() selectedMarket,
if (!button) { theme,
return spotOrPerp,
} defaultProps,
button.textContent = 'SP' deleteLines,
if (showStablePriceLocalStorage) { drawLinesForMarket,
button.style.color = COLORS.ACTIVE[theme] showOrderLines,
} else { showStablePrice,
button.style.color = COLORS.FGD4[theme] stablePrice,
} drawStablePriceLine,
button.setAttribute('title', t('tv-chart:toggle-stable-price')) setChartReady,
button.addEventListener('click', toggleStablePrice) isMobile,
} ])
function toggleStablePrice(this: HTMLElement) {
toggleShowStablePrice((prevState: boolean) => !prevState)
if (this.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
this.style.color = COLORS.FGD4[theme]
} else {
this.style.color = COLORS.ACTIVE[theme]
}
}
useEffect(() => {
if (showStablePrice !== showStablePriceLocalStorage) {
toggleShowStablePriceLocalStorage(showStablePrice)
}
}, [showStablePrice])
useEffect(() => {
if (tvWidgetRef.current && chartReady) {
if (stablePriceLine) {
removeStablePrice(stablePriceLine)
}
if (showStablePrice && stablePrice) {
const set = mangoStore.getState().set
set((s) => {
s.tradingView.stablePriceLine = drawStablePriceLine(stablePrice)
})
}
}
}, [stablePrice, showStablePrice, chartReady, tvWidgetRef])
function drawStablePriceLine(price: number) {
if (!tvWidgetRef?.current?.chart()) return
const newStablePrice: Map<string, EntityId> = new Map()
const now = Date.now() / 1000
try {
const id = tvWidgetRef.current.chart().createShape(
{ time: now, price: price },
{
shape: 'horizontal_line',
overrides: {
linecolor: COLORS.FGD4[theme],
linestyle: 1,
linewidth: 1,
},
}
)
if (id) {
try {
newStablePrice.set(`${now}${price}`, id)
} catch (error) {
console.warn('failed to set stable price line')
}
} else {
console.warn('failed to create stable price line')
}
} catch {
console.warn('failed to create stable price line')
}
return newStablePrice
}
const removeStablePrice = (stablePrice: Map<string, EntityId>) => {
if (!tvWidgetRef?.current?.chart()) return
const set = mangoStore.getState().set
for (const val of stablePrice.values()) {
try {
tvWidgetRef.current.chart().removeEntity(val)
} catch (error) {
console.warn('stable price could not be removed')
}
}
set((s) => {
s.tradingView.stablePriceLine = new Map()
})
}
const createOLButton = () => {
const button = tvWidgetRef?.current?.createButton()
if (!button) {
return
}
button.textContent = 'OL'
if (showOrderLinesLocalStorage) {
button.style.color = COLORS.ACTIVE[theme]
} else {
button.style.color = COLORS.FGD4[theme]
}
button.setAttribute('title', t('tv-chart:toggle-order-line'))
button.addEventListener('click', toggleOrderLines)
}
function toggleOrderLines(this: HTMLElement) {
toggleShowOrderLines((prevState: boolean) => !prevState)
if (this.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
deleteLines()
this.style.color = COLORS.FGD4[theme]
} else {
const openOrders = mangoStore.getState().mangoAccount.openOrders
drawLinesForMarket(openOrders)
this.style.color = COLORS.ACTIVE[theme]
}
}
useEffect(() => {
if (showOrderLines !== showOrderLinesLocalStorage) {
toggleShowOrderLinesLocalStorage(showOrderLines)
}
}, [showOrderLines])
// update order lines if a user's open orders change // update order lines if a user's open orders change
useEffect(() => { useEffect(() => {
@ -441,336 +823,7 @@ const TradingViewChart = () => {
) )
} }
return subscription return subscription
}, [chartReady, showOrderLines]) }, [chartReady, showOrderLines, deleteLines, drawLinesForMarket])
const drawLinesForMarket = (
openOrders: Record<string, Order[] | PerpOrder[]>
) => {
const set = mangoStore.getState().set
const newOrderLines = new Map()
const oOrders = Object.entries(openOrders).map(([marketPk, orders]) => ({
orders,
marketPk,
}))
if (oOrders?.length) {
const selectedMarket = mangoStore.getState().selectedMarket.current
const selectedMarketPk =
selectedMarket instanceof Serum3Market
? selectedMarket?.serumMarketExternal.toString()
: selectedMarket?.publicKey.toString()
for (const { orders, marketPk } of oOrders) {
if (marketPk === selectedMarketPk) {
for (const order of orders) {
newOrderLines.set(order.orderId.toString(), drawLine(order))
}
}
}
}
set((state) => {
state.tradingView.orderLines = newOrderLines
})
}
const deleteLines = () => {
const set = mangoStore.getState().set
const orderLines = mangoStore.getState().tradingView.orderLines
if (orderLines.size > 0) {
orderLines?.forEach((value: IOrderLineAdapter, key: string | BN) => {
orderLines.get(key)?.remove()
})
set((state) => {
state.tradingView.orderLines = new Map()
})
}
}
function getOrderDecimals() {
const selectedMarket = mangoStore.getState().selectedMarket.current
let minOrderDecimals = 4
let tickSizeDecimals = 2
if (!selectedMarket) return [minOrderDecimals, tickSizeDecimals]
if (selectedMarket instanceof PerpMarket) {
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
tickSizeDecimals = getDecimalCount(selectedMarket.tickSize)
} else {
const group = mangoStore.getState().group
const market = group?.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
if (market) {
minOrderDecimals = getDecimalCount(market.minOrderSize)
tickSizeDecimals = getDecimalCount(market.tickSize)
}
}
return [minOrderDecimals, tickSizeDecimals]
}
function drawLine(order: Order | PerpOrder) {
const side =
typeof order.side === 'string'
? t(order.side)
: 'bid' in order.side
? t('buy')
: t('sell')
const isLong = side.toLowerCase() === 'buy'
const isShort = side.toLowerCase() === 'sell'
const [minOrderDecimals, tickSizeDecimals] = getOrderDecimals()
const orderSizeUi: string = formatNumericValue(order.size, minOrderDecimals)
if (!tvWidgetRef?.current?.chart()) return
return (
tvWidgetRef.current
.chart()
.createOrderLine({ disableUndo: false })
.onMove(function (this: IOrderLineAdapter) {
const currentOrderPrice = order.price
const updatedOrderPrice = this.getPrice()
const selectedMarketPrice =
mangoStore.getState().selectedMarket.markPrice
if (
(isLong && updatedOrderPrice > 1.05 * selectedMarketPrice) ||
(isShort && updatedOrderPrice < 0.95 * selectedMarketPrice)
) {
tvWidgetRef.current?.showNoticeDialog({
title: t('tv-chart:outside-range'),
body:
t('tv-chart:slippage-warning', {
updatedOrderPrice: updatedOrderPrice,
aboveBelow:
side == 'buy' || side === 'long' ? t('above') : t('below'),
selectedMarketPrice: selectedMarketPrice,
}) +
'<p><p>' +
t('tv-chart:slippage-accept'),
callback: () => {
this.setPrice(currentOrderPrice)
},
})
} else {
tvWidgetRef.current?.showConfirmDialog({
title: t('tv-chart:modify-order'),
body: t('tv-chart:modify-order-details', {
marketName: selectedMarketName,
orderSize: orderSizeUi,
orderSide: side.toUpperCase(),
currentOrderPrice: formatNumericValue(
currentOrderPrice,
tickSizeDecimals
),
updatedOrderPrice: formatNumericValue(
updatedOrderPrice,
tickSizeDecimals
),
}),
callback: (res) => {
if (res) {
modifyOrder(order, updatedOrderPrice)
} else {
this.setPrice(currentOrderPrice)
}
},
})
}
})
.onCancel(function () {
tvWidgetRef.current?.showConfirmDialog({
title: t('tv-chart:cancel-order'),
body: t('tv-chart:cancel-order-details', {
marketName: selectedMarketName,
orderSize: orderSizeUi,
orderSide: side.toUpperCase(),
orderPrice: formatNumericValue(order.price, tickSizeDecimals),
}),
callback: (res) => {
if (res) {
if (order instanceof PerpOrder) {
cancelPerpOrder(order)
} else {
cancelSpotOrder(order)
}
}
},
})
})
.setPrice(order.price)
.setQuantity(orderSizeUi)
.setText(side.toUpperCase())
// .setTooltip(
// order.perpTrigger?.clientOrderId
// ? `${order.orderType} Order #: ${order.orderId}`
// : `Order #: ${order.orderId}`
// )
.setBodyTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setQuantityTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setCancelButtonIconColor(COLORS.FGD4[theme])
.setBodyBorderColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setQuantityBorderColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setCancelButtonBorderColor(
isLong ? COLORS.UP[theme] : COLORS.DOWN[theme]
)
.setBodyBackgroundColor(COLORS.BKG1[theme])
.setQuantityBackgroundColor(COLORS.BKG1[theme])
.setCancelButtonBackgroundColor(COLORS.BKG1[theme])
.setLineColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setLineLength(3)
.setLineWidth(1)
.setLineStyle(1)
)
}
const modifyOrder = useCallback(
async (o: PerpOrder | Order, price: number) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
const baseSize = o.size
if (!group || !mangoAccount) return
try {
let tx = ''
if (o instanceof PerpOrder) {
tx = await client.modifyPerpOrder(
group,
mangoAccount,
o.perpMarketIndex,
o.orderId,
o.side,
price,
Math.abs(baseSize),
undefined, // maxQuoteQuantity
Date.now(),
PerpOrderType.limit,
undefined,
undefined
)
} else {
const marketPk = findSerum3MarketPkInOpenOrders(o)
if (!marketPk) return
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
tx = await client.modifySerum3Order(
group,
o.orderId,
mangoAccount,
market.serumMarketExternal,
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
price,
baseSize,
Serum3SelfTradeBehavior.decrementTake,
Serum3OrderType.limit,
Date.now(),
10
)
}
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: 'Unable to modify order',
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[t]
)
const cancelSpotOrder = useCallback(
async (o: Order) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!group || !mangoAccount) return
const marketPk = findSerum3MarketPkInOpenOrders(o)
if (!marketPk) return
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
try {
const tx = await client.serum3CancelOrder(
group,
mangoAccount,
market!.serumMarketExternal,
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
o.orderId
)
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: t('trade:cancel-order-error'),
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[t]
)
const cancelPerpOrder = useCallback(
async (o: PerpOrder) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!group || !mangoAccount) return
try {
const tx = await client.perpCancelOrder(
group,
mangoAccount,
o.perpMarketIndex,
o.orderId
)
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: t('trade:cancel-order-error'),
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[t]
)
const findSerum3MarketPkInOpenOrders = (o: Order): string | undefined => {
const openOrders = mangoStore.getState().mangoAccount.openOrders
let foundedMarketPk: string | undefined = undefined
for (const [marketPk, orders] of Object.entries(openOrders)) {
for (const order of orders) {
if (order.orderId.eq(o.orderId)) {
foundedMarketPk = marketPk
break
}
}
if (foundedMarketPk) {
break
}
}
return foundedMarketPk
}
return ( return (
<div id={defaultProps.container as string} className="tradingview-chart" /> <div id={defaultProps.container as string} className="tradingview-chart" />

View File

@ -33,10 +33,6 @@ export function useLocalStorageStringState(
if (!localStorageListeners[key]) { if (!localStorageListeners[key]) {
localStorageListeners[key] = [] localStorageListeners[key] = []
} }
const changed = state !== newState
if (!changed) {
return
}
if (newState === null) { if (newState === null) {
localStorage.removeItem(key) localStorage.removeItem(key)
@ -47,7 +43,7 @@ export function useLocalStorageStringState(
listener(key + '\n' + newState) listener(key + '\n' + newState)
) )
}, },
[state, key] [key]
) )
return [state, setState] return [state, setState]
@ -64,8 +60,15 @@ export default function useLocalStorageState(
JSON.stringify(defaultState) JSON.stringify(defaultState)
) )
const setState = useCallback(
(newState: string | number | object) => {
setStringState(JSON.stringify(newState))
},
[setStringState]
)
return [ return [
useMemo(() => stringState && JSON.parse(stringState), [stringState]), useMemo(() => stringState && JSON.parse(stringState), [stringState]),
(newState) => setStringState(JSON.stringify(newState)), setState,
] ]
} }

View File

@ -213,7 +213,7 @@ export type MangoStore = {
} }
tradeForm: TradeForm tradeForm: TradeForm
tradingView: { tradingView: {
stablePriceLine: Map<string, EntityId> | undefined stablePriceLine: EntityId | undefined
orderLines: Map<string | BN, IOrderLineAdapter> orderLines: Map<string | BN, IOrderLineAdapter>
} }
wallet: { wallet: {
@ -365,7 +365,7 @@ const mangoStore = create<MangoStore>()(
}, },
tradeForm: DEFAULT_TRADE_FORM, tradeForm: DEFAULT_TRADE_FORM,
tradingView: { tradingView: {
stablePriceLine: new Map(), stablePriceLine: undefined,
orderLines: new Map(), orderLines: new Map(),
}, },
wallet: { wallet: {