Bonfida trades (#32)

* Use bonfida public trades

* use market address instead + rebase

* Use Typescript + rebase

* BonfidaTrade in types.tsx

* Add optional parameter setCacheToNull

* Add BonfidaConnector to fetch trades

* Some tweaks

* Fix refactor

Co-authored-by: Philippe Maes <philippe@ftx.com>
Co-authored-by: Nathaniel Parke <nathaniel.parke@gmail.com>
This commit is contained in:
David Ratiney 2020-10-18 17:52:09 +08:00 committed by GitHub
parent 01ae8ff7dc
commit 63bf480d18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 403 additions and 241 deletions

View File

@ -1,9 +1,10 @@
import { Col, Row } from 'antd';
import React from 'react';
import styled from 'styled-components';
import { useMarket, useTrades } from '../utils/markets';
import { useMarket, useBonfidaTrades } from '../utils/markets';
import { getDecimalCount } from '../utils/utils';
import FloatingElement from './layout/FloatingElement';
import { BonfidaTrade } from '../utils/types';
const Title = styled.div`
color: rgba(255, 255, 255, 1);
@ -13,17 +14,10 @@ const SizeTitle = styled(Row)`
color: #434a59;
`;
// TODO: Put in some scrolling
const TradesContainer = styled.div`
height: calc(100% - 75px);
margin-right: -20px;
padding-right: 5px;
overflow-y: scroll;
`;
export default function PublicTrades({ smallScreen }) {
const { baseCurrency, quoteCurrency, market } = useMarket();
const trades = useTrades();
const [trades, loaded] = useBonfidaTrades();
return (
<FloatingElement
style={
@ -32,34 +26,36 @@ export default function PublicTrades({ smallScreen }) {
: {
marginTop: '10px',
minHeight: '270px',
height: 'calc(100vh - 710px)',
maxHeight: 'calc(100vh - 700px)',
}
}
>
<Title>Recent Market trades</Title>
<SizeTitle>
<Col span={12} style={{ textAlign: 'left' }}>
Size ({baseCurrency})
</Col>
<Col span={12} style={{ textAlign: 'right' }}>
<Col span={8}>Size ({baseCurrency})</Col>
<Col span={8} style={{ textAlign: 'right' }}>
Price ({quoteCurrency}){' '}
</Col>
<Col span={8} style={{ textAlign: 'right' }}>
Time
</Col>
</SizeTitle>
{!!trades && (
<TradesContainer>
{trades.map((trade, i) => (
{!!trades && loaded && (
<div
style={{
marginRight: '-20px',
paddingRight: '5px',
overflowY: 'scroll',
maxHeight: smallScreen
? 'calc(100% - 75px)'
: 'calc(100vh - 800px)',
}}
>
{trades.map((trade: BonfidaTrade, i: number) => (
<Row key={i} style={{ marginBottom: 4 }}>
<Col span={12} style={{ textAlign: 'left' }}>
{market?.minOrderSize && !isNaN(trade.size)
? Number(trade.size).toFixed(
getDecimalCount(market.minOrderSize),
)
: trade.size}
</Col>
<Col
span={12}
span={8}
style={{
textAlign: 'right',
color: trade.side === 'buy' ? '#41C77A' : '#F23B69',
}}
>
@ -69,9 +65,19 @@ export default function PublicTrades({ smallScreen }) {
)
: trade.price}
</Col>
<Col span={8} style={{ textAlign: 'right' }}>
{market?.minOrderSize && !isNaN(trade.size)
? Number(trade.size).toFixed(
getDecimalCount(market.minOrderSize),
)
: trade.size}
</Col>
<Col span={8} style={{ textAlign: 'right', color: '#434a59' }}>
{trade.time && new Date(trade.time).toLocaleTimeString()}
</Col>
</Row>
))}
</TradesContainer>
</div>
)}
</FloatingElement>
);

View File

@ -0,0 +1,26 @@
import { BonfidaTrade } from './types';
export default class BonfidaApi {
static URL: string = 'https://serum-api.bonfida.com/';
static async get(path: string) {
try {
const response = await fetch(
this.URL + path,
);
if (response.ok) {
const responseJson = await response.json();
return responseJson.success ? responseJson.data : null;
}
} catch (err) {
console.log(`Error fetching from Bonfida API ${path}: ${err}`);
}
return null;
}
static async getRecentTrades(
marketAddress: string,
): Promise<BonfidaTrade[] | null> {
return BonfidaApi.get(`trades/address/${marketAddress}`);
}
}

View File

@ -1,4 +1,4 @@
import {useEffect, useReducer} from 'react';
import { useEffect, useReducer } from 'react';
import assert from 'assert';
@ -12,19 +12,22 @@ class FetchLoopListener<T = any> {
refreshInterval: number;
refreshIntervalOnError: number | null;
callback: () => void;
cacheNullValues: Boolean = true;
constructor(
cacheKey: any,
fn: () => Promise<T>,
refreshInterval: number,
refreshIntervalOnError: number | null,
callback: () => void
cacheKey: any,
fn: () => Promise<T>,
refreshInterval: number,
refreshIntervalOnError: number | null,
callback: () => void,
cacheNullValues: Boolean,
) {
this.cacheKey = cacheKey;
this.fn = fn;
this.refreshInterval = refreshInterval;
this.refreshIntervalOnError = refreshIntervalOnError;
this.callback = callback;
this.cacheNullValues = cacheNullValues;
}
}
@ -34,13 +37,15 @@ class FetchLoopInternal<T = any> {
timeoutId: null | any;
listeners: Set<FetchLoopListener<T>>;
errors: number;
cacheNullValues: Boolean = true;
constructor(cacheKey: any, fn: () => Promise<T>) {
constructor(cacheKey: any, fn: () => Promise<T>, cacheNullValues: Boolean) {
this.cacheKey = cacheKey;
this.fn = fn;
this.timeoutId = null;
this.listeners = new Set();
this.errors = 0;
this.cacheNullValues = cacheNullValues;
}
get refreshInterval(): number {
@ -51,14 +56,12 @@ class FetchLoopInternal<T = any> {
get refreshIntervalOnError(): number | null {
const refreshIntervalsOnError: number[] = [...this.listeners]
.map((listener) => listener.refreshIntervalOnError)
.filter((x): x is number => x !== null);
.map((listener) => listener.refreshIntervalOnError)
.filter((x): x is number => x !== null);
if (refreshIntervalsOnError.length === 0) {
return null;
}
return Math.min(
...refreshIntervalsOnError,
);
return Math.min(...refreshIntervalsOnError);
}
get stopped(): boolean {
@ -99,10 +102,17 @@ class FetchLoopInternal<T = any> {
let errored = false;
try {
const data = await this.fn();
globalCache.set(this.cacheKey, data);
this.errors = 0;
this.notifyListeners();
return data;
if (!this.cacheNullValues && data === null) {
console.log(`Not caching null value for ${this.cacheKey}`)
// cached data has not changed so no need to re-render
this.errors = 0;
return data;
} else {
globalCache.set(this.cacheKey, data);
this.errors = 0;
this.notifyListeners();
return data;
}
} catch (error) {
++this.errors;
console.warn(error);
@ -152,7 +162,11 @@ class FetchLoops {
if (!this.loops.has(listener.cacheKey)) {
this.loops.set(
listener.cacheKey,
new FetchLoopInternal<T>(listener.cacheKey, listener.fn),
new FetchLoopInternal<T>(
listener.cacheKey,
listener.fn,
listener.cacheNullValues,
),
);
}
this.loops.get(listener.cacheKey).addListener(listener);
@ -182,6 +196,7 @@ export function useAsyncData<T = any>(
asyncFn: () => Promise<T>,
cacheKey: any,
{ refreshInterval = 60000, refreshIntervalOnError = null } = {},
cacheNullValues: Boolean = true,
): [null | undefined | T, boolean] {
const [, rerender] = useReducer((i) => i + 1, 0);
@ -196,6 +211,7 @@ export function useAsyncData<T = any>(
refreshInterval,
refreshIntervalOnError,
rerender,
cacheNullValues,
);
globalLoops.addListener(listener);
return () => globalLoops.removeListener(listener);
@ -230,7 +246,11 @@ export function refreshAllCaches(): void {
}
}
export function setCache(cacheKey: any, value: any, { initializeOnly = false } = {}): void {
export function setCache(
cacheKey: any,
value: any,
{ initializeOnly = false } = {},
): void {
if (initializeOnly && globalCache.has(cacheKey)) {
return;
}

View File

@ -7,16 +7,25 @@ import {
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 { 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 tuple from 'immutable-tuple';
import {notify} from './notifications';
import {BN} from 'bn.js';
import {getTokenAccountInfo, parseTokenAccountData, useMintInfos} from './tokens';
import { notify } from './notifications';
import { BN } from 'bn.js';
import {
getTokenAccountInfo,
parseTokenAccountData,
useMintInfos,
} from './tokens';
import {
Balances,
CustomMarketInfo,
@ -28,9 +37,10 @@ import {
SelectedTokenAccounts,
TokenAccount,
Trade,
} from "./types";
import {WRAPPED_SOL_MINT} from "@project-serum/serum/lib/token-instructions";
import {Order} from "@project-serum/serum/lib/market";
} from './types';
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions';
import { Order } from '@project-serum/serum/lib/market';
import BonfidaApi from './bonfidaConnector';
// Used in debugging, should be false in production
const _IGNORE_DEPRECATED = false;
@ -51,15 +61,20 @@ export function useAllMarkets(customMarkets) {
market: Market;
marketName: string;
programId: PublicKey;
} | null> = await Promise.all(getMarketInfos(customMarkets).map(
async marketInfo => {
} | null> = await Promise.all(
getMarketInfos(customMarkets).map(async (marketInfo) => {
try {
const market = await Market.load(connection, marketInfo.address, {}, marketInfo.programId)
const market = await Market.load(
connection,
marketInfo.address,
{},
marketInfo.programId,
);
return {
market,
marketName: marketInfo.name,
programId: marketInfo.programId,
}
};
} catch (e) {
notify({
message: 'Error loading all market',
@ -68,19 +83,18 @@ export function useAllMarkets(customMarkets) {
});
return null;
}
}
))
return markets.filter((m): m is {market: Market; marketName: string; programId: PublicKey;} => !!m);
}),
);
return markets.filter(
(m): m is { market: Market; marketName: string; programId: PublicKey } =>
!!m,
);
};
return useAsyncData(
getAllMarkets,
tuple(
'getAllMarkets',
customMarkets.length,
connection,
),
{refreshInterval: _VERY_SLOW_REFRESH_INTERVAL}
)
tuple('getAllMarkets', customMarkets.length, connection),
{ refreshInterval: _VERY_SLOW_REFRESH_INTERVAL },
);
}
export function useUnmigratedOpenOrdersAccounts() {
@ -149,7 +163,9 @@ export function useUnmigratedOpenOrdersAccounts() {
};
}
const MarketContext: React.Context<null | MarketContextValues> = React.createContext<null | MarketContextValues>(null);
const MarketContext: React.Context<null | MarketContextValues> = React.createContext<null | MarketContextValues>(
null,
);
const _VERY_SLOW_REFRESH_INTERVAL = 5000 * 1000;
@ -163,7 +179,10 @@ export const DEFAULT_MARKET = USE_MARKETS.find(
({ name, deprecated }) => name === 'SRM/USDT' && !deprecated,
);
export function getMarketDetails(market: Market | undefined | null, customMarkets: CustomMarketInfo[]): FullMarketInfo {
export function getMarketDetails(
market: Market | undefined | null,
customMarkets: CustomMarketInfo[],
): FullMarketInfo {
if (!market) {
return {};
}
@ -198,17 +217,15 @@ export function MarketProvider({ children }) {
'marketAddress',
DEFAULT_MARKET?.address.toBase58(),
);
const [customMarkets, setCustomMarkets] = useLocalStorageState<CustomMarketInfo[]>(
'customMarkets',
[],
);
const [customMarkets, setCustomMarkets] = useLocalStorageState<
CustomMarketInfo[]
>('customMarkets', []);
const address = marketAddress && new PublicKey(marketAddress);
const connection = useConnection();
const marketInfos = getMarketInfos(customMarkets);
const marketInfo = address && marketInfos.find((market) =>
market.address.equals(address),
);
const marketInfo =
address && marketInfos.find((market) => market.address.equals(address));
// Replace existing market with a non-deprecated one on first load
useEffect(() => {
@ -267,19 +284,23 @@ export function MarketProvider({ children }) {
);
}
export function useSelectedTokenAccounts(): [SelectedTokenAccounts, (newSelectedTokenAccounts: SelectedTokenAccounts) => void] {
const [selectedTokenAccounts, setSelectedTokenAccounts] = useLocalStorageState<SelectedTokenAccounts>(
'selectedTokenAccounts', {}
);
return [selectedTokenAccounts, setSelectedTokenAccounts]
export function useSelectedTokenAccounts(): [
SelectedTokenAccounts,
(newSelectedTokenAccounts: SelectedTokenAccounts) => void,
] {
const [
selectedTokenAccounts,
setSelectedTokenAccounts,
] = useLocalStorageState<SelectedTokenAccounts>('selectedTokenAccounts', {});
return [selectedTokenAccounts, setSelectedTokenAccounts];
}
export function useMarket() {
const context = useContext(MarketContext);
if (!context) {
throw new Error('Missing market context')
throw new Error('Missing market context');
}
return context
return context;
}
export function useMarkPrice() {
@ -334,6 +355,25 @@ export function _useUnfilteredTrades(limit = 10000) {
// .map(market.parseFillEvent.bind(market));
}
export function useBonfidaTrades() {
const { market } = useMarket();
const marketAddress = market?.address.toBase58();
async function getBonfidaTrades() {
if (!marketAddress) {
return null;
}
return await BonfidaApi.getRecentTrades(marketAddress);
}
return useAsyncData(
getBonfidaTrades,
tuple('getBonfidaTrades', marketAddress),
{ refreshInterval: _SLOW_REFRESH_INTERVAL },
false,
);
}
export function useOrderbookAccounts() {
const { market } = useMarket();
// @ts-ignore
@ -346,7 +386,9 @@ export function useOrderbookAccounts() {
};
}
export function useOrderbook(depth = 20): [{bids: number[][]; asks: number[][];}, boolean] {
export function useOrderbook(
depth = 20,
): [{ bids: number[][]; asks: number[][] }, boolean] {
const { bidOrderbook, askOrderbook } = useOrderbookAccounts();
const { market } = useMarket();
const bids =
@ -393,7 +435,10 @@ export function useSelectedOpenOrdersAccount(fast = false) {
return accounts[0];
}
export function useTokenAccounts(): [TokenAccount[] | null | undefined, boolean] {
export function useTokenAccounts(): [
TokenAccount[] | null | undefined,
boolean,
] {
const { connected, wallet } = useWallet();
const connection = useConnection();
async function getTokenAccounts() {
@ -417,9 +462,13 @@ export function getSelectedTokenAccountForMint(
if (!accounts || !mint) {
return null;
}
const filtered = accounts.filter(({ effectiveMint, pubkey }) =>
mint.equals(effectiveMint) && (!selectedPubKey ||
(typeof selectedPubKey === 'string' ? selectedPubKey : selectedPubKey.toBase58()) === pubkey.toBase58())
const filtered = accounts.filter(
({ effectiveMint, pubkey }) =>
mint.equals(effectiveMint) &&
(!selectedPubKey ||
(typeof selectedPubKey === 'string'
? selectedPubKey
: selectedPubKey.toBase58()) === pubkey.toBase58()),
);
return filtered && filtered[0];
}
@ -428,11 +477,11 @@ export function useSelectedQuoteCurrencyAccount() {
const [accounts] = useTokenAccounts();
const { market } = useMarket();
const [selectedTokenAccounts] = useSelectedTokenAccounts();
const mintAddress = market?.quoteMintAddress;
const mintAddress = market?.quoteMintAddress;
return getSelectedTokenAccountForMint(
accounts,
mintAddress,
mintAddress && selectedTokenAccounts[mintAddress.toBase58()]
mintAddress && selectedTokenAccounts[mintAddress.toBase58()],
);
}
@ -440,11 +489,11 @@ export function useSelectedBaseCurrencyAccount() {
const [accounts] = useTokenAccounts();
const { market } = useMarket();
const [selectedTokenAccounts] = useSelectedTokenAccounts();
const mintAddress = market?.baseMintAddress;
const mintAddress = market?.baseMintAddress;
return getSelectedTokenAccountForMint(
accounts,
mintAddress,
mintAddress && selectedTokenAccounts[mintAddress.toBase58()]
mintAddress && selectedTokenAccounts[mintAddress.toBase58()],
);
}
@ -506,12 +555,19 @@ export function useTrades(limit = 100) {
}));
}
export function useFeeDiscountKeys(): [{
pubkey: PublicKey;
feeTier: number;
balance: number;
mint: PublicKey;
}[] | null | undefined, boolean] {
export function useFeeDiscountKeys(): [
(
| {
pubkey: PublicKey;
feeTier: number;
balance: number;
mint: PublicKey;
}[]
| null
| undefined
),
boolean,
] {
const { market } = useMarket();
const { connected, wallet } = useWallet();
const connection = useConnection();
@ -609,22 +665,26 @@ export function useFillsForAllMarkets(limit = 100) {
}
export function useAllOpenOrdersAccounts() {
const {wallet, connected} = useWallet();
const { wallet, connected } = useWallet();
const connection = useConnection();
const {customMarkets} = useMarket();
const { customMarkets } = useMarket();
const marketInfos = getMarketInfos(customMarkets);
const programIds = [
...new Set(marketInfos.map(info => info.programId.toBase58()))
].map(stringProgramId => new PublicKey(stringProgramId));
...new Set(marketInfos.map((info) => info.programId.toBase58())),
].map((stringProgramId) => new PublicKey(stringProgramId));
const getAllOpenOrdersAccounts = async () => {
if (!connected) {
return [];
}
return (await Promise.all(
programIds.map(programId => OpenOrders.findForOwner(connection, wallet.publicKey, programId)))
).flat()
}
return (
await Promise.all(
programIds.map((programId) =>
OpenOrders.findForOwner(connection, wallet.publicKey, programId),
),
)
).flat();
};
return useAsyncData(
getAllOpenOrdersAccounts,
tuple(
@ -635,25 +695,29 @@ export function useAllOpenOrdersAccounts() {
customMarkets.length,
(programIds || []).length,
),
{refreshInterval: _SLOW_REFRESH_INTERVAL}
)
{ refreshInterval: _SLOW_REFRESH_INTERVAL },
);
}
export function useAllOpenOrdersBalances() {
const [
openOrdersAccounts,
loadedOpenOrdersAccounts
loadedOpenOrdersAccounts,
] = useAllOpenOrdersAccounts();
const [mintInfos, mintInfosConnected] = useMintInfos();
const {customMarkets} = useMarket();
const { customMarkets } = useMarket();
const [allMarkets] = useAllMarkets(customMarkets);
if (!loadedOpenOrdersAccounts || !mintInfosConnected) {
return {};
}
const marketsByAddress = Object.fromEntries((allMarkets || []).map(m => [m.market.address.toBase58(), m]))
const openOrdersBalances: {[mint: string]: { market: PublicKey; free: number; total: number; }[] } = {};
for (let account of (openOrdersAccounts || [])) {
const marketsByAddress = Object.fromEntries(
(allMarkets || []).map((m) => [m.market.address.toBase58(), m]),
);
const openOrdersBalances: {
[mint: string]: { market: PublicKey; free: number; total: number }[];
} = {};
for (let account of openOrdersAccounts || []) {
const marketInfo = marketsByAddress[account.market.toBase58()];
const baseMint = marketInfo.market.baseMintAddress.toBase58();
const quoteMint = marketInfo.market.quoteMintAddress.toBase58();
@ -664,23 +728,23 @@ export function useAllOpenOrdersBalances() {
openOrdersBalances[quoteMint] = [];
}
const baseMintInfo = mintInfos && mintInfos[baseMint]
const baseMintInfo = mintInfos && mintInfos[baseMint];
const baseFree = divideBnToNumber(
new BN(account.baseTokenFree),
getTokenMultiplierFromDecimals(baseMintInfo?.decimals || 0.)
getTokenMultiplierFromDecimals(baseMintInfo?.decimals || 0),
);
const baseTotal = divideBnToNumber(
new BN(account.baseTokenTotal),
getTokenMultiplierFromDecimals(baseMintInfo?.decimals || 0.)
getTokenMultiplierFromDecimals(baseMintInfo?.decimals || 0),
);
const quoteMintInfo = mintInfos && mintInfos[quoteMint]
const quoteMintInfo = mintInfos && mintInfos[quoteMint];
const quoteFree = divideBnToNumber(
new BN(account.quoteTokenFree),
getTokenMultiplierFromDecimals(quoteMintInfo?.decimals || 0.)
getTokenMultiplierFromDecimals(quoteMintInfo?.decimals || 0),
);
const quoteTotal = divideBnToNumber(
new BN(account.quoteTokenTotal),
getTokenMultiplierFromDecimals(quoteMintInfo?.decimals || 0.)
getTokenMultiplierFromDecimals(quoteMintInfo?.decimals || 0),
);
openOrdersBalances[baseMint].push({
@ -694,56 +758,69 @@ export function useAllOpenOrdersBalances() {
total: quoteTotal,
});
}
return openOrdersBalances
return openOrdersBalances;
}
export function useAllOpenOrders(): {
openOrders: { orders: Order[]; marketAddress: string; }[] | null | undefined;
loaded: boolean,
refreshOpenOrders: () => void,
openOrders: { orders: Order[]; marketAddress: string }[] | null | undefined;
loaded: boolean;
refreshOpenOrders: () => void;
} {
const connection = useConnection();
const { connected } = useWallet();
const [openOrdersAccounts, openOrdersAccountsConnected] = useAllOpenOrdersAccounts();
const [
openOrdersAccounts,
openOrdersAccountsConnected,
] = useAllOpenOrdersAccounts();
const { customMarkets } = useMarket();
const [marketInfos, marketInfosConnected] = useAllMarkets(customMarkets);
const openOrdersAccountsByAddress: {[marketAddress: string]: OpenOrders[]} = {}
for (let account of (openOrdersAccounts || [])) {
const marketsAddr = account.market.toBase58()
const openOrdersAccountsByAddress: {
[marketAddress: string]: OpenOrders[];
} = {};
for (let account of openOrdersAccounts || []) {
const marketsAddr = account.market.toBase58();
if (!(marketsAddr in openOrdersAccountsByAddress)) {
openOrdersAccountsByAddress[marketsAddr] = [];
}
openOrdersAccountsByAddress[marketsAddr].push(account);
}
const marketsByAddress = Object.fromEntries((marketInfos || []).map(info => [info.market.publicKey.toBase58(), info]))
const marketsByAddress = Object.fromEntries(
(marketInfos || []).map((info) => [info.market.publicKey.toBase58(), info]),
);
const getAllOpenOrders = async () => {
return await Promise.all(Object.keys(openOrdersAccountsByAddress).map(async (marketAddr) => {
const market = marketsByAddress[marketAddr].market;
const [bids, asks] = await Promise.all([
market.loadBids(connection),
market.loadAsks(connection),
]);
return {
orders: market.filterForOpenOrders(bids, asks, openOrdersAccountsByAddress[marketAddr]),
marketAddress: marketAddr,
};
}));
}
return await Promise.all(
Object.keys(openOrdersAccountsByAddress).map(async (marketAddr) => {
const market = marketsByAddress[marketAddr].market;
const [bids, asks] = await Promise.all([
market.loadBids(connection),
market.loadAsks(connection),
]);
return {
orders: market.filterForOpenOrders(
bids,
asks,
openOrdersAccountsByAddress[marketAddr],
),
marketAddress: marketAddr,
};
}),
);
};
const cacheKey = tuple(
'getAllOpenOrders',
openOrdersAccountsConnected,
(openOrdersAccounts || []).length,
connection,
connected,
marketInfosConnected
);
const [openOrders, loaded] = useAsyncData(
getAllOpenOrders,
cacheKey,
{refreshInterval: _VERY_SLOW_REFRESH_INTERVAL}
marketInfosConnected,
);
const [openOrders, loaded] = useAsyncData(getAllOpenOrders, cacheKey, {
refreshInterval: _VERY_SLOW_REFRESH_INTERVAL,
});
return {
openOrders, loaded, refreshOpenOrders: () => refreshCache(cacheKey)
openOrders,
loaded,
refreshOpenOrders: () => refreshCache(cacheKey),
};
}
@ -802,7 +879,10 @@ export function useBalances(): Balances[] {
];
}
export function useWalletBalancesForAllMarkets(): {mint:string, balance: number}[] {
export function useWalletBalancesForAllMarkets(): {
mint: string;
balance: number;
}[] {
const [tokenAccounts] = useTokenAccounts();
const { connected } = useWallet();
const [mintInfos, mintInfosConnected] = useMintInfos();
@ -811,8 +891,8 @@ export function useWalletBalancesForAllMarkets(): {mint:string, balance: number}
return [];
}
let balances: {[mint: string]: number} = {};
for (let account of (tokenAccounts || [])) {
let balances: { [mint: string]: number } = {};
for (let account of tokenAccounts || []) {
if (!account.account) {
continue;
}
@ -821,22 +901,24 @@ export function useWalletBalancesForAllMarkets(): {mint:string, balance: number}
parsedAccount = {
mint: WRAPPED_SOL_MINT,
owner: account.pubkey,
amount: account.account.lamports
}
amount: account.account.lamports,
};
} else {
parsedAccount = parseTokenAccountData(account.account.data);
}
if (!(parsedAccount.mint.toBase58() in balances)) {
balances[parsedAccount.mint.toBase58()] = 0.
balances[parsedAccount.mint.toBase58()] = 0;
}
const mintInfo = mintInfos && mintInfos[parsedAccount.mint.toBase58()]
const mintInfo = mintInfos && mintInfos[parsedAccount.mint.toBase58()];
const additionalAmount = divideBnToNumber(
new BN(parsedAccount.amount),
getTokenMultiplierFromDecimals(mintInfo?.decimals || 0.)
getTokenMultiplierFromDecimals(mintInfo?.decimals || 0),
);
balances[parsedAccount.mint.toBase58()] += additionalAmount;
}
return Object.entries(balances).map(([mint, balance]) => { return {mint, balance} });
return Object.entries(balances).map(([mint, balance]) => {
return { mint, balance };
});
}
export function useUnmigratedDeprecatedMarkets() {
@ -898,8 +980,8 @@ export function useUnmigratedDeprecatedMarkets() {
}
return markets.map((market) => ({
market,
openOrdersList: accounts?.filter((openOrders) =>
market && openOrders.market.equals(market.address),
openOrdersList: accounts?.filter(
(openOrders) => market && openOrders.market.equals(market.address),
),
}));
}
@ -910,7 +992,10 @@ export function useGetOpenOrdersForDeprecatedMarkets(): {
refreshOpenOrders: () => void;
} {
const { connected, wallet } = useWallet();
const [customMarkets] = useLocalStorageState<CustomMarketInfo[]>('customMarkets', []);
const [customMarkets] = useLocalStorageState<CustomMarketInfo[]>(
'customMarkets',
[],
);
const connection = useConnection();
const marketsAndOrders = useUnmigratedDeprecatedMarkets();
const marketsList =
@ -918,7 +1003,10 @@ export function useGetOpenOrdersForDeprecatedMarkets(): {
// This isn't quite right: open order balances could change
const deps =
marketsList && marketsList.filter((market): market is Market => !!market).map((market) => market.address.toBase58());
marketsList &&
marketsList
.filter((market): market is Market => !!market)
.map((market) => market.address.toBase58());
async function getOpenOrdersForDeprecatedMarkets() {
if (!connected) {
@ -949,9 +1037,9 @@ export function useGetOpenOrdersForDeprecatedMarkets(): {
return null;
}
};
return (await Promise.all(marketsList.map(getOrders))).filter(
(x): x is OrderWithMarketAndMarketName[] => !!x
).flat();
return (await Promise.all(marketsList.map(getOrders)))
.filter((x): x is OrderWithMarketAndMarketName[] => !!x)
.flat();
}
const cacheKey = tuple(
@ -978,7 +1066,10 @@ export function useGetOpenOrdersForDeprecatedMarkets(): {
export function useBalancesForDeprecatedMarkets() {
const markets = useUnmigratedDeprecatedMarkets();
const [customMarkets] = useLocalStorageState<CustomMarketInfo[]>('customMarkets', []);
const [customMarkets] = useLocalStorageState<CustomMarketInfo[]>(
'customMarkets',
[],
);
if (!markets) {
return null;
}
@ -1035,7 +1126,9 @@ export function useBalancesForDeprecatedMarkets() {
return openOrderAccountBalances;
}
export function getMarketInfos(customMarkets: CustomMarketInfo[]): MarketInfo[] {
export function getMarketInfos(
customMarkets: CustomMarketInfo[],
): MarketInfo[] {
const customMarketsInfo = customMarkets.map((m) => ({
...m,
address: new PublicKey(m.address),
@ -1069,7 +1162,7 @@ export function getMarketOrderPrice(
if (orderbook.isBids) {
return orderbook.market.tickSize;
}
let spentCost = 0.;
let spentCost = 0;
let price, sizeAtLevel, costAtLevel: number;
const asks = orderbook.getL2(1000);
for ([price, sizeAtLevel] of asks) {

View File

@ -1,123 +1,140 @@
import {AccountInfo, Connection, PublicKey} from '@solana/web3.js';
import Wallet from "@project-serum/sol-wallet-adapter";
import {Market, OpenOrders} from "@project-serum/serum";
import {Event} from "@project-serum/serum/lib/queue";
import {Order} from "@project-serum/serum/lib/market";
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
import Wallet from '@project-serum/sol-wallet-adapter';
import { Market, OpenOrders } from '@project-serum/serum';
import { Event } from '@project-serum/serum/lib/queue';
import { Order } from '@project-serum/serum/lib/market';
export interface ConnectionContextValues {
endpoint: string;
setEndpoint: (newEndpoint: string) => void;
connection: Connection;
sendConnection: Connection;
availableEndpoints: EndpointInfo[];
setCustomEndpoints: (newCustomEndpoints: EndpointInfo[]) => void;
endpoint: string;
setEndpoint: (newEndpoint: string) => void;
connection: Connection;
sendConnection: Connection;
availableEndpoints: EndpointInfo[];
setCustomEndpoints: (newCustomEndpoints: EndpointInfo[]) => void;
}
export interface WalletContextValues {
wallet: Wallet;
connected: boolean;
providerUrl: string;
setProviderUrl: (newProviderUrl: string) => void;
providerName: string;
wallet: Wallet;
connected: boolean;
providerUrl: string;
setProviderUrl: (newProviderUrl: string) => void;
providerName: string;
}
export interface MarketInfo {
address: PublicKey;
name: string;
programId: PublicKey;
deprecated: boolean;
quoteLabel?: string;
baseLabel?: string;
address: PublicKey;
name: string;
programId: PublicKey;
deprecated: boolean;
quoteLabel?: string;
baseLabel?: string;
}
export interface CustomMarketInfo {
address: string;
name: string;
programId: string;
quoteLabel?: string;
baseLabel?: string;
address: string;
name: string;
programId: string;
quoteLabel?: string;
baseLabel?: string;
}
export interface FullMarketInfo {
address?: PublicKey;
name?: string;
programId?: PublicKey;
deprecated?: boolean;
quoteLabel?: string;
baseLabel?: string;
marketName?: string;
baseCurrency?: string;
quoteCurrency?: string;
marketInfo?: MarketInfo;
address?: PublicKey;
name?: string;
programId?: PublicKey;
deprecated?: boolean;
quoteLabel?: string;
baseLabel?: string;
marketName?: string;
baseCurrency?: string;
quoteCurrency?: string;
marketInfo?: MarketInfo;
}
export interface MarketContextValues extends FullMarketInfo{
market: Market | undefined | null;
setMarketAddress: (newMarketAddress: string) => void;
customMarkets: CustomMarketInfo[];
setCustomMarkets: (newCustomMarkets: CustomMarketInfo[]) => void;
export interface MarketContextValues extends FullMarketInfo {
market: Market | undefined | null;
setMarketAddress: (newMarketAddress: string) => void;
customMarkets: CustomMarketInfo[];
setCustomMarkets: (newCustomMarkets: CustomMarketInfo[]) => void;
}
export interface TokenAccount {
pubkey: PublicKey;
account: AccountInfo<Buffer> | null;
effectiveMint: PublicKey
pubkey: PublicKey;
account: AccountInfo<Buffer> | null;
effectiveMint: PublicKey;
}
export interface Trade extends Event {
side: string;
price: number;
feeCost: number;
size: number;
side: string;
price: number;
feeCost: number;
size: number;
}
export interface OrderWithMarket extends Order {
marketName: string;
marketName: string;
}
export interface OrderWithMarketAndMarketName extends Order {
market: Market;
marketName: string | undefined;
market: Market;
marketName: string | undefined;
}
interface BalancesBase {
key: string;
coin: string;
wallet?: number | null | undefined;
orders?: number | null | undefined;
openOrders?: OpenOrders | null | undefined;
unsettled?: number| null | undefined;
key: string;
coin: string;
wallet?: number | null | undefined;
orders?: number | null | undefined;
openOrders?: OpenOrders | null | undefined;
unsettled?: number | null | undefined;
}
export interface Balances extends BalancesBase {
market?: Market | null | undefined;
market?: Market | null | undefined;
}
export interface OpenOrdersBalances extends BalancesBase {
market?: string | null | undefined;
baseCurrencyAccount: { pubkey: PublicKey; account: AccountInfo<Buffer> } | null | undefined;
quoteCurrencyAccount: { pubkey: PublicKey; account: AccountInfo<Buffer> } | null | undefined;
market?: string | null | undefined;
baseCurrencyAccount:
| { pubkey: PublicKey; account: AccountInfo<Buffer> }
| null
| undefined;
quoteCurrencyAccount:
| { pubkey: PublicKey; account: AccountInfo<Buffer> }
| null
| undefined;
}
export interface DeprecatedOpenOrdersBalances extends BalancesBase {
market: Market | null | undefined;
marketName: string | null | undefined;
market: Market | null | undefined;
marketName: string | null | undefined;
}
export interface PreferencesContextValues {
autoSettleEnabled: boolean;
setAutoSettleEnabled: (newAutoSettleEnabled: boolean) => void;
autoSettleEnabled: boolean;
setAutoSettleEnabled: (newAutoSettleEnabled: boolean) => void;
}
export interface EndpointInfo {
name: string;
endpoint: string;
custom: boolean;
name: string;
endpoint: string;
custom: boolean;
}
/**
* {tokenMint: preferred token account's base58 encoded public key}
*/
export interface SelectedTokenAccounts {
[tokenMint: string]: string;
[tokenMint: string]: string;
}
export interface BonfidaTrade {
market: string;
size: number;
price: number;
orderId: string;
time: number;
side: string;
feeCost: number;
marketAddress: string;
}