diff --git a/web3.js/module.d.ts b/web3.js/module.d.ts index 7d4d6bb854..f49d189d66 100644 --- a/web3.js/module.d.ts +++ b/web3.js/module.d.ts @@ -48,6 +48,13 @@ declare module '@solana/web3.js' { export type Commitment = 'max' | 'recent' | 'root' | 'single'; + export type LargestAccountsFilter = 'circulating' | 'nonCirculating'; + + export type GetLargestAccountsConfig = { + commitment?: Commitment; + filter?: LargestAccountsFilter; + }; + export type SignatureStatusConfig = { searchTransactionHistory: boolean; }; @@ -180,6 +187,11 @@ declare module '@solana/web3.js' { nonCirculatingAccounts: Array; }; + export type AccountBalancePair = { + address: PublicKey; + lamports: number; + }; + export type VoteAccountStatus = { current: Array; delinquent: Array; @@ -208,6 +220,9 @@ declare module '@solana/web3.js' { getBlockTime(slot: number): Promise; getMinimumLedgerSlot(): Promise; getSupply(commitment?: Commitment): Promise>; + getLargestAccounts( + config?: GetLargestAccountsConfig, + ): Promise>>; getClusterNodes(): Promise>; getConfirmedBlock(slot: number): Promise; getConfirmedTransaction( diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index 9efe8726be..75ef51ec2e 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -61,6 +61,13 @@ declare module '@solana/web3.js' { declare export type Commitment = 'max' | 'recent' | 'root' | 'single'; + declare export type LargestAccountsFilter = 'circulating' | 'nonCirculating'; + + declare export type GetLargestAccountsConfig = { + commitment: ?Commitment, + filter: ?LargestAccountsFilter, + }; + declare export type SignatureStatusConfig = { searchTransactionHistory: boolean, }; @@ -193,6 +200,11 @@ declare module '@solana/web3.js' { nonCirculatingAccounts: Array, }; + declare export type AccountBalancePair = { + address: PublicKey, + lamports: number, + }; + declare export type VoteAccountStatus = { current: Array, delinquent: Array, @@ -221,6 +233,9 @@ declare module '@solana/web3.js' { getBlockTime(slot: number): Promise; getMinimumLedgerSlot(): Promise; getSupply(commitment: ?Commitment): Promise>; + getLargestAccounts( + config: ?GetLargestAccountsConfig, + ): Promise>>; getClusterNodes(): Promise>; getConfirmedBlock(slot: number): Promise; getConfirmedTransaction( diff --git a/web3.js/src/connection.js b/web3.js/src/connection.js index d0019e760b..21649dc934 100644 --- a/web3.js/src/connection.js +++ b/web3.js/src/connection.js @@ -100,6 +100,29 @@ function notificationResultAndContext(resultDescription: any) { */ export type Commitment = 'max' | 'recent' | 'root' | 'single'; +/** + * Filter for largest accounts query + *
+ *   'circulating':    Return the largest accounts that are part of the circulating supply
+ *   'nonCirculating': Return the largest accounts that are not part of the circulating supply
+ * 
+ * + * @typedef {'circulating' | 'nonCirculating'} LargestAccountsFilter + */ +export type LargestAccountsFilter = 'circulating' | 'nonCirculating'; + +/** + * Configuration object for changing `getLargestAccounts` query behavior + * + * @typedef {Object} GetLargestAccountsConfig + * @property {Commitment|undefined} commitment The level of commitment desired + * @property {LargestAccountsFilter|undefined} filter Filter largest accounts by whether they are part of the circulating supply + */ +type GetLargestAccountsConfig = { + commitment: ?Commitment, + filter: ?LargestAccountsFilter, +}; + /** * Configuration object for changing query behavior * @@ -431,6 +454,30 @@ const GetSupplyRpcResult = jsonRpcResultAndContext( }), ); +/** + * Pair of an account address and its balance + * + * @typedef {Object} AccountBalancePair + * @property {PublicKey} address + * @property {number} lamports + */ +type AccountBalancePair = { + address: PublicKey, + lamports: number, +}; + +/** + * Expected JSON RPC response for the "getLargestAccounts" message + */ +const GetLargestAccountsRpcResult = jsonRpcResultAndContext( + struct.array([ + struct({ + lamports: 'number', + address: 'string', + }), + ]), +); + /** * Expected JSON RPC response for the "getVersion" message */ @@ -1063,6 +1110,30 @@ export class Connection { return res.result; } + /** + * Fetch the 20 largest accounts with their current balances + */ + async getLargestAccounts( + config: ?GetLargestAccountsConfig, + ): Promise>> { + const arg = { + ...config, + commitment: (config && config.commitment) || this.commitment, + }; + const args = arg.filter || arg.commitment ? [arg] : []; + const unsafeRes = await this._rpcRequest('getLargestAccounts', args); + const res = GetLargestAccountsRpcResult(unsafeRes); + if (res.error) { + throw new Error('failed to get largest accounts: ' + res.error.message); + } + assert(typeof res.result !== 'undefined'); + res.result.value = res.result.value.map(({address, lamports}) => ({ + address: new PublicKey(address), + lamports, + })); + return res.result; + } + /** * Fetch all the account info for the specified public key, return with context */ diff --git a/web3.js/test/connection.test.js b/web3.js/test/connection.test.js index 55e8abd291..94cf459b9e 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -1141,6 +1141,33 @@ test('get supply', async () => { expect(supply.nonCirculatingAccounts.length).toBeGreaterThan(0); }); +test('get largest accounts', async () => { + const connection = new Connection(url); + + mockRpc.push([ + url, + { + method: 'getLargestAccounts', + params: [], + }, + { + error: null, + result: { + context: { + slot: 1, + }, + value: new Array(20).fill(0).map(() => ({ + address: new Account().publicKey.toBase58(), + lamports: 1000, + })), + }, + }, + ]); + + const largestAccounts = (await connection.getLargestAccounts()).value; + expect(largestAccounts.length).toEqual(20); +}); + test('getVersion', async () => { const connection = new Connection(url);