Add Tradingview Order Lines (#32)
* Update Cancel and Place Order Functions Updated function to cancel and place a new order. Requires 2 tx's until mango-client can be updated with the modify order tx. * Completed handleModifyOrder Completed handleModifyOrder and removed handlePlaceOrder as it is no longer required. * Formatting and Context Updates Added some additional context to the Modify order notifications, specifying modified orders as Limit orders, and updated USD formatting. * Update yarn.lock * Added toggles for OpenOrders initialization and OrderInProgress Added an additional order line update on initialization of an OpenOrders account and added a toggle for orders in progress to prevent a moved order line from moving back to it's original price whilst a transaction for an order line is pending.
This commit is contained in:
parent
d09b9a4283
commit
abfc8c6c69
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import {
|
import {
|
||||||
widget,
|
widget,
|
||||||
|
@ -6,9 +6,14 @@ import {
|
||||||
IChartingLibraryWidget,
|
IChartingLibraryWidget,
|
||||||
ResolutionString,
|
ResolutionString,
|
||||||
} from '../charting_library' // Make sure to follow step 1 of the README
|
} from '../charting_library' // Make sure to follow step 1 of the README
|
||||||
// import { useMarket } from '../../utils/markets';
|
|
||||||
import { CHART_DATA_FEED } from '../../utils/chartDataConnector'
|
import { CHART_DATA_FEED } from '../../utils/chartDataConnector'
|
||||||
import useMangoStore from '../../stores/useMangoStore'
|
import useMangoStore, { mangoClient } from '../../stores/useMangoStore'
|
||||||
|
import { useOpenOrders } from '../../hooks/useOpenOrders'
|
||||||
|
import { useSortableData } from '../../hooks/useSortableData'
|
||||||
|
import { Order, Market } from '@project-serum/serum/lib/market'
|
||||||
|
import { PerpOrder, PerpMarket } from '@blockworks-foundation/mango-client'
|
||||||
|
import { notify } from '../../utils/notifications'
|
||||||
|
import { sleep, formatUsdValue } from '../../utils'
|
||||||
|
|
||||||
// This is a basic example of how to create a TV widget
|
// This is a basic example of how to create a TV widget
|
||||||
// You can add more feature such as storing charts in localStorage
|
// You can add more feature such as storing charts in localStorage
|
||||||
|
@ -35,6 +40,19 @@ const TVChartContainer = () => {
|
||||||
const selectedMarketConfig = useMangoStore((s) => s.selectedMarket.config)
|
const selectedMarketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const selectedMarketName = selectedMarketConfig.name
|
||||||
|
const openOrders = useOpenOrders()
|
||||||
|
const { items } = useSortableData(openOrders)
|
||||||
|
const actions = useMangoStore((s) => s.actions)
|
||||||
|
const connected = useMangoStore((s) => s.wallet.connected)
|
||||||
|
const selectedMarginAccount =
|
||||||
|
useMangoStore.getState().selectedMangoAccount.current
|
||||||
|
const selectedMarketPrice = useMangoStore((s) => s.selectedMarket.markPrice)
|
||||||
|
const [lines, setLines] = useState(new Map())
|
||||||
|
const [moveInProgress, toggleMoveInProgress] = useState(false)
|
||||||
|
const [openOrdersIntialized, toggleOpenOrdersIntialized] = useState(false)
|
||||||
|
const [orderInProgress, toggleOrderInProgress] = useState(false)
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const defaultProps: ChartContainerProps = {
|
const defaultProps: ChartContainerProps = {
|
||||||
symbol: selectedMarketConfig.name,
|
symbol: selectedMarketConfig.name,
|
||||||
|
@ -123,27 +141,251 @@ const TVChartContainer = () => {
|
||||||
|
|
||||||
const tvWidget = new widget(widgetOptions)
|
const tvWidget = new widget(widgetOptions)
|
||||||
tvWidgetRef.current = tvWidget
|
tvWidgetRef.current = tvWidget
|
||||||
|
|
||||||
tvWidget.onChartReady(() => {
|
|
||||||
// tvWidget.headerReady().then(() => {
|
|
||||||
// const button = tvWidget.createButton()
|
|
||||||
// button.setAttribute('title', 'Click to show a notification popup')
|
|
||||||
// button.classList.add('apply-common-tooltip')
|
|
||||||
// button.addEventListener('click', () =>
|
|
||||||
// tvWidget.showNoticeDialog({
|
|
||||||
// title: 'Notification',
|
|
||||||
// body: 'TradingView Charting Library API works correctly',
|
|
||||||
// callback: () => {
|
|
||||||
// // console.log('It works!!');
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// button.innerHTML = 'Check API'
|
|
||||||
// })
|
|
||||||
})
|
|
||||||
//eslint-disable-next-line
|
|
||||||
}, [selectedMarketConfig, theme])
|
}, [selectedMarketConfig, theme])
|
||||||
|
|
||||||
|
const handleCancelOrder = async (
|
||||||
|
order: Order | PerpOrder,
|
||||||
|
market: Market | PerpMarket
|
||||||
|
) => {
|
||||||
|
const wallet = useMangoStore.getState().wallet.current
|
||||||
|
const selectedMangoGroup =
|
||||||
|
useMangoStore.getState().selectedMangoGroup.current
|
||||||
|
const selectedMangoAccount =
|
||||||
|
useMangoStore.getState().selectedMangoAccount.current
|
||||||
|
|
||||||
|
let txid
|
||||||
|
try {
|
||||||
|
if (!selectedMangoGroup || !selectedMangoAccount) return
|
||||||
|
if (market instanceof Market) {
|
||||||
|
txid = await mangoClient.cancelSpotOrder(
|
||||||
|
selectedMangoGroup,
|
||||||
|
selectedMangoAccount,
|
||||||
|
wallet,
|
||||||
|
market,
|
||||||
|
order as Order
|
||||||
|
)
|
||||||
|
} else if (market instanceof PerpMarket) {
|
||||||
|
txid = await mangoClient.cancelPerpOrder(
|
||||||
|
selectedMangoGroup,
|
||||||
|
selectedMangoAccount,
|
||||||
|
wallet,
|
||||||
|
market,
|
||||||
|
order as PerpOrder
|
||||||
|
)
|
||||||
|
}
|
||||||
|
notify({ title: 'Successfully cancelled order', txid })
|
||||||
|
} catch (e) {
|
||||||
|
notify({
|
||||||
|
title: 'Error cancelling order',
|
||||||
|
description: e.message,
|
||||||
|
txid: e.txid,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
sleep(500).then(() => {
|
||||||
|
actions.fetchMangoAccounts()
|
||||||
|
actions.updateOpenOrders()
|
||||||
|
toggleOrderInProgress(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleModifyOrder = async (
|
||||||
|
order: Order | PerpOrder,
|
||||||
|
market: Market | PerpMarket,
|
||||||
|
price: number
|
||||||
|
) => {
|
||||||
|
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
||||||
|
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||||
|
const { askInfo, bidInfo } = useMangoStore.getState().selectedMarket
|
||||||
|
const wallet = useMangoStore.getState().wallet.current
|
||||||
|
|
||||||
|
if (!wallet || !mangoGroup || !mangoAccount || !market) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const orderPrice = price
|
||||||
|
|
||||||
|
if (!orderPrice) {
|
||||||
|
notify({
|
||||||
|
title: 'Price not available',
|
||||||
|
description: 'Please try again',
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const orderType = 'limit'
|
||||||
|
let txid
|
||||||
|
if (market instanceof Market) {
|
||||||
|
txid = await mangoClient.modifySpotOrder(
|
||||||
|
mangoGroup,
|
||||||
|
mangoAccount,
|
||||||
|
mangoGroup.mangoCache,
|
||||||
|
market,
|
||||||
|
wallet,
|
||||||
|
order,
|
||||||
|
order.side,
|
||||||
|
orderPrice,
|
||||||
|
order.size,
|
||||||
|
orderType
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
txid = await mangoClient.modifyPerpOrder(
|
||||||
|
mangoGroup,
|
||||||
|
mangoAccount,
|
||||||
|
mangoGroup.mangoCache,
|
||||||
|
market,
|
||||||
|
wallet,
|
||||||
|
order,
|
||||||
|
order.side,
|
||||||
|
orderPrice,
|
||||||
|
order.size,
|
||||||
|
orderType,
|
||||||
|
0,
|
||||||
|
order.side === 'buy' ? askInfo : bidInfo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
notify({ title: 'Successfully placed trade', txid })
|
||||||
|
} catch (e) {
|
||||||
|
notify({
|
||||||
|
title: 'Error placing order',
|
||||||
|
description: e.message,
|
||||||
|
txid: e.txid,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
sleep(1000).then(() => {
|
||||||
|
actions.fetchMangoAccounts()
|
||||||
|
actions.updateOpenOrders()
|
||||||
|
toggleOrderInProgress(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLine(order, market) {
|
||||||
|
return tvWidgetRef.current
|
||||||
|
.chart()
|
||||||
|
.createOrderLine({ disableUndo: false })
|
||||||
|
.onMove(function () {
|
||||||
|
toggleMoveInProgress(true)
|
||||||
|
toggleOrderInProgress(true)
|
||||||
|
const currentOrderPrice = order.price
|
||||||
|
const updatedOrderPrice = this.getPrice()
|
||||||
|
if (
|
||||||
|
(order.side === 'buy' &&
|
||||||
|
updatedOrderPrice > 1.05 * selectedMarketPrice) ||
|
||||||
|
(order.side === 'sell' &&
|
||||||
|
updatedOrderPrice < 0.95 * selectedMarketPrice)
|
||||||
|
) {
|
||||||
|
tvWidgetRef.current.showNoticeDialog({
|
||||||
|
title: 'Order Price Outside Range',
|
||||||
|
body:
|
||||||
|
`Your order price (${formatUsdValue(
|
||||||
|
updatedOrderPrice
|
||||||
|
)}) is greater than 5% ${
|
||||||
|
order.side == 'buy' ? 'above' : 'below'
|
||||||
|
} the current market price (${formatUsdValue(
|
||||||
|
selectedMarketPrice
|
||||||
|
)}). ` +
|
||||||
|
' indicating you might incur significant slippage. <p><p>Please use the trade input form if you wish to accept the potential slippage.',
|
||||||
|
callback: () => {
|
||||||
|
this.setPrice(currentOrderPrice)
|
||||||
|
toggleMoveInProgress(false)
|
||||||
|
toggleOrderInProgress(false)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
tvWidgetRef.current.showConfirmDialog({
|
||||||
|
title: 'Modify Your Order?',
|
||||||
|
body: `Would you like to change your order from a
|
||||||
|
${order.size} ${market.config.baseSymbol} ${
|
||||||
|
order.side
|
||||||
|
} at ${formatUsdValue(currentOrderPrice)}
|
||||||
|
to a
|
||||||
|
${order.size} ${market.config.baseSymbol} LIMIT ${
|
||||||
|
order.side
|
||||||
|
} at ${formatUsdValue(updatedOrderPrice)}?
|
||||||
|
`,
|
||||||
|
callback: (res) => {
|
||||||
|
if (res) {
|
||||||
|
handleModifyOrder(order, market.account, updatedOrderPrice)
|
||||||
|
} else {
|
||||||
|
this.setPrice(currentOrderPrice)
|
||||||
|
toggleOrderInProgress(false)
|
||||||
|
}
|
||||||
|
toggleMoveInProgress(false)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onCancel(function () {
|
||||||
|
toggleOrderInProgress(true)
|
||||||
|
tvWidgetRef.current.showConfirmDialog({
|
||||||
|
title: 'Cancel Your Order?',
|
||||||
|
body: `Would you like to cancel your order for
|
||||||
|
${order.size} ${market.config.baseSymbol} ${
|
||||||
|
order.side
|
||||||
|
} at ${formatUsdValue(order.price)}
|
||||||
|
`,
|
||||||
|
callback: (res) => {
|
||||||
|
if (res) {
|
||||||
|
handleCancelOrder(order, market.account)
|
||||||
|
} else {
|
||||||
|
toggleOrderInProgress(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.setText(`${market.config.baseSymbol} ${order.side.toUpperCase()}`)
|
||||||
|
.setBodyBorderColor(order.side == 'buy' ? '#AFD803' : '#E54033')
|
||||||
|
.setBodyBackgroundColor('#000000')
|
||||||
|
.setBodyTextColor('#F2C94C')
|
||||||
|
.setLineLength(3)
|
||||||
|
.setLineColor(order.side == 'buy' ? '#AFD803' : '#E54033')
|
||||||
|
.setQuantity(order.size)
|
||||||
|
.setTooltip(`Order #: ${order.orderId}`)
|
||||||
|
.setQuantityBorderColor(order.side == 'buy' ? '#AFD803' : '#E54033')
|
||||||
|
.setQuantityBackgroundColor('#000000')
|
||||||
|
.setQuantityTextColor('#F2C94C')
|
||||||
|
.setCancelButtonBorderColor(order.side == 'buy' ? '#AFD803' : '#E54033')
|
||||||
|
.setCancelButtonBackgroundColor('#000000')
|
||||||
|
.setCancelButtonIconColor('#F2C94C')
|
||||||
|
.setPrice(order.price)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (openOrders != null && openOrdersIntialized === false) {
|
||||||
|
toggleOpenOrdersIntialized(true)
|
||||||
|
}
|
||||||
|
}, [openOrders])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!moveInProgress && openOrdersIntialized && orderInProgress === false) {
|
||||||
|
const tempLines = new Map()
|
||||||
|
tvWidgetRef.current.onChartReady(() => {
|
||||||
|
if (lines.size > 0) {
|
||||||
|
lines.forEach((value, key) => {
|
||||||
|
lines.get(key).remove()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setLines(lines)
|
||||||
|
|
||||||
|
items.map(({ order, market }) => {
|
||||||
|
if (market.config.name == selectedMarketName) {
|
||||||
|
tempLines.set(order.orderId.toString(), getLine(order, market))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setLines(tempLines)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
selectedMarginAccount,
|
||||||
|
connected,
|
||||||
|
selectedMarketName,
|
||||||
|
selectedMarketPrice,
|
||||||
|
openOrdersIntialized,
|
||||||
|
])
|
||||||
|
|
||||||
return <div id={defaultProps.containerId} className="tradingview-chart" />
|
return <div id={defaultProps.containerId} className="tradingview-chart" />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// If Operating System is Windows, fix Husky for Yarn
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const huskyScript = fs.readFileSync('.git/hooks/husky.sh', {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
});
|
||||||
|
const fixedHuskyScript = huskyScript.replace(
|
||||||
|
'run_command yarn run --silent;;',
|
||||||
|
'run_command npx --no-install;;'
|
||||||
|
);
|
||||||
|
fs.writeFileSync('.git/hooks/husky.sh', fixedHuskyScript);
|
||||||
|
}
|
30
yarn.lock
30
yarn.lock
|
@ -993,18 +993,19 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@blockworks-foundation/mango-client@git+https://github.com/blockworks-foundation/mango-client-v3.git":
|
"@blockworks-foundation/mango-client@^3.0.18":
|
||||||
version "3.0.12"
|
version "3.0.18"
|
||||||
resolved "git+https://github.com/blockworks-foundation/mango-client-v3.git#5eeadbc3529af84db705951335cfe6592cd94683"
|
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.0.18.tgz#f665406365610fc9bbe3fecdd191fec9bd36df88"
|
||||||
|
integrity sha512-tcKrkp8R8fIpVnrcUWFeQLKQK/bRHDntHOIVwo1R3MbwCWcPrnt2Y68Ev4LEpBzQ1B8WXcHqwOXc2hS2Wx/bnA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@project-serum/serum" "0.13.55"
|
"@project-serum/serum" "0.13.55"
|
||||||
"@project-serum/sol-wallet-adapter" "^0.2.0"
|
"@project-serum/sol-wallet-adapter" "^0.2.0"
|
||||||
"@solana/spl-token" "^0.1.6"
|
"@solana/spl-token" "^0.1.6"
|
||||||
"@solana/web3.js" "1.21.0"
|
"@solana/web3.js" "1.21.0"
|
||||||
|
axios "^0.21.1"
|
||||||
big.js "^6.1.1"
|
big.js "^6.1.1"
|
||||||
bigint-buffer "^1.1.5"
|
bigint-buffer "^1.1.5"
|
||||||
bn.js "^5.1.0"
|
bn.js "^5.1.0"
|
||||||
borsh "https://github.com/defactojob/borsh-js#field-mapper"
|
|
||||||
buffer-layout "^1.2.1"
|
buffer-layout "^1.2.1"
|
||||||
yargs "^17.0.1"
|
yargs "^17.0.1"
|
||||||
|
|
||||||
|
@ -2379,6 +2380,13 @@ available-typed-arrays@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9"
|
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9"
|
||||||
integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==
|
integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==
|
||||||
|
|
||||||
|
axios@^0.21.1:
|
||||||
|
version "0.21.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
||||||
|
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.14.0"
|
||||||
|
|
||||||
babel-jest@^26.6.3:
|
babel-jest@^26.6.3:
|
||||||
version "26.6.3"
|
version "26.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
|
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
|
||||||
|
@ -2596,15 +2604,6 @@ borsh@^0.4.0:
|
||||||
bs58 "^4.0.0"
|
bs58 "^4.0.0"
|
||||||
text-encoding-utf-8 "^1.0.2"
|
text-encoding-utf-8 "^1.0.2"
|
||||||
|
|
||||||
"borsh@https://github.com/defactojob/borsh-js#field-mapper":
|
|
||||||
version "0.3.1"
|
|
||||||
resolved "https://github.com/defactojob/borsh-js#33a0d24af281112c0a48efb3fa503f3212443de9"
|
|
||||||
dependencies:
|
|
||||||
"@types/bn.js" "^4.11.5"
|
|
||||||
bn.js "^5.0.0"
|
|
||||||
bs58 "^4.0.0"
|
|
||||||
text-encoding-utf-8 "^1.0.2"
|
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
brace-expansion@^1.1.7:
|
||||||
version "1.1.11"
|
version "1.1.11"
|
||||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
|
@ -4297,6 +4296,11 @@ flatten@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
||||||
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
||||||
|
|
||||||
|
follow-redirects@^1.14.0:
|
||||||
|
version "1.14.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
|
||||||
|
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
|
||||||
|
|
||||||
for-in@^1.0.2:
|
for-in@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||||
|
|
Loading…
Reference in New Issue