diff --git a/web3.js/module.d.ts b/web3.js/module.d.ts index 0c600331ff..03076b75bb 100644 --- a/web3.js/module.d.ts +++ b/web3.js/module.d.ts @@ -215,6 +215,13 @@ declare module '@solana/web3.js' { amount: string; }; + export type TokenAccountBalancePair = { + address: PublicKey; + amount: string; + decimals: number; + uiAmount: number; + }; + export type AccountChangeCallback = ( accountInfo: AccountInfo, context: Context, @@ -343,6 +350,10 @@ declare module '@solana/web3.js' { getLargestAccounts( config?: GetLargestAccountsConfig, ): Promise>>; + getTokenLargestAccounts( + mintAddress: PublicKey, + commitment?: Commitment, + ): Promise>>; getClusterNodes(): Promise>; getConfirmedBlock(slot: number): Promise; getConfirmedTransaction( diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index bd57b1c847..f936be1594 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -237,6 +237,13 @@ declare module '@solana/web3.js' { amount: string, }; + declare export type TokenAccountBalancePair = { + address: PublicKey, + amount: string, + decimals: number, + uiAmount: number, + }; + declare type AccountChangeCallback = ( accountInfo: AccountInfo, context: Context, @@ -356,6 +363,10 @@ declare module '@solana/web3.js' { getLargestAccounts( config: ?GetLargestAccountsConfig, ): Promise>>; + getTokenLargestAccounts( + mintAddress: PublicKey, + commitment: ?Commitment, + ): Promise>>; getClusterNodes(): Promise>; getConfirmedBlock(slot: number): Promise; getConfirmedTransaction( diff --git a/web3.js/src/connection.js b/web3.js/src/connection.js index 0a57263a2c..927e70ac0e 100644 --- a/web3.js/src/connection.js +++ b/web3.js/src/connection.js @@ -616,21 +616,60 @@ const GetSupplyRpcResult = jsonRpcResultAndContext( }), ); +/** + * Token amount object which returns a token amount in different formats + * for various client use cases. + * + * @typedef {Object} TokenAmount + * @property {string} amount Raw amount of tokens as string ignoring decimals + * @property {number} decimals Number of decimals configured for token's mint + * @property {number} uiAmount Token account as float, accounts for decimals + */ type TokenAmount = { amount: string, - decimals: 2, + decimals: number, uiAmount: number, }; /** * Expected JSON RPC structure for token amounts */ -const TokenAmountResult = struct({ +const TokenAmountResult = struct.object({ amount: 'string', uiAmount: 'number', decimals: 'number', }); +/** + * Token address and balance. + * + * @typedef {Object} TokenAccountBalancePair + * @property {PublicKey} address Address of the token account + * @property {string} amount Raw amount of tokens as string ignoring decimals + * @property {number} decimals Number of decimals configured for token's mint + * @property {number} uiAmount Token account as float, accounts for decimals + */ +type TokenAccountBalancePair = { + address: PublicKey, + amount: string, + decimals: number, + uiAmount: number, +}; + +/** + * Expected JSON RPC response for the "getTokenLargestAccounts" message + */ +const GetTokenLargestAccountsResult = jsonRpcResultAndContext( + struct.array([ + struct.pick({ + address: 'string', + amount: 'string', + uiAmount: 'number', + decimals: 'number', + }), + ]), +); + /** * Expected JSON RPC response for the "getTokenAccountBalance" message */ @@ -1653,6 +1692,30 @@ export class Connection { return res.result; } + /** + * Fetch the 20 largest token accounts with their current balances + * for a given mint. + */ + async getTokenLargestAccounts( + mintAddress: PublicKey, + commitment: ?Commitment, + ): Promise>> { + const args = this._buildArgs([mintAddress.toBase58()], commitment); + const unsafeRes = await this._rpcRequest('getTokenLargestAccounts', args); + const res = GetTokenLargestAccountsResult(unsafeRes); + if (res.error) { + throw new Error( + 'failed to get token largest accounts: ' + res.error.message, + ); + } + assert(typeof res.result !== 'undefined'); + res.result.value = res.result.value.map(pair => ({ + ...pair, + address: new PublicKey(pair.address), + })); + 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 382fdef41a..57f9ad2e1f 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -1399,6 +1399,23 @@ describe('token methods', () => { await expect(connection.getTokenSupply(newAccount)).rejects.toThrow(); }); + test('get token largest accounts', async () => { + const largestAccounts = ( + await connection.getTokenLargestAccounts(testToken.publicKey) + ).value; + + expect(largestAccounts.length).toEqual(2); + const largestAccount = largestAccounts[0]; + expect(largestAccount.address.equals(testTokenAccount)).toBe(true); + expect(largestAccount.amount).toEqual('11110'); + expect(largestAccount.decimals).toEqual(2); + expect(largestAccount.uiAmount).toEqual(111.1); + + await expect( + connection.getTokenLargestAccounts(newAccount), + ).rejects.toThrow(); + }); + test('get confirmed token transaction', async () => { const parsedTx = await connection.getParsedConfirmedTransaction( testSignature,