Auto settle (#24)
* Auto settle funds function * Auto settle * Add settings panel * Cleanup * Use OpenOrders.findForOwner and support custom markets * Some touch ups Co-authored-by: Nathaniel Parke <nathaniel.parke@gmail.com>
This commit is contained in:
parent
9765d90df7
commit
190122a669
|
@ -7,6 +7,7 @@ import { GlobalStyle } from './global_style';
|
|||
import { Spin } from 'antd';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import { Routes } from './routes';
|
||||
import { PreferencesProvider } from './utils/preferences';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
@ -16,9 +17,11 @@ export default function App() {
|
|||
<ConnectionProvider>
|
||||
<MarketProvider>
|
||||
<WalletProvider>
|
||||
<Suspense fallback={() => <Spin size="large" />}>
|
||||
<Routes />
|
||||
</Suspense>
|
||||
<PreferencesProvider>
|
||||
<Suspense fallback={() => <Spin size="large" />}>
|
||||
<Routes />
|
||||
</Suspense>
|
||||
</PreferencesProvider>
|
||||
</WalletProvider>
|
||||
</MarketProvider>
|
||||
</ConnectionProvider>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import { Switch, Typography } from 'antd';
|
||||
import { usePreferences } from '../utils/preferences';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
export default function Settings({ autoApprove }) {
|
||||
const { autoSettleEnabled, setAutoSettleEnabled } = usePreferences();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Switch
|
||||
style={{ marginRight: 10 }}
|
||||
disabled={!autoApprove}
|
||||
checked={autoApprove && autoSettleEnabled}
|
||||
onChange={setAutoSettleEnabled}
|
||||
/>{' '}
|
||||
Auto settle
|
||||
{!autoApprove && (
|
||||
<Paragraph style={{ color: 'rgba(255,255,255,0.5)', marginTop: 10 }}>
|
||||
To use auto settle, first enable auto approval in your wallet
|
||||
</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
import { InfoCircleOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
InfoCircleOutlined,
|
||||
SettingOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Menu, Popover, Select } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
|
@ -7,6 +11,7 @@ import styled from 'styled-components';
|
|||
import { useWallet, WALLET_PROVIDERS } from '../utils/wallet';
|
||||
import { ENDPOINTS, useConnectionConfig } from '../utils/connection';
|
||||
import LinkAddress from './LinkAddress';
|
||||
import Settings from './Settings';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background-color: #0d1017;
|
||||
|
@ -63,6 +68,21 @@ export default function TopBar() {
|
|||
>
|
||||
<Menu.Item key="/">TRADE</Menu.Item>
|
||||
</Menu>
|
||||
{connected && (
|
||||
<div>
|
||||
<Popover
|
||||
content={<Settings autoApprove={wallet?.autoApprove} />}
|
||||
placement="bottomRight"
|
||||
title="Settings"
|
||||
trigger="click"
|
||||
>
|
||||
<Button style={{ marginRight: 8 }}>
|
||||
<SettingOutlined />
|
||||
Settings
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Select
|
||||
onSelect={setEndpoint}
|
||||
|
|
|
@ -29,15 +29,14 @@ export function useMarketsList() {
|
|||
return USE_MARKETS.filter(({ deprecated }) => !deprecated);
|
||||
}
|
||||
|
||||
export function useAllMarkets() {
|
||||
export function useAllMarkets(customMarkets) {
|
||||
const connection = useConnection();
|
||||
const [markets, setMarkets] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const getAllMarkets = async () => {
|
||||
const markets = [];
|
||||
let marketInfo;
|
||||
for (marketInfo of USE_MARKETS) {
|
||||
for (let marketInfo of getMarketInfos(customMarkets)) {
|
||||
try {
|
||||
const market = await Market.load(
|
||||
connection,
|
||||
|
@ -45,7 +44,11 @@ export function useAllMarkets() {
|
|||
{},
|
||||
marketInfo.programId,
|
||||
);
|
||||
markets.push({ market, marketName: marketInfo.name });
|
||||
markets.push({
|
||||
market,
|
||||
marketName: marketInfo.name,
|
||||
programId: marketInfo.programId,
|
||||
});
|
||||
} catch (e) {
|
||||
notify({
|
||||
message: 'Error loading all market',
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { useLocalStorageState } from './utils';
|
||||
import { useInterval } from './useInterval';
|
||||
import { useConnection } from './connection';
|
||||
import { useWallet } from './wallet';
|
||||
import { useAllMarkets, useTokenAccounts, useMarket } from './markets';
|
||||
import { settleAllFunds } from './send';
|
||||
|
||||
const PreferencesContext = React.createContext(null);
|
||||
|
||||
export function PreferencesProvider({ children }) {
|
||||
const [autoSettleEnabled, setAutoSettleEnabled] = useLocalStorageState(
|
||||
'autoSettleEnabled',
|
||||
false,
|
||||
);
|
||||
|
||||
const [tokenAccounts] = useTokenAccounts();
|
||||
const { connected, wallet } = useWallet();
|
||||
const { customMarkets } = useMarket();
|
||||
const marketList = useAllMarkets(customMarkets);
|
||||
const connection = useConnection();
|
||||
|
||||
useInterval(() => {
|
||||
const autoSettle = async () => {
|
||||
const markets = marketList.map((m) => m.market);
|
||||
try {
|
||||
console.log('Auto settling');
|
||||
await settleAllFunds({ connection, wallet, tokenAccounts, markets });
|
||||
} catch (e) {
|
||||
console.log('Error auto settling funds: ' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
connected && wallet?.autoApprove && autoSettleEnabled && autoSettle();
|
||||
}, 10000);
|
||||
|
||||
return (
|
||||
<PreferencesContext.Provider
|
||||
value={{
|
||||
autoSettleEnabled,
|
||||
setAutoSettleEnabled,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</PreferencesContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function usePreferences() {
|
||||
const context = useContext(PreferencesContext);
|
||||
return {
|
||||
autoSettleEnabled: context.autoSettleEnabled,
|
||||
setAutoSettleEnabled: context.setAutoSettleEnabled,
|
||||
};
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { notify } from './notifications';
|
||||
import { getDecimalCount, sleep } from './utils';
|
||||
import { getSelectedTokenAccountForMint } from './markets';
|
||||
import {
|
||||
Account,
|
||||
PublicKey,
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
Market,
|
||||
TOKEN_MINTS,
|
||||
TokenInstructions,
|
||||
OpenOrders,
|
||||
} from '@project-serum/serum';
|
||||
|
||||
export async function createTokenAccountTransaction({
|
||||
|
@ -135,6 +137,91 @@ export async function settleFunds({
|
|||
});
|
||||
}
|
||||
|
||||
export async function settleAllFunds({
|
||||
connection,
|
||||
wallet,
|
||||
tokenAccounts,
|
||||
markets,
|
||||
}) {
|
||||
if (!markets || !wallet || !connection || !tokenAccounts) {
|
||||
return;
|
||||
}
|
||||
|
||||
const programIds = [];
|
||||
markets
|
||||
.reduce((cumulative, m) => {
|
||||
cumulative.push(m._programId);
|
||||
return cumulative;
|
||||
}, [])
|
||||
.forEach((programId) => {
|
||||
if (!programIds.find((p) => p.equals(programId))) {
|
||||
programIds.push(programId);
|
||||
}
|
||||
});
|
||||
|
||||
const getOpenOrdersAccountsForProgramId = async (programId) => {
|
||||
const openOrdersAccounts = await OpenOrders.findForOwner(
|
||||
connection,
|
||||
wallet.publicKey,
|
||||
programId,
|
||||
);
|
||||
return openOrdersAccounts.filter(
|
||||
(openOrders) =>
|
||||
openOrders.baseTokenTotal.toNumber() ||
|
||||
openOrders.quoteTokenTotal.toNumber(),
|
||||
);
|
||||
};
|
||||
|
||||
const openOrdersAccountsForProgramIds = await Promise.all(
|
||||
programIds.map((programId) => getOpenOrdersAccountsForProgramId(programId)),
|
||||
);
|
||||
const openOrdersAccounts = openOrdersAccountsForProgramIds.reduce(
|
||||
(accounts, current) => accounts.concat(current),
|
||||
[],
|
||||
);
|
||||
|
||||
const settleTransactions = await Promise.all(
|
||||
openOrdersAccounts.map((openOrdersAccount) => {
|
||||
const market = markets.find((m) =>
|
||||
m._decoded?.ownAddress?.equals(openOrdersAccount.market),
|
||||
);
|
||||
return (
|
||||
market &&
|
||||
market.makeSettleFundsTransaction(
|
||||
connection,
|
||||
openOrdersAccount,
|
||||
getSelectedTokenAccountForMint(tokenAccounts, market?.baseMintAddress)
|
||||
?.pubkey,
|
||||
getSelectedTokenAccountForMint(
|
||||
tokenAccounts,
|
||||
market?.quoteMintAddress,
|
||||
)?.pubkey,
|
||||
)
|
||||
);
|
||||
}),
|
||||
);
|
||||
if (!settleTransactions || settleTransactions.length === 0) return;
|
||||
|
||||
const transactions = settleTransactions.slice(0, 4).map((t) => t.transaction);
|
||||
const signers = [];
|
||||
settleTransactions
|
||||
.reduce((cumulative, t) => cumulative.concat(t.signers), [])
|
||||
.forEach((signer) => {
|
||||
if (!signers.find((s) => s.constructor.name === signer.constructor.name && s.equals(signer))) {
|
||||
signers.push(signer);
|
||||
}
|
||||
});
|
||||
|
||||
const transaction = mergeTransactions(transactions);
|
||||
|
||||
return await sendTransaction({
|
||||
transaction,
|
||||
signers,
|
||||
wallet,
|
||||
connection,
|
||||
});
|
||||
}
|
||||
|
||||
export async function cancelOrder(params) {
|
||||
return cancelOrders({ ...params, orders: [params.order] });
|
||||
}
|
||||
|
|
|
@ -12,16 +12,19 @@ const WalletContext = React.createContext(null);
|
|||
|
||||
export function WalletProvider({ children }) {
|
||||
const { endpoint } = useConnectionConfig();
|
||||
|
||||
const [providerUrl, setProviderUrl] = useLocalStorageState(
|
||||
'walletProvider',
|
||||
'https://www.sollet.io',
|
||||
);
|
||||
|
||||
const wallet = useMemo(() => new Wallet(providerUrl, endpoint), [
|
||||
providerUrl,
|
||||
endpoint,
|
||||
]);
|
||||
|
||||
const [connected, setConnected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('trying to connect');
|
||||
wallet.on('connect', () => {
|
||||
|
@ -52,6 +55,7 @@ export function WalletProvider({ children }) {
|
|||
setConnected(false);
|
||||
};
|
||||
}, [wallet]);
|
||||
|
||||
return (
|
||||
<WalletContext.Provider
|
||||
value={{
|
||||
|
|
Loading…
Reference in New Issue