diff --git a/packages/rpc-graphql/src/__tests__/account-test.ts b/packages/rpc-graphql/src/__tests__/account-test.ts index cccba11a1340..f16fd43a48fa 100644 --- a/packages/rpc-graphql/src/__tests__/account-test.ts +++ b/packages/rpc-graphql/src/__tests__/account-test.ts @@ -220,28 +220,23 @@ describe('account', () => { }); }); describe('account data queries', () => { + // See scripts/fixtures/gpa1.json + const address = 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk'; it('can get account data as base58', async () => { expect.assertions(1); - // See scripts/fixtures/gpa1.json - const variableValues = { - address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk', - encoding: 'BASE_58', - }; const source = /* GraphQL */ ` - query testQuery($address: Address!, $encoding: AccountEncoding) { - account(address: $address, encoding: $encoding) { + query testQuery($address: Address!) { + account(address: $address) { address - ... on AccountBase58 { - data - } + data(encoding: BASE_58) } } `; - const result = await rpcGraphQL.query(source, variableValues); + const result = await rpcGraphQL.query(source, { address }); expect(result).toMatchObject({ data: { account: { - address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk', + address, data: '2Uw1bpnsXxu3e', }, }, @@ -249,26 +244,19 @@ describe('account', () => { }); it('can get account data as base64', async () => { expect.assertions(1); - // See scripts/fixtures/gpa1.json - const variableValues = { - address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk', - encoding: 'BASE_64', - }; const source = /* GraphQL */ ` - query testQuery($address: Address!, $encoding: AccountEncoding) { - account(address: $address, encoding: $encoding) { + query testQuery($address: Address!) { + account(address: $address) { address - ... on AccountBase64 { - data - } + data(encoding: BASE_64) } } `; - const result = await rpcGraphQL.query(source, variableValues); + const result = await rpcGraphQL.query(source, { address }); expect(result).toMatchObject({ data: { account: { - address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk', + address, data: 'dGVzdCBkYXRh', }, }, @@ -276,75 +264,50 @@ describe('account', () => { }); it('can get account data as base64+zstd', async () => { expect.assertions(1); - // See scripts/fixtures/gpa1.json - const variableValues = { - address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk', - encoding: 'BASE_64_ZSTD', - }; const source = /* GraphQL */ ` - query testQuery($address: Address!, $encoding: AccountEncoding) { - account(address: $address, encoding: $encoding) { + query testQuery($address: Address!) { + account(address: $address) { address - ... on AccountBase64Zstd { - data - } + data(encoding: BASE_64_ZSTD) } } `; - const result = await rpcGraphQL.query(source, variableValues); + const result = await rpcGraphQL.query(source, { address }); expect(result).toMatchObject({ data: { account: { - address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk', + address, data: 'KLUv/QBYSQAAdGVzdCBkYXRh', }, }, }); }); - it('can get account data as jsonParsed', async () => { - expect.assertions(2); + it('can get account data with multiple encodings', async () => { + expect.assertions(1); const source = /* GraphQL */ ` query testQuery($address: Address!) { - account(address: $address, encoding: PARSED) { - ... on AccountBase64 { - data - } - ... on MintAccount { - supply - } + account(address: $address) { + address + dataBase58: data(encoding: BASE_58) + dataBase64: data(encoding: BASE_64) + dataBase64Zstd: data(encoding: BASE_64_ZSTD) } } `; - const resultParsed = await rpcGraphQL.query(source, { - // See scripts/fixtures/spl-token-mint-account.json - address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', - }); - expect(resultParsed).toMatchObject({ - data: { - account: { - supply: expect.any(String), - }, - }, - }); - // Defaults to base64 if can't be parsed - const resultBase64 = await rpcGraphQL.query(source, { - // See scripts/fixtures/gpa1.json - address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk', - }); - expect(resultBase64).toMatchObject({ + const result = await rpcGraphQL.query(source, { address }); + expect(result).toMatchObject({ data: { account: { - data: 'dGVzdCBkYXRh', + address, + dataBase58: '2Uw1bpnsXxu3e', + dataBase64: 'dGVzdCBkYXRh', + dataBase64Zstd: 'KLUv/QBYSQAAdGVzdCBkYXRh', }, }, }); }); - it('defaults to jsonParsed if possible', async () => { + it('can get account data as jsonParsed', async () => { expect.assertions(1); - // See scripts/fixtures/spl-token-mint-account.json - const variableValues = { - address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', - }; const source = /* GraphQL */ ` query testQuery($address: Address!) { account(address: $address) { @@ -354,8 +317,11 @@ describe('account', () => { } } `; - const result = await rpcGraphQL.query(source, variableValues); - expect(result).toMatchObject({ + const resultParsed = await rpcGraphQL.query(source, { + // See scripts/fixtures/spl-token-mint-account.json + address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', + }); + expect(resultParsed).toMatchObject({ data: { account: { supply: expect.any(String), diff --git a/packages/rpc-graphql/src/__tests__/program-accounts-test.ts b/packages/rpc-graphql/src/__tests__/program-accounts-test.ts index f0bf42e21616..62f508af57f1 100644 --- a/packages/rpc-graphql/src/__tests__/program-accounts-test.ts +++ b/packages/rpc-graphql/src/__tests__/program-accounts-test.ts @@ -87,13 +87,12 @@ describe('programAccounts', () => { programAddress: 'AmtpVzo6H6qQCP9dH9wfu5hfa8kKaAFpTJ4aamPYR6V6', }; const source = /* GraphQL */ ` - query testQuery($programAddress: Address!, $commitment: Commitment, $encoding: AccountEncoding) { + query testQuery($programAddress: Address!, $commitment: Commitment, $encoding: AccountEncoding!) { + # TODO: Program accounts needs refactor programAccounts(programAddress: $programAddress, commitment: $commitment, encoding: $encoding) { address executable - ... on AccountBase58 { - data - } + data(encoding: $encoding) } } `; @@ -124,13 +123,12 @@ describe('programAccounts', () => { programAddress: 'AmtpVzo6H6qQCP9dH9wfu5hfa8kKaAFpTJ4aamPYR6V6', }; const source = /* GraphQL */ ` - query testQuery($programAddress: Address!, $commitment: Commitment, $encoding: AccountEncoding) { + query testQuery($programAddress: Address!, $commitment: Commitment, $encoding: AccountEncoding!) { + # TODO: Program accounts needs refactor programAccounts(programAddress: $programAddress, commitment: $commitment, encoding: $encoding) { address executable - ... on AccountBase64 { - data - } + data(encoding: $encoding) } } `; @@ -161,13 +159,12 @@ describe('programAccounts', () => { programAddress: 'AmtpVzo6H6qQCP9dH9wfu5hfa8kKaAFpTJ4aamPYR6V6', }; const source = /* GraphQL */ ` - query testQuery($programAddress: Address!, $commitment: Commitment, $encoding: AccountEncoding) { + query testQuery($programAddress: Address!, $commitment: Commitment, $encoding: AccountEncoding!) { + # TODO: Program accounts needs refactor programAccounts(programAddress: $programAddress, commitment: $commitment, encoding: $encoding) { address executable - ... on AccountBase64Zstd { - data - } + data(encoding: $encoding) } } `; @@ -574,17 +571,16 @@ describe('programAccounts', () => { $programAddress: Address! $commitment: Commitment $dataSlice: DataSlice - $encoding: AccountEncoding + $encoding: AccountEncoding! ) { + # TODO: Program accounts needs refactor programAccounts( programAddress: $programAddress commitment: $commitment dataSlice: $dataSlice encoding: $encoding ) { - ... on AccountBase58 { - data - } + data(encoding: $encoding, dataSlice: $dataSlice) } } `; @@ -618,17 +614,16 @@ describe('programAccounts', () => { $programAddress: Address! $commitment: Commitment $dataSlice: DataSlice - $encoding: AccountEncoding + $encoding: AccountEncoding! ) { + # TODO: Program accounts needs refactor programAccounts( programAddress: $programAddress commitment: $commitment dataSlice: $dataSlice encoding: $encoding ) { - ... on AccountBase64 { - data - } + data(dataSlice: $dataSlice, encoding: $encoding) } } `; diff --git a/packages/rpc-graphql/src/loaders/account.ts b/packages/rpc-graphql/src/loaders/account.ts index 6236386ccd3d..1ce4868f9f84 100644 --- a/packages/rpc-graphql/src/loaders/account.ts +++ b/packages/rpc-graphql/src/loaders/account.ts @@ -7,7 +7,7 @@ function applyDefaultArgs({ address, commitment, dataSlice, - encoding = 'jsonParsed', + encoding = 'base64', // Fixed by batch loader minContextSlot, }: AccountLoaderArgs): AccountLoaderArgs { return { diff --git a/packages/rpc-graphql/src/loaders/index.ts b/packages/rpc-graphql/src/loaders/index.ts index 32d08935d33d..2af2a0b97f7e 100644 --- a/packages/rpc-graphql/src/loaders/index.ts +++ b/packages/rpc-graphql/src/loaders/index.ts @@ -1,5 +1,5 @@ export { createAccountLoader } from './account'; export { createBlockLoader } from './block'; -export type * from './loader'; +export * from './loader'; export { createProgramAccountsLoader } from './program-accounts'; export { createTransactionLoader } from './transaction'; diff --git a/packages/rpc-graphql/src/resolvers/__tests__/account-resolver-test.ts b/packages/rpc-graphql/src/resolvers/__tests__/account-resolver-test.ts index f35021997349..1eb801a35dc5 100644 --- a/packages/rpc-graphql/src/resolvers/__tests__/account-resolver-test.ts +++ b/packages/rpc-graphql/src/resolvers/__tests__/account-resolver-test.ts @@ -57,6 +57,41 @@ describe('account resolver', () => { jest.runAllTimers(); expect(rpc.getAccountInfo).not.toHaveBeenCalled(); }); + it('will not call the RPC for only an address in an inline fragment', async () => { + expect.assertions(1); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ... on MintAccount { + address + } + } + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).not.toHaveBeenCalled(); + }); + it('will not call the RPC for only an address in a fragment spread', async () => { + expect.assertions(1); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ...GetAddress + } + } + fragment GetAddress { + address + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).not.toHaveBeenCalled(); + }); }); describe('in the second level', () => { it('will not call the RPC for only an address', async () => { @@ -77,6 +112,47 @@ describe('account resolver', () => { jest.runAllTimers(); expect(rpc.getAccountInfo).toHaveBeenCalledTimes(1); }); + it('will not call the RPC for only an address in an inline fragment', async () => { + expect.assertions(1); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + address + ownerProgram { + ... on MintAccount { + address + } + } + } + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(1); + }); + it('will not call the RPC for only an address in a fragment spread', async () => { + expect.assertions(1); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + address + ownerProgram { + ...GetAddress + } + } + } + fragment GetAddress { + address + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).not.toHaveBeenCalled(); + }); }); describe('in the third level', () => { it('will not call the RPC for only an address', async () => { @@ -102,6 +178,250 @@ describe('account resolver', () => { jest.runAllTimers(); expect(rpc.getAccountInfo).toHaveBeenCalledTimes(2); }); + it('will not call the RPC for only an address in an inline fragment', async () => { + expect.assertions(1); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + address + ownerProgram { + address + ownerProgram { + ... on MintAccount { + address + } + } + } + } + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(1); + }); + it('will not call the RPC for only an address in a fragment spread', async () => { + expect.assertions(1); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + address + ownerProgram { + address + ownerProgram { + ...GetAddress + } + } + } + } + fragment GetAddress { + address + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).not.toHaveBeenCalled(); + }); + }); + }); + describe('inline fragments', () => { + it('will resolve inline fragments with `jsonParsed` when `jsonParsed` fields are requested', async () => { + expect.assertions(2); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ... on MintAccount { + supply + } + } + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(2); // Fixed with batch loader + expect(rpc.getAccountInfo).toHaveBeenLastCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'jsonParsed', + }); + }); + it('will resolve inline fragments with `jsonParsed` and the proper encoding when data and `jsonParsed` fields are requested', async () => { + expect.assertions(3); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ... on MintAccount { + data(encoding: BASE_58) + supply + } + } + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(3); // Fixed with batch loader + expect(rpc.getAccountInfo).toHaveBeenCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'jsonParsed', + }); + expect(rpc.getAccountInfo).toHaveBeenCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'base58', + }); + }); + it('will resolve inline fragments with only the proper encoding not `jsonParsed` when no `jsonParsed` fields are requested', async () => { + expect.assertions(3); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ... on MintAccount { + data(encoding: BASE_58) + lamports + space + } + } + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(2); // Fixed with batch loader + expect(rpc.getAccountInfo).toHaveBeenCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'base58', + }); + expect(rpc.getAccountInfo).not.toHaveBeenCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'jsonParsed', + }); + }); + }); + describe('fragment spreads', () => { + it('will resolve fields from fragment spreads', async () => { + expect.assertions(2); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ...GetLamports + } + } + fragment GetLamports on Account { + lamports + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(1); + expect(rpc.getAccountInfo).toHaveBeenLastCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'base64', + }); + }); + it('will resolve fields from multiple fragment spreads', async () => { + expect.assertions(2); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ...GetLamports + ...GetDataBase64 + } + } + fragment GetLamports on Account { + lamports + } + fragment GetDataBase64 on Account { + data(encoding: BASE_64_ZSTD) + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(2); // Fixed with batch loader + expect(rpc.getAccountInfo).toHaveBeenLastCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'base64+zstd', + }); + }); + it('will resolve fragment spreads with `jsonParsed` when `jsonParsed` fields are requested', async () => { + expect.assertions(2); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ...GetLamportsAndSupplyFromMintAccount + } + } + fragment GetLamportsAndSupplyFromMintAccount on Account { + ... on MintAccount { + lamports + supply + } + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(2); // Fixed with batch loader + expect(rpc.getAccountInfo).toHaveBeenLastCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'jsonParsed', + }); + }); + it('will resolve fragment spreads with `jsonParsed` and the proper encoding when data and `jsonParsed` fields are requested', async () => { + expect.assertions(3); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ...GetDataAndSupplyFromMintAccount + } + } + fragment GetDataAndSupplyFromMintAccount on Account { + ... on MintAccount { + data(encoding: BASE_58) + supply + } + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(3); // Fixed with batch loader + expect(rpc.getAccountInfo).toHaveBeenCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'jsonParsed', + }); + expect(rpc.getAccountInfo).toHaveBeenCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'base58', + }); + }); + it('will resolve fragment spreads with only the proper encoding not `jsonParsed` when no `jsonParsed` fields are requested', async () => { + expect.assertions(3); + const source = /* GraphQL */ ` + query testQuery { + account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") { + ...GetLamportsAndDataFromMintAccount + } + } + fragment GetLamportsAndDataFromMintAccount on Account { + ... on MintAccount { + lamports + data(encoding: BASE_64_ZSTD) + } + } + `; + rpcGraphQL.query(source); + // FIXME: Prefer async version of this timer runner. See https://github.com/jestjs/jest/issues/14549 + await Promise.resolve(); + jest.runAllTimers(); + expect(rpc.getAccountInfo).toHaveBeenCalledTimes(2); // Fixed with batch loader + expect(rpc.getAccountInfo).toHaveBeenLastCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'base64+zstd', + }); + expect(rpc.getAccountInfo).not.toHaveBeenCalledWith('AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca', { + encoding: 'jsonParsed', + }); }); }); }); diff --git a/packages/rpc-graphql/src/resolvers/account.ts b/packages/rpc-graphql/src/resolvers/account.ts index f299f8a62148..e2966d72f7cd 100644 --- a/packages/rpc-graphql/src/resolvers/account.ts +++ b/packages/rpc-graphql/src/resolvers/account.ts @@ -1,141 +1,179 @@ import { Address } from '@solana/addresses'; +import { Commitment, Slot } from '@solana/rpc-types'; import { GraphQLResolveInfo } from 'graphql'; import { RpcGraphQLContext } from '../context'; -import { AccountLoaderArgs } from '../loaders'; -import { onlyPresentFieldRequested } from './resolve-info'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function transformParsedAccountData(parsedAccountData: any) { - const { - parsed: { info: result, type: accountType }, - program: programName, - programId, - } = parsedAccountData; - // Tells GraphQL which account type has been - // returned by the RPC. - result.accountType = accountType; - result.programId = programId; - // Tells GraphQL which program the returned - // account belongs to. - result.programName = programName; - return result; +import { AccountLoaderValue, cacheKeyFn } from '../loaders'; +import { buildAccountLoaderArgSetFromResolveInfo, onlyFieldsRequested } from './resolve-info'; + +type Encoding = 'base58' | 'base64' | 'base64+zstd'; +type DataSlice = { length: number; offset: number }; + +export type EncodedAccountData = { + [key: string]: string; } -export function transformLoadedAccount({ - account, - address, - encoding = 'jsonParsed', -}: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - account: any; +export type AccountResult = { address: Address; - encoding: AccountLoaderArgs['encoding']; -}) { - const [ - // The account's data, either encoded or parsed. - data, - // Tells GraphQL which encoding has been returned - // by the RPC. - responseEncoding, - ] = Array.isArray(account.data) - ? encoding === 'jsonParsed' - ? // The requested encoding is jsonParsed, - // but the data could not be parsed. - // Defaults to base64 encoding. - [{ data: account.data[0] }, 'base64'] - : // The requested encoding is base58, - // base64, or base64+zstd. - [{ data: account.data[0] }, encoding] - : // The account data was returned as an object, - // so it was parsed successfully. - [transformParsedAccountData(account.data), 'jsonParsed']; - account.address = address; - account.encoding = responseEncoding; - account.ownerProgram = account.owner; - return { - ...account, - ...data, + ownerProgram?: Address; + encodedData?: EncodedAccountData; + jsonParsedConfigs?: { + accountType: string; + programId: Address; + programName: string; }; -} +} & Partial>; + +const resolveAccountData = () => { + return ( + parent: AccountResult | null, + args: { + encoding: Encoding; + dataSlice?: DataSlice; + }, + ) => { + return parent === null + ? null + : parent.encodedData + ? parent.encodedData[cacheKeyFn(args)] + : null; + }; +}; export const resolveAccount = (fieldName?: string) => { return async ( parent: { [x: string]: Address }, - args: AccountLoaderArgs, + args: { address?: Address; commitment?: Commitment; minContextSlot?: Slot }, context: RpcGraphQLContext, - info: GraphQLResolveInfo | undefined, - ) => { + info: GraphQLResolveInfo, + ): Promise => { const address = fieldName ? parent[fieldName] : args.address; - if (!address) { - return null; - } - if (onlyPresentFieldRequested('address', info)) { - return { address }; - } - const account = await context.loaders.account.load({ ...args, address }); - return account === null ? { address } : transformLoadedAccount({ account, address, encoding: args.encoding }); - }; -}; -export const accountResolvers = { - Account: { - __resolveType(account: { encoding: string; programName: string; accountType: string }) { - if (account.encoding === 'base58') { - return 'AccountBase58'; + if (address) { + // Do not load any accounts if only the address is requested + if (onlyFieldsRequested(['address'], info)) { + return { address }; } - if (account.encoding === 'base64') { - return 'AccountBase64'; - } - if (account.encoding === 'base64+zstd') { - return 'AccountBase64Zstd'; - } - if (account.encoding === 'jsonParsed') { - if (account.programName === 'nonce') { - return 'NonceAccount'; - } - if (account.accountType === 'mint' && account.programName === 'spl-token') { - return 'MintAccount'; - } - if (account.accountType === 'account' && account.programName === 'spl-token') { - return 'TokenAccount'; + + const argsSet = buildAccountLoaderArgSetFromResolveInfo({ ...args, address }, info); + const loadedAccounts = await context.loaders.account.loadMany(argsSet); + + let result: AccountResult = { + address, + encodedData: {}, + }; + + loadedAccounts.forEach((account, i) => { + if (account instanceof Error) { + console.error(account); + return; } - if (account.programName === 'stake') { - return 'StakeAccount'; + if (account === null) { + return; } - if (account.accountType === 'vote' && account.programName === 'vote') { - return 'VoteAccount'; + if (!result.ownerProgram) { + result = { + ...result, + ...account, + ownerProgram: account.owner, + }; } - if (account.accountType === 'lookupTable' && account.programName === 'address-lookup-table') { - return 'LookupTableAccount'; + + const { data } = account; + const { encoding, dataSlice } = argsSet[i]; + + if (encoding && result.encodedData) { + if (Array.isArray(data)) { + result.encodedData[cacheKeyFn({ + dataSlice, + encoding: encoding === 'jsonParsed' ? 'base64' : encoding, + })] = data[0]; + } else if (typeof data === 'string') { + result.encodedData[cacheKeyFn({ + dataSlice, + encoding: 'base58', + })] = data; + } else if (typeof data === 'object') { + const { + parsed: { info: parsedData, type: accountType }, + program: programName, + programId, + } = data; + result.jsonParsedConfigs = { + accountType, + programId, + programName, + }; + result = { + ...result, + ...(parsedData as object), + }; + } } - } - return 'AccountBase64'; - }, - }, - AccountBase58: { - ownerProgram: resolveAccount('ownerProgram'), - }, - AccountBase64: { - ownerProgram: resolveAccount('ownerProgram'), + }); + + return result; + } + + return null; + }; +}; + +function resolveAccountType(accountResult: AccountResult) { + const { jsonParsedConfigs } = accountResult; + if (jsonParsedConfigs) { + if (jsonParsedConfigs.programName === 'nonce') { + return 'NonceAccount'; + } + if (jsonParsedConfigs.accountType === 'mint' && jsonParsedConfigs.programName === 'spl-token') { + return 'MintAccount'; + } + if (jsonParsedConfigs.accountType === 'account' && jsonParsedConfigs.programName === 'spl-token') { + return 'TokenAccount'; + } + if (jsonParsedConfigs.programName === 'stake') { + return 'StakeAccount'; + } + if (jsonParsedConfigs.accountType === 'vote' && jsonParsedConfigs.programName === 'vote') { + return 'VoteAccount'; + } + if ( + jsonParsedConfigs.accountType === 'lookupTable' && + jsonParsedConfigs.programName === 'address-lookup-table' + ) { + return 'LookupTableAccount'; + } + } + return 'GenericAccount'; +} + +export const accountResolvers = { + Account: { + __resolveType: resolveAccountType, + data: resolveAccountData(), }, - AccountBase64Zstd: { + GenericAccount: { + data: resolveAccountData(), ownerProgram: resolveAccount('ownerProgram'), }, LookupTableAccount: { authority: resolveAccount('authority'), + data: resolveAccountData(), ownerProgram: resolveAccount('ownerProgram'), }, MintAccount: { + data: resolveAccountData(), freezeAuthority: resolveAccount('freezeAuthority'), mintAuthority: resolveAccount('mintAuthority'), ownerProgram: resolveAccount('ownerProgram'), }, NonceAccount: { authority: resolveAccount('authority'), + data: resolveAccountData(), ownerProgram: resolveAccount('ownerProgram'), }, StakeAccount: { + data: resolveAccountData(), ownerProgram: resolveAccount('ownerProgram'), }, StakeAccountDataMetaAuthorized: { @@ -149,12 +187,14 @@ export const accountResolvers = { voter: resolveAccount('voter'), }, TokenAccount: { + data: resolveAccountData(), mint: resolveAccount('mint'), owner: resolveAccount('owner'), ownerProgram: resolveAccount('ownerProgram'), }, VoteAccount: { authorizedWithdrawer: resolveAccount('authorizedWithdrawer'), + data: resolveAccountData(), node: resolveAccount('nodePubkey'), ownerProgram: resolveAccount('ownerProgram'), }, diff --git a/packages/rpc-graphql/src/resolvers/block.ts b/packages/rpc-graphql/src/resolvers/block.ts index bca6ef3e3f3b..3714636d726f 100644 --- a/packages/rpc-graphql/src/resolvers/block.ts +++ b/packages/rpc-graphql/src/resolvers/block.ts @@ -3,7 +3,7 @@ import { GraphQLResolveInfo } from 'graphql'; import { RpcGraphQLContext } from '../context'; import { BlockLoaderArgs } from '../loaders'; -import { onlyPresentFieldRequested } from './resolve-info'; +import { onlyFieldsRequested } from './resolve-info'; import { transformLoadedTransaction } from './transaction'; export function transformLoadedBlock({ @@ -41,13 +41,13 @@ export const resolveBlock = (fieldName?: string) => { parent: { [x: string]: Slot }, args: BlockLoaderArgs, context: RpcGraphQLContext, - info: GraphQLResolveInfo | undefined, + info: GraphQLResolveInfo, ) => { const slot = fieldName ? parent[fieldName] : args.slot; if (!slot) { return null; } - if (onlyPresentFieldRequested('slot', info)) { + if (onlyFieldsRequested(['slot'], info)) { return { slot }; } const block = await context.loaders.block.load({ ...args, slot }); diff --git a/packages/rpc-graphql/src/resolvers/program-accounts.ts b/packages/rpc-graphql/src/resolvers/program-accounts.ts index 7ca3f7cd8904..bfd233e06901 100644 --- a/packages/rpc-graphql/src/resolvers/program-accounts.ts +++ b/packages/rpc-graphql/src/resolvers/program-accounts.ts @@ -2,33 +2,76 @@ import { Address } from '@solana/addresses'; import type { GraphQLResolveInfo } from 'graphql'; import { RpcGraphQLContext } from '../context'; -import { ProgramAccountsLoaderArgs } from '../loaders'; -import { transformLoadedAccount } from './account'; -import { onlyPresentFieldRequested } from './resolve-info'; +import { cacheKeyFn,ProgramAccountsLoaderArgs } from '../loaders'; +import { AccountResult } from './account'; +import { onlyFieldsRequested } from './resolve-info'; export function resolveProgramAccounts(fieldName?: string) { return async ( parent: { [x: string]: Address }, args: ProgramAccountsLoaderArgs, context: RpcGraphQLContext, - info: GraphQLResolveInfo | undefined, + info: GraphQLResolveInfo, ) => { const programAddress = fieldName ? parent[fieldName] : args.programAddress; - if (!programAddress) { - return null; - } - if (onlyPresentFieldRequested('programAddress', info)) { - return { programAddress }; + + if (programAddress) { + if (onlyFieldsRequested(['programAddress'], info)) { + return { programAddress }; + } } + + // TODO: This needs to be split out from accounts. This only works for one data field at a time. const programAccounts = await context.loaders.programAccounts.load({ ...args, programAddress }); - return programAccounts === null - ? { programAddress } - : programAccounts.map(programAccount => - transformLoadedAccount({ - account: programAccount.account, - address: programAccount.pubkey, - encoding: args.encoding, - }), - ); + if (programAccounts) { + return programAccounts.map(programAccount => { + const { account, pubkey: address } = programAccount; + + let result: AccountResult = { + ...account, + address, + encodedData: {}, + ownerProgram: account.owner, + }; + + const { data } = account; + const { encoding, dataSlice } = args; + + // TODO: Add encoding to this conditional once the program accounts + // batch loader is implemented. + if (result.encodedData) { + if (Array.isArray(data)) { + result.encodedData[cacheKeyFn({ + dataSlice, + encoding: encoding === 'jsonParsed' ? 'base64' : encoding ?? 'base64', + })] = data[0]; + } else if (typeof data === 'string') { + result.encodedData[cacheKeyFn({ + dataSlice, + encoding: 'base58', + })] = data; + } else if (typeof data === 'object') { + const { + parsed: { info: parsedData, type: accountType }, + program: programName, + programId, + } = data; + result.jsonParsedConfigs = { + accountType, + programId, + programName, + }; + result = { + ...result, + ...(parsedData as object), + }; + } + } + + return result; + }); + } + + return null; }; } diff --git a/packages/rpc-graphql/src/resolvers/resolve-info.ts b/packages/rpc-graphql/src/resolvers/resolve-info.ts deleted file mode 100644 index b4c9a737def4..000000000000 --- a/packages/rpc-graphql/src/resolvers/resolve-info.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { GraphQLResolveInfo } from 'graphql'; - -export function onlyPresentFieldRequested(fieldName: string, info?: GraphQLResolveInfo): boolean { - if (info && info.fieldNodes[0].selectionSet) { - const selectionSet = info.fieldNodes[0].selectionSet; - const requestedFields = selectionSet.selections.map(field => { - if (field.kind === 'Field') { - return field.name.value; - } - return null; - }); - if (requestedFields && requestedFields.length === 1 && requestedFields[0] === fieldName) { - return true; - } - } - return false; -} diff --git a/packages/rpc-graphql/src/resolvers/resolve-info/account.ts b/packages/rpc-graphql/src/resolvers/resolve-info/account.ts new file mode 100644 index 000000000000..ae0d6b314c43 --- /dev/null +++ b/packages/rpc-graphql/src/resolvers/resolve-info/account.ts @@ -0,0 +1,121 @@ +import { Address } from '@solana/addresses'; +import { Commitment, DataSlice, Slot } from '@solana/rpc-types'; +import { ArgumentNode, GraphQLResolveInfo, isInterfaceType } from 'graphql'; + +import { AccountLoaderArgs } from '../../loaders'; +import { injectableRootVisitor, onlyFieldsRequested } from './visitor'; + +function findArgumentNodeByName(argumentNodes: readonly ArgumentNode[], name: string): ArgumentNode | undefined { + return argumentNodes.find(argumentNode => argumentNode.name.value === name); +} + +function parseAccountEncodingArgument( + argumentNodes: readonly ArgumentNode[], + variableValues: { + [variable: string]: unknown; + }, +): AccountLoaderArgs['encoding'] | undefined { + const argumentNode = findArgumentNodeByName(argumentNodes, 'encoding'); + if (argumentNode) { + if (argumentNode.value.kind === 'EnumValue') { + if (argumentNode.value.value === 'BASE_58') { + return 'base58'; + } + if (argumentNode.value.value === 'BASE_64') { + return 'base64'; + } + if (argumentNode.value.value === 'BASE_64_ZSTD') { + return 'base64+zstd'; + } + } + if (argumentNode.value.kind === 'Variable') { + return variableValues[argumentNode.value.name.value] as AccountLoaderArgs['encoding']; + } + } else { + return undefined; + } +} + +function parseAccountDataSliceArgument( + argumentNodes: readonly ArgumentNode[], + variableValues: { [variable: string]: unknown }, +): AccountLoaderArgs['dataSlice'] { + const argumentNode = findArgumentNodeByName(argumentNodes, 'dataSlice'); + if (argumentNode) { + if (argumentNode.value.kind === 'ObjectValue') { + const offsetArg = argumentNode.value.fields?.find(field => field.name.value === 'offset'); + const lengthArg = argumentNode.value.fields?.find(field => field.name.value === 'length'); + const length = + lengthArg?.value.kind === 'IntValue' + ? parseInt(lengthArg.value.value) + : lengthArg?.value.kind === 'Variable' + ? (variableValues[lengthArg.value.name.value] as number) + : undefined; + const offset = + offsetArg?.value.kind === 'IntValue' + ? parseInt(offsetArg.value.value) + : offsetArg?.value.kind === 'Variable' + ? (variableValues[offsetArg.value.name.value] as number) + : undefined; + return length !== undefined && length !== null && offset !== undefined && offset !== null + ? { length, offset } + : undefined; + } + if (argumentNode.value.kind === 'Variable') { + return variableValues[argumentNode.value.name.value] as DataSlice; + } + } else { + return undefined; + } +} + +/** + * Build a set of account loader args by inspecting which fields have + * been requested in the query (ie. `data` or inline fragments). + */ +export function buildAccountLoaderArgSetFromResolveInfo( + args: { + address: Address; + commitment?: Commitment; + minContextSlot?: Slot; + }, + info: GraphQLResolveInfo, +): AccountLoaderArgs[] { + const argSet: AccountLoaderArgs[] = [args]; + + function buildArgSetWithVisitor(root: Parameters[1]) { + injectableRootVisitor(info, root, { + fieldNodeOperation(info, node) { + if (node.name.value !== 'data') { + return; + } + // At least `encoding` is required on the `data` field. + if (node.arguments) { + const { variableValues } = info; + const encoding = parseAccountEncodingArgument(node.arguments, variableValues); + const dataSlice = parseAccountDataSliceArgument(node.arguments, variableValues); + argSet.push({ ...args, dataSlice, encoding }); + } + }, + fragmentSpreadNodeOperation(_info, fragment) { + buildArgSetWithVisitor(fragment); + }, + inlineFragmentNodeOperation(info, node) { + const { schema } = info; + const accountInterface = schema.getType('Account'); + if ( + isInterfaceType(accountInterface) && + // Recursively check if the inline fragment requests any + // fields outside of the `Account` interface. + !onlyFieldsRequested(Object.keys(accountInterface.getFields()), info, node) + ) { + argSet.push({ ...args, encoding: 'jsonParsed' }); + } + }, + }); + } + + buildArgSetWithVisitor(null); + + return argSet; +} diff --git a/packages/rpc-graphql/src/resolvers/resolve-info/index.ts b/packages/rpc-graphql/src/resolvers/resolve-info/index.ts new file mode 100644 index 000000000000..4a42dab49b38 --- /dev/null +++ b/packages/rpc-graphql/src/resolvers/resolve-info/index.ts @@ -0,0 +1,2 @@ +export * from './account'; +export * from './visitor'; diff --git a/packages/rpc-graphql/src/resolvers/resolve-info/visitor.ts b/packages/rpc-graphql/src/resolvers/resolve-info/visitor.ts new file mode 100644 index 000000000000..9e19774ae28d --- /dev/null +++ b/packages/rpc-graphql/src/resolvers/resolve-info/visitor.ts @@ -0,0 +1,82 @@ +import { + ASTNode, + ASTVisitFn, + BREAK, + FieldNode, + FragmentDefinitionNode, + FragmentSpreadNode, + GraphQLResolveInfo, + InlineFragmentNode, + visit, +} from 'graphql'; + +type InjectableVisitorOperations = { + fieldNodeOperation: (info: GraphQLResolveInfo, ...args: Parameters>) => void; + fragmentSpreadNodeOperation: ( + info: GraphQLResolveInfo, + fragment: FragmentDefinitionNode, + ...args: Parameters> + ) => void; + inlineFragmentNodeOperation: ( + info: GraphQLResolveInfo, + ...args: Parameters> + ) => void; +}; +type RootNode = FieldNode | FragmentDefinitionNode | InlineFragmentNode; + +/** + * An AST visitor that keys on the root field provided. + * This visitor can be injected with custom logic for various types of nodes. + */ +export function injectableRootVisitor( + info: GraphQLResolveInfo, + rootNode: RootNode | null, + operations: InjectableVisitorOperations, +) { + const { fieldNodes } = info; + const root = rootNode ?? fieldNodes[0]; + const parentIsRoot = (ancestors: readonly (ASTNode | readonly ASTNode[])[]) => + (Array.isArray(ancestors) ? ancestors[0] : ancestors) === root; + visit(root, { + Field(node, key, parent, path, ancestors) { + if (!parentIsRoot(ancestors)) return; + return operations.fieldNodeOperation(info, node, key, parent, path, ancestors); + }, + FragmentSpread(node, key, parent, path, ancestors) { + const fragmentDefinition = info.fragments[node.name.value]; + return operations.fragmentSpreadNodeOperation(info, fragmentDefinition, node, key, parent, path, ancestors); + }, + InlineFragment(node, key, parent, path, ancestors) { + if (!parentIsRoot(ancestors)) return; + return operations.inlineFragmentNodeOperation(info, node, key, parent, path, ancestors); + }, + }); +} + +/** + * Determine if the query only requests the provided field names + */ +export function onlyFieldsRequested(fieldNames: string[], info: GraphQLResolveInfo, rootNode?: RootNode): boolean { + let onlyFieldsRequested = true; + + function checkFieldsWithVisitor(root: RootNode | null) { + injectableRootVisitor(info, root, { + fieldNodeOperation(_info, node) { + onlyFieldsRequested = fieldNames.includes(node.name.value); + if (!onlyFieldsRequested) { + return BREAK; + } + }, + fragmentSpreadNodeOperation(_info, fragment) { + checkFieldsWithVisitor(fragment); + }, + inlineFragmentNodeOperation(_info, node) { + checkFieldsWithVisitor(node); + }, + }); + } + + checkFieldsWithVisitor(rootNode ?? null); + + return onlyFieldsRequested; +} diff --git a/packages/rpc-graphql/src/resolvers/transaction.ts b/packages/rpc-graphql/src/resolvers/transaction.ts index ba3b00e5c4c8..05f44a49d1e1 100644 --- a/packages/rpc-graphql/src/resolvers/transaction.ts +++ b/packages/rpc-graphql/src/resolvers/transaction.ts @@ -3,7 +3,7 @@ import type { GraphQLResolveInfo } from 'graphql'; import { RpcGraphQLContext } from '../context'; import { TransactionLoaderArgs } from '../loaders'; -import { onlyPresentFieldRequested } from './resolve-info'; +import { onlyFieldsRequested } from './resolve-info'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function transformParsedInstruction(parsedInstruction: any) { @@ -63,13 +63,13 @@ export function resolveTransaction(fieldName?: string) { parent: { [x: string]: Signature }, args: TransactionLoaderArgs, context: RpcGraphQLContext, - info: GraphQLResolveInfo | undefined, + info: GraphQLResolveInfo, ) => { const signature = fieldName ? parent[fieldName] : args.signature; if (!signature) { return null; } - if (onlyPresentFieldRequested('signature', info)) { + if (onlyFieldsRequested(['signature'], info)) { return { signature }; } const transaction = await context.loaders.transaction.load({ ...args, signature }); diff --git a/packages/rpc-graphql/src/resolvers/types.ts b/packages/rpc-graphql/src/resolvers/types.ts index cc45fe56b774..f92a45dcc5bb 100644 --- a/packages/rpc-graphql/src/resolvers/types.ts +++ b/packages/rpc-graphql/src/resolvers/types.ts @@ -37,7 +37,6 @@ export const typeTypeResolvers = { BASE_58: 'base58', BASE_64: 'base64', BASE_64_ZSTD: 'base64+zstd', - PARSED: 'jsonParsed', }, Address: stringScalarAlias, Base58EncodedBytes: stringScalarAlias, diff --git a/packages/rpc-graphql/src/schema/account.ts b/packages/rpc-graphql/src/schema/account.ts index 78bd3273d857..6c29b7beae49 100644 --- a/packages/rpc-graphql/src/schema/account.ts +++ b/packages/rpc-graphql/src/schema/account.ts @@ -4,6 +4,7 @@ export const accountTypeDefs = /* GraphQL */ ` """ interface Account { address: Address + data(encoding: AccountEncoding!, dataSlice: DataSlice): String executable: Boolean lamports: BigInt ownerProgram: Account @@ -12,37 +13,11 @@ export const accountTypeDefs = /* GraphQL */ ` } """ - An account with base58 encoded data + Generic base account type """ - type AccountBase58 implements Account { + type GenericAccount implements Account { address: Address - data: Base58EncodedBytes - executable: Boolean - lamports: BigInt - ownerProgram: Account - space: BigInt - rentEpoch: BigInt - } - - """ - An account with base64 encoded data - """ - type AccountBase64 implements Account { - address: Address - data: Base64EncodedBytes - executable: Boolean - lamports: BigInt - ownerProgram: Account - space: BigInt - rentEpoch: BigInt - } - - """ - An account with base64+zstd encoded data - """ - type AccountBase64Zstd implements Account { - address: Address - data: Base64ZstdEncodedBytes + data(encoding: AccountEncoding!, dataSlice: DataSlice): String executable: Boolean lamports: BigInt ownerProgram: Account @@ -58,6 +33,7 @@ export const accountTypeDefs = /* GraphQL */ ` """ type NonceAccount implements Account { address: Address + data(encoding: AccountEncoding!, dataSlice: DataSlice): String executable: Boolean lamports: BigInt ownerProgram: Account @@ -73,6 +49,7 @@ export const accountTypeDefs = /* GraphQL */ ` """ type LookupTableAccount implements Account { address: Address + data(encoding: AccountEncoding!, dataSlice: DataSlice): String executable: Boolean lamports: BigInt ownerProgram: Account @@ -90,6 +67,7 @@ export const accountTypeDefs = /* GraphQL */ ` """ type MintAccount implements Account { address: Address + data(encoding: AccountEncoding!, dataSlice: DataSlice): String executable: Boolean lamports: BigInt ownerProgram: Account @@ -107,6 +85,7 @@ export const accountTypeDefs = /* GraphQL */ ` """ type TokenAccount implements Account { address: Address + data(encoding: AccountEncoding!, dataSlice: DataSlice): String executable: Boolean lamports: BigInt ownerProgram: Account @@ -149,6 +128,7 @@ export const accountTypeDefs = /* GraphQL */ ` """ type StakeAccount implements Account { address: Address + data(encoding: AccountEncoding!, dataSlice: DataSlice): String executable: Boolean lamports: BigInt ownerProgram: Account @@ -180,6 +160,7 @@ export const accountTypeDefs = /* GraphQL */ ` """ type VoteAccount implements Account { address: Address + data(encoding: AccountEncoding!, dataSlice: DataSlice): String executable: Boolean lamports: BigInt ownerProgram: Account diff --git a/packages/rpc-graphql/src/schema/root.ts b/packages/rpc-graphql/src/schema/root.ts index 6eaee544bc43..9b9a7664a122 100644 --- a/packages/rpc-graphql/src/schema/root.ts +++ b/packages/rpc-graphql/src/schema/root.ts @@ -1,12 +1,6 @@ export const rootTypeDefs = /* GraphQL */ ` type Query { - account( - address: Address! - commitment: Commitment - dataSlice: DataSlice - encoding: AccountEncoding - minContextSlot: Slot - ): Account + account(address: Address!, commitment: Commitment, minContextSlot: Slot): Account block( slot: Slot! commitment: Commitment diff --git a/packages/rpc-graphql/src/schema/types.ts b/packages/rpc-graphql/src/schema/types.ts index 00c709726e8e..2040c8dc07f4 100644 --- a/packages/rpc-graphql/src/schema/types.ts +++ b/packages/rpc-graphql/src/schema/types.ts @@ -3,7 +3,6 @@ export const typeTypeDefs = /* GraphQL */ ` BASE_58 BASE_64 BASE_64_ZSTD - PARSED } scalar Address