serum-js/src/market.ts

750 lines
20 KiB
TypeScript
Raw Normal View History

2020-08-10 09:26:22 -07:00
import { blob, seq, struct, u8 } from 'buffer-layout';
2020-08-13 07:24:51 -07:00
import { accountFlagsLayout, publicKeyLayout, u128, u64 } from './layout';
2020-08-13 10:11:40 -07:00
import { Slab, SLAB_LAYOUT } from './slab';
2020-08-12 11:45:42 -07:00
import { DEX_PROGRAM_ID, DexInstructions } from './instructions';
2020-08-10 06:06:31 -07:00
import BN from 'bn.js';
2020-08-12 11:45:42 -07:00
import {
Account,
2020-08-13 10:11:40 -07:00
AccountInfo,
Connection,
2020-08-12 11:45:42 -07:00
PublicKey,
SystemProgram,
Transaction,
TransactionSignature,
2020-08-12 11:45:42 -07:00
} from '@solana/web3.js';
import { decodeEventQueue, decodeRequestQueue } from './queue';
2020-08-13 10:11:40 -07:00
import { Buffer } from 'buffer';
2020-08-07 01:38:49 -07:00
export const MARKET_STATE_LAYOUT = struct([
blob(5),
2020-08-13 07:24:51 -07:00
accountFlagsLayout('accountFlags'),
2020-08-07 01:38:49 -07:00
publicKeyLayout('ownAddress'),
u64('vaultSignerNonce'),
publicKeyLayout('baseMint'),
publicKeyLayout('quoteMint'),
publicKeyLayout('baseVault'),
u64('baseDepositsTotal'),
u64('baseFeesAccrued'),
publicKeyLayout('quoteVault'),
u64('quoteDepositsTotal'),
u64('quoteFeesAccrued'),
u64('quoteDustThreshold'),
publicKeyLayout('requestQueue'),
publicKeyLayout('eventQueue'),
publicKeyLayout('bids'),
publicKeyLayout('asks'),
u64('baseLotSize'),
u64('quoteLotSize'),
2020-08-12 06:37:05 -07:00
u64('feeRateBps'),
blob(7),
2020-08-07 01:38:49 -07:00
]);
2020-08-10 06:06:31 -07:00
export class Market {
2020-08-13 10:11:40 -07:00
private _decoded: any;
private _baseSplTokenDecimals: number;
private _quoteSplTokenDecimals: number;
private _skipPreflight: boolean;
private _confirmations: number;
constructor(
decoded,
baseMintDecimals: number,
quoteMintDecimals: number,
options: MarketOptions = {},
) {
const { skipPreflight = false, confirmations = 0 } = options;
2020-08-09 05:08:30 -07:00
if (!decoded.accountFlags.initialized || !decoded.accountFlags.market) {
throw new Error('Invalid market state');
}
this._decoded = decoded;
this._baseSplTokenDecimals = baseMintDecimals;
this._quoteSplTokenDecimals = quoteMintDecimals;
this._skipPreflight = skipPreflight;
this._confirmations = confirmations;
2020-08-09 05:08:30 -07:00
}
2020-08-12 11:45:42 -07:00
static get LAYOUT() {
return MARKET_STATE_LAYOUT;
2020-08-09 05:08:30 -07:00
}
static async load(
connection: Connection,
address: PublicKey,
options: MarketOptions = {},
) {
2020-08-13 10:11:40 -07:00
const { owner, data } = throwIfNull(
await connection.getAccountInfo(address),
'Market not found',
);
2020-08-09 05:08:30 -07:00
if (!owner.equals(DEX_PROGRAM_ID)) {
throw new Error('Address not owned by program');
}
2020-08-10 09:26:22 -07:00
const decoded = MARKET_STATE_LAYOUT.decode(data);
2020-08-12 11:45:42 -07:00
if (
!decoded.accountFlags.initialized ||
!decoded.accountFlags.market ||
!decoded.ownAddress.equals(address)
) {
throw new Error('Invalid market');
}
2020-08-12 06:37:05 -07:00
const [baseMintDecimals, quoteMintDecimals] = await Promise.all([
getMintDecimals(connection, decoded.baseMint),
getMintDecimals(connection, decoded.quoteMint),
2020-08-10 09:26:22 -07:00
]);
return new Market(decoded, baseMintDecimals, quoteMintDecimals, options);
2020-08-09 05:08:30 -07:00
}
2020-08-13 10:11:40 -07:00
get address(): PublicKey {
2020-08-12 11:45:42 -07:00
return this._decoded.ownAddress;
}
2020-08-13 10:11:40 -07:00
get publicKey(): PublicKey {
2020-08-12 11:45:42 -07:00
return this.address;
}
2020-08-13 10:11:40 -07:00
get baseMintAddress(): PublicKey {
2020-08-12 11:45:42 -07:00
return this._decoded.baseMint;
}
2020-08-13 10:11:40 -07:00
get quoteMintAddress(): PublicKey {
2020-08-12 11:45:42 -07:00
return this._decoded.quoteMint;
}
2020-08-13 10:11:40 -07:00
async loadBids(connection: Connection): Promise<Orderbook> {
const { data } = throwIfNull(
await connection.getAccountInfo(this._decoded.bids),
);
2020-08-09 05:08:30 -07:00
return Orderbook.decode(this, data);
}
2020-08-13 10:11:40 -07:00
async loadAsks(connection: Connection): Promise<Orderbook> {
const { data } = throwIfNull(
await connection.getAccountInfo(this._decoded.asks),
);
2020-08-09 05:08:30 -07:00
return Orderbook.decode(this, data);
}
async loadOrdersForOwner(connection: Connection, ownerAddress: PublicKey) {
const [bids, asks, openOrdersAccounts] = await Promise.all([
this.loadBids(connection),
this.loadAsks(connection),
this.findOpenOrdersAccountsForOwner(connection, ownerAddress),
]);
return [...bids, ...asks].filter((order) =>
openOrdersAccounts.some((openOrders) =>
order.openOrdersAddress.equals(openOrders.address),
),
);
}
2020-08-13 10:11:40 -07:00
async findBaseTokenAccountsForOwner(
connection: Connection,
ownerAddress: PublicKey,
): Promise<Array<{ pubkey: PublicKey; account: AccountInfo<Buffer> }>> {
2020-08-12 11:45:42 -07:00
return (
await connection.getTokenAccountsByOwner(ownerAddress, {
mint: this.baseMintAddress,
})
).value;
}
2020-08-13 10:11:40 -07:00
async findQuoteTokenAccountsForOwner(
connection: Connection,
ownerAddress: PublicKey,
): Promise<{ pubkey: PublicKey; account: AccountInfo<Buffer> }[]> {
2020-08-12 11:45:42 -07:00
return (
await connection.getTokenAccountsByOwner(ownerAddress, {
mint: this.quoteMintAddress,
})
).value;
}
2020-08-13 10:11:40 -07:00
async findOpenOrdersAccountsForOwner(
connection: Connection,
ownerAddress: PublicKey,
): Promise<OpenOrders[]> {
2020-08-12 11:45:42 -07:00
return OpenOrders.findForMarketAndOwner(
connection,
this.address,
ownerAddress,
);
}
2020-08-10 09:26:22 -07:00
async placeOrder(
2020-08-13 10:11:40 -07:00
connection: Connection,
{ owner, payer, side, price, size, orderType = 'limit' }: OrderParams,
2020-08-10 09:26:22 -07:00
) {
2020-08-12 11:45:42 -07:00
const { transaction, signers } = await this.makePlaceOrderTransaction(
connection,
{
owner,
payer,
side,
price,
size,
orderType,
},
);
return await this._sendTransaction(connection, transaction, signers);
2020-08-12 11:45:42 -07:00
}
2020-08-13 10:11:40 -07:00
async makePlaceOrderTransaction<T extends PublicKey | Account>(
connection: Connection,
{ owner, payer, side, price, size, orderType = 'limit' }: OrderParams<T>,
2020-08-12 11:45:42 -07:00
) {
2020-08-13 10:11:40 -07:00
// @ts-ignore
const ownerAddress: PublicKey = owner.publicKey ?? owner;
2020-08-12 11:45:42 -07:00
const openOrdersAccounts = await this.findOpenOrdersAccountsForOwner(
connection,
ownerAddress,
); // TODO: cache this
const transaction = new Transaction();
2020-08-13 10:11:40 -07:00
const signers: (T | Account)[] = [owner];
2020-08-12 11:45:42 -07:00
let openOrdersAddress;
if (openOrdersAccounts.length === 0) {
const newOpenOrdersAccount = new Account();
transaction.add(
await OpenOrders.makeCreateAccountTransaction(
connection,
this.address,
ownerAddress,
newOpenOrdersAccount.publicKey,
),
);
openOrdersAddress = newOpenOrdersAccount.publicKey;
signers.push(newOpenOrdersAccount);
} else {
openOrdersAddress = openOrdersAccounts[0].address;
}
if (this.baseSizeNumberToLots(size).lte(new BN(0))) {
throw new Error('size too small');
}
if (this.priceNumberToLots(price).lte(new BN(0))) {
throw new Error('invalid price');
}
2020-08-12 11:45:42 -07:00
transaction.add(
DexInstructions.newOrder({
market: this.address,
requestQueue: this._decoded.requestQueue,
baseVault: this._decoded.baseVault,
quoteVault: this._decoded.quoteVault,
openOrders: openOrdersAddress,
owner: ownerAddress,
payer,
side,
limitPrice: this.priceNumberToLots(price),
maxQuantity: this.baseSizeNumberToLots(size),
orderType,
}),
);
return { transaction, signers };
2020-08-10 09:26:22 -07:00
}
private async _sendTransaction(
connection: Connection,
transaction: Transaction,
signers: Array<Account>,
): Promise<TransactionSignature> {
const signature = await connection.sendTransaction(transaction, signers, {
skipPreflight: this._skipPreflight,
});
if (this._confirmations > 0) {
2020-08-18 04:23:24 -07:00
const { value } = await connection.confirmTransaction(
signature,
this._confirmations,
);
if (value?.err) {
throw new Error(JSON.stringify(value.err));
}
}
return signature;
}
async cancelOrder(connection: Connection, owner: Account, order: Order) {
const transaction = await this.makeCancelOrderTransaction(
connection,
owner.publicKey,
order,
);
return await this._sendTransaction(connection, transaction, [owner]);
}
async makeCancelOrderTransaction(
connection: Connection,
owner: PublicKey,
order: Order,
) {
const transaction = new Transaction();
transaction.add(
DexInstructions.cancelOrder({
market: this.address,
owner,
openOrders: order.openOrdersAddress,
requestQueue: this._decoded.requestQueue,
side: order.side,
orderId: order.orderId,
openOrdersSlot: order.openOrdersSlot,
}),
);
return transaction;
}
2020-08-18 04:23:24 -07:00
async settleFunds(
connection: Connection,
owner: Account,
openOrders: OpenOrders,
baseWallet: PublicKey,
quoteWallet: PublicKey,
) {
if (!openOrders.owner.equals(owner.publicKey)) {
throw new Error('Invalid open orders account');
}
const transaction = await this.makeSettleFundsTransaction(
connection,
openOrders,
baseWallet,
quoteWallet,
);
return await this._sendTransaction(connection, transaction, [owner]);
}
async makeSettleFundsTransaction(
connection: Connection,
openOrders: OpenOrders,
baseWallet: PublicKey,
quoteWallet: PublicKey,
) {
const tx = new Transaction();
// @ts-ignore
const vaultSigner = await PublicKey.createProgramAddress(
[
this.address.toBuffer(),
this._decoded.vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
],
DEX_PROGRAM_ID,
);
tx.add(
DexInstructions.settleFunds({
market: this.address,
openOrders: openOrders.address,
owner: openOrders.owner,
baseVault: this._decoded.baseVault,
quoteVault: this._decoded.quoteVault,
baseWallet,
quoteWallet,
vaultSigner,
}),
);
return tx;
}
2020-08-13 10:11:40 -07:00
async loadRequestQueue(connection: Connection) {
const { data } = throwIfNull(
await connection.getAccountInfo(this._decoded.requestQueue),
);
return decodeRequestQueue(data);
}
2020-08-13 10:11:40 -07:00
async loadEventQueue(connection: Connection) {
const { data } = throwIfNull(
await connection.getAccountInfo(this._decoded.eventQueue),
);
return decodeEventQueue(data);
}
2020-08-13 12:45:16 -07:00
async loadFills(connection: Connection, limit = 100) {
// TODO: once there's a separate source of fills use that instead
const { data } = throwIfNull(
await connection.getAccountInfo(this._decoded.eventQueue),
);
const events = decodeEventQueue(data, limit);
return events
.filter((event) => event.eventFlags.fill && event.quantityPaid.gtn(0))
.map((event) =>
event.eventFlags.bid
? {
...event,
size: this.baseSizeLotsToNumber(event.quantityReleased),
price: this.priceLotsToNumber(
event.quantityPaid.divRound(event.quantityReleased),
),
side: 'buy',
}
: {
...event,
size: this.baseSizeLotsToNumber(event.quantityPaid),
price: this.priceLotsToNumber(
event.quantityReleased.divRound(event.quantityPaid),
),
side: 'sell',
},
);
}
2020-08-13 10:11:40 -07:00
private get _baseSplTokenMultiplier() {
return new BN(10).pow(new BN(this._baseSplTokenDecimals));
}
2020-08-13 10:11:40 -07:00
private get _quoteSplTokenMultiplier() {
return new BN(10).pow(new BN(this._quoteSplTokenDecimals));
2020-08-10 09:26:22 -07:00
}
2020-08-13 10:11:40 -07:00
priceLotsToNumber(price: BN) {
2020-08-09 05:08:30 -07:00
return divideBnToNumber(
price.mul(this._decoded.quoteLotSize).mul(this._baseSplTokenMultiplier),
this._decoded.baseLotSize.mul(this._quoteSplTokenMultiplier),
2020-08-09 05:08:30 -07:00
);
}
2020-08-13 10:11:40 -07:00
priceNumberToLots(price: number): BN {
2020-08-12 11:45:42 -07:00
return new BN(
Math.round(
(price *
Math.pow(10, this._quoteSplTokenDecimals) *
2020-08-12 11:45:42 -07:00
this._decoded.baseLotSize.toNumber()) /
(Math.pow(10, this._baseSplTokenDecimals) *
2020-08-12 11:45:42 -07:00
this._decoded.quoteLotSize.toNumber()),
),
);
}
2020-08-13 10:11:40 -07:00
baseSizeLotsToNumber(size: BN) {
2020-08-09 05:08:30 -07:00
return divideBnToNumber(
size.mul(this._decoded.baseLotSize),
this._baseSplTokenMultiplier,
2020-08-09 05:08:30 -07:00
);
}
2020-08-13 10:11:40 -07:00
baseSizeNumberToLots(size: number): BN {
2020-08-12 11:45:42 -07:00
const native = new BN(
Math.round(size * Math.pow(10, this._baseSplTokenDecimals)),
2020-08-12 11:45:42 -07:00
);
// rounds down to the nearest lot size
return native.div(this._decoded.baseLotSize);
}
2020-08-13 10:11:40 -07:00
quoteSizeLotsToNumber(size: BN) {
2020-08-09 05:08:30 -07:00
return divideBnToNumber(
size.mul(this._decoded.quoteLotSize),
this._quoteSplTokenMultiplier,
2020-08-09 05:08:30 -07:00
);
}
2020-08-12 11:45:42 -07:00
2020-08-13 10:11:40 -07:00
quoteSizeNumberToLots(size: number): BN {
2020-08-12 11:45:42 -07:00
const native = new BN(
Math.round(size * Math.pow(10, this._quoteSplTokenDecimals)),
2020-08-12 11:45:42 -07:00
);
// rounds down to the nearest lot size
return native.div(this._decoded.quoteLotSize);
}
get minOrderSize() {
return this.baseSizeLotsToNumber(new BN(1));
}
get tickSize() {
return this.priceLotsToNumber(new BN(1));
}
2020-08-13 10:11:40 -07:00
async matchOrders(connection: Connection, feePayer: Account, limit: number) {
const tx = new Transaction();
tx.add(
DexInstructions.matchOrders({
market: this.address,
requestQueue: this._decoded.requestQueue,
eventQueue: this._decoded.eventQueue,
bids: this._decoded.bids,
asks: this._decoded.asks,
baseVault: this._decoded.baseVault,
quoteVault: this._decoded.quoteVault,
limit,
}),
);
return await this._sendTransaction(connection, tx, [feePayer]);
}
2020-08-09 05:08:30 -07:00
}
export interface MarketOptions {
skipPreflight?: boolean;
confirmations?: number;
}
2020-08-13 10:11:40 -07:00
export interface OrderParams<T = Account> {
owner: T;
payer: PublicKey;
side: 'buy' | 'sell';
price: number;
size: number;
orderType?: 'limit' | 'ioc' | 'postOnly';
}
2020-08-07 01:38:49 -07:00
export const OPEN_ORDERS_LAYOUT = struct([
blob(5),
2020-08-13 07:24:51 -07:00
accountFlagsLayout('accountFlags'),
2020-08-07 01:38:49 -07:00
publicKeyLayout('market'),
publicKeyLayout('owner'),
// These are in spl-token (i.e. not lot) units
u64('baseTokenFree'),
u64('baseTokenTotal'),
u64('quoteTokenFree'),
u64('quoteTokenTotal'),
2020-08-07 01:38:49 -07:00
u128('freeSlotBits'),
u128('isBidBits'),
seq(u128(), 128, 'orders'),
blob(7),
2020-08-07 01:38:49 -07:00
]);
2020-08-12 11:45:42 -07:00
export class OpenOrders {
2020-08-13 10:11:40 -07:00
address: PublicKey;
market!: PublicKey;
owner!: PublicKey;
baseTokenFree!: BN;
baseTokenTotal!: BN;
quoteTokenFree!: BN;
quoteTokenTotal!: BN;
orders!: BN[];
constructor(address: PublicKey, decoded) {
2020-08-12 11:45:42 -07:00
this.address = address;
Object.assign(this, decoded);
}
static get LAYOUT() {
return OPEN_ORDERS_LAYOUT;
}
2020-08-13 10:11:40 -07:00
static async findForMarketAndOwner(
connection: Connection,
marketAddress: PublicKey,
ownerAddress: PublicKey,
) {
2020-08-12 11:45:42 -07:00
const filters = [
{
memcmp: {
offset: OPEN_ORDERS_LAYOUT.offsetOf('market'),
bytes: marketAddress.toBase58(),
},
},
{
memcmp: {
offset: OPEN_ORDERS_LAYOUT.offsetOf('owner'),
bytes: ownerAddress.toBase58(),
},
},
{
dataSize: OPEN_ORDERS_LAYOUT.span,
},
];
const accounts = await getFilteredProgramAccounts(
connection,
DEX_PROGRAM_ID,
filters,
);
return accounts.map(({ publicKey, accountInfo }) =>
OpenOrders.fromAccountInfo(publicKey, accountInfo),
);
}
2020-08-13 10:11:40 -07:00
static async load(connection: Connection, address: PublicKey) {
2020-08-12 11:45:42 -07:00
const accountInfo = await connection.getAccountInfo(address);
if (accountInfo === null) {
throw new Error('Open orders account not found');
}
2020-08-13 10:11:40 -07:00
return OpenOrders.fromAccountInfo(address, accountInfo);
2020-08-12 11:45:42 -07:00
}
2020-08-13 10:11:40 -07:00
static fromAccountInfo(address: PublicKey, accountInfo: AccountInfo<Buffer>) {
2020-08-12 11:45:42 -07:00
const { owner, data } = accountInfo;
if (!owner.equals(DEX_PROGRAM_ID)) {
throw new Error('Address not owned by program');
}
const decoded = OPEN_ORDERS_LAYOUT.decode(data);
if (!decoded.accountFlags.initialized || !decoded.accountFlags.openOrders) {
throw new Error('Invalid open orders account');
}
return new OpenOrders(address, decoded);
}
static async makeCreateAccountTransaction(
2020-08-13 10:11:40 -07:00
connection: Connection,
marketAddress: PublicKey,
ownerAddress: PublicKey,
newAccountAddress: PublicKey,
2020-08-12 11:45:42 -07:00
) {
return SystemProgram.createAccount({
fromPubkey: ownerAddress,
newAccountPubkey: newAccountAddress,
lamports: await connection.getMinimumBalanceForRentExemption(
OPEN_ORDERS_LAYOUT.span,
),
space: OPEN_ORDERS_LAYOUT.span,
programId: DEX_PROGRAM_ID,
});
}
get publicKey() {
return this.address;
}
}
2020-08-10 06:06:31 -07:00
2020-08-07 01:38:49 -07:00
export const ORDERBOOK_LAYOUT = struct([
blob(5),
2020-08-13 07:24:51 -07:00
accountFlagsLayout('accountFlags'),
2020-08-07 01:38:49 -07:00
SLAB_LAYOUT.replicate('slab'),
blob(7),
2020-08-07 01:38:49 -07:00
]);
2020-08-09 05:08:30 -07:00
export class Orderbook {
2020-08-13 10:11:40 -07:00
market: Market;
isBids: boolean;
slab: Slab;
constructor(market: Market, accountFlags, slab: Slab) {
2020-08-09 05:08:30 -07:00
if (!accountFlags.initialized || !(accountFlags.bids ^ accountFlags.asks)) {
throw new Error('Invalid orderbook');
}
this.market = market;
this.isBids = accountFlags.bids;
this.slab = slab;
}
static get LAYOUT() {
return ORDERBOOK_LAYOUT;
}
2020-08-13 10:11:40 -07:00
static decode(market: Market, buffer: Buffer) {
2020-08-09 05:08:30 -07:00
const { accountFlags, slab } = ORDERBOOK_LAYOUT.decode(buffer);
return new Orderbook(market, accountFlags, slab);
}
2020-08-13 10:11:40 -07:00
getL2(depth: number): [number, number, BN, BN][] {
2020-08-09 05:08:30 -07:00
const descending = this.isBids;
2020-08-13 10:11:40 -07:00
const levels: [BN, BN][] = []; // (price, size)
2020-08-10 06:06:31 -07:00
for (const { key, quantity } of this.slab.items(descending)) {
2020-08-09 05:08:30 -07:00
const price = getPriceFromKey(key);
2020-08-13 10:11:40 -07:00
if (levels.length > 0 && levels[levels.length - 1][0].eq(price)) {
2020-08-13 07:24:51 -07:00
levels[levels.length - 1][1].iadd(quantity);
2020-08-09 05:08:30 -07:00
} else if (levels.length === depth) {
break;
} else {
levels.push([price, quantity]);
}
}
2020-08-12 11:45:42 -07:00
return levels.map(([priceLots, sizeLots]) => [
this.market.priceLotsToNumber(priceLots),
2020-08-13 10:11:40 -07:00
this.market.baseSizeLotsToNumber(sizeLots),
2020-08-12 11:45:42 -07:00
priceLots,
sizeLots,
2020-08-09 05:08:30 -07:00
]);
}
2020-08-10 09:26:22 -07:00
*[Symbol.iterator](): Generator<Order> {
2020-08-10 09:26:22 -07:00
for (const { key, ownerSlot, owner, quantity } of this.slab) {
const price = getPriceFromKey(key);
yield {
orderId: key,
openOrdersAddress: owner,
openOrdersSlot: ownerSlot,
2020-08-12 11:45:42 -07:00
price: this.market.priceLotsToNumber(price),
priceLots: price,
size: this.market.baseSizeLotsToNumber(quantity),
sizeLots: quantity,
side: (this.isBids ? 'buy' : 'sell') as 'buy' | 'sell',
2020-08-10 09:26:22 -07:00
};
}
}
2020-08-09 05:08:30 -07:00
}
export interface Order {
orderId: BN;
openOrdersAddress: PublicKey;
openOrdersSlot: number;
price: number;
priceLots: BN;
size: number;
sizeLots: BN;
side: 'buy' | 'sell';
}
2020-08-09 05:08:30 -07:00
function getPriceFromKey(key) {
return key.ushrn(64);
}
2020-08-13 10:11:40 -07:00
function divideBnToNumber(numerator: BN, denominator: BN): number {
2020-08-09 05:08:30 -07:00
const quotient = numerator.div(denominator).toNumber();
const rem = numerator.umod(denominator);
const gcd = rem.gcd(denominator);
return quotient + rem.div(gcd).toNumber() / denominator.div(gcd).toNumber();
}
2020-08-10 09:26:22 -07:00
const MINT_LAYOUT = struct([blob(36), u8('decimals'), blob(3)]);
2020-08-13 10:11:40 -07:00
export async function getMintDecimals(
connection: Connection,
mint: PublicKey,
): Promise<number> {
const { data } = throwIfNull(
await connection.getAccountInfo(mint),
'mint not found',
);
2020-08-10 09:26:22 -07:00
const { decimals } = MINT_LAYOUT.decode(data);
return decimals;
}
2020-08-12 11:45:42 -07:00
2020-08-13 10:11:40 -07:00
async function getFilteredProgramAccounts(
connection: Connection,
programId: PublicKey,
filters,
): Promise<{ publicKey: PublicKey; accountInfo: AccountInfo<Buffer> }[]> {
// @ts-ignore
2020-08-12 11:45:42 -07:00
const resp = await connection._rpcRequest('getProgramAccounts', [
programId.toBase58(),
{
commitment: connection.commitment,
filters,
encoding: 'base64',
2020-08-12 11:45:42 -07:00
},
]);
if (resp.error) {
throw new Error(resp.error.message);
}
return resp.result.map(
({ pubkey, account: { data, executable, owner, lamports } }) => ({
publicKey: new PublicKey(pubkey),
accountInfo: {
data: Buffer.from(data[0], 'base64'),
2020-08-12 11:45:42 -07:00
executable,
owner: new PublicKey(owner),
lamports,
},
}),
);
}
2020-08-13 10:11:40 -07:00
function throwIfNull<T>(value: T | null, message = 'account not found'): T {
if (value === null) {
throw new Error(message);
}
return value;
}