feat: add parsed account data APIs
This commit is contained in:
committed by
Justin Starry
parent
b36e60738e
commit
c7a2fbe7eb
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user