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:
Nathaniel Parke 2021-04-30 18:22:00 +08:00 committed by GitHub
parent 1d37d4c5bf
commit 0f5f04faf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 164 additions and 169 deletions

View File

@ -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) {

View File

@ -261,3 +261,9 @@ export function setCache(
loop.notifyListeners();
}
}
export function getCache(
cacheKey: any
) {
return globalCache.get(cacheKey);
}

View File

@ -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];
}

View File

@ -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

View File

@ -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(): [
(
| {