diff --git a/packages/library/README.md b/packages/library/README.md
index f0deb4c9aa4..14364df3590 100644
--- a/packages/library/README.md
+++ b/packages/library/README.md
@@ -1169,6 +1169,8 @@ Here’s an example of retrieving account data with GraphQL:
const source = `
query myQuery($address: String!) {
account(address: $address) {
+ dataBase58: data(encoding: BASE_58)
+ dataBase64: data(encoding: BASE_64)
lamports
}
}
@@ -1183,6 +1185,8 @@ const result = await rpcGraphQL.query(source, variableValues);
expect(result).toMatchObject({
data: {
account: {
+ dataBase58: '2Uw1bpnsXxu3e',
+ dataBase64: 'dGVzdCBkYXRh',
lamports: 10290815n,
},
},
@@ -1198,15 +1202,9 @@ const source = `
query getLamportsOfOwnersOfOwnersOfTokenAccounts {
programAccounts(programAddress: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") {
... on TokenAccount {
- data {
- parsed {
- info {
- owner {
- owner {
- lamports
- }
- }
- }
+ owner {
+ ownerProgram {
+ lamports
}
}
}
@@ -1217,7 +1215,7 @@ const source = `
const result = await rpcGraphQL.query(source);
const sumOfAllLamportsOfOwnersOfOwnersOfTokenAccounts = result
- .map(o => o.account.data.parsed.info.owner.owner.lamports)
+ .map(o => o.account.owner.ownerProgram.lamports)
.reduce((acc, lamports) => acc + lamports, 0);
```
@@ -1227,22 +1225,12 @@ The new GraphQL package supports this same style of nested querying on transacti
const source = `
query myQuery($signature: String!, $commitment: Commitment) {
transaction(signature: $signature, commitment: $commitment) {
- ... on TransactionJsonParsed {
- transaction {
- message {
- ... on TransactionMessageParsed {
- instructions {
- ... on CreateAccountInstruction {
- parsed {
- info {
- lamports
- space
- }
- program
- }
- }
- }
- }
+ message {
+ instructions {
+ ... on CreateAccountInstruction {
+ lamports
+ programId
+ space
}
}
}
@@ -1260,20 +1248,14 @@ const result = await rpcGraphQL.query(source, variableValues);
expect(result).toMatchObject({
data: {
transaction: {
- transaction: {
- message: {
- instructions: expect.arrayContaining([
- {
- parsed: {
- info: {
- lamports: expect.any(BigInt),
- space: expect.any(BigInt),
- },
- program: 'system',
- },
- },
- ]),
- },
+ message: {
+ instructions: expect.arrayContaining([
+ {
+ lamports: expect.any(BigInt),
+ programId: '11111111111111111111111111111111',
+ space: expect.any(BigInt),
+ },
+ ]),
},
},
},
diff --git a/packages/rpc-graphql/README.md b/packages/rpc-graphql/README.md
index 945f0f5f777..f6bb1b53ad5 100644
--- a/packages/rpc-graphql/README.md
+++ b/packages/rpc-graphql/README.md
@@ -3,60 +3,57 @@
This package defines a GraphQL client resolver built on top of the
[Solana JSON-RPC](https://docs.solana.com/api/http).
+A client resolver in this context is simply a client-side RPC interface
+designed to give application developers the ability to use GraphQL to interact
+with data on the Solana blockchain.
+
+The resolver presents developers with a new schema for working with Solana data
+(see [Schema](#schema)), as well as new features only possible with GraphQL.
+Additionally, the resolver is designed to make highly-optimized use of the
+Solana JSON RPC, balancing RPC requests, batch loading, and caching
+(see [RPC Optimizations](#rpc-optimizations)).
+
GraphQL is a query language for your API, and a server-side runtime for
executing queries using a type system you define for your data.
[**GraphQL**](https://graphql.org/learn/)
-This library attempts to define a type schema for Solana. With the proper
-type schema, developers can take advantage of the best features of GraphQL
-to make interacting with Solana via RPC smoother, faster, more reliable,
-and involve less code.
-
-# Design
-
-On-chain data can be categorized into three main types:
+# Quick Start
-- Accounts
-- Transactions
-- Blocks
+The RPC-GraphQL client requires an RPC client, as defined by the package
+`@solana/rpc-spec`. Such a client is available in `@solana/web3.js:2.0` or
+can be created manually with a custom implementation.
-These types encompass everything that can be queried from the Solana ledger.
-
-The Solana RPC provides a parsing method known as `jsonParsed` for supported
-types, such as accounts and transaction instructions.
-
-This library leverages GraphQL **interfaces** for each of these types paired
-with specific GraphQL types for each `jsonParsed` object. This allows for
-powerful querying of `jsonParsed` data, including nested and chained queries!
+```ts
+Rpc;
+```
-## Setting up a GraphQL RPC Client
+The RPC-GraphQL requires an RPC client with the following API methods available
+for use in order to properly execute all queries.
-Initializing an RPC-GraphQL using `@solana/rpc-graphql` requires an RPC client,
-either built using `@solana/web3.js` or it's child libraries `@solana/rpc-core`
-and `@solana/rpc-transport`.
+```ts
+Rpc;
+```
-```typescript
-import { createSolanaRpc, createDefaultRpcTransport } from '@solana/web3.js';
-import { createRpcGraphQL } from '@solana/rpc-graphql';
+To initialize the RPC-GraphQL client, simple use `createRpcGraphQL`.
-// Set up an HTTP transport
-const transport = createDefaultRpcTransport({ url: 'http://127.0.0.1:8899' });
+```ts
+import { createSolanaRpc } from '@solana/rpc';
// Create the RPC client
-const rpc = createSolanaRpc({ transport });
+const rpc = createSolanaRpc('https://api.devnet.solana.com');
// Create the RPC-GraphQL client
const rpcGraphQL = createRpcGraphQL(rpc);
```
-The `RpcGraphQL` type supports one method `query(..)` which accepts a string
+The `RpcGraphQL` type supports one method `query` which accepts a string
query source and an optional `variableValues` parameter - which is an object
containing any variables to pipe into the query string.
You can define queries with hard-coded parameters.
-```typescript
+```ts
const source = `
query myQuery {
account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") {
@@ -78,7 +75,7 @@ data: {
You can also pass the variable values.
-```typescript
+```ts
const source = `
query myQuery($address: String!) {
account(address: $address) {
@@ -104,7 +101,7 @@ data: {
Queries with variable values can also be re-used!
-```typescript
+```ts
const source = `
query myQuery($address: String!) {
account(address: $address) {
@@ -122,19 +119,28 @@ const lamportsAccountB = await rpcGraphQL.query(source, {
});
```
-## Querying Accounts
+# Schema
-The `Account` interface contains common fields across all accounts.
+Solana data can be categorized into three main types:
-`src/schema/account/types.ts: AccountInterface`
+- Accounts
+- Transactions
+- Blocks
+
+These types encompass everything that can be queried from the Solana ledger.
+
+## Accounts
+
+The `Account` interface contains common fields across all accounts.
```graphql
interface Account {
- address: String
- encoding: String
+ address: Address
+ data(encoding: AccountEncoding!, dataSlice: DataSlice): String
executable: Boolean
lamports: BigInt
- owner: Account
+ ownerProgram: Account
+ space: BigInt
rentEpoch: BigInt
}
```
@@ -142,7 +148,7 @@ interface Account {
Any account can be queried by these fields without specifying the specific
account type.
-```typescript
+```ts
const source = `
query myQuery($address: String!) {
account(address: $address) {
@@ -170,25 +176,51 @@ data: {
}
```
-### Specific Account Types
+### Querying Account Data
+
+Querying accounts by their encoded data (`base58`, `base64`, `base64+zstd`) is
+still fully supported.
+
+```ts
+const source = `
+ query myQuery($address: String!) {
+ account(address: $address) {
+ data(encoding: BASE_64)
+ }
+ }
+`;
+
+const variableValues = {
+ address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
+};
+
+const result = await rpcGraphQL.query(source, variableValues);
+```
+
+```
+data: {
+ account: {
+ data: 'dGVzdCBkYXRh',
+ },
+}
+```
+
+### Querying Specific Account Types
-Each `jsonParsed` account type has its own GraphQL type that can be used in a
-GraphQL query.
+A set of specific parsed account types are supported in GraphQL.
-- `AccountBase58`: A Solana account with base58 encoded data
-- `AccountBase64`: A Solana account with base64 encoded data
-- `AccountBase64Zstd`: A Solana account with base64 encoded data compressed with zstd
-- `NonceAccount`: A nonce account
-- `LookupTableAccount`: An address lookup table account
-- `MintAccount`: An SPL mint
-- `TokenAccount`: An SPL token account
-- `StakeAccount`: A stake account
-- `VoteAccount`: A vote account
+- `GenericAccount`: A generic base account type
+- `NonceAccount`: A nonce account
+- `LookupTableAccount`: An address lookup table account
+- `MintAccount`: An SPL mint
+- `TokenAccount`: An SPL token account
+- `StakeAccount`: A stake account
+- `VoteAccount`: A vote account
You can choose how to handle querying of specific account types. For example,
you might _only_ want specifically any account that matches `MintAccount`.
-```typescript
+```ts
const maybeMintAddresses = [
'J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ',
'JAbWqZ7S2c6jomQr8ofAYBo257bE1QJtHwbX1yWc2osZ',
@@ -218,9 +250,7 @@ for (const address of maybeMintAddresses) {
if (result != null) {
const {
data: {
- account: {
- data: mintInfo,
- },
+ account: { data: mintInfo },
},
} = result;
mintAccounts.push(mintInfo);
@@ -230,7 +260,7 @@ for (const address of maybeMintAddresses) {
Maybe you want to handle both mints _and_ token accounts.
-```typescript
+```ts
const mintOrTokenAccountAddresses = [
'J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ',
'JAbWqZ7S2c6jomQr8ofAYBo257bE1QJtHwbX1yWc2osZ',
@@ -245,24 +275,16 @@ const source = `
query myQuery($address: String!) {
account(address: $address) {
... on MintAccount {
- data {
- decimals
- isInitialized
- supply
- }
- meta {
- type
- }
+ __typename
+ decimals
+ isInitialized
+ supply
}
... on TokenAccount {
- data {
- isNative
- mint
- state
- }
- meta {
- type
- }
+ __typename
+ isNative
+ mint
+ state
}
}
}
@@ -272,41 +294,36 @@ for (const address of mintOrTokenAccountAddresses) {
const result = await rpcGraphQL.query(source, { address });
if (result != null) {
const {
- data: {
- account: {
- data: accountParsedInfo,
- meta: {
- type: accountType,
- }
- }
- }
+ data: { account: accountParsedData },
} = result;
- if (accountType === 'mint') {
- mintAccounts.push(accountParsedInfo)
+ if (accountParsedData.__typename === 'MintAccount') {
+ mintAccounts.push(accountParsedInfo);
} else {
- tokenAccounts.push(accountParsedInfo)
+ tokenAccounts.push(accountParsedInfo);
}
}
}
```
-Querying accounts by their encoded data (`base58`, `base64`, `base64+zstd`) is
-still fully supported.
+### Querying Program Accounts
+
+Another account-based query that can be performed with RPC-GraphQL is the
+`programAccounts` query. The response will be a list of `Account` types as
+defined above.
-```typescript
+```ts
const source = `
- query myQuery($address: String!, $encoding: AccountEncoding) {
- account(address: $address, encoding: $encoding) {
- ... on AccountBase64 {
- data
- }
+ query myQuery($programAddress: String!) {
+ programAccounts(programAddress: $address) {
+ executable
+ lamports
+ rentEpoch
}
}
`;
const variableValues = {
- address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
- encoding: 'base64',
+ programAddress: 'AmtpVzo6H6qQCP9dH9wfu5hfa8kKaAFpTJ4aamPYR6V6',
};
const result = await rpcGraphQL.query(source, variableValues);
@@ -314,18 +331,103 @@ const result = await rpcGraphQL.query(source, variableValues);
```
data: {
- account: {
- data: 'dGVzdCBkYXRh',
- },
+ programAccounts: [
+ {
+ executable: false,
+ lamports: 10290815n,
+ rentEpoch: 0n,
+ },
+ {
+ executable: false,
+ lamports: 10290815n,
+ rentEpoch: 0n,
+ },
+ /* .. */
+ ]
}
```
+Account data encoding in `base58`, `base64`, and `base64+zstd` is also
+supported with this query, as well as `dataSlice` and `filter`.
+
+```ts
+const source = `
+ query myQuery($programAddress: String!) {
+ programAccounts(programAddress: $programAddress) {
+ data(encoding: BASE_64, dataSlice: { length: 5, offset: 0 })
+ }
+ }
+`;
+
+const variableValues = {
+ programAddress: 'DXngmJfjurhnAwbMPgpUGPH6qNvetCKRJ6PiD4ag4PTj',
+};
+
+const result = await rpcGraphQL.query(source, variableValues);
+```
+
+```
+data: {
+ programAccounts: [
+ {
+ data: 'dGVzdCA=',
+ },
+ /* .. */
+ ],
+}
+```
+
+Although specific parsed account types are directly tied to the program which
+owns them, it's still possible to handle various specific account types within
+the same program accounts response.
+
+```ts
+const source = `
+ query myQuery($programAddress: String!) {
+ programAccounts(programAddress: $address) {
+ ... on MintAccount {
+ __typename
+ decimals
+ isInitialized
+ mintAuthority
+ supply
+ }
+ ... on TokenAccount {
+ __typename
+ isNative
+ mint
+ owner
+ state
+ }
+ }
+ }
+`;
+
+const variableValues = {
+ programAddress: 'AmtpVzo6H6qQCP9dH9wfu5hfa8kKaAFpTJ4aamPYR6V6',
+};
+
+const result = await rpcGraphQL.query(source, variableValues);
+
+const { mints, tokenAccounts } = result.data.programAccounts.reduce(
+ (acc: { mints: any[]; tokenAccounts: any[] }, account) => {
+ if (account.__typename === 'MintAccount') {
+ acc.mints.push(accountParsedInfo);
+ } else {
+ acc.tokenAccounts.push(accountParsedInfo);
+ }
+ return acc;
+ },
+ { mints: [], tokenAccounts: [] },
+);
+```
+
### Nested Account Queries
Notice the `owner` field of the `Account` interface is also an `Account`
interface. This powers nested queries against the `owner` field of an account.
-```typescript
+```ts
const source = `
query myQuery($address: String!) {
account(address: $address) {
@@ -365,7 +467,7 @@ information!
You can nest queries as far as you want!
-```typescript
+```ts
const source = `
query myQuery($address: String!) {
account(address: $address) {
@@ -409,7 +511,7 @@ data: {
Nested queries can also be applied to specific account types.
-```typescript
+```ts
const source = `
query myQuery($address: String!) {
account(address: $address) {
@@ -447,216 +549,42 @@ data: {
}
```
-## Querying Program Accounts
-
-A very common way to query Solana accounts from an RPC is to request all of
-the accounts owned by a particular program.
-
-With RPC-GraphQL, querying program-owned accounts is a list-based extension of
-the account query defined previously. This means program accounts queries will
-return a list of objects implementing the `Account` interface.
-
-Setting up the query is very similar to the `account` query.
-
-```typescript
-const source = `
- query myQuery($programAddress: String!) {
- programAccounts(programAddress: $address) {
- executable
- lamports
- rentEpoch
- }
- }
-`;
-
-const variableValues = {
- programAddress: 'AmtpVzo6H6qQCP9dH9wfu5hfa8kKaAFpTJ4aamPYR6V6',
-};
+Nested account queries are also supported on `programAccounts` queries.
-const result = await rpcGraphQL.query(source, variableValues);
-```
+## Transactions
-```
-data: {
- programAccounts: [
- {
- executable: false,
- lamports: 10290815n,
- rentEpoch: 0n,
- },
- {
- executable: false,
- lamports: 10290815n,
- rentEpoch: 0n,
- }
- ]
-}
-```
-
-### Specific Program Account Types
+The `Transaction` type contains common fields across all transactions.
-Although specific parsed account types are directly tied to the program which
-owns them, it's still possible to handle various specific account types within
-the same program accounts response.
-
-```typescript
-const source = `
- query myQuery($programAddress: String!) {
- programAccounts(programAddress: $address) {
- ... on MintAccount {
- data {
- decimals
- isInitialized
- mintAuthority
- supply
- }
- meta {
- type
- }
- }
- ... on TokenAccount {
- data {
- isNative
- mint
- owner
- state
- }
- meta {
- type
- }
- }
- }
- }
-`;
-
-const variableValues = {
- programAddress: 'AmtpVzo6H6qQCP9dH9wfu5hfa8kKaAFpTJ4aamPYR6V6',
-};
-
-const result = await rpcGraphQL.query(source, variableValues);
-
-const { mints, tokenAccounts } = result.data.programAccounts.reduce(
- (acc: { mints: any[]; tokenAccounts: any[] }, account) => {
- const {
- data: accountParsedInfo,
- meta: {
- type: accountType,
- }
- } = account;
- if (accountType === "mint") {
- acc.mints.push(accountParsedInfo);
- } else {
- acc.tokenAccounts.push(accountParsedInfo);
- }
- return acc;
- },
- { mints: [], tokenAccounts: [] }
-);
-```
-
-Account data encoding in `base58`, `base64`, and `base64+zstd` is also
-supported, as well as `dataSlice` and `filter`!
-
-```typescript
-const source = `
- query myQuery(
- $programAddress: String!,
- $commitment: Commitment,
- $dataSlice: DataSlice,
- $encoding: AccountEncoding,
- ) {
- programAccounts(
- programAddress: $programAddress,
- commitment: $commitment,
- dataSlice: $dataSlice,
- encoding: $encoding,
- ) {
- ... on AccountBase64 {
- data
- }
- }
- }
-`;
-
-const variableValues = {
- programAddress: 'DXngmJfjurhnAwbMPgpUGPH6qNvetCKRJ6PiD4ag4PTj',
- commitment: 'confirmed',
- dataSlice: {
- length: 5,
- offset: 0,
- },
- encoding: 'base64',
-};
-
-const result = await rpcGraphQL.query(source, variableValues);
-```
-
-```
-data: {
- programAccounts: [
- {
- data: 'dGVzdCA=',
- },
- ],
+```graphql
+type Transaction {
+ blockTime: BigInt
+ data(encoding: TransactionEncoding!): String
+ message: TransactionMessage
+ meta: TransactionMeta
+ signatures: [Signature]
+ slot: Slot
+ version: String
}
```
-### Nested Program Account Queries
-
-Querying program accounts and applying nested queries to the objects within the
-response list is an area where RPC-GraphQL really shines.
-
-Consider an example where we want to get the **sum** of every lamports balance
-of every **owner of the owner** of each token account, while discarding any
-mint accounts.
-
-```typescript
-const source = `
- query getLamportsOfOwnersOfOwnersOfTokenAccounts {
- programAccounts(programAddress: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") {
- ... on TokenAccount {
- data {
- owner {
- owner {
- lamports
- }
- }
- }
- }
- }
- }
-`;
-
-const result = await rpcGraphQL.query(source);
-
-const sumOfAllLamportsOfOwnersOfOwnersOfTokenAccounts = result.data
- .map(o => o.account.data.owner.owner.lamports)
- .reduce((acc, lamports) => acc + lamports, 0);
-```
-
-## Querying Transactions
-
-The `Transaction` interface contains common fields across all transactions.
-
-`src/schema/transaction/types.ts: TransactionInterface`
+Note that unlike accounts, the `Transaction` type is not an interface, so the
+base response type of a transaction query remains constant. However, the list
+of instructions contained in a parsed transaction are returned as the
+`TransactionInstruction` interface, which can be queried by specific type.
+See [Querying Specific Transaction Instruction Types](#querying-specific-transaction-instruction-types).
```graphql
-interface Transaction {
- blockTime: String
- encoding: String
- meta: TransactionMeta
- slot: BigInt
+interface TransactionInstruction {
+ programId: Address
}
```
-Similar to account types, any transaction can be queried by these fields
-without specifying the specific transaction type or the transaction meta
-type.
+A transaction can be queried by the `transaction` query.
-```typescript
+```ts
const source = `
- query myQuery($signature: String!, $commitment: Commitment) {
- transaction(signature: $signature, commitment: $commitment) {
+ query myQuery($signature: String!) {
+ transaction(signature: $signature) {
blockTime
meta {
computeUnitsConsumed
@@ -669,7 +597,6 @@ const source = `
const variableValues = {
signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
- commitment: 'confirmed',
};
const result = await rpcGraphQL.query(source, variableValues);
@@ -694,58 +621,15 @@ data: {
}
```
-### Specific Transaction Types
-
-Each `jsonParsed` instruction type has its own GraphQL type that can be used in
-a GraphQL transaction query.
-
-Instructions for the following programs are supported.
-
-- Address Lookup Table
-- BPF Loader
-- BPF Upgradeable Loader
-- Stake
-- SPL Associated Token
-- SPL Memo
-- SPL Token
-- System
-- Vote
-
-Note at this time Token 2022 extensions are not yet supported.
-
-Similar to accounts, transactions with encoded data are also supported.
-
-- `TransactionBase58`: A Solana transaction with base58 encoded data
-- `TransactionBase64`: A Solana transaction with base64 encoded data
-- `TransactionJson`: A Solana transaction with JSON data
+### Querying Transaction Data
-Specific instruction types can be used in the transaction's instructions. The
-default instruction if it cannot be parsed using `jsonParsed` is the JSON
-version dubbed `GenericInstruction`.
+Querying encoded transaction data (`base58`, `base64`) is fully supported.
-```typescript
+```ts
const source = `
query myQuery($signature: String!, $commitment: Commitment) {
transaction(signature: $signature, commitment: $commitment) {
- ... on TransactionParsed {
- data {
- message {
- accountKeys {
- pubkey
- signer
- source
- writable
- }
- instructions {
- ... on GenericInstruction {
- accounts
- data
- programId
- }
- }
- }
- }
- }
+ data(encoding: BASE_64)
}
}
`;
@@ -754,120 +638,63 @@ const variableValues = {
signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
commitment: 'confirmed',
};
-
+
const result = await rpcGraphQL.query(source, variableValues);
```
```
-data: {
- transaction: {
- data: {
- message: {
- accountKeys: [
- {
- pubkey: '81EBmTWaMkFqW6LPNPfU2478nJkrhCLuiFUPSdvQKQj7',
- signer: false,
- source: 'transaction',
- writable: true,
- },
- {
- pubkey: 'G6TmQyEoxbUzdyncwxVN9GgfALpYErHSkXZeqJj7fwFz',
- signer: true,
- source: 'transaction',
- writable: true,
- },
- ],
- instructions: [
- {
- accounts: [
- '81EBmTWaMkFqW6LPNPfU2478nJkrhCLuiFUPSdvQKQj7',
- 'G6TmQyEoxbUzdyncwxVN9GgfALpYErHSkXZeqJj7fwFz'
- ]
- data: 'WzIsIDU0LCA5LCAgNzYsIDM1LCA2NCwgOCwgOCwgNCwgMywgMiwgNV0=',
- programId: 'EksBYH1iSR8farQc9X26pYrXotj1D2JjXGuj8uM8xMcb',
- }
- ]
- },
- },
- },
+{
+ "data": {
+ "transaction": {
+ "data": "AbgFjqLTBtoAaHXexSN1OYXf+UNox6qe3JcyCmEwE57iUHxCkHp8zKTJVznd6nLtUFNMYJWHCtMb+yPjk7QIxAQBAAEDeXJtpS2Z1gsH6tc7L28L9gg8yFx3qU401pHXj4vK/skUvD7y/LnapfCdSx3FfTguy49UDQVvGgOK0ix/P42YuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5uE0ATTHEABQrIB1+aoEdYJxvQthXPLHFxSH2y+ACK4BAgIAAQwCAAAAAMqaOwAAAAA="
+ }
+ }
}
```
-However, whenever JSON-parseable instructions are present in the list of
-instructions, they can be queried using specific instruction types.
+### Querying Specific Transaction Instruction Types
-```typescript
-const source = `
- query myQuery($signature: String!, $commitment: Commitment) {
- transaction(signature: $signature, commitment: $commitment) {
- ... on TransactionParsed {
- data {
- message {
- instructions {
- ... on CreateAccountInstruction {
- data {
- lamports
- space
- }
- meta {
- program
- }
- }
- }
- }
- }
- }
- }
- }
-`;
+As mentioned above, parsed transactions return a list of instructions that
+implement the `TransactionInstruction` interface. These instructions can be
+queried by specific instruction types.
-const variableValues = {
- signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
- commitment: 'confirmed',
-};
-
-const result = await rpcGraphQL.query(source, variableValues);
-```
+Instructions for the following programs are supported.
-```
-data: {
- transaction: {
- data: {
- message: {
- instructions: [
- {
- data: {
- lamports: 890880n,
- space: 0n,
- },
- meta: {
- program: 'system',
- },
- }
- ]
- },
- },
- },
+- Address Lookup Table
+- BPF Loader
+- BPF Upgradeable Loader
+- Stake
+- SPL Associated Token
+- SPL Memo
+- SPL Token
+- System
+- Vote
+
+Additionally, the `GenericInstruction` type is the base parsed instruction type.
+
+```graphql
+type GenericInstruction implements TransactionInstruction {
+ accounts: [Address]
+ data: Base64EncodedBytes
+ programId: Address
}
```
-Querying transactions by their encoded data (`base58`, `base64`, `json`) is
-still fully supported.
+Specific transaction instruction types can be queried within a `Transaction`
+response like so.
-```typescript
+```ts
const source = `
- query myQuery(
- $signature: String!,
- $commitment: Commitment,
- $encoding: TransactionEncoding!,
- ) {
- transaction(
- signature: $signature,
- commitment: $commitment,
- encoding: $encoding,
- ) {
- ... on TransactionBase64 {
- data
+ query myQuery($signature: String!, $commitment: Commitment) {
+ transaction(signature: $signature, commitment: $commitment) {
+ message {
+ instructions {
+ ... on CreateAccountInstruction {
+ lamports
+ programId
+ space
+ }
+ }
}
}
}
@@ -876,16 +703,24 @@ const source = `
const variableValues = {
signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
commitment: 'confirmed',
- encoding: 'base64',
};
-
+
const result = await rpcGraphQL.query(source, variableValues);
```
```
data: {
transaction: {
- data: 'WzIsIDU0LCA5LCAgNzYsIDM1LCA2NCwgOCwgOCwgNCwgMywgMiwgNV0=',
+ message: {
+ instructions: [
+ {
+ lamports: 890880n,
+ programId: '11111111111111111111111111111111',
+ space: 0n,
+ },
+ /* .. */
+ ]
+ },
},
}
```
@@ -899,62 +734,52 @@ Similar to nested querying accounts, it's possible to nest queries inside your
transaction queries to look up other objects, such as accounts, as they appear
in the transaction response.
-```typescript
+```ts
const source = `
query myQuery($signature: String!, $commitment: Commitment) {
transaction(signature: $signature, commitment: $commitment) {
- ... on TransactionParsed {
- data {
- message {
- instructions {
- ... on SplTokenTransferInstruction {
- data {
- amount
- authority {
+ message {
+ instructions {
+ ... on SplTokenTransferInstruction {
+ amount
+ authority {
+ # Account
+ address
+ lamports
+ }
+ destination {
+ # Account
+ ... on TokenAccount {
+ address
+ mint {
+ ... on MintAccount {
+ # Account
address
- lamports
+ decimals
}
- destination {
- ... on TokenAccount {
- data {
- address
- mint {
- ... on MintAccount {
- data {
- address
- decimals
- }
- }
- }
- owner {
- address
- lamports
- }
- }
- }
- }
- source {
- ... on TokenAccount {
- data {
- address
- mint {
- ... on MintAccount {
- data {
- address
- decimals
- }
- }
- }
- owner {
- address
- lamports
- }
- }
- }
+ }
+ owner {
+ # Account
+ address
+ lamports
+ }
+ }
+ }
+ source {
+ # Account
+ ... on TokenAccount {
+ address
+ mint {
+ ... on MintAccount {
+ # Account
+ address
+ decimals
}
}
- meta {
- program
+ owner {
+ # Account
+ address
+ lamports
}
}
}
@@ -969,74 +794,74 @@ const variableValues = {
signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
commitment: 'confirmed',
};
-
+
const result = await rpcGraphQL.query(source, variableValues);
```
```
data: {
transaction: {
- data: {
- message: {
- instructions: [
- {
- data: {
- amount: '50',
- authority: {
- address: 'AHPPMhzDQix9sKULBqeaQ5BUZgrKdz8tg6DzPxsofB12',
- lamports: 890880n,
- },
- destination: {
- data: {
- address: '2W8mUY75zxqwAcpirn75r3Cc7TStMirFyHwKqo13fmB1',
- mint: data: {
- address: '8poKMotB2cEYVv5sbjrdyssASZj1vwYCe7GJFeXo2QP7',
- decimals: 6,
- },
- owner: {
- address: '7tRxJ2znbTFpwW9XaMMiDsXDudoPEUXRcpDpm8qjWgAZ',
- lamports: 890880n,
- },
- }
- },
- source: {
- data: {
- parsed: {
- info: {
- address: 'BqFCPqXUm4cq6jaZZx1TDTvUR1wdEuNNwAHBEVR6mJhM',
- mint: data: {
- address: '8poKMotB2cEYVv5sbjrdyssASZj1vwYCe7GJFeXo2QP7',
- decimals: 6,
- },
- owner: {
- address: '3dPmVLMD7PC5faZNyJUH9WFrUxAsbjydJfoozwmR1wDG',
- lamports: e890880n,
- },
- }
- }
- }
- },
+ message: {
+ instructions: [
+ {
+ amount: '50',
+ authority: {
+ address: 'AHPPMhzDQix9sKULBqeaQ5BUZgrKdz8tg6DzPxsofB12',
+ lamports: 890880n,
+ },
+ destination: {
+ address: '2W8mUY75zxqwAcpirn75r3Cc7TStMirFyHwKqo13fmB1',
+ mint: {
+ address: '8poKMotB2cEYVv5sbjrdyssASZj1vwYCe7GJFeXo2QP7',
+ decimals: 6,
+ },
+ owner: {
+ address: '7tRxJ2znbTFpwW9XaMMiDsXDudoPEUXRcpDpm8qjWgAZ',
+ lamports: 890880n,
+ }
+ },
+ source: {
+ address: 'BqFCPqXUm4cq6jaZZx1TDTvUR1wdEuNNwAHBEVR6mJhM',
+ mint: {
+ address: '8poKMotB2cEYVv5sbjrdyssASZj1vwYCe7GJFeXo2QP7',
+ decimals: 6,
},
- meta: {
- program: 'spl-token',
+ owner: {
+ address: '3dPmVLMD7PC5faZNyJUH9WFrUxAsbjydJfoozwmR1wDG',
+ lamports: e890880n,
}
}
- ]
- },
- },
- },
+ },
+ /* .. */
+ ]
+ }
+ }
}
```
-## Querying Blocks
+## Blocks
-Querying blocks is very similar to querying transactions, since a block
-contains a list of transactions. There's a bit more data at the highest level
-for a block, but you can query the list of transactions using a block query and
-transaction types in the same fashion you can query the lsit of accounts using
-a program accounts query and account types.
+The `Block` type contains common fields across all blocks.
-```typescript
+```graphql
+type Block {
+ blockhash: String
+ blockHeight: BigInt
+ blockTime: BigInt
+ parentSlot: Slot
+ previousBlockhash: String
+ rewards: [Reward]
+ signatures: [Signature]
+ transactions: [Transaction]
+}
+```
+
+Just like the `programAccounts` query will return a list of `Account` types, on
+which you can perform many operations, the `block` query will return a list of
+`Transaction` types, however blocks also contain their own high-level data
+fields, such as `blockhash` and `blockTime`.
+
+```ts
const source = `
query myQuery($slot: BigInt!, $commitment: Commitment) {
block(slot: $slot, commitment: $commitment) {
@@ -1049,20 +874,12 @@ const source = `
rewardType
}
transactions {
- ... on TransactionParsed {
- data {
- message {
- instructions {
- ... on CreateAccountInstruction {
- data {
- lamports
- space
- }
- meta {
- program
- }
- }
- }
+ message {
+ instructions {
+ ... on CreateAccountInstruction {
+ lamports
+ programId
+ space
}
}
}
@@ -1075,7 +892,7 @@ const variableValues = {
slot: 43596n,
commitment: 'confirmed',
};
-
+
const result = await rpcGraphQL.query(source, variableValues);
```
@@ -1098,24 +915,213 @@ data: {
}
],
transactions: [
- data: {
- {
- message: {
- instructions: [
- {
- data: {
- lamports: 890880n,
- space: 0n,
- },
- meta: {
- program: 'system',
- },
- }
- ]
- },
- }
+ {
+ message: {
+ instructions: [
+ {
+ lamports: 890880n,
+ programId: '11111111111111111111111111111111',
+ space: 0n,
+ },
+ /* .. */
+ ]
+ },
}
],
},
}
-```
\ No newline at end of file
+```
+
+# RPC Optimizations
+
+RPC-GraphQL ships highly-optimized use of the Solana JSON RPC out of the box,
+so developers can focus on building dynamic web applications without worrying
+about abusing their RPC endpoint.
+
+The resolver leverages query inspection before making any requests to the RPC,
+in order to determine the most resource-conservative way to vend to your
+application the requested response.
+
+This results in four main benefits:
+
+- Caching
+- Request coalescing
+- Minimized network payloads
+- Batch loading
+
+## Caching
+
+Caching is a fairly standard part of any good GraphQL library, and
+`@solana/rpc-graphql` makes no exception.
+
+If a query contains fetches for the same resource, the resolver can simply
+fetch this information from the cache, ensuring no duplicate RPC requests
+are ever made.
+
+For example, if we were to query for a `MintAccount` and the `mintAuthority`
+also happened to be the mint itself, the following query would ensure we only
+fetch this account once.
+
+```graphql
+query {
+ account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
+ lamports
+ data(encoding: BASE_64)
+ ... on MintAccount {
+ mintAuthority {
+ lamports
+ data(encoding: BASE_64)
+ }
+ }
+ }
+}
+```
+
+## Request Coalescing
+
+Sometimes more than one request can be coalesced into the same request, again
+saving on network round-trips.
+
+In the example below, we're making two queries for the same account, but
+different fields.
+
+```graphql
+query {
+ account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
+ lamports
+ space
+ }
+ account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
+ ... on NonceAccount {
+ authority {
+ address
+ }
+ blockhash
+ feeCalculator {
+ lamportsPerSignature
+ }
+ }
+ }
+}
+```
+
+Rather than requesting this account twice, the resolver will combine these
+two queries into the same RPC request, and then split the response out to the
+corresponding query results.
+
+## Minimized Network Payloads
+
+When it comes to retrieving data from an RPC endpoint, fetching more
+information than you need can be a signifcant waste of network resources, and
+even impact application performance.
+
+The RPC-GraphQL resolver takes steps to minimize this network overhead based on
+the contents of the query provided.
+
+For example, in the following `account` query, we're going to request multiple
+repsonses for `base64` encoded data on the same account.
+
+```graphql
+query {
+ account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
+ firstEightBytes: data(encoding: BASE_64, dataSlice: { length: 8, offset: 0 })
+ nextEightBytes: data(encoding: BASE_64, dataSlice: { length: 8, offset: 8 })
+ anotherEightBytes: data(encoding: BASE_64, dataSlice: { length: 8, offset: 16 })
+ }
+}
+```
+
+To gather this information, a developer may elect for one of two solutions:
+
+1. Call the RPC three times with each data slice. This will result in `3n`
+ requests where `n` is the number of times your application may invoke this
+ query.
+2. Call the RPC once for the data, convert from `base64` to raw bytes, slice
+ the raw bytes, then encode each subset back to `base64`. This requires a lot
+ of overhead on application development.
+
+RPC-GraphQL will perform solution two for you automatically, choosing to save
+on network calls and bytes over the wire in favor of slicing the returned data
+locally.
+
+In fact, the resolver will minimize bytes over the wire by only requesting the
+specific slice of the data that encompasses all requested data slices. In the
+above example, we've requested three ranges of data:
+
+- `0` - `8`
+- `8` - `16`
+- `16` - `24`
+
+If this account has a massive amount of data, fetching more than the query asks
+for would be wasteful. The resolver will only fetch `0` - `24` and slice the
+response to serve the requested query.
+
+## Batch Loading
+
+In some cases, the Solana JSON RPC offers batch loading for certain data types.
+One such example is the RPC methods `getAccountInfo` and `getMultipleAccounts`.
+
+As one might predict, whenever multiple accounts are requested with parameters
+that can be coalesced, one single call to `getMultipleAccounts` can be made.
+
+In the example above from [Request Coalescing](#request-coalescing), let's
+simply change the query to request two different accounts.
+
+```graphql
+query {
+ account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
+ lamports
+ space
+ }
+ account(address: "EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b") {
+ ... on NonceAccount {
+ authority {
+ address
+ }
+ blockhash
+ feeCalculator {
+ lamportsPerSignature
+ }
+ }
+ }
+}
+```
+
+Now the resolver would recognize the distinction between the two accounts, but
+it would still see the ability to coalesce request parameters. As a result,
+RPC-GraphQL would make one call to `getMultipleAccounts` as follows.
+
+```ts
+rpc.getMultipleAccounts([
+ 'J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ',
+ 'EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b',
+]);
+```
+
+This batch loading can work in conjunction with the other forms of
+optimization as well, such as minimized network payloads.
+
+```graphql
+query {
+ account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
+ data(encoding: BASE_64, dataSlice: { length: 32, offset: 0 })
+ }
+ account(address: "EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b") {
+ authorityData: data(encoding: BASE_64, dataSlice: { length: 32, offset: 0 })
+ u64Data: data(encoding: BASE_64, dataSlice: { length: 8, offset: 32 })
+ }
+}
+```
+
+```ts
+rpc.getMultipleAccounts(
+ ['J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ', 'EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b'],
+ {
+ encoding: 'base64',
+ dataSlice: { length: 40, offset: 0 },
+ },
+);
+```
+
+In this case the resolver would ensure the proper data slices are dealt out
+from the single `getMultipleAccounts` response.