@solana/rpc-graphql 2.0.0-20241006045741
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +1127 -0
- package/dist/index.browser.cjs +5169 -0
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.mjs +5159 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.native.mjs +5159 -0
- package/dist/index.native.mjs.map +1 -0
- package/dist/index.node.cjs +5169 -0
- package/dist/index.node.cjs.map +1 -0
- package/dist/index.node.mjs +5159 -0
- package/dist/index.node.mjs.map +1 -0
- package/dist/types/context.d.ts +20 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/index.d.ts +29 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/loaders/account.d.ts +14 -0
- package/dist/types/loaders/account.d.ts.map +1 -0
- package/dist/types/loaders/block.d.ts +4 -0
- package/dist/types/loaders/block.d.ts.map +1 -0
- package/dist/types/loaders/coalescer.d.ts +55 -0
- package/dist/types/loaders/coalescer.d.ts.map +1 -0
- package/dist/types/loaders/index.d.ts +6 -0
- package/dist/types/loaders/index.d.ts.map +1 -0
- package/dist/types/loaders/loader.d.ts +85 -0
- package/dist/types/loaders/loader.d.ts.map +1 -0
- package/dist/types/loaders/program-accounts.d.ts +8 -0
- package/dist/types/loaders/program-accounts.d.ts.map +1 -0
- package/dist/types/loaders/transaction.d.ts +4 -0
- package/dist/types/loaders/transaction.d.ts.map +1 -0
- package/dist/types/resolvers/account.d.ts +36 -0
- package/dist/types/resolvers/account.d.ts.map +1 -0
- package/dist/types/resolvers/block.d.ts +68 -0
- package/dist/types/resolvers/block.d.ts.map +1 -0
- package/dist/types/resolvers/program-accounts.d.ts +21 -0
- package/dist/types/resolvers/program-accounts.d.ts.map +1 -0
- package/dist/types/resolvers/resolve-info/account.d.ts +15 -0
- package/dist/types/resolvers/resolve-info/account.d.ts.map +1 -0
- package/dist/types/resolvers/resolve-info/block.d.ts +13 -0
- package/dist/types/resolvers/resolve-info/block.d.ts.map +1 -0
- package/dist/types/resolvers/resolve-info/index.d.ts +6 -0
- package/dist/types/resolvers/resolve-info/index.d.ts.map +1 -0
- package/dist/types/resolvers/resolve-info/program-accounts.d.ts +23 -0
- package/dist/types/resolvers/resolve-info/program-accounts.d.ts.map +1 -0
- package/dist/types/resolvers/resolve-info/transaction.d.ts +15 -0
- package/dist/types/resolvers/resolve-info/transaction.d.ts.map +1 -0
- package/dist/types/resolvers/resolve-info/visitor.d.ts +18 -0
- package/dist/types/resolvers/resolve-info/visitor.d.ts.map +1 -0
- package/dist/types/resolvers/transaction.d.ts +38 -0
- package/dist/types/resolvers/transaction.d.ts.map +1 -0
- package/dist/types/schema/type-defs/account.d.ts +2 -0
- package/dist/types/schema/type-defs/account.d.ts.map +1 -0
- package/dist/types/schema/type-defs/block.d.ts +2 -0
- package/dist/types/schema/type-defs/block.d.ts.map +1 -0
- package/dist/types/schema/type-defs/index.d.ts +7 -0
- package/dist/types/schema/type-defs/index.d.ts.map +1 -0
- package/dist/types/schema/type-defs/instruction.d.ts +2 -0
- package/dist/types/schema/type-defs/instruction.d.ts.map +1 -0
- package/dist/types/schema/type-defs/root.d.ts +2 -0
- package/dist/types/schema/type-defs/root.d.ts.map +1 -0
- package/dist/types/schema/type-defs/transaction.d.ts +2 -0
- package/dist/types/schema/type-defs/transaction.d.ts.map +1 -0
- package/dist/types/schema/type-defs/types.d.ts +2 -0
- package/dist/types/schema/type-defs/types.d.ts.map +1 -0
- package/dist/types/schema/type-resolvers/account.d.ts +576 -0
- package/dist/types/schema/type-resolvers/account.d.ts.map +1 -0
- package/dist/types/schema/type-resolvers/block.d.ts +7 -0
- package/dist/types/schema/type-resolvers/block.d.ts.map +1 -0
- package/dist/types/schema/type-resolvers/index.d.ts +8 -0
- package/dist/types/schema/type-resolvers/index.d.ts.map +1 -0
- package/dist/types/schema/type-resolvers/instruction.d.ts +3549 -0
- package/dist/types/schema/type-resolvers/instruction.d.ts.map +1 -0
- package/dist/types/schema/type-resolvers/root.d.ts +3 -0
- package/dist/types/schema/type-resolvers/root.d.ts.map +1 -0
- package/dist/types/schema/type-resolvers/transaction.d.ts +8 -0
- package/dist/types/schema/type-resolvers/transaction.d.ts.map +1 -0
- package/dist/types/schema/type-resolvers/types.d.ts +154 -0
- package/dist/types/schema/type-resolvers/types.d.ts.map +1 -0
- package/package.json +90 -0
package/README.md
ADDED
|
@@ -0,0 +1,1127 @@
|
|
|
1
|
+
# @solana/rpc-graphql
|
|
2
|
+
|
|
3
|
+
This package defines a GraphQL client resolver built on top of the
|
|
4
|
+
[Solana JSON-RPC](https://docs.solana.com/api/http).
|
|
5
|
+
|
|
6
|
+
A client resolver in this context is simply a client-side RPC interface
|
|
7
|
+
designed to give application developers the ability to use GraphQL to interact
|
|
8
|
+
with data on the Solana blockchain.
|
|
9
|
+
|
|
10
|
+
The resolver presents developers with a new schema for working with Solana data
|
|
11
|
+
(see [Schema](#schema)), as well as new features only possible with GraphQL.
|
|
12
|
+
Additionally, the resolver is designed to make highly-optimized use of the
|
|
13
|
+
Solana JSON RPC, balancing RPC requests, batch loading, and caching
|
|
14
|
+
(see [RPC Optimizations](#rpc-optimizations)).
|
|
15
|
+
|
|
16
|
+
GraphQL is a query language for your API, and a server-side runtime for
|
|
17
|
+
executing queries using a type system you define for your data.
|
|
18
|
+
|
|
19
|
+
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/1024px-GraphQL_Logo.svg.png?20161105194737" alt="graphql-icon" width="24" align="center"/> [**GraphQL**](https://graphql.org/learn/)
|
|
20
|
+
|
|
21
|
+
# Quick Start
|
|
22
|
+
|
|
23
|
+
The RPC-GraphQL client requires an RPC client, as defined by the package
|
|
24
|
+
`@solana/rpc-spec`. Such a client is available in `@solana/web3.js:2.0` or
|
|
25
|
+
can be created manually with a custom implementation.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
Rpc<TRpcMethods>;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The RPC-GraphQL requires an RPC client with the following API methods available
|
|
32
|
+
for use in order to properly execute all queries.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
Rpc<GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi>;
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To initialize the RPC-GraphQL client, simple use `createSolanaRpcGraphQL`.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { createSolanaRpc } from '@solana/rpc';
|
|
42
|
+
|
|
43
|
+
// Create the RPC client
|
|
44
|
+
const rpc = createSolanaRpc('https://api.devnet.solana.com');
|
|
45
|
+
|
|
46
|
+
// Create the RPC-GraphQL client
|
|
47
|
+
const rpcGraphQL = createSolanaRpcGraphQL(rpc);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The `RpcGraphQL` type supports one method `query` which accepts a string
|
|
51
|
+
query source and an optional `variableValues` parameter - which is an object
|
|
52
|
+
containing any variables to pipe into the query string.
|
|
53
|
+
|
|
54
|
+
You can define queries with hard-coded parameters.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const source = `
|
|
58
|
+
query myQuery {
|
|
59
|
+
account(address: "AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca") {
|
|
60
|
+
lamports
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
const result = await rpcGraphQL.query(source);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
data: {
|
|
70
|
+
account: {
|
|
71
|
+
lamports: 10290815n,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
You can also pass the variable values.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const source = `
|
|
80
|
+
query myQuery($address: String!) {
|
|
81
|
+
account(address: $address) {
|
|
82
|
+
lamports
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
const variableValues = {
|
|
88
|
+
address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
data: {
|
|
96
|
+
account: {
|
|
97
|
+
lamports: 10290815n,
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Queries with variable values can also be re-used!
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const source = `
|
|
106
|
+
query myQuery($address: String!) {
|
|
107
|
+
account(address: $address) {
|
|
108
|
+
lamports
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
const lamportsAccountA = await rpcGraphQL.query(source, {
|
|
114
|
+
address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const lamportsAccountB = await rpcGraphQL.query(source, {
|
|
118
|
+
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
# Schema
|
|
123
|
+
|
|
124
|
+
Solana data can be categorized into three main types:
|
|
125
|
+
|
|
126
|
+
- Accounts
|
|
127
|
+
- Transactions
|
|
128
|
+
- Blocks
|
|
129
|
+
|
|
130
|
+
These types encompass everything that can be queried from the Solana ledger.
|
|
131
|
+
|
|
132
|
+
## Accounts
|
|
133
|
+
|
|
134
|
+
The `Account` interface contains common fields across all accounts.
|
|
135
|
+
|
|
136
|
+
```graphql
|
|
137
|
+
interface Account {
|
|
138
|
+
address: Address
|
|
139
|
+
data(encoding: AccountEncoding!, dataSlice: DataSlice): String
|
|
140
|
+
executable: Boolean
|
|
141
|
+
lamports: BigInt
|
|
142
|
+
ownerProgram: Account
|
|
143
|
+
space: BigInt
|
|
144
|
+
rentEpoch: BigInt
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Any account can be queried by these fields without specifying the specific
|
|
149
|
+
account type.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const source = `
|
|
153
|
+
query myQuery($address: String!) {
|
|
154
|
+
account(address: $address) {
|
|
155
|
+
executable
|
|
156
|
+
lamports
|
|
157
|
+
rentEpoch
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
const variableValues = {
|
|
163
|
+
address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
data: {
|
|
171
|
+
account: {
|
|
172
|
+
executable: false,
|
|
173
|
+
lamports: 10290815n,
|
|
174
|
+
rentEpoch: 0n,
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Querying Account Data
|
|
180
|
+
|
|
181
|
+
Querying accounts by their encoded data (`base58`, `base64`, `base64+zstd`) is
|
|
182
|
+
still fully supported.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
const source = `
|
|
186
|
+
query myQuery($address: String!) {
|
|
187
|
+
account(address: $address) {
|
|
188
|
+
data(encoding: BASE_64)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
192
|
+
|
|
193
|
+
const variableValues = {
|
|
194
|
+
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
data: {
|
|
202
|
+
account: {
|
|
203
|
+
data: 'dGVzdCBkYXRh',
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Querying Specific Account Types
|
|
209
|
+
|
|
210
|
+
A set of specific parsed account types are supported in GraphQL.
|
|
211
|
+
|
|
212
|
+
- `GenericAccount`: A generic base account type
|
|
213
|
+
- `NonceAccount`: A nonce account
|
|
214
|
+
- `LookupTableAccount`: An address lookup table account
|
|
215
|
+
- `MintAccount`: An SPL mint
|
|
216
|
+
- `TokenAccount`: An SPL token account
|
|
217
|
+
- `StakeAccount`: A stake account
|
|
218
|
+
- `VoteAccount`: A vote account
|
|
219
|
+
|
|
220
|
+
You can choose how to handle querying of specific account types. For example,
|
|
221
|
+
you might _only_ want specifically any account that matches `MintAccount`.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const maybeMintAddresses = [
|
|
225
|
+
'J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ',
|
|
226
|
+
'JAbWqZ7S2c6jomQr8ofAYBo257bE1QJtHwbX1yWc2osZ',
|
|
227
|
+
'2AQ4CSNu6zNUZsUq4aLNUSjyrLv4qFFXQuKs5RTHbg2Y',
|
|
228
|
+
'EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b',
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
const mintAccounts = [];
|
|
232
|
+
|
|
233
|
+
const source = `
|
|
234
|
+
query myQuery($address: String!) {
|
|
235
|
+
account(address: $address) {
|
|
236
|
+
... on MintAccount {
|
|
237
|
+
data {
|
|
238
|
+
decimals
|
|
239
|
+
isInitialized
|
|
240
|
+
mintAuthority
|
|
241
|
+
supply
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
for (const address of maybeMintAddresses) {
|
|
249
|
+
const result = await rpcGraphQL.query(source, { address });
|
|
250
|
+
if (result != null) {
|
|
251
|
+
const {
|
|
252
|
+
data: {
|
|
253
|
+
account: { data: mintInfo },
|
|
254
|
+
},
|
|
255
|
+
} = result;
|
|
256
|
+
mintAccounts.push(mintInfo);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Maybe you want to handle both mints _and_ token accounts.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
const mintOrTokenAccountAddresses = [
|
|
265
|
+
'J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ',
|
|
266
|
+
'JAbWqZ7S2c6jomQr8ofAYBo257bE1QJtHwbX1yWc2osZ',
|
|
267
|
+
'2AQ4CSNu6zNUZsUq4aLNUSjyrLv4qFFXQuKs5RTHbg2Y',
|
|
268
|
+
'EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b',
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
const mintAccounts = [];
|
|
272
|
+
const tokenAccounts = [];
|
|
273
|
+
|
|
274
|
+
const source = `
|
|
275
|
+
query myQuery($address: String!) {
|
|
276
|
+
account(address: $address) {
|
|
277
|
+
... on MintAccount {
|
|
278
|
+
__typename
|
|
279
|
+
decimals
|
|
280
|
+
isInitialized
|
|
281
|
+
supply
|
|
282
|
+
}
|
|
283
|
+
... on TokenAccount {
|
|
284
|
+
__typename
|
|
285
|
+
isNative
|
|
286
|
+
mint
|
|
287
|
+
state
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
`;
|
|
292
|
+
|
|
293
|
+
for (const address of mintOrTokenAccountAddresses) {
|
|
294
|
+
const result = await rpcGraphQL.query(source, { address });
|
|
295
|
+
if (result != null) {
|
|
296
|
+
const {
|
|
297
|
+
data: { account: accountParsedData },
|
|
298
|
+
} = result;
|
|
299
|
+
if (accountParsedData.__typename === 'MintAccount') {
|
|
300
|
+
mintAccounts.push(accountParsedInfo);
|
|
301
|
+
} else {
|
|
302
|
+
tokenAccounts.push(accountParsedInfo);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Querying Program Accounts
|
|
309
|
+
|
|
310
|
+
Another account-based query that can be performed with RPC-GraphQL is the
|
|
311
|
+
`programAccounts` query. The response will be a list of `Account` types as
|
|
312
|
+
defined above.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
const source = `
|
|
316
|
+
query myQuery($programAddress: String!) {
|
|
317
|
+
programAccounts(programAddress: $address) {
|
|
318
|
+
executable
|
|
319
|
+
lamports
|
|
320
|
+
rentEpoch
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
`;
|
|
324
|
+
|
|
325
|
+
const variableValues = {
|
|
326
|
+
programAddress: 'AmtpVzo6H6qQCP9dH9wfu5hfa8kKaAFpTJ4aamPYR6V6',
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
data: {
|
|
334
|
+
programAccounts: [
|
|
335
|
+
{
|
|
336
|
+
executable: false,
|
|
337
|
+
lamports: 10290815n,
|
|
338
|
+
rentEpoch: 0n,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
executable: false,
|
|
342
|
+
lamports: 10290815n,
|
|
343
|
+
rentEpoch: 0n,
|
|
344
|
+
},
|
|
345
|
+
/* .. */
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Account data encoding in `base58`, `base64`, and `base64+zstd` is also
|
|
351
|
+
supported with this query, as well as `dataSlice` and `filter`.
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
const source = `
|
|
355
|
+
query myQuery($programAddress: String!) {
|
|
356
|
+
programAccounts(programAddress: $programAddress) {
|
|
357
|
+
data(encoding: BASE_64, dataSlice: { length: 5, offset: 0 })
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
`;
|
|
361
|
+
|
|
362
|
+
const variableValues = {
|
|
363
|
+
programAddress: 'DXngmJfjurhnAwbMPgpUGPH6qNvetCKRJ6PiD4ag4PTj',
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
data: {
|
|
371
|
+
programAccounts: [
|
|
372
|
+
{
|
|
373
|
+
data: 'dGVzdCA=',
|
|
374
|
+
},
|
|
375
|
+
/* .. */
|
|
376
|
+
],
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Although specific parsed account types are directly tied to the program which
|
|
381
|
+
owns them, it's still possible to handle various specific account types within
|
|
382
|
+
the same program accounts response.
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
const source = `
|
|
386
|
+
query myQuery($programAddress: String!) {
|
|
387
|
+
programAccounts(programAddress: $address) {
|
|
388
|
+
... on MintAccount {
|
|
389
|
+
__typename
|
|
390
|
+
decimals
|
|
391
|
+
isInitialized
|
|
392
|
+
mintAuthority
|
|
393
|
+
supply
|
|
394
|
+
}
|
|
395
|
+
... on TokenAccount {
|
|
396
|
+
__typename
|
|
397
|
+
isNative
|
|
398
|
+
mint
|
|
399
|
+
owner
|
|
400
|
+
state
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
`;
|
|
405
|
+
|
|
406
|
+
const variableValues = {
|
|
407
|
+
programAddress: 'AmtpVzo6H6qQCP9dH9wfu5hfa8kKaAFpTJ4aamPYR6V6',
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
411
|
+
|
|
412
|
+
const { mints, tokenAccounts } = result.data.programAccounts.reduce(
|
|
413
|
+
(acc: { mints: any[]; tokenAccounts: any[] }, account) => {
|
|
414
|
+
if (account.__typename === 'MintAccount') {
|
|
415
|
+
acc.mints.push(accountParsedInfo);
|
|
416
|
+
} else {
|
|
417
|
+
acc.tokenAccounts.push(accountParsedInfo);
|
|
418
|
+
}
|
|
419
|
+
return acc;
|
|
420
|
+
},
|
|
421
|
+
{ mints: [], tokenAccounts: [] },
|
|
422
|
+
);
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Nested Account Queries
|
|
426
|
+
|
|
427
|
+
Notice the `owner` field of the `Account` interface is also an `Account`
|
|
428
|
+
interface. This powers nested queries against the `owner` field of an account.
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
const source = `
|
|
432
|
+
query myQuery($address: String!) {
|
|
433
|
+
account(address: $address) {
|
|
434
|
+
address
|
|
435
|
+
owner {
|
|
436
|
+
address
|
|
437
|
+
executable
|
|
438
|
+
lamports
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
`;
|
|
443
|
+
|
|
444
|
+
const variableValues = {
|
|
445
|
+
address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
```
|
|
452
|
+
data: {
|
|
453
|
+
account: {
|
|
454
|
+
address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
|
|
455
|
+
owner: {
|
|
456
|
+
address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
|
|
457
|
+
executable: true,
|
|
458
|
+
lamports: 10290815n,
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
As you can see, simply defining a nested query with RPC-GraphQL will augment
|
|
465
|
+
the multiple RPC calls and parsing code required to gather the necessary
|
|
466
|
+
information!
|
|
467
|
+
|
|
468
|
+
You can nest queries as far as you want!
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
const source = `
|
|
472
|
+
query myQuery($address: String!) {
|
|
473
|
+
account(address: $address) {
|
|
474
|
+
address
|
|
475
|
+
owner {
|
|
476
|
+
address
|
|
477
|
+
owner {
|
|
478
|
+
address
|
|
479
|
+
owner {
|
|
480
|
+
address
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
`;
|
|
487
|
+
|
|
488
|
+
const variableValues = {
|
|
489
|
+
address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
```
|
|
496
|
+
data: {
|
|
497
|
+
account: {
|
|
498
|
+
address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
|
|
499
|
+
owner: {
|
|
500
|
+
address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
|
|
501
|
+
owner: {
|
|
502
|
+
address: 'BPFLoader2111111111111111111111111111111111',
|
|
503
|
+
owner: {
|
|
504
|
+
address: 'NativeLoader1111111111111111111111111111111',
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Nested queries can also be applied to specific account types.
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
const source = `
|
|
516
|
+
query myQuery($address: String!) {
|
|
517
|
+
account(address: $address) {
|
|
518
|
+
... on MintAccount {
|
|
519
|
+
address
|
|
520
|
+
data {
|
|
521
|
+
mintAuthority {
|
|
522
|
+
address
|
|
523
|
+
lamports
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
`;
|
|
530
|
+
|
|
531
|
+
const variableValues = {
|
|
532
|
+
address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
```
|
|
539
|
+
data: {
|
|
540
|
+
account: {
|
|
541
|
+
address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
|
|
542
|
+
data: {
|
|
543
|
+
mintAuthority: {
|
|
544
|
+
address: 'DpfJkNonoVB3sor9H9ceajhex4XHVPrDAGAq2ahdG4JZ',
|
|
545
|
+
lamports: 10290815n,
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Nested account queries are also supported on `programAccounts` queries.
|
|
553
|
+
|
|
554
|
+
## Transactions
|
|
555
|
+
|
|
556
|
+
The `Transaction` type contains common fields across all transactions.
|
|
557
|
+
|
|
558
|
+
```graphql
|
|
559
|
+
type Transaction {
|
|
560
|
+
blockTime: BigInt
|
|
561
|
+
data(encoding: TransactionEncoding!): String
|
|
562
|
+
message: TransactionMessage
|
|
563
|
+
meta: TransactionMeta
|
|
564
|
+
signatures: [Signature]
|
|
565
|
+
slot: Slot
|
|
566
|
+
version: String
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Note that unlike accounts, the `Transaction` type is not an interface, so the
|
|
571
|
+
base response type of a transaction query remains constant. However, the list
|
|
572
|
+
of instructions contained in a parsed transaction are returned as the
|
|
573
|
+
`TransactionInstruction` interface, which can be queried by specific type.
|
|
574
|
+
See [Querying Specific Transaction Instruction Types](#querying-specific-transaction-instruction-types).
|
|
575
|
+
|
|
576
|
+
```graphql
|
|
577
|
+
interface TransactionInstruction {
|
|
578
|
+
programId: Address
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
A transaction can be queried by the `transaction` query.
|
|
583
|
+
|
|
584
|
+
```ts
|
|
585
|
+
const source = `
|
|
586
|
+
query myQuery($signature: String!) {
|
|
587
|
+
transaction(signature: $signature) {
|
|
588
|
+
blockTime
|
|
589
|
+
meta {
|
|
590
|
+
computeUnitsConsumed
|
|
591
|
+
logMessages
|
|
592
|
+
}
|
|
593
|
+
slot
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
`;
|
|
597
|
+
|
|
598
|
+
const variableValues = {
|
|
599
|
+
signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
```
|
|
606
|
+
data: {
|
|
607
|
+
transaction: {
|
|
608
|
+
blockTime: 230860412n,
|
|
609
|
+
meta: {
|
|
610
|
+
computeUnitsConsumed: 120000n,
|
|
611
|
+
logMessages: [
|
|
612
|
+
"Program 8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz invoke [1]",
|
|
613
|
+
"Program 8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz consumed 2164 of 452155 compute units",
|
|
614
|
+
"Program 8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz success",
|
|
615
|
+
"Program ComputeBudget111111111111111111111111111111 invoke [1]",
|
|
616
|
+
"Program ComputeBudget111111111111111111111111111111 success"
|
|
617
|
+
]
|
|
618
|
+
},
|
|
619
|
+
slot: 230860693n,
|
|
620
|
+
},
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Querying Transaction Data
|
|
625
|
+
|
|
626
|
+
Querying encoded transaction data (`base58`, `base64`) is fully supported.
|
|
627
|
+
|
|
628
|
+
```ts
|
|
629
|
+
const source = `
|
|
630
|
+
query myQuery($signature: String!, $commitment: Commitment) {
|
|
631
|
+
transaction(signature: $signature, commitment: $commitment) {
|
|
632
|
+
data(encoding: BASE_64)
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
`;
|
|
636
|
+
|
|
637
|
+
const variableValues = {
|
|
638
|
+
signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
|
|
639
|
+
commitment: 'confirmed',
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
```
|
|
646
|
+
{
|
|
647
|
+
"data": {
|
|
648
|
+
"transaction": {
|
|
649
|
+
"data": "AbgFjqLTBtoAaHXexSN1OYXf+UNox6qe3JcyCmEwE57iUHxCkHp8zKTJVznd6nLtUFNMYJWHCtMb+yPjk7QIxAQBAAEDeXJtpS2Z1gsH6tc7L28L9gg8yFx3qU401pHXj4vK/skUvD7y/LnapfCdSx3FfTguy49UDQVvGgOK0ix/P42YuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5uE0ATTHEABQrIB1+aoEdYJxvQthXPLHFxSH2y+ACK4BAgIAAQwCAAAAAMqaOwAAAAA="
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### Querying Specific Transaction Instruction Types
|
|
656
|
+
|
|
657
|
+
As mentioned above, parsed transactions return a list of instructions that
|
|
658
|
+
implement the `TransactionInstruction` interface. These instructions can be
|
|
659
|
+
queried by specific instruction types.
|
|
660
|
+
|
|
661
|
+
Instructions for the following programs are supported.
|
|
662
|
+
|
|
663
|
+
- Address Lookup Table
|
|
664
|
+
- BPF Loader
|
|
665
|
+
- BPF Upgradeable Loader
|
|
666
|
+
- Stake
|
|
667
|
+
- SPL Associated Token
|
|
668
|
+
- SPL Memo
|
|
669
|
+
- SPL Token
|
|
670
|
+
- System
|
|
671
|
+
- Vote
|
|
672
|
+
|
|
673
|
+
Additionally, the `GenericInstruction` type is the base parsed instruction type.
|
|
674
|
+
|
|
675
|
+
```graphql
|
|
676
|
+
type GenericInstruction implements TransactionInstruction {
|
|
677
|
+
accounts: [Address]
|
|
678
|
+
data: Base64EncodedBytes
|
|
679
|
+
programId: Address
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
Specific transaction instruction types can be queried within a `Transaction`
|
|
684
|
+
response like so.
|
|
685
|
+
|
|
686
|
+
```ts
|
|
687
|
+
const source = `
|
|
688
|
+
query myQuery($signature: String!, $commitment: Commitment) {
|
|
689
|
+
transaction(signature: $signature, commitment: $commitment) {
|
|
690
|
+
message {
|
|
691
|
+
instructions {
|
|
692
|
+
... on CreateAccountInstruction {
|
|
693
|
+
lamports
|
|
694
|
+
programId
|
|
695
|
+
space
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
`;
|
|
702
|
+
|
|
703
|
+
const variableValues = {
|
|
704
|
+
signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
|
|
705
|
+
commitment: 'confirmed',
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
```
|
|
712
|
+
data: {
|
|
713
|
+
transaction: {
|
|
714
|
+
message: {
|
|
715
|
+
instructions: [
|
|
716
|
+
{
|
|
717
|
+
lamports: 890880n,
|
|
718
|
+
programId: '11111111111111111111111111111111',
|
|
719
|
+
space: 0n,
|
|
720
|
+
},
|
|
721
|
+
/* .. */
|
|
722
|
+
]
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Nested Transaction Queries
|
|
729
|
+
|
|
730
|
+
Since transactions have a relatively large number of data points, they are
|
|
731
|
+
particularly useful for nested queries!
|
|
732
|
+
|
|
733
|
+
Similar to nested querying accounts, it's possible to nest queries inside your
|
|
734
|
+
transaction queries to look up other objects, such as accounts, as they appear
|
|
735
|
+
in the transaction response.
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
const source = `
|
|
739
|
+
query myQuery($signature: String!, $commitment: Commitment) {
|
|
740
|
+
transaction(signature: $signature, commitment: $commitment) {
|
|
741
|
+
message {
|
|
742
|
+
instructions {
|
|
743
|
+
... on SplTokenTransferInstruction {
|
|
744
|
+
amount
|
|
745
|
+
authority {
|
|
746
|
+
# Account
|
|
747
|
+
address
|
|
748
|
+
lamports
|
|
749
|
+
}
|
|
750
|
+
destination {
|
|
751
|
+
# Account
|
|
752
|
+
... on TokenAccount {
|
|
753
|
+
address
|
|
754
|
+
mint {
|
|
755
|
+
... on MintAccount {
|
|
756
|
+
# Account
|
|
757
|
+
address
|
|
758
|
+
decimals
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
owner {
|
|
762
|
+
# Account
|
|
763
|
+
address
|
|
764
|
+
lamports
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
source {
|
|
769
|
+
# Account
|
|
770
|
+
... on TokenAccount {
|
|
771
|
+
address
|
|
772
|
+
mint {
|
|
773
|
+
... on MintAccount {
|
|
774
|
+
# Account
|
|
775
|
+
address
|
|
776
|
+
decimals
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
owner {
|
|
780
|
+
# Account
|
|
781
|
+
address
|
|
782
|
+
lamports
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
`;
|
|
792
|
+
|
|
793
|
+
const variableValues = {
|
|
794
|
+
signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
|
|
795
|
+
commitment: 'confirmed',
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
```
|
|
802
|
+
data: {
|
|
803
|
+
transaction: {
|
|
804
|
+
message: {
|
|
805
|
+
instructions: [
|
|
806
|
+
{
|
|
807
|
+
amount: '50',
|
|
808
|
+
authority: {
|
|
809
|
+
address: 'AHPPMhzDQix9sKULBqeaQ5BUZgrKdz8tg6DzPxsofB12',
|
|
810
|
+
lamports: 890880n,
|
|
811
|
+
},
|
|
812
|
+
destination: {
|
|
813
|
+
address: '2W8mUY75zxqwAcpirn75r3Cc7TStMirFyHwKqo13fmB1',
|
|
814
|
+
mint: {
|
|
815
|
+
address: '8poKMotB2cEYVv5sbjrdyssASZj1vwYCe7GJFeXo2QP7',
|
|
816
|
+
decimals: 6,
|
|
817
|
+
},
|
|
818
|
+
owner: {
|
|
819
|
+
address: '7tRxJ2znbTFpwW9XaMMiDsXDudoPEUXRcpDpm8qjWgAZ',
|
|
820
|
+
lamports: 890880n,
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
source: {
|
|
824
|
+
address: 'BqFCPqXUm4cq6jaZZx1TDTvUR1wdEuNNwAHBEVR6mJhM',
|
|
825
|
+
mint: {
|
|
826
|
+
address: '8poKMotB2cEYVv5sbjrdyssASZj1vwYCe7GJFeXo2QP7',
|
|
827
|
+
decimals: 6,
|
|
828
|
+
},
|
|
829
|
+
owner: {
|
|
830
|
+
address: '3dPmVLMD7PC5faZNyJUH9WFrUxAsbjydJfoozwmR1wDG',
|
|
831
|
+
lamports: e890880n,
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
/* .. */
|
|
836
|
+
]
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
## Blocks
|
|
843
|
+
|
|
844
|
+
The `Block` type contains common fields across all blocks.
|
|
845
|
+
|
|
846
|
+
```graphql
|
|
847
|
+
type Block {
|
|
848
|
+
blockhash: String
|
|
849
|
+
blockHeight: BigInt
|
|
850
|
+
blockTime: BigInt
|
|
851
|
+
parentSlot: Slot
|
|
852
|
+
previousBlockhash: String
|
|
853
|
+
rewards: [Reward]
|
|
854
|
+
signatures: [Signature]
|
|
855
|
+
transactions: [Transaction]
|
|
856
|
+
}
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
Just like the `programAccounts` query will return a list of `Account` types, on
|
|
860
|
+
which you can perform many operations, the `block` query will return a list of
|
|
861
|
+
`Transaction` types, however blocks also contain their own high-level data
|
|
862
|
+
fields, such as `blockhash` and `blockTime`.
|
|
863
|
+
|
|
864
|
+
```ts
|
|
865
|
+
const source = `
|
|
866
|
+
query myQuery($slot: BigInt!, $commitment: Commitment) {
|
|
867
|
+
block(slot: $slot, commitment: $commitment) {
|
|
868
|
+
blockHeight
|
|
869
|
+
blockhash
|
|
870
|
+
parentSlot
|
|
871
|
+
rewards {
|
|
872
|
+
commission
|
|
873
|
+
lamports
|
|
874
|
+
rewardType
|
|
875
|
+
}
|
|
876
|
+
transactions {
|
|
877
|
+
message {
|
|
878
|
+
instructions {
|
|
879
|
+
... on CreateAccountInstruction {
|
|
880
|
+
lamports
|
|
881
|
+
programId
|
|
882
|
+
space
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
`;
|
|
890
|
+
|
|
891
|
+
const variableValues = {
|
|
892
|
+
slot: 43596n,
|
|
893
|
+
commitment: 'confirmed',
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
const result = await rpcGraphQL.query(source, variableValues);
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
```
|
|
900
|
+
data: {
|
|
901
|
+
block: {
|
|
902
|
+
blockHeight: 196758578n,
|
|
903
|
+
blockhash: 'BqFCPqXUm4cq6jaZZx1TDTvUR1wdEuNNwAHBEVR6mJhM',
|
|
904
|
+
parentSlot: 230862408n,
|
|
905
|
+
rewards: [
|
|
906
|
+
{
|
|
907
|
+
commission: 0.05,
|
|
908
|
+
lamports: 58578n,
|
|
909
|
+
rewardType: 'staking',
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
commission: 0.05,
|
|
913
|
+
lamports: 58578n,
|
|
914
|
+
rewardType: 'staking',
|
|
915
|
+
}
|
|
916
|
+
],
|
|
917
|
+
transactions: [
|
|
918
|
+
{
|
|
919
|
+
message: {
|
|
920
|
+
instructions: [
|
|
921
|
+
{
|
|
922
|
+
lamports: 890880n,
|
|
923
|
+
programId: '11111111111111111111111111111111',
|
|
924
|
+
space: 0n,
|
|
925
|
+
},
|
|
926
|
+
/* .. */
|
|
927
|
+
]
|
|
928
|
+
},
|
|
929
|
+
}
|
|
930
|
+
],
|
|
931
|
+
},
|
|
932
|
+
}
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
# RPC Optimizations
|
|
936
|
+
|
|
937
|
+
RPC-GraphQL ships highly-optimized use of the Solana JSON RPC out of the box,
|
|
938
|
+
so developers can focus on building dynamic web applications without worrying
|
|
939
|
+
about abusing their RPC endpoint.
|
|
940
|
+
|
|
941
|
+
The resolver leverages query inspection before making any requests to the RPC,
|
|
942
|
+
in order to determine the most resource-conservative way to vend to your
|
|
943
|
+
application the requested response.
|
|
944
|
+
|
|
945
|
+
This results in four main benefits:
|
|
946
|
+
|
|
947
|
+
- Caching
|
|
948
|
+
- Request coalescing
|
|
949
|
+
- Minimized network payloads
|
|
950
|
+
- Batch loading
|
|
951
|
+
|
|
952
|
+
## Caching
|
|
953
|
+
|
|
954
|
+
Caching is a fairly standard part of any good GraphQL library, and
|
|
955
|
+
`@solana/rpc-graphql` makes no exception.
|
|
956
|
+
|
|
957
|
+
If a query contains fetches for the same resource, the resolver can simply
|
|
958
|
+
fetch this information from the cache, ensuring no duplicate RPC requests
|
|
959
|
+
are ever made.
|
|
960
|
+
|
|
961
|
+
For example, if we were to query for a `MintAccount` and the `mintAuthority`
|
|
962
|
+
also happened to be the mint itself, the following query would ensure we only
|
|
963
|
+
fetch this account once.
|
|
964
|
+
|
|
965
|
+
```graphql
|
|
966
|
+
query {
|
|
967
|
+
account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
|
|
968
|
+
lamports
|
|
969
|
+
data(encoding: BASE_64)
|
|
970
|
+
... on MintAccount {
|
|
971
|
+
mintAuthority {
|
|
972
|
+
lamports
|
|
973
|
+
data(encoding: BASE_64)
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
## Request Coalescing
|
|
981
|
+
|
|
982
|
+
Sometimes more than one request can be coalesced into the same request, again
|
|
983
|
+
saving on network round-trips.
|
|
984
|
+
|
|
985
|
+
In the example below, we're making two queries for the same account, but
|
|
986
|
+
different fields.
|
|
987
|
+
|
|
988
|
+
```graphql
|
|
989
|
+
query {
|
|
990
|
+
account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
|
|
991
|
+
lamports
|
|
992
|
+
space
|
|
993
|
+
}
|
|
994
|
+
account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
|
|
995
|
+
... on NonceAccount {
|
|
996
|
+
authority {
|
|
997
|
+
address
|
|
998
|
+
}
|
|
999
|
+
blockhash
|
|
1000
|
+
feeCalculator {
|
|
1001
|
+
lamportsPerSignature
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
Rather than requesting this account twice, the resolver will combine these
|
|
1009
|
+
two queries into the same RPC request, and then split the response out to the
|
|
1010
|
+
corresponding query results.
|
|
1011
|
+
|
|
1012
|
+
## Minimized Network Payloads
|
|
1013
|
+
|
|
1014
|
+
When it comes to retrieving data from an RPC endpoint, fetching more
|
|
1015
|
+
information than you need can be a significant waste of network resources, and
|
|
1016
|
+
even impact application performance.
|
|
1017
|
+
|
|
1018
|
+
The RPC-GraphQL resolver takes steps to minimize this network overhead based on
|
|
1019
|
+
the contents of the query provided.
|
|
1020
|
+
|
|
1021
|
+
For example, in the following `account` query, we're going to request multiple
|
|
1022
|
+
responses for `base64` encoded data on the same account.
|
|
1023
|
+
|
|
1024
|
+
```graphql
|
|
1025
|
+
query {
|
|
1026
|
+
account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
|
|
1027
|
+
firstEightBytes: data(encoding: BASE_64, dataSlice: { length: 8, offset: 0 })
|
|
1028
|
+
nextEightBytes: data(encoding: BASE_64, dataSlice: { length: 8, offset: 8 })
|
|
1029
|
+
anotherEightBytes: data(encoding: BASE_64, dataSlice: { length: 8, offset: 16 })
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
To gather this information, a developer may elect for one of two solutions:
|
|
1035
|
+
|
|
1036
|
+
1. Call the RPC three times with each data slice. This will result in `3n`
|
|
1037
|
+
requests where `n` is the number of times your application may invoke this
|
|
1038
|
+
query.
|
|
1039
|
+
2. Call the RPC once for the data, convert from `base64` to raw bytes, slice
|
|
1040
|
+
the raw bytes, then encode each subset back to `base64`. This requires a lot
|
|
1041
|
+
of overhead on application development.
|
|
1042
|
+
|
|
1043
|
+
RPC-GraphQL will perform solution two for you automatically, choosing to save
|
|
1044
|
+
on network calls and bytes over the wire in favor of slicing the returned data
|
|
1045
|
+
locally.
|
|
1046
|
+
|
|
1047
|
+
In fact, the resolver will minimize bytes over the wire by only requesting the
|
|
1048
|
+
specific slice of the data that encompasses all requested data slices. In the
|
|
1049
|
+
above example, we've requested three ranges of data:
|
|
1050
|
+
|
|
1051
|
+
- `0` - `8`
|
|
1052
|
+
- `8` - `16`
|
|
1053
|
+
- `16` - `24`
|
|
1054
|
+
|
|
1055
|
+
If this account has a massive amount of data, fetching more than the query asks
|
|
1056
|
+
for would be wasteful. The resolver will only fetch `0` - `24` and slice the
|
|
1057
|
+
response to serve the requested query.
|
|
1058
|
+
|
|
1059
|
+
## Batch Loading
|
|
1060
|
+
|
|
1061
|
+
In some cases, the Solana JSON RPC offers batch loading for certain data types.
|
|
1062
|
+
One such example is the RPC methods `getAccountInfo` and `getMultipleAccounts`.
|
|
1063
|
+
|
|
1064
|
+
As one might predict, whenever multiple accounts are requested with parameters
|
|
1065
|
+
that can be coalesced, one single call to `getMultipleAccounts` can be made.
|
|
1066
|
+
|
|
1067
|
+
In the example above from [Request Coalescing](#request-coalescing), let's
|
|
1068
|
+
simply change the query to request two different accounts.
|
|
1069
|
+
|
|
1070
|
+
```graphql
|
|
1071
|
+
query {
|
|
1072
|
+
account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
|
|
1073
|
+
lamports
|
|
1074
|
+
space
|
|
1075
|
+
}
|
|
1076
|
+
account(address: "EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b") {
|
|
1077
|
+
... on NonceAccount {
|
|
1078
|
+
authority {
|
|
1079
|
+
address
|
|
1080
|
+
}
|
|
1081
|
+
blockhash
|
|
1082
|
+
feeCalculator {
|
|
1083
|
+
lamportsPerSignature
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
Now the resolver would recognize the distinction between the two accounts, but
|
|
1091
|
+
it would still see the ability to coalesce request parameters. As a result,
|
|
1092
|
+
RPC-GraphQL would make one call to `getMultipleAccounts` as follows.
|
|
1093
|
+
|
|
1094
|
+
```ts
|
|
1095
|
+
rpc.getMultipleAccounts([
|
|
1096
|
+
'J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ',
|
|
1097
|
+
'EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b',
|
|
1098
|
+
]);
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
This batch loading can work in conjunction with the other forms of
|
|
1102
|
+
optimization as well, such as minimized network payloads.
|
|
1103
|
+
|
|
1104
|
+
```graphql
|
|
1105
|
+
query {
|
|
1106
|
+
account(address: "J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ") {
|
|
1107
|
+
data(encoding: BASE_64, dataSlice: { length: 32, offset: 0 })
|
|
1108
|
+
}
|
|
1109
|
+
account(address: "EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b") {
|
|
1110
|
+
authorityData: data(encoding: BASE_64, dataSlice: { length: 32, offset: 0 })
|
|
1111
|
+
u64Data: data(encoding: BASE_64, dataSlice: { length: 8, offset: 32 })
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
```ts
|
|
1117
|
+
rpc.getMultipleAccounts(
|
|
1118
|
+
['J7iup799j5BVjKXACZycYef7WQ4x1wfzhUsc5v357yWQ', 'EVW3CoyogapBfQxBFFEKGMM1bn3JyoFiqkAJdw3FHX1b'],
|
|
1119
|
+
{
|
|
1120
|
+
encoding: 'base64',
|
|
1121
|
+
dataSlice: { length: 40, offset: 0 },
|
|
1122
|
+
},
|
|
1123
|
+
);
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
In this case the resolver would ensure the proper data slices are dealt out
|
|
1127
|
+
from the single `getMultipleAccounts` response.
|