Batch settle
This commit is contained in:
parent
0f5f04faf7
commit
fb5055ae27
|
@ -1,5 +1,5 @@
|
|||
import { Button, Col, Divider, Popover, Row } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import {Button, Col, Divider, Popover, Row} from 'antd';
|
||||
import React, {useState} from 'react';
|
||||
import FloatingElement from './layout/FloatingElement';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
|
@ -11,15 +11,17 @@ import {
|
|||
useTokenAccounts,
|
||||
} from '../utils/markets';
|
||||
import DepositDialog from './DepositDialog';
|
||||
import { useWallet } from '../utils/wallet';
|
||||
import {useWallet} from '../utils/wallet';
|
||||
import Link from './Link';
|
||||
import { settleFunds } from '../utils/send';
|
||||
import { useSendConnection } from '../utils/connection';
|
||||
import { notify } from '../utils/notifications';
|
||||
import { Balances } from '../utils/types';
|
||||
import {settleFunds} from '../utils/send';
|
||||
import {useSendConnection} from '../utils/connection';
|
||||
import {notify} from '../utils/notifications';
|
||||
import {Balances} from '../utils/types';
|
||||
import StandaloneTokenAccountsSelect from './StandaloneTokenAccountSelect';
|
||||
import LinkAddress from './LinkAddress';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import {InfoCircleOutlined} from '@ant-design/icons';
|
||||
import {useInterval} from "../utils/useInterval";
|
||||
import {useLocalStorageState} from "../utils/utils";
|
||||
|
||||
const RowBox = styled(Row)`
|
||||
padding-bottom: 20px;
|
||||
|
@ -50,6 +52,11 @@ export default function StandaloneBalancesDisplay() {
|
|||
balances && balances.find((b) => b.coin === baseCurrency);
|
||||
const quoteCurrencyBalances =
|
||||
balances && balances.find((b) => b.coin === quoteCurrency);
|
||||
const [autoSettleEnabled] = useLocalStorageState(
|
||||
'autoSettleEnabled',
|
||||
true,
|
||||
);
|
||||
const [lastSettledAt, setLastSettledAt] = useState<number>(0);
|
||||
|
||||
async function onSettleFunds() {
|
||||
if (!wallet) {
|
||||
|
@ -112,6 +119,42 @@ export default function StandaloneBalancesDisplay() {
|
|||
}
|
||||
}
|
||||
|
||||
useInterval(() => {
|
||||
const autoSettle = async () => {
|
||||
if (!wallet || !market || !openOrdersAccount || !baseCurrencyAccount || !quoteCurrencyAccount || !autoSettleEnabled) {
|
||||
return;
|
||||
}
|
||||
if (!baseCurrencyBalances?.unsettled && !quoteCurrencyBalances?.unsettled) {
|
||||
return;
|
||||
}
|
||||
if (Date.now() - lastSettledAt < 15000) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
console.log('Settling funds...');
|
||||
setLastSettledAt(Date.now());
|
||||
await settleFunds({
|
||||
market,
|
||||
openOrders: openOrdersAccount,
|
||||
connection,
|
||||
wallet,
|
||||
baseCurrencyAccount,
|
||||
quoteCurrencyAccount,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Error auto settling funds: ' + e.message);
|
||||
return;
|
||||
}
|
||||
console.log('Finished settling funds.');
|
||||
};
|
||||
(
|
||||
connected &&
|
||||
wallet?.autoApprove &&
|
||||
autoSettleEnabled &&
|
||||
autoSettle()
|
||||
);
|
||||
}, 1000);
|
||||
|
||||
const formattedBalances: [
|
||||
string | undefined,
|
||||
Balances | undefined,
|
||||
|
|
|
@ -14,14 +14,13 @@ import {
|
|||
} from '../utils/markets';
|
||||
import {useWallet} from '../utils/wallet';
|
||||
import {notify} from '../utils/notifications';
|
||||
import {floorToDecimal, getDecimalCount, roundToDecimal, useLocalStorageState,} from '../utils/utils';
|
||||
import {floorToDecimal, getDecimalCount, roundToDecimal,} from '../utils/utils';
|
||||
import {useSendConnection} from '../utils/connection';
|
||||
import FloatingElement from './layout/FloatingElement';
|
||||
import {getUnixTs, placeOrder, settleFunds} from '../utils/send';
|
||||
import {getUnixTs, placeOrder} 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;
|
||||
|
@ -74,10 +73,6 @@ 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
|
||||
|
@ -133,33 +128,6 @@ 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) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {Market, MARKETS, OpenOrders, Orderbook, TOKEN_MINTS, TokenInstructions,} from '@project-serum/serum';
|
||||
import {Connection, PublicKey} from '@solana/web3.js';
|
||||
import {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 {refreshCache, useAsyncData} from './fetch-loop';
|
||||
import {useAccountData, useAccountInfo, useConnection} from './connection';
|
||||
import {useWallet} from './wallet';
|
||||
import tuple from 'immutable-tuple';
|
||||
|
@ -422,34 +422,6 @@ export function useOpenOrdersAccounts(fast = false) {
|
|||
);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import React, {useContext} from 'react';
|
||||
import React, {useContext, useState} from 'react';
|
||||
import {sleep, useLocalStorageState} from './utils';
|
||||
import {useInterval} from './useInterval';
|
||||
import {useConnection} from './connection';
|
||||
import {useWallet} from './wallet';
|
||||
import {
|
||||
getCachedMarket,
|
||||
getCachedOpenOrderAccounts,
|
||||
getSelectedTokenAccountForMint,
|
||||
useCurrentlyAutoSettling,
|
||||
useMarketInfos,
|
||||
useTokenAccounts,
|
||||
} from './markets';
|
||||
import {settleFunds} from './send';
|
||||
import {settleAllFunds} from './send';
|
||||
import {PreferencesContextValues} from './types';
|
||||
import {getAssociatedTokenAddress} from "@project-serum/associated-token";
|
||||
import {Market} from "@project-serum/serum";
|
||||
|
||||
const PreferencesContext = React.createContext<PreferencesContextValues | null>(
|
||||
null,
|
||||
|
@ -28,7 +24,11 @@ export function PreferencesProvider({ children }) {
|
|||
const [tokenAccounts] = useTokenAccounts();
|
||||
const { connected, wallet } = useWallet();
|
||||
const marketInfoList = useMarketInfos();
|
||||
const [currentlyAutoSettling, setCurrentlyAutoSettling] = useCurrentlyAutoSettling();
|
||||
const [currentlyFetchingMarkets, setCurrentlyFetchingMarkets] = useState<boolean>(false);
|
||||
const [markets, setMarkets] = useState<Map<string, Market>>(new Map())
|
||||
const addToMarketsMap = (marketId, market) => {
|
||||
setMarkets(prev => new Map(prev).set(marketId, market));
|
||||
}
|
||||
const connection = useConnection();
|
||||
|
||||
useInterval(() => {
|
||||
|
@ -36,42 +36,58 @@ export function PreferencesProvider({ children }) {
|
|||
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);
|
||||
}
|
||||
try {
|
||||
console.log('Settling funds...');
|
||||
await settleAllFunds({
|
||||
connection,
|
||||
wallet,
|
||||
tokenAccounts: tokenAccounts || [],
|
||||
markets: [...markets.values()],
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Error auto settling funds: ' + e.message);
|
||||
return;
|
||||
}
|
||||
setCurrentlyAutoSettling(false);
|
||||
console.log('Finished settling funds.');
|
||||
};
|
||||
(
|
||||
connected &&
|
||||
wallet?.autoApprove &&
|
||||
autoSettleEnabled &&
|
||||
!currentlyAutoSettling &&
|
||||
autoSettle()
|
||||
);
|
||||
}, 60000);
|
||||
}, 20000);
|
||||
|
||||
// warms up the market and open orders cache for auto-settlement
|
||||
useInterval(() => {
|
||||
const fetchMarkets = async () => {
|
||||
if (!wallet) {
|
||||
// only need these markets for auto-settlement, so don't fetch unless we are connected.
|
||||
return;
|
||||
}
|
||||
setCurrentlyFetchingMarkets(true);
|
||||
for (const marketInfo of marketInfoList) {
|
||||
if (markets.has(marketInfo.address.toString())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const market = await Market.load(connection, marketInfo.address, {}, marketInfo.programId)
|
||||
addToMarketsMap(marketInfo.address.toString(), market);
|
||||
await sleep(1000);
|
||||
} catch (e) {
|
||||
console.log('Error fetching market: ' + e.message);
|
||||
}
|
||||
}
|
||||
setCurrentlyFetchingMarkets(false);
|
||||
}
|
||||
(
|
||||
connected &&
|
||||
wallet?.autoApprove &&
|
||||
autoSettleEnabled &&
|
||||
!currentlyFetchingMarkets &&
|
||||
fetchMarkets()
|
||||
);
|
||||
}, 60000)
|
||||
|
||||
return (
|
||||
<PreferencesContext.Provider
|
||||
|
|
|
@ -72,6 +72,7 @@ export async function settleFunds({
|
|||
wallet,
|
||||
baseCurrencyAccount,
|
||||
quoteCurrencyAccount,
|
||||
sendNotification = true,
|
||||
}: {
|
||||
market: Market;
|
||||
openOrders: OpenOrders;
|
||||
|
@ -79,6 +80,7 @@ export async function settleFunds({
|
|||
wallet: WalletAdapter;
|
||||
baseCurrencyAccount: TokenAccount;
|
||||
quoteCurrencyAccount: TokenAccount;
|
||||
sendNotification?: boolean,
|
||||
}): Promise<string | undefined> {
|
||||
if (
|
||||
!market ||
|
||||
|
@ -87,7 +89,9 @@ export async function settleFunds({
|
|||
!openOrders ||
|
||||
(!baseCurrencyAccount && !quoteCurrencyAccount)
|
||||
) {
|
||||
notify({ message: 'Not connected' });
|
||||
if (sendNotification) {
|
||||
notify({message: 'Not connected'});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -163,6 +167,7 @@ export async function settleFunds({
|
|||
wallet,
|
||||
connection,
|
||||
sendingMessage: 'Settling funds...',
|
||||
sendNotification
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -224,6 +229,10 @@ export async function settleAllFunds({
|
|||
// @ts-ignore
|
||||
m._decoded?.ownAddress?.equals(openOrdersAccount.market),
|
||||
);
|
||||
if (openOrdersAccount.baseTokenFree.isZero() && openOrdersAccount.quoteTokenFree.isZero()) {
|
||||
// nothing to settle for this market.
|
||||
return null;
|
||||
}
|
||||
const baseMint = market?.baseMintAddress;
|
||||
const quoteMint = market?.quoteMintAddress;
|
||||
|
||||
|
@ -635,6 +644,7 @@ export async function sendTransaction({
|
|||
sentMessage = 'Transaction sent',
|
||||
successMessage = 'Transaction confirmed',
|
||||
timeout = DEFAULT_TIMEOUT,
|
||||
sendNotification = true,
|
||||
}: {
|
||||
transaction: Transaction;
|
||||
wallet: WalletAdapter;
|
||||
|
@ -644,6 +654,7 @@ export async function sendTransaction({
|
|||
sentMessage?: string;
|
||||
successMessage?: string;
|
||||
timeout?: number;
|
||||
sendNotification?: boolean
|
||||
}) {
|
||||
const signedTransaction = await signTransaction({
|
||||
transaction,
|
||||
|
@ -658,6 +669,7 @@ export async function sendTransaction({
|
|||
sentMessage,
|
||||
successMessage,
|
||||
timeout,
|
||||
sendNotification
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -717,6 +729,7 @@ export async function sendSignedTransaction({
|
|||
sentMessage = 'Transaction sent',
|
||||
successMessage = 'Transaction confirmed',
|
||||
timeout = DEFAULT_TIMEOUT,
|
||||
sendNotification = true,
|
||||
}: {
|
||||
signedTransaction: Transaction;
|
||||
connection: Connection;
|
||||
|
@ -724,17 +737,22 @@ export async function sendSignedTransaction({
|
|||
sentMessage?: string;
|
||||
successMessage?: string;
|
||||
timeout?: number;
|
||||
sendNotification?: boolean
|
||||
}): Promise<string> {
|
||||
const rawTransaction = signedTransaction.serialize();
|
||||
const startTime = getUnixTs();
|
||||
notify({ message: sendingMessage });
|
||||
if (sendNotification){
|
||||
notify({ message: sendingMessage });
|
||||
}
|
||||
const txid: TransactionSignature = await connection.sendRawTransaction(
|
||||
rawTransaction,
|
||||
{
|
||||
skipPreflight: true,
|
||||
},
|
||||
);
|
||||
notify({ message: sentMessage, type: 'success', txid });
|
||||
if (sendNotification) {
|
||||
notify({ message: sentMessage, type: 'success', txid });
|
||||
}
|
||||
|
||||
console.log('Started awaiting confirmation for', txid);
|
||||
|
||||
|
@ -783,7 +801,9 @@ export async function sendSignedTransaction({
|
|||
} finally {
|
||||
done = true;
|
||||
}
|
||||
notify({ message: successMessage, type: 'success', txid });
|
||||
if (sendNotification) {
|
||||
notify({ message: successMessage, type: 'success', txid });
|
||||
}
|
||||
|
||||
console.log('Latency', txid, getUnixTs() - startTime);
|
||||
return txid;
|
||||
|
|
Loading…
Reference in New Issue