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:
philippe-ftx 2020-10-05 13:26:34 +02:00 committed by GitHub
parent 9765d90df7
commit 190122a669
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 206 additions and 8 deletions

View File

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

View File

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

View File

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

View File

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

55
src/utils/preferences.js Normal file
View File

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

View File

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

View File

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