184 lines
4.9 KiB
TypeScript
184 lines
4.9 KiB
TypeScript
import * as BufferLayout from 'buffer-layout';
|
|
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';
|
|
|
|
export const ACCOUNT_LAYOUT = BufferLayout.struct([
|
|
BufferLayout.blob(32, 'mint'),
|
|
BufferLayout.blob(32, 'owner'),
|
|
BufferLayout.nu64('amount'),
|
|
BufferLayout.blob(93),
|
|
]);
|
|
|
|
export const MINT_LAYOUT = BufferLayout.struct([
|
|
BufferLayout.blob(36),
|
|
BufferLayout.blob(8, 'supply'),
|
|
BufferLayout.u8('decimals'),
|
|
BufferLayout.u8('initialized'),
|
|
BufferLayout.blob(36),
|
|
]);
|
|
|
|
export function parseTokenAccountData(
|
|
data: Buffer,
|
|
): { mint: PublicKey; owner: PublicKey; amount: number } {
|
|
let { mint, owner, amount } = ACCOUNT_LAYOUT.decode(data);
|
|
return {
|
|
mint: new PublicKey(mint),
|
|
owner: new PublicKey(owner),
|
|
amount,
|
|
};
|
|
}
|
|
|
|
export interface MintInfo {
|
|
decimals: number;
|
|
initialized: boolean;
|
|
supply: BN;
|
|
}
|
|
|
|
export function parseTokenMintData(data): MintInfo {
|
|
let { decimals, initialized, supply } = MINT_LAYOUT.decode(data);
|
|
return {
|
|
decimals,
|
|
initialized: !!initialized,
|
|
supply: new BN(supply, 10, 'le'),
|
|
};
|
|
}
|
|
|
|
export function getOwnedAccountsFilters(publicKey: PublicKey) {
|
|
return [
|
|
{
|
|
memcmp: {
|
|
offset: ACCOUNT_LAYOUT.offsetOf('owner'),
|
|
bytes: publicKey.toBase58(),
|
|
},
|
|
},
|
|
{
|
|
dataSize: ACCOUNT_LAYOUT.span,
|
|
},
|
|
];
|
|
}
|
|
|
|
export const TOKEN_PROGRAM_ID = new PublicKey(
|
|
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
|
|
);
|
|
|
|
export async function getOwnedTokenAccounts(
|
|
connection: Connection,
|
|
publicKey: PublicKey,
|
|
): Promise<Array<{ publicKey: PublicKey; accountInfo: AccountInfo<Buffer> }>> {
|
|
let filters = getOwnedAccountsFilters(publicKey);
|
|
let resp = await connection.getProgramAccounts(TOKEN_PROGRAM_ID, {
|
|
filters,
|
|
});
|
|
return resp.map(
|
|
({ pubkey, account: { data, executable, owner, lamports } }) => ({
|
|
publicKey: new PublicKey(pubkey),
|
|
accountInfo: {
|
|
data,
|
|
executable,
|
|
owner: new PublicKey(owner),
|
|
lamports,
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
|
|
export async function getTokenAccountInfo(
|
|
connection: Connection,
|
|
ownerAddress: PublicKey,
|
|
) {
|
|
let [splAccounts, account] = await Promise.all([
|
|
getOwnedTokenAccounts(connection, ownerAddress),
|
|
connection.getAccountInfo(ownerAddress),
|
|
]);
|
|
const parsedSplAccounts: TokenAccount[] = splAccounts.map(
|
|
({ publicKey, accountInfo }) => {
|
|
return {
|
|
pubkey: publicKey,
|
|
account: accountInfo,
|
|
effectiveMint: parseTokenAccountData(accountInfo.data).mint,
|
|
};
|
|
},
|
|
);
|
|
return parsedSplAccounts.concat({
|
|
pubkey: ownerAddress,
|
|
account,
|
|
effectiveMint: WRAPPED_SOL_MINT,
|
|
});
|
|
}
|
|
|
|
// 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();
|
|
return useMemo(() => {
|
|
return Object.fromEntries(
|
|
TOKEN_MINTS.map((mint) => [mint.address.toBase58(), mint.name]),
|
|
);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [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(): [
|
|
(
|
|
| {
|
|
[mintAddress: string]: {
|
|
decimals: number;
|
|
initialized: boolean;
|
|
} | null;
|
|
}
|
|
| null
|
|
| undefined
|
|
),
|
|
boolean,
|
|
] {
|
|
const connection = useConnection();
|
|
const [tokenAccounts] = useTokenAccounts();
|
|
const [allMarkets] = useAllMarkets();
|
|
|
|
const allMints = (tokenAccounts || [])
|
|
.map((account) => account.effectiveMint)
|
|
.concat(
|
|
(allMarkets || []).map((marketInfo) => marketInfo.market.baseMintAddress),
|
|
)
|
|
.concat(
|
|
(allMarkets || []).map(
|
|
(marketInfo) => marketInfo.market.quoteMintAddress,
|
|
),
|
|
);
|
|
const uniqueMints = [...new Set(allMints.map((mint) => mint.toBase58()))].map(
|
|
(stringMint) => new PublicKey(stringMint),
|
|
);
|
|
|
|
const getAllMintInfo = async () => {
|
|
const mintInfos = await getMultipleSolanaAccounts(connection, uniqueMints);
|
|
return Object.fromEntries(
|
|
Object.entries(mintInfos.value).map(([key, accountInfo]) => [
|
|
key,
|
|
accountInfo && parseTokenMintData(accountInfo.data),
|
|
]),
|
|
);
|
|
};
|
|
|
|
return useAsyncData(
|
|
getAllMintInfo,
|
|
tuple(
|
|
'getAllMintInfo',
|
|
connection,
|
|
(tokenAccounts || []).length,
|
|
(allMarkets || []).length,
|
|
),
|
|
{ refreshInterval: _VERY_SLOW_REFRESH_INTERVAL },
|
|
);
|
|
}
|