Support transitioning from deprecated markets

This commit is contained in:
Nishad 2020-09-07 16:10:15 +08:00
parent 1891c159a8
commit 6140b5dfd8
5 changed files with 241 additions and 35 deletions

View File

@ -6,7 +6,7 @@
"dependencies": {
"@ant-design/icons": "^4.2.1",
"@craco/craco": "^5.6.4",
"@project-serum/serum": "^0.12.0",
"@project-serum/serum": "^0.12.2",
"@project-serum/sol-wallet-adapter": "^0.1.0",
"@solana/web3.js": "^0.71.10",
"@testing-library/jest-dom": "^4.2.4",

View File

@ -0,0 +1,68 @@
import { Divider, Typography, Button } from 'antd';
import React from 'react';
import styled from 'styled-components';
import {
useMarket,
useOpenOrders,
useBalances,
useMarketsList,
DEFAULT_MARKET,
} from '../utils/markets';
import FloatingElement from './layout/FloatingElement';
import CheckOutlined from '@ant-design/icons/lib/icons/CheckOutlined';
import BalancesTable from './UserInfoTable/BalancesTable';
import OpenOrderTable from './UserInfoTable/OpenOrderTable';
const Title = styled.div`
color: rgba(255, 255, 255, 1);
`;
export default function DeprecatedMarketInstructions() {
const { marketName, setMarketAddress } = useMarket();
const liveMarkets = useMarketsList();
const openOrders = useOpenOrders();
const balances = useBalances();
const needToCancelOrders = !openOrders || openOrders.length > 0;
const needToSettleFunds =
!balances ||
balances.some(({ orders, unsettled }) => orders > 0 || unsettled > 0);
const liveMarket =
liveMarkets.find(({ name }) => name === marketName) || DEFAULT_MARKET;
console.log('liveMarkets', liveMarkets);
return (
<FloatingElement>
<Title>Migrate off of deprecated market</Title>
<br />
<Typography>
This {marketName} market is deprecated (inactive).
</Typography>
<br />
<Typography>
To transition to the new and upgraded {marketName} market, please do the
following:
</Typography>
<br />
<Divider>
{!needToCancelOrders && <CheckOutlined />} Cancel your orders
</Divider>
{needToCancelOrders && <OpenOrderTable openOrders={openOrders} />}
<Divider>
{!needToSettleFunds && <CheckOutlined />} Settle your funds
</Divider>
{needToSettleFunds && <BalancesTable balances={balances} />}
<Divider>Switch to new market</Divider>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Button
onClick={() =>
liveMarket?.address &&
setMarketAddress(liveMarket.address.toBase58())
}
disabled={needToSettleFunds || needToCancelOrders}
>
Switch to live {liveMarket.name} market
</Button>
</div>
</FloatingElement>
);
}

View File

@ -1,13 +1,18 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Col, Popover, Row, Select } from 'antd';
import { Col, Popover, Row, Select, Typography } from 'antd';
import styled from 'styled-components';
import Orderbook from '../components/Orderbook';
import UserInfoTable from '../components/UserInfoTable';
import StandaloneBalancesDisplay from '../components/StandaloneBalancesDisplay';
import { useMarket, useMarketsList } from '../utils/markets';
import {
useMarket,
useMarketsList,
useUnmigratedDeprecatedMarketsList,
} from '../utils/markets';
import TradeForm from '../components/TradeForm';
import TradesTable from '../components/TradesTable';
import LinkAddress from '../components/LinkAddress';
import DeprecatedMarketInstructions from '../components/DeprecatedMarketInstructions';
import { InfoCircleOutlined } from '@ant-design/icons';
const { Option } = Select;
@ -23,8 +28,9 @@ const Wrapper = styled.div`
`;
export default function TradePage() {
const { market, marketName, setMarketAddress } = useMarket();
const { marketName, market, deprecated } = useMarket();
const markets = useMarketsList();
const deprecatedMarkets = useUnmigratedDeprecatedMarketsList();
const [dimensions, setDimensions] = useState({
height: window.innerHeight,
width: window.innerWidth,
@ -57,14 +63,16 @@ export default function TradePage() {
changeOrderRef.current && changeOrderRef.current({ size }),
};
const getComponent = useCallback(() => {
if (width < 1000) {
if (deprecated) {
return <RenderDeprecatedMarket />;
} else if (width < 1000) {
return <RenderSmaller {...componentProps} />;
} else if (width < 1450) {
return <RenderSmall {...componentProps} />;
} else {
return <RenderNormal {...componentProps} />;
}
}, [width, componentProps]);
}, [width, componentProps, deprecated]);
return (
<>
@ -75,27 +83,7 @@ export default function TradePage() {
gutter={16}
>
<Col>
<Select
size={'large'}
bordered={true}
onSelect={setMarketAddress}
value={market?.address?.toBase58()}
listHeight={400}
>
{markets.map(({ name, address }, i) => (
<Option
value={address.toBase58()}
key={address}
style={{
padding: '10px 0',
textAlign: 'center',
backgroundColor: i % 2 === 0 ? 'rgb(39, 44, 61)' : null,
}}
>
{name}
</Option>
))}
</Select>
<MarketSelector markets={markets} placeholder={'Select market'} />
</Col>
{market ? (
<Col>
@ -109,6 +97,22 @@ export default function TradePage() {
</Popover>
</Col>
) : null}
{deprecatedMarkets && deprecatedMarkets.length > 0 && (
<React.Fragment>
<Col>
<Typography>
You have unsettled funds on deprecated markets! Please go
through them to claim your funds.
</Typography>
</Col>
<Col>
<MarketSelector
markets={deprecatedMarkets}
placeholder={'Select deprecated market'}
/>
</Col>
</React.Fragment>
)}
</Row>
{getComponent()}
</Wrapper>
@ -116,6 +120,51 @@ export default function TradePage() {
);
}
function MarketSelector({ markets, placeholder }) {
const { market, setMarketAddress } = useMarket();
return (
<Select
size={'large'}
bordered={true}
onSelect={setMarketAddress}
value={markets
.find(
(proposedMarket) =>
market?.address && proposedMarket.address.equals(market.address),
)
?.address?.toBase58()}
listHeight={400}
placeholder={placeholder}
>
{markets.map(({ address, name, deprecated }, i) => (
<Option
value={address.toBase58()}
key={address}
style={{
padding: '10px 0',
textAlign: 'center',
backgroundColor: i % 2 === 0 ? 'rgb(39, 44, 61)' : null,
}}
>
{name} {deprecated ? ' (Deprecated)' : null}
</Option>
))}
</Select>
);
}
const RenderDeprecatedMarket = () => {
return (
<>
<Row>
<Col flex="auto">
<DeprecatedMarketInstructions />
</Col>
</Row>
</>
);
};
const RenderNormal = ({ onChangeOrderRef, onPrice, onSize }) => {
return (
<Row

View File

@ -5,6 +5,7 @@ import {
TokenInstructions,
MARKETS,
TOKEN_MINTS,
OpenOrders,
} from '@project-serum/serum';
import { PublicKey } from '@solana/web3.js';
import React, { useContext, useEffect, useState } from 'react';
@ -16,8 +17,15 @@ import tuple from 'immutable-tuple';
import { notify } from './notifications';
import { BN } from 'bn.js';
// Used in debugging, should be false in production
const _IGNORE_DEPRECATED = false;
const USE_MARKETS = _IGNORE_DEPRECATED
? MARKETS.map((m) => ({ ...m, deprecated: false }))
: MARKETS;
export function useMarketsList() {
return MARKETS;
return USE_MARKETS.filter(({ deprecated }) => !deprecated);
}
export function useAllMarkets() {
@ -28,7 +36,7 @@ export function useAllMarkets() {
const getAllMarkets = async () => {
const markets = [];
let marketInfo;
for (marketInfo of MARKETS) {
for (marketInfo of USE_MARKETS) {
try {
const market = await Market.load(
connection,
@ -54,6 +62,73 @@ export function useAllMarkets() {
return markets;
}
export function useUnmigratedDeprecatedMarketsList() {
const connection = useConnection();
const { wallet } = useWallet();
async function getUnmigratedDeprecatedMarkets() {
if (!wallet || !connection || !wallet.publicKey) {
return [];
}
let marketAddresses = [];
const deprecatedProgramIds = Array.from(
new Set(
USE_MARKETS.filter(({ deprecated }) => deprecated).map(
({ programId }) => programId,
),
),
);
let programId;
for (programId of deprecatedProgramIds) {
try {
const openOrdersAccounts = await OpenOrders.findForOwner(
connection,
wallet.publicKey,
programId,
);
marketAddresses = marketAddresses.concat(
Array.from(
new Set(
openOrdersAccounts
.filter(
(openOrders) =>
openOrders.baseTokenTotal.toNumber() ||
openOrders.quoteTokenTotal.toNumber(),
)
.map((openOrders) => openOrders.market),
),
).filter((address) =>
USE_MARKETS.some(
(market) => market.deprecated && market.address.equals(address),
),
),
);
} catch (e) {
console.log(
'Error loading deprecated markets',
programId?.toBase58(),
e.message,
);
}
}
return USE_MARKETS.filter((market) =>
marketAddresses.some((address) => address.equals(market.address)),
);
}
const [markets] = useAsyncData(
getUnmigratedDeprecatedMarkets,
tuple(
'useUnmigratedDeprecatedMarketsList',
connection,
wallet?.publicKey?.toBase58(),
),
{ refreshInterval: _SLOW_REFRESH_INTERVAL },
);
return markets;
}
const MarketContext = React.createContext(null);
// For things that don't really change
@ -62,16 +137,30 @@ const _SLOW_REFRESH_INTERVAL = 5 * 1000;
// For things that change frequently
const _FAST_REFRESH_INTERVAL = 1000;
export const DEFAULT_MARKET = USE_MARKETS.find(
({ name }) => name === 'SRM/USDT',
);
export function MarketProvider({ children }) {
const [marketAddress, setMarketAddress] = useLocalStorageState(
'marketAddress',
MARKETS.find(({ name }) => name === 'SRM/USDT')?.address?.toBase58(),
DEFAULT_MARKET.address.toBase58(),
);
const connection = useConnection();
const marketInfo = MARKETS.find((market) =>
const marketInfo = USE_MARKETS.find((market) =>
market.address.equals(new PublicKey(marketAddress)),
);
// Replace existing market with a non-deprecated one on first load
useEffect(() => {
if (marketInfo && marketInfo.deprecated) {
console.log('Switching markets from deprecated', marketInfo);
setMarketAddress(DEFAULT_MARKET.address.toBase58());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [market, setMarket] = useState();
useEffect(() => {
setMarket(null);

View File

@ -1467,10 +1467,10 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
"@project-serum/serum@^0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.12.0.tgz#27110e4c28a4cdb44a8539f442bbf9c926f27dbe"
integrity sha512-QaHZsdhmxfVXMJpCApbFD3gg80sknr/t5LXhmCxdZePf21LLtGl2wug4DYgSMOV73JULiEzU6FpH9qSRAIYtiA==
"@project-serum/serum@^0.12.2":
version "0.12.2"
resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.12.2.tgz#d3f5e69ad2d62229c91b344583e6841cf1fa34c5"
integrity sha512-KZ2Jvh38ys0i0PWcfkbxoJC7AOq2wSrzmYx2vZo8RQB9E2n+a4nhffJ/0uMnANB9T46zCOJ6eeXQHS8id5BThA==
dependencies:
"@solana/web3.js" "^0.71.10"
bn.js "^5.1.2"