Conserve calls to getAccountInfo (#74)
* Conserve get markets request * Add mints to market infos * A few changes * Also autosettle active market page * Fixes
This commit is contained in:
parent
1d37d4c5bf
commit
0f5f04faf7
|
@ -1,30 +1,27 @@
|
|||
import { Button, Input, Radio, Switch, Slider } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {Button, Input, Radio, Slider, Switch} from 'antd';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
useSelectedBaseCurrencyBalances,
|
||||
useSelectedQuoteCurrencyBalances,
|
||||
useMarket,
|
||||
useMarkPrice,
|
||||
useSelectedOpenOrdersAccount,
|
||||
useSelectedBaseCurrencyAccount,
|
||||
useSelectedQuoteCurrencyAccount,
|
||||
useFeeDiscountKeys,
|
||||
useLocallyStoredFeeDiscountKey,
|
||||
useMarket,
|
||||
useMarkPrice,
|
||||
useSelectedBaseCurrencyAccount,
|
||||
useSelectedBaseCurrencyBalances,
|
||||
useSelectedOpenOrdersAccount,
|
||||
useSelectedQuoteCurrencyAccount,
|
||||
useSelectedQuoteCurrencyBalances,
|
||||
} from '../utils/markets';
|
||||
import { useWallet } from '../utils/wallet';
|
||||
import { notify } from '../utils/notifications';
|
||||
import {
|
||||
getDecimalCount,
|
||||
roundToDecimal,
|
||||
floorToDecimal,
|
||||
} from '../utils/utils';
|
||||
import { useSendConnection } from '../utils/connection';
|
||||
import {useWallet} from '../utils/wallet';
|
||||
import {notify} from '../utils/notifications';
|
||||
import {floorToDecimal, getDecimalCount, roundToDecimal, useLocalStorageState,} from '../utils/utils';
|
||||
import {useSendConnection} from '../utils/connection';
|
||||
import FloatingElement from './layout/FloatingElement';
|
||||
import { getUnixTs, placeOrder } from '../utils/send';
|
||||
import { SwitchChangeEventHandler } from 'antd/es/switch';
|
||||
import { refreshCache } from '../utils/fetch-loop';
|
||||
import {getUnixTs, placeOrder, settleFunds} from '../utils/send';
|
||||
import {SwitchChangeEventHandler} from 'antd/es/switch';
|
||||
import {refreshCache} from '../utils/fetch-loop';
|
||||
import tuple from 'immutable-tuple';
|
||||
import {useInterval} from "../utils/useInterval";
|
||||
|
||||
const SellButton = styled(Button)`
|
||||
margin: 20px 0px 0px 0px;
|
||||
|
@ -77,6 +74,10 @@ export default function TradeForm({
|
|||
const [price, setPrice] = useState<number | undefined>(undefined);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [sizeFraction, setSizeFraction] = useState(0);
|
||||
const [autoSettleEnabled] = useLocalStorageState(
|
||||
'autoSettleEnabled',
|
||||
true,
|
||||
);
|
||||
|
||||
const availableQuote =
|
||||
openOrdersAccount && market
|
||||
|
@ -132,6 +133,33 @@ export default function TradeForm({
|
|||
return () => clearInterval(id);
|
||||
}, [market, sendConnection, wallet, publicKey]);
|
||||
|
||||
useInterval(() => {
|
||||
const autoSettle = async () => {
|
||||
if (!wallet || !market || !openOrdersAccount || !baseCurrencyAccount || !quoteCurrencyAccount) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// settle funds into selected token wallets
|
||||
await settleFunds({
|
||||
market,
|
||||
openOrders: openOrdersAccount,
|
||||
connection: sendConnection,
|
||||
wallet,
|
||||
baseCurrencyAccount,
|
||||
quoteCurrencyAccount
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Error auto settling funds: ' + e.message);
|
||||
}
|
||||
};
|
||||
(
|
||||
connected &&
|
||||
wallet?.autoApprove &&
|
||||
autoSettleEnabled &&
|
||||
autoSettle()
|
||||
);
|
||||
}, 10000);
|
||||
|
||||
const onSetBaseSize = (baseSize: number | undefined) => {
|
||||
setBaseSize(baseSize);
|
||||
if (!baseSize) {
|
||||
|
|
|
@ -261,3 +261,9 @@ export function setCache(
|
|||
loop.notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
export function getCache(
|
||||
cacheKey: any
|
||||
) {
|
||||
return globalCache.get(cacheKey);
|
||||
}
|
||||
|
|
|
@ -1,31 +1,14 @@
|
|||
import {
|
||||
decodeEventQueue,
|
||||
Market,
|
||||
MARKETS,
|
||||
OpenOrders,
|
||||
Orderbook,
|
||||
TOKEN_MINTS,
|
||||
TokenInstructions,
|
||||
} from '@project-serum/serum';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
divideBnToNumber,
|
||||
floorToDecimal,
|
||||
getTokenMultiplierFromDecimals,
|
||||
useLocalStorageState,
|
||||
} from './utils';
|
||||
import { refreshCache, useAsyncData } from './fetch-loop';
|
||||
import { useAccountData, useAccountInfo, useConnection } from './connection';
|
||||
import { useWallet } from './wallet';
|
||||
import {Market, MARKETS, OpenOrders, Orderbook, TOKEN_MINTS, TokenInstructions,} from '@project-serum/serum';
|
||||
import {Connection, PublicKey} from '@solana/web3.js';
|
||||
import React, {useContext, useEffect, useState} from 'react';
|
||||
import {divideBnToNumber, floorToDecimal, getTokenMultiplierFromDecimals, sleep, useLocalStorageState,} from './utils';
|
||||
import {getCache, refreshCache, setCache, useAsyncData} from './fetch-loop';
|
||||
import {useAccountData, useAccountInfo, useConnection} from './connection';
|
||||
import {useWallet} from './wallet';
|
||||
import tuple from 'immutable-tuple';
|
||||
import { notify } from './notifications';
|
||||
import {notify} from './notifications';
|
||||
import BN from 'bn.js';
|
||||
import {
|
||||
getTokenAccountInfo,
|
||||
parseTokenAccountData,
|
||||
useMintInfos,
|
||||
} from './tokens';
|
||||
import {getTokenAccountInfo, parseTokenAccountData, useMintInfos,} from './tokens';
|
||||
import {
|
||||
Balances,
|
||||
CustomMarketInfo,
|
||||
|
@ -36,12 +19,10 @@ import {
|
|||
OrderWithMarketAndMarketName,
|
||||
SelectedTokenAccounts,
|
||||
TokenAccount,
|
||||
Trade,
|
||||
} from './types';
|
||||
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions';
|
||||
import { Order } from '@project-serum/serum/lib/market';
|
||||
import {WRAPPED_SOL_MINT} from '@project-serum/serum/lib/token-instructions';
|
||||
import {Order} from '@project-serum/serum/lib/market';
|
||||
import BonfidaApi from './bonfidaConnector';
|
||||
import { sleep } from './utils';
|
||||
|
||||
// Used in debugging, should be false in production
|
||||
const _IGNORE_DEPRECATED = false;
|
||||
|
@ -434,13 +415,41 @@ export function useOpenOrdersAccounts(fast = false) {
|
|||
wallet.publicKey,
|
||||
);
|
||||
}
|
||||
return useAsyncData(
|
||||
return useAsyncData<OpenOrders[] | null>(
|
||||
getOpenOrdersAccounts,
|
||||
tuple('getOpenOrdersAccounts', wallet, market, connected),
|
||||
{ refreshInterval: fast ? _FAST_REFRESH_INTERVAL : _SLOW_REFRESH_INTERVAL },
|
||||
);
|
||||
}
|
||||
|
||||
// todo: refresh cache after some time?
|
||||
export async function getCachedMarket(connection: Connection, address: PublicKey, programId: PublicKey) {
|
||||
let market;
|
||||
const cacheKey = tuple('getCachedMarket', 'market', address.toString(), connection);
|
||||
if (!getCache(cacheKey)) {
|
||||
market = await Market.load(connection, address, {}, programId)
|
||||
setCache(cacheKey, market)
|
||||
} else {
|
||||
market = getCache(cacheKey);
|
||||
}
|
||||
return market;
|
||||
}
|
||||
|
||||
export async function getCachedOpenOrderAccounts(connection: Connection, market: Market, owner: PublicKey) {
|
||||
let accounts;
|
||||
const cacheKey = tuple('getCachedOpenOrderAccounts', market.address.toString(), owner.toString(), connection);
|
||||
if (!getCache(cacheKey)) {
|
||||
accounts = await market.findOpenOrdersAccountsForOwner(
|
||||
connection,
|
||||
owner,
|
||||
);
|
||||
setCache(cacheKey, accounts);
|
||||
} else {
|
||||
accounts = getCache(cacheKey);
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
|
||||
export function useSelectedOpenOrdersAccount(fast = false) {
|
||||
const [accounts] = useOpenOrdersAccounts(fast);
|
||||
if (!accounts) {
|
||||
|
@ -644,63 +653,6 @@ export function useFills(limit = 100) {
|
|||
.map((fill) => ({ ...fill, marketName }));
|
||||
}
|
||||
|
||||
// TODO: Update to use websocket
|
||||
export function useFillsForAllMarkets(limit = 100) {
|
||||
const { connected, wallet } = useWallet();
|
||||
|
||||
const connection = useConnection();
|
||||
const allMarkets = useAllMarkets();
|
||||
|
||||
async function getFillsForAllMarkets() {
|
||||
let fills: Trade[] = [];
|
||||
if (!connected) {
|
||||
return fills;
|
||||
}
|
||||
|
||||
let marketData;
|
||||
for (marketData of allMarkets) {
|
||||
const { market, marketName } = marketData;
|
||||
if (!market || !wallet) {
|
||||
return fills;
|
||||
}
|
||||
const openOrdersAccounts = await market.findOpenOrdersAccountsForOwner(
|
||||
connection,
|
||||
wallet.publicKey,
|
||||
);
|
||||
const openOrdersAccount = openOrdersAccounts && openOrdersAccounts[0];
|
||||
if (!openOrdersAccount) {
|
||||
return fills;
|
||||
}
|
||||
const eventQueueData = await connection.getAccountInfo(
|
||||
market && market._decoded.eventQueue,
|
||||
);
|
||||
let data = eventQueueData?.data;
|
||||
if (!data) {
|
||||
return fills;
|
||||
}
|
||||
const events = decodeEventQueue(data, limit);
|
||||
const fillsForMarket: Trade[] = events
|
||||
.filter(
|
||||
(event) => event.eventFlags.fill && event.nativeQuantityPaid.gtn(0),
|
||||
)
|
||||
.map(market.parseFillEvent.bind(market));
|
||||
const ownFillsForMarket = fillsForMarket
|
||||
.filter((fill) => fill.openOrders.equals(openOrdersAccount.publicKey))
|
||||
.map((fill) => ({ ...fill, marketName }));
|
||||
fills = fills.concat(ownFillsForMarket);
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(fills));
|
||||
return fills;
|
||||
}
|
||||
|
||||
return useAsyncData(
|
||||
getFillsForAllMarkets,
|
||||
tuple('getFillsForAllMarkets', connected, connection, allMarkets, wallet),
|
||||
{ refreshInterval: _FAST_REFRESH_INTERVAL },
|
||||
);
|
||||
}
|
||||
|
||||
export function useAllOpenOrdersAccounts() {
|
||||
const { wallet, connected } = useWallet();
|
||||
const connection = useConnection();
|
||||
|
@ -1242,3 +1194,8 @@ export function getExpectedFillPrice(
|
|||
}
|
||||
return formattedPrice;
|
||||
}
|
||||
|
||||
export function useCurrentlyAutoSettling(): [boolean, (currentlyAutoSettling: boolean) => void] {
|
||||
const [currentlyAutoSettling, setCurrentlyAutosettling] = useState<boolean>(false);
|
||||
return [currentlyAutoSettling, setCurrentlyAutosettling];
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { useLocalStorageState } from './utils';
|
||||
import { useInterval } from './useInterval';
|
||||
import { useConnection } from './connection';
|
||||
import { useWallet } from './wallet';
|
||||
import React, {useContext} from 'react';
|
||||
import {sleep, useLocalStorageState} from './utils';
|
||||
import {useInterval} from './useInterval';
|
||||
import {useConnection} from './connection';
|
||||
import {useWallet} from './wallet';
|
||||
import {
|
||||
useAllMarkets,
|
||||
useSelectedTokenAccounts,
|
||||
getCachedMarket,
|
||||
getCachedOpenOrderAccounts,
|
||||
getSelectedTokenAccountForMint,
|
||||
useCurrentlyAutoSettling,
|
||||
useMarketInfos,
|
||||
useTokenAccounts,
|
||||
} from './markets';
|
||||
import { settleAllFunds } from './send';
|
||||
import { PreferencesContextValues } from './types';
|
||||
import {settleFunds} from './send';
|
||||
import {PreferencesContextValues} from './types';
|
||||
import {getAssociatedTokenAddress} from "@project-serum/associated-token";
|
||||
|
||||
const PreferencesContext = React.createContext<PreferencesContextValues | null>(
|
||||
null,
|
||||
|
@ -23,33 +27,51 @@ export function PreferencesProvider({ children }) {
|
|||
|
||||
const [tokenAccounts] = useTokenAccounts();
|
||||
const { connected, wallet } = useWallet();
|
||||
const [marketList] = useAllMarkets();
|
||||
const marketInfoList = useMarketInfos();
|
||||
const [currentlyAutoSettling, setCurrentlyAutoSettling] = useCurrentlyAutoSettling();
|
||||
const connection = useConnection();
|
||||
const [selectedTokenAccounts] = useSelectedTokenAccounts();
|
||||
|
||||
useInterval(() => {
|
||||
const autoSettle = async () => {
|
||||
const markets = (marketList || []).map((m) => m.market);
|
||||
try {
|
||||
if (!wallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Auto settling');
|
||||
await settleAllFunds({
|
||||
connection,
|
||||
wallet,
|
||||
tokenAccounts: tokenAccounts || [],
|
||||
markets,
|
||||
selectedTokenAccounts,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Error auto settling funds: ' + e.message);
|
||||
if (!wallet) {
|
||||
return;
|
||||
}
|
||||
setCurrentlyAutoSettling(true);
|
||||
for (const marketInfo of marketInfoList) {
|
||||
try {
|
||||
console.log(`Autosettling ${marketInfo.name} ${marketInfo.address.toString()}`);
|
||||
const market = await getCachedMarket(connection, marketInfo.address, marketInfo.programId);
|
||||
const openOrderAccounts = await getCachedOpenOrderAccounts(connection, market, wallet.publicKey);
|
||||
// settle funds into selected token wallets
|
||||
const [baseAssocTokenAddress, quoteAssocTokenAddress] = await Promise.all([
|
||||
getAssociatedTokenAddress(wallet.publicKey, market.baseMintAddress),
|
||||
getAssociatedTokenAddress(wallet.publicKey, market.quoteMintAddress)
|
||||
]);
|
||||
const baseCurrencyAccount = getSelectedTokenAccountForMint(
|
||||
tokenAccounts, market.baseMintAddress, baseAssocTokenAddress);
|
||||
const quoteCurrencyAccount = getSelectedTokenAccountForMint(
|
||||
tokenAccounts, market.quoteMintAddress, quoteAssocTokenAddress);
|
||||
const openOrders = openOrderAccounts.find(oo => oo.market.equals(marketInfo.address));
|
||||
if (baseCurrencyAccount && quoteCurrencyAccount && openOrders) {
|
||||
await settleFunds({
|
||||
market, openOrders, connection, wallet, baseCurrencyAccount, quoteCurrencyAccount
|
||||
});
|
||||
await sleep(1000);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error auto settling funds: ' + e.message);
|
||||
}
|
||||
}
|
||||
setCurrentlyAutoSettling(false);
|
||||
};
|
||||
|
||||
connected && wallet?.autoApprove && autoSettleEnabled && autoSettle();
|
||||
}, 10000);
|
||||
(
|
||||
connected &&
|
||||
wallet?.autoApprove &&
|
||||
autoSettleEnabled &&
|
||||
!currentlyAutoSettling &&
|
||||
autoSettle()
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
return (
|
||||
<PreferencesContext.Provider
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import * as BufferLayout from 'buffer-layout';
|
||||
import bs58 from 'bs58';
|
||||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions';
|
||||
import { TokenAccount } from './types';
|
||||
import { TOKEN_MINTS } from '@project-serum/serum';
|
||||
import { useAllMarkets, useCustomMarkets, useTokenAccounts } from './markets';
|
||||
import { getMultipleSolanaAccounts } from './send';
|
||||
import { useConnection } from './connection';
|
||||
import { useAsyncData } from './fetch-loop';
|
||||
import {AccountInfo, Connection, PublicKey} from '@solana/web3.js';
|
||||
import {WRAPPED_SOL_MINT} from '@project-serum/serum/lib/token-instructions';
|
||||
import {TokenAccount} from './types';
|
||||
import {TOKEN_MINTS} from '@project-serum/serum';
|
||||
import {useAllMarkets, useCustomMarkets, useTokenAccounts} from './markets';
|
||||
import {getMultipleSolanaAccounts} from './send';
|
||||
import {useConnection} from './connection';
|
||||
import {useAsyncData} from './fetch-loop';
|
||||
import tuple from 'immutable-tuple';
|
||||
import BN from 'bn.js';
|
||||
import { useMemo } from 'react';
|
||||
import {useMemo} from 'react';
|
||||
|
||||
export const ACCOUNT_LAYOUT = BufferLayout.struct([
|
||||
BufferLayout.blob(32, 'mint'),
|
||||
|
@ -145,38 +145,20 @@ export async function getTokenAccountInfo(
|
|||
});
|
||||
}
|
||||
|
||||
// todo: use this to map custom mints to custom tickers. Add functionality once custom markets store mints
|
||||
export function useMintToTickers(): { [mint: string]: string } {
|
||||
const { customMarkets } = useCustomMarkets();
|
||||
const [markets] = useAllMarkets();
|
||||
return useMemo(() => {
|
||||
const mintsToTickers = Object.fromEntries(
|
||||
return Object.fromEntries(
|
||||
TOKEN_MINTS.map((mint) => [mint.address.toBase58(), mint.name]),
|
||||
);
|
||||
for (let market of markets || []) {
|
||||
const customMarketInfo = customMarkets.find(
|
||||
(customMarket) =>
|
||||
customMarket.address === market.market.address.toBase58(),
|
||||
);
|
||||
if (!(market.market.baseMintAddress.toBase58() in mintsToTickers)) {
|
||||
if (customMarketInfo) {
|
||||
mintsToTickers[market.market.baseMintAddress.toBase58()] =
|
||||
customMarketInfo.baseLabel || `${customMarketInfo.name}_BASE`;
|
||||
}
|
||||
}
|
||||
if (!(market.market.quoteMintAddress.toBase58() in mintsToTickers)) {
|
||||
if (customMarketInfo) {
|
||||
mintsToTickers[market.market.quoteMintAddress.toBase58()] =
|
||||
customMarketInfo.quoteLabel || `${customMarketInfo.name}_QUOTE`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mintsToTickers;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [markets?.length, customMarkets.length]);
|
||||
}, [customMarkets.length]);
|
||||
}
|
||||
|
||||
const _VERY_SLOW_REFRESH_INTERVAL = 5000 * 1000;
|
||||
|
||||
// todo: move this to using mints stored in static market infos once custom markets support that.
|
||||
export function useMintInfos(): [
|
||||
(
|
||||
| {
|
||||
|
|
Loading…
Reference in New Issue