diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index 1daf312b55..ca11f303cd 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -87,6 +87,9 @@ declare module '@solana/web3.js' { declare export class Connection { constructor(endpoint: string): Connection; getAccountInfo(publicKey: PublicKey): Promise; + getProgramAccounts( + programId: PublicKey, + ): Promise>; getBalance(publicKey: PublicKey): Promise; getClusterNodes(): Promise>; getEpochVoteAccounts(): Promise>; diff --git a/web3.js/package-lock.json b/web3.js/package-lock.json index 28e65aecba..9efb5e838f 100644 --- a/web3.js/package-lock.json +++ b/web3.js/package-lock.json @@ -6561,7 +6561,7 @@ "dependencies": { "marked": { "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, @@ -6825,7 +6825,7 @@ }, "marked": { "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, @@ -13022,7 +13022,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -17988,7 +17988,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } diff --git a/web3.js/package.json b/web3.js/package.json index a1d0dae695..03de34aebe 100644 --- a/web3.js/package.json +++ b/web3.js/package.json @@ -55,7 +55,7 @@ "localnet:logs": "bin/localnet.sh logs -f", "localnet:up": "bin/localnet.sh up $npm_package_testnetDefaultChannel", "localnet:update": "bin/localnet.sh update $npm_package_testnetDefaultChannel", - "ok": "run-s --serial lint flow test doc", + "ok": "run-s lint flow test doc", "prepare": "run-s clean bpf-sdk:install bpf-sdk:remove-symlinks build", "pretty": "prettier --write '{,{examples,src,test}/**/}*.js'", "re": "semantic-release --repository-url git@github.com:solana-labs/solana-web3.js.git", diff --git a/web3.js/src/connection.js b/web3.js/src/connection.js index 70ede99e9a..f8b2eff186 100644 --- a/web3.js/src/connection.js +++ b/web3.js/src/connection.js @@ -148,6 +148,13 @@ const ProgramAccountNotificationResult = struct({ result: ProgramAccountInfoResult, }); +/** + * Expected JSON RPC response for the "getProgramAccounts" message + */ +const GetProgramAccountsRpcResult = jsonRpcResult( + struct.list([ProgramAccountInfoResult]), +); + /** * Expected JSON RPC response for the "confirmTransaction" message */ @@ -338,6 +345,11 @@ export type TransactionError = {| */ type BlockhashAndFeeCalculator = [Blockhash, FeeCalculator]; // This type exists to workaround an esdoc parse error +/** + * @ignore + */ +type PublicKeyAndAccount = [PublicKey, AccountInfo]; // This type exists to workaround an esdoc parse error + /** * A connection to a fullnode JSON RPC endpoint */ @@ -435,6 +447,36 @@ export class Connection { }; } + /** + * Fetch all the accounts owned by the specified program id + */ + async getProgramAccounts( + programId: PublicKey, + ): Promise> { + const unsafeRes = await this._rpcRequest('getProgramAccounts', [ + programId.toBase58(), + ]); + const res = GetProgramAccountsRpcResult(unsafeRes); + if (res.error) { + throw new Error(res.error.message); + } + + const {result} = res; + assert(typeof result !== 'undefined'); + + return result.map(result => { + return [ + result[0], + { + executable: result[1].executable, + owner: new PublicKey(result[1].owner), + lamports: result[1].lamports, + data: Buffer.from(result[1].data), + }, + ]; + }); + } + /** * Confirm the transaction identified by the specified signature */ diff --git a/web3.js/test/connection.test.js b/web3.js/test/connection.test.js index b4abea830d..d480cd7d2f 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -1,5 +1,12 @@ // @flow -import {Account, Connection, BpfLoader, Loader, SystemProgram} from '../src'; +import { + Account, + Connection, + BpfLoader, + Loader, + SystemProgram, + sendAndConfirmTransaction, +} from '../src'; import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing'; import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch'; import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash'; @@ -37,6 +44,48 @@ test('get account info - error', () => { ); }); +test('get program accounts', async () => { + if (mockRpcEnabled) { + console.log('non-live test skipped'); + return; + } + + const connection = new Connection(url); + const account0 = new Account(); + const account1 = new Account(); + const programId = new Account(); + await connection.requestAirdrop(account0.publicKey, 42); + await connection.requestAirdrop(account1.publicKey, 84); + + let transaction = SystemProgram.assign( + account0.publicKey, + programId.publicKey, + ); + await sendAndConfirmTransaction(connection, transaction, account0); + + transaction = SystemProgram.assign(account1.publicKey, programId.publicKey); + await sendAndConfirmTransaction(connection, transaction, account1); + + const [, feeCalculator] = await connection.getRecentBlockhash(); + + const programAccounts = await connection.getProgramAccounts( + programId.publicKey, + ); + expect(programAccounts.length).toBe(2); + + programAccounts.forEach(function(element) { + expect([ + account0.publicKey.toBase58(), + account1.publicKey.toBase58(), + ]).toEqual(expect.arrayContaining([element[0]])); + if (element[0] == account0.publicKey) { + expect(element[1].lamports).toBe(42 - feeCalculator.lamportsPerSignature); + } else { + expect(element[1].lamports).toBe(84 - feeCalculator.lamportsPerSignature); + } + }); +}); + test('fullnodeExit', async () => { if (!mockRpcEnabled) { console.log('fullnodeExit skipped on live node');