feat: add parsed account data APIs

This commit is contained in:
Justin Starry
2020-08-06 23:47:22 +08:00
committed by Justin Starry
parent b36e60738e
commit c7a2fbe7eb
5 changed files with 452 additions and 59 deletions

View File

@ -25,12 +25,12 @@ export const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
type RpcRequest = (methodName: string, args: Array<any>) => any;
type TokenAccountsFilter =
| {
| {|
mint: PublicKey,
}
| {
|}
| {|
programId: PublicKey,
};
|};
/**
* Extra contextual information for RPC responses
@ -634,13 +634,34 @@ const GetTokenSupplyRpcResult = jsonRpcResultAndContext(TokenAmountResult);
*/
const GetTokenAccountsByOwner = jsonRpcResultAndContext(
struct.array([
struct({
struct.object({
pubkey: 'string',
account: struct({
account: struct.object({
executable: 'boolean',
owner: 'string',
lamports: 'number',
data: 'any',
data: 'string',
rentEpoch: 'number?',
}),
}),
]),
);
/**
* Expected JSON RPC response for the "getTokenAccountsByOwner" message with parsed data
*/
const GetParsedTokenAccountsByOwner = jsonRpcResultAndContext(
struct.array([
struct.object({
pubkey: 'string',
account: struct.object({
executable: 'boolean',
owner: 'string',
lamports: 'number',
data: struct.object({
program: 'string',
parsed: 'any',
}),
rentEpoch: 'number?',
}),
}),
@ -692,6 +713,23 @@ const AccountInfoResult = struct({
rentEpoch: 'number?',
});
/**
* @private
*/
const ParsedAccountInfoResult = struct.object({
executable: 'boolean',
owner: 'string',
lamports: 'number',
data: struct.union([
'string',
struct.object({
program: 'string',
parsed: 'any',
}),
]),
rentEpoch: 'number?',
});
/**
* Expected JSON RPC response for the "getAccountInfo" message
*/
@ -699,6 +737,13 @@ const GetAccountInfoAndContextRpcResult = jsonRpcResultAndContext(
struct.union(['null', AccountInfoResult]),
);
/**
* Expected JSON RPC response for the "getAccountInfo" message with jsonParsed param
*/
const GetParsedAccountInfoResult = jsonRpcResultAndContext(
struct.union(['null', ParsedAccountInfoResult]),
);
/**
* Expected JSON RPC response for the "getConfirmedSignaturesForAddress" message
*/
@ -737,6 +782,14 @@ const ProgramAccountInfoResult = struct({
account: AccountInfoResult,
});
/**
* @private
*/
const ParsedProgramAccountInfoResult = struct({
pubkey: 'string',
account: ParsedAccountInfoResult,
});
/***
* Expected JSON RPC response for the "programNotification" message
*/
@ -785,6 +838,13 @@ const GetProgramAccountsRpcResult = jsonRpcResult(
struct.array([ProgramAccountInfoResult]),
);
/**
* Expected JSON RPC response for the "getProgramAccounts" message
*/
const GetParsedProgramAccountsRpcResult = jsonRpcResult(
struct.array([ParsedProgramAccountInfoResult]),
);
/**
* Expected JSON RPC response for the "getSlot" message
*/
@ -1051,20 +1111,32 @@ type SlotInfo = {
root: number,
};
/**
* Parsed account data
*
* @typedef {Object} ParsedAccountData
* @property {string} program Name of the program that owns this account
* @property {any} parsed Parsed account data
*/
type ParsedAccountData = {
program: string,
parsed: any,
};
/**
* Information describing an account
*
* @typedef {Object} AccountInfo
* @property {number} lamports Number of lamports assigned to the account
* @property {PublicKey} owner Identifier of the program that owns the account
* @property {?Buffer} data Optional data assigned to the account
* @property {T} data Optional data assigned to the account
* @property {boolean} executable `true` if this account's data contains a loaded program
*/
type AccountInfo = {
type AccountInfo<T> = {
executable: boolean,
owner: PublicKey,
lamports: number,
data: Buffer,
data: T,
};
/**
@ -1072,18 +1144,18 @@ type AccountInfo = {
*
* @typedef {Object} KeyedAccountInfo
* @property {PublicKey} accountId
* @property {AccountInfo} accountInfo
* @property {AccountInfo<Buffer>} accountInfo
*/
type KeyedAccountInfo = {
accountId: PublicKey,
accountInfo: AccountInfo,
accountInfo: AccountInfo<Buffer>,
};
/**
* Callback function for account change notifications
*/
export type AccountChangeCallback = (
accountInfo: AccountInfo,
accountInfo: AccountInfo<Buffer>,
context: Context,
) => void;
@ -1450,25 +1522,23 @@ export class Connection {
/**
* Fetch all the token accounts owned by the specified account
*
* @return {Promise<RpcResponseAndContext<Array<{pubkey: PublicKey, account: AccountInfo}>>>}
* @return {Promise<RpcResponseAndContext<Array<{pubkey: PublicKey, account: AccountInfo<Buffer>}>>>}
*/
async getTokenAccountsByOwner(
ownerAddress: PublicKey,
filter: TokenAccountsFilter,
commitment: ?Commitment,
): Promise<
RpcResponseAndContext<Array<{pubkey: PublicKey, account: AccountInfo}>>,
RpcResponseAndContext<
Array<{pubkey: PublicKey, account: AccountInfo<Buffer>}>,
>,
> {
let _args = [ownerAddress.toBase58()];
// Strip flow types to make flow happy
((filter: any) => {
if ('mint' in filter) {
_args.push({mint: filter.mint.toBase58()});
} else {
_args.push({programId: filter.programId.toBase58()});
}
})(filter);
if (filter.mint) {
_args.push({mint: filter.mint.toBase58()});
} else {
_args.push({programId: filter.programId.toBase58()});
}
const args = this._argsWithCommitment(_args, commitment);
const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args);
@ -1489,7 +1559,7 @@ export class Connection {
return {
context,
value: value.map(result => ({
pubkey: result.pubkey,
pubkey: new PublicKey(result.pubkey),
account: {
executable: result.account.executable,
owner: new PublicKey(result.account.owner),
@ -1500,6 +1570,57 @@ export class Connection {
};
}
/**
* Fetch parsed token accounts owned by the specified account
*
* @return {Promise<RpcResponseAndContext<Array<{pubkey: PublicKey, account: AccountInfo<ParsedAccountData>}>>>}
*/
async getParsedTokenAccountsByOwner(
ownerAddress: PublicKey,
filter: TokenAccountsFilter,
commitment: ?Commitment,
): Promise<
RpcResponseAndContext<
Array<{pubkey: PublicKey, account: AccountInfo<ParsedAccountData>}>,
>,
> {
let _args = [ownerAddress.toBase58()];
if (filter.mint) {
_args.push({mint: filter.mint.toBase58()});
} else {
_args.push({programId: filter.programId.toBase58()});
}
const args = this._argsWithCommitment(_args, commitment, 'jsonParsed');
const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args);
const res = GetParsedTokenAccountsByOwner(unsafeRes);
if (res.error) {
throw new Error(
'failed to get token accounts owned by account ' +
ownerAddress.toBase58() +
': ' +
res.error.message,
);
}
const {result} = res;
const {context, value} = result;
assert(typeof result !== 'undefined');
return {
context,
value: value.map(result => ({
pubkey: new PublicKey(result.pubkey),
account: {
executable: result.account.executable,
owner: new PublicKey(result.account.owner),
lamports: result.account.lamports,
data: result.account.data,
},
})),
};
}
/**
* Fetch the 20 largest accounts with their current balances
*/
@ -1530,7 +1651,7 @@ export class Connection {
async getAccountInfoAndContext(
publicKey: PublicKey,
commitment: ?Commitment,
): Promise<RpcResponseAndContext<AccountInfo | null>> {
): Promise<RpcResponseAndContext<AccountInfo<Buffer> | null>> {
const args = this._argsWithCommitment([publicKey.toBase58()], commitment);
const unsafeRes = await this._rpcRequest('getAccountInfo', args);
const res = GetAccountInfoAndContextRpcResult(unsafeRes);
@ -1563,13 +1684,64 @@ export class Connection {
};
}
/**
* Fetch parsed account info for the specified public key
*/
async getParsedAccountInfo(
publicKey: PublicKey,
commitment: ?Commitment,
): Promise<
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>,
> {
const args = this._argsWithCommitment(
[publicKey.toBase58()],
commitment,
'jsonParsed',
);
const unsafeRes = await this._rpcRequest('getAccountInfo', args);
const res = GetParsedAccountInfoResult(unsafeRes);
if (res.error) {
throw new Error(
'failed to get info about account ' +
publicKey.toBase58() +
': ' +
res.error.message,
);
}
assert(typeof res.result !== 'undefined');
let value = null;
if (res.result.value) {
const {executable, owner, lamports, data: resultData} = res.result.value;
let data = resultData;
if (!data.program) {
data = bs58.decode(data);
}
value = {
executable,
owner: new PublicKey(owner),
lamports,
data,
};
}
return {
context: {
slot: res.result.context.slot,
},
value,
};
}
/**
* Fetch all the account info for the specified public key
*/
async getAccountInfo(
publicKey: PublicKey,
commitment: ?Commitment,
): Promise<AccountInfo | null> {
): Promise<AccountInfo<Buffer> | null> {
return await this.getAccountInfoAndContext(publicKey, commitment)
.then(x => x.value)
.catch(e => {
@ -1582,12 +1754,12 @@ export class Connection {
/**
* Fetch all the accounts owned by the specified program id
*
* @return {Promise<Array<{pubkey: PublicKey, account: AccountInfo}>>}
* @return {Promise<Array<{pubkey: PublicKey, account: AccountInfo<Buffer>}>>}
*/
async getProgramAccounts(
programId: PublicKey,
commitment: ?Commitment,
): Promise<Array<{pubkey: PublicKey, account: AccountInfo}>> {
): Promise<Array<{pubkey: PublicKey, account: AccountInfo<Buffer>}>> {
const args = this._argsWithCommitment([programId.toBase58()], commitment);
const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
const res = GetProgramAccountsRpcResult(unsafeRes);
@ -1605,7 +1777,7 @@ export class Connection {
return result.map(result => {
return {
pubkey: result.pubkey,
pubkey: new PublicKey(result.pubkey),
account: {
executable: result.account.executable,
owner: new PublicKey(result.account.owner),
@ -1616,6 +1788,59 @@ export class Connection {
});
}
/**
* Fetch and parse all the accounts owned by the specified program id
*
* @return {Promise<Array<{pubkey: PublicKey, account: AccountInfo<Buffer | ParsedAccountData>}>>}
*/
async getParsedProgramAccounts(
programId: PublicKey,
commitment: ?Commitment,
): Promise<
Array<{
pubkey: PublicKey,
account: AccountInfo<Buffer | ParsedAccountData>,
}>,
> {
const args = this._argsWithCommitment(
[programId.toBase58()],
commitment,
'jsonParsed',
);
const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
const res = GetParsedProgramAccountsRpcResult(unsafeRes);
if (res.error) {
throw new Error(
'failed to get accounts owned by program ' +
programId.toBase58() +
': ' +
res.error.message,
);
}
const {result} = res;
assert(typeof result !== 'undefined');
return result.map(result => {
const resultData = result.account.data;
let data = resultData;
if (!data.program) {
data = bs58.decode(data);
}
return {
pubkey: new PublicKey(result.pubkey),
account: {
executable: result.account.executable,
owner: new PublicKey(result.account.owner),
lamports: result.account.lamports,
data,
},
};
});
}
/**
* Confirm the transaction identified by the specified signature
*/
@ -2625,10 +2850,21 @@ export class Connection {
}
}
_argsWithCommitment(args: Array<any>, override: ?Commitment): Array<any> {
_argsWithCommitment(
args: Array<any>,
override: ?Commitment,
encoding?: 'jsonParsed',
): Array<any> {
const commitment = override || this._commitment;
if (commitment) {
args.push({commitment});
if (commitment || encoding) {
let options: any = {};
if (encoding) {
options.encoding = encoding;
}
if (commitment) {
options.commitment = commitment;
}
args.push(options);
}
return args;
}