@solana/web3.js 0.0.0-next
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 +158 -0
- package/lib/index.browser.cjs.js +10062 -0
- package/lib/index.browser.cjs.js.map +1 -0
- package/lib/index.browser.esm.js +9976 -0
- package/lib/index.browser.esm.js.map +1 -0
- package/lib/index.cjs.js +9568 -0
- package/lib/index.cjs.js.map +1 -0
- package/lib/index.d.ts +3311 -0
- package/lib/index.esm.js +9479 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.iife.js +30136 -0
- package/lib/index.iife.js.map +1 -0
- package/lib/index.iife.min.js +40 -0
- package/lib/index.iife.min.js.map +1 -0
- package/package.json +140 -0
- package/src/account.ts +46 -0
- package/src/agent-manager.ts +44 -0
- package/src/blockhash.ts +4 -0
- package/src/bpf-loader-deprecated.ts +5 -0
- package/src/bpf-loader.ts +45 -0
- package/src/connection.ts +4935 -0
- package/src/ed25519-program.ts +157 -0
- package/src/epoch-schedule.ts +102 -0
- package/src/errors.ts +9 -0
- package/src/fee-calculator.ts +16 -0
- package/src/index.ts +31 -0
- package/src/instruction.ts +58 -0
- package/src/keypair.ts +98 -0
- package/src/layout.ts +147 -0
- package/src/loader.ts +236 -0
- package/src/message.ts +271 -0
- package/src/nonce-account.ts +78 -0
- package/src/publickey.ts +296 -0
- package/src/secp256k1-program.ts +229 -0
- package/src/stake-program.ts +923 -0
- package/src/system-program.ts +1007 -0
- package/src/sysvar.ts +37 -0
- package/src/timing.ts +23 -0
- package/src/transaction.ts +808 -0
- package/src/util/assert.ts +8 -0
- package/src/util/borsh-schema.ts +38 -0
- package/src/util/cluster.ts +31 -0
- package/src/util/promise-timeout.ts +14 -0
- package/src/util/send-and-confirm-raw-transaction.ts +46 -0
- package/src/util/send-and-confirm-transaction.ts +50 -0
- package/src/util/shortvec-encoding.ts +28 -0
- package/src/util/sleep.ts +4 -0
- package/src/util/to-buffer.ts +11 -0
- package/src/util/url.ts +18 -0
- package/src/validator-info.ts +106 -0
- package/src/vote-account.ts +236 -0
- package/src/vote-program.ts +413 -0
|
@@ -0,0 +1,4935 @@
|
|
|
1
|
+
import bs58 from 'bs58';
|
|
2
|
+
import {Buffer} from 'buffer';
|
|
3
|
+
import crossFetch from 'cross-fetch';
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import fastStableStringify from 'fast-stable-stringify';
|
|
6
|
+
import {
|
|
7
|
+
type as pick,
|
|
8
|
+
number,
|
|
9
|
+
string,
|
|
10
|
+
array,
|
|
11
|
+
boolean,
|
|
12
|
+
literal,
|
|
13
|
+
record,
|
|
14
|
+
union,
|
|
15
|
+
optional,
|
|
16
|
+
nullable,
|
|
17
|
+
coerce,
|
|
18
|
+
instance,
|
|
19
|
+
create,
|
|
20
|
+
tuple,
|
|
21
|
+
unknown,
|
|
22
|
+
any,
|
|
23
|
+
} from 'superstruct';
|
|
24
|
+
import type {Struct} from 'superstruct';
|
|
25
|
+
import {Client as RpcWebSocketClient} from 'rpc-websockets';
|
|
26
|
+
import RpcClient from 'jayson/lib/client/browser';
|
|
27
|
+
import {IWSRequestParams} from 'rpc-websockets/dist/lib/client';
|
|
28
|
+
|
|
29
|
+
import {AgentManager} from './agent-manager';
|
|
30
|
+
import {EpochSchedule} from './epoch-schedule';
|
|
31
|
+
import {SendTransactionError} from './errors';
|
|
32
|
+
import {NonceAccount} from './nonce-account';
|
|
33
|
+
import {PublicKey} from './publickey';
|
|
34
|
+
import {Signer} from './keypair';
|
|
35
|
+
import {MS_PER_SLOT} from './timing';
|
|
36
|
+
import {Transaction} from './transaction';
|
|
37
|
+
import {Message} from './message';
|
|
38
|
+
import assert from './util/assert';
|
|
39
|
+
import {sleep} from './util/sleep';
|
|
40
|
+
import {promiseTimeout} from './util/promise-timeout';
|
|
41
|
+
import {toBuffer} from './util/to-buffer';
|
|
42
|
+
import {makeWebsocketUrl} from './util/url';
|
|
43
|
+
import type {Blockhash} from './blockhash';
|
|
44
|
+
import type {FeeCalculator} from './fee-calculator';
|
|
45
|
+
import type {TransactionSignature} from './transaction';
|
|
46
|
+
import type {CompiledInstruction} from './message';
|
|
47
|
+
|
|
48
|
+
const PublicKeyFromString = coerce(
|
|
49
|
+
instance(PublicKey),
|
|
50
|
+
string(),
|
|
51
|
+
value => new PublicKey(value),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const RawAccountDataResult = tuple([string(), literal('base64')]);
|
|
55
|
+
|
|
56
|
+
const BufferFromRawAccountData = coerce(
|
|
57
|
+
instance(Buffer),
|
|
58
|
+
RawAccountDataResult,
|
|
59
|
+
value => Buffer.from(value[0], 'base64'),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Attempt to use a recent blockhash for up to 30 seconds
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
export const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
|
|
67
|
+
|
|
68
|
+
type ClientSubscriptionId = number;
|
|
69
|
+
/** @internal */ type ServerSubscriptionId = number;
|
|
70
|
+
/** @internal */ type SubscriptionConfigHash = string;
|
|
71
|
+
/** @internal */ type SubscriptionDisposeFn = () => Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* @internal
|
|
74
|
+
* Every subscription has a list of callers interested in publishes.
|
|
75
|
+
*/
|
|
76
|
+
type BaseSubscription<TMethod = SubscriptionConfig['method']> = Readonly<{
|
|
77
|
+
callbacks: Set<Extract<SubscriptionConfig, {method: TMethod}>['callback']>;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* @internal
|
|
81
|
+
* A subscription may be in various states of connectedness. Only when it is
|
|
82
|
+
* fully connected will it have a server subscription id associated with it.
|
|
83
|
+
* This id can be returned to the server to unsubscribe the client entirely.
|
|
84
|
+
*/
|
|
85
|
+
type StatefulSubscription = Readonly<
|
|
86
|
+
// New subscriptions that have not yet been
|
|
87
|
+
// sent to the server start in this state.
|
|
88
|
+
| {
|
|
89
|
+
state: 'pending';
|
|
90
|
+
}
|
|
91
|
+
// These subscriptions have been sent to the server
|
|
92
|
+
// and are waiting for the server to acknowledge them.
|
|
93
|
+
| {
|
|
94
|
+
state: 'subscribing';
|
|
95
|
+
}
|
|
96
|
+
// These subscriptions have been acknowledged by the
|
|
97
|
+
// server and have been assigned server subscription ids.
|
|
98
|
+
| {
|
|
99
|
+
serverSubscriptionId: ServerSubscriptionId;
|
|
100
|
+
state: 'subscribed';
|
|
101
|
+
}
|
|
102
|
+
// These subscriptions are intended to be torn down and
|
|
103
|
+
// are waiting on an acknowledgement from the server.
|
|
104
|
+
| {
|
|
105
|
+
serverSubscriptionId: ServerSubscriptionId;
|
|
106
|
+
state: 'unsubscribing';
|
|
107
|
+
}
|
|
108
|
+
// The request to tear down these subscriptions has been
|
|
109
|
+
// acknowledged by the server. The `serverSubscriptionId`
|
|
110
|
+
// is the id of the now-dead subscription.
|
|
111
|
+
| {
|
|
112
|
+
serverSubscriptionId: ServerSubscriptionId;
|
|
113
|
+
state: 'unsubscribed';
|
|
114
|
+
}
|
|
115
|
+
>;
|
|
116
|
+
/**
|
|
117
|
+
* Different RPC subscription methods accept different params.
|
|
118
|
+
*/
|
|
119
|
+
type SubscriptionConfig = Readonly<
|
|
120
|
+
| {
|
|
121
|
+
callback: AccountChangeCallback;
|
|
122
|
+
method: 'accountSubscribe';
|
|
123
|
+
params: Readonly<{
|
|
124
|
+
commitment?: Commitment; // Must default to 'finalized'
|
|
125
|
+
publicKey: string; // PublicKey of the account as a base 58 string
|
|
126
|
+
}>;
|
|
127
|
+
unsubscribeMethod: 'accountUnsubscribe';
|
|
128
|
+
}
|
|
129
|
+
| {
|
|
130
|
+
callback: LogsCallback;
|
|
131
|
+
method: 'logsSubscribe';
|
|
132
|
+
params: Readonly<{
|
|
133
|
+
commitment?: Commitment; // Must default to 'finalized'
|
|
134
|
+
filter: LogsFilter;
|
|
135
|
+
}>;
|
|
136
|
+
unsubscribeMethod: 'logsUnsubscribe';
|
|
137
|
+
}
|
|
138
|
+
| {
|
|
139
|
+
callback: ProgramAccountChangeCallback;
|
|
140
|
+
method: 'programSubscribe';
|
|
141
|
+
params: Readonly<{
|
|
142
|
+
commitment?: Commitment; // Must default to 'finalized'
|
|
143
|
+
filters?: GetProgramAccountsFilter[];
|
|
144
|
+
programId: string; // PublicKey of the program as a base 58 string
|
|
145
|
+
}>;
|
|
146
|
+
unsubscribeMethod: 'programUnsubscribe';
|
|
147
|
+
}
|
|
148
|
+
| {
|
|
149
|
+
callback: RootChangeCallback;
|
|
150
|
+
method: 'rootSubscribe';
|
|
151
|
+
params: undefined;
|
|
152
|
+
unsubscribeMethod: 'rootUnsubscribe';
|
|
153
|
+
}
|
|
154
|
+
| {
|
|
155
|
+
callback: SignatureSubscriptionCallback;
|
|
156
|
+
method: 'signatureSubscribe';
|
|
157
|
+
params: Readonly<{
|
|
158
|
+
signature: TransactionSignature; // TransactionSignature as a base 58 string
|
|
159
|
+
options?: SignatureSubscriptionOptions;
|
|
160
|
+
}>;
|
|
161
|
+
unsubscribeMethod: 'signatureUnsubscribe';
|
|
162
|
+
}
|
|
163
|
+
| {
|
|
164
|
+
callback: SlotChangeCallback;
|
|
165
|
+
method: 'slotSubscribe';
|
|
166
|
+
params: undefined;
|
|
167
|
+
unsubscribeMethod: 'slotUnsubscribe';
|
|
168
|
+
}
|
|
169
|
+
| {
|
|
170
|
+
callback: SlotUpdateCallback;
|
|
171
|
+
method: 'slotsUpdatesSubscribe';
|
|
172
|
+
params: undefined;
|
|
173
|
+
unsubscribeMethod: 'slotsUpdatesUnsubscribe';
|
|
174
|
+
}
|
|
175
|
+
>;
|
|
176
|
+
/**
|
|
177
|
+
* @internal
|
|
178
|
+
* Utility type that keeps tagged unions intact while omitting properties.
|
|
179
|
+
*/
|
|
180
|
+
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
|
|
181
|
+
? Omit<T, K>
|
|
182
|
+
: never;
|
|
183
|
+
type Subscription = BaseSubscription &
|
|
184
|
+
StatefulSubscription &
|
|
185
|
+
DistributiveOmit<SubscriptionConfig, 'callback'>;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
function hashSubscriptionConfig({
|
|
191
|
+
method,
|
|
192
|
+
params,
|
|
193
|
+
}: SubscriptionConfig): SubscriptionConfigHash {
|
|
194
|
+
return fastStableStringify([method, params], true /* isArrayProp */);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
type RpcRequest = (methodName: string, args: Array<any>) => any;
|
|
198
|
+
|
|
199
|
+
type RpcBatchRequest = (requests: RpcParams[]) => any;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
export type RpcParams = {
|
|
205
|
+
methodName: string;
|
|
206
|
+
args: Array<any>;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export type TokenAccountsFilter =
|
|
210
|
+
| {
|
|
211
|
+
mint: PublicKey;
|
|
212
|
+
}
|
|
213
|
+
| {
|
|
214
|
+
programId: PublicKey;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Extra contextual information for RPC responses
|
|
219
|
+
*/
|
|
220
|
+
export type Context = {
|
|
221
|
+
slot: number;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Options for sending transactions
|
|
226
|
+
*/
|
|
227
|
+
export type SendOptions = {
|
|
228
|
+
/** disable transaction verification step */
|
|
229
|
+
skipPreflight?: boolean;
|
|
230
|
+
/** preflight commitment level */
|
|
231
|
+
preflightCommitment?: Commitment;
|
|
232
|
+
/** Maximum number of times for the RPC node to retry sending the transaction to the leader. */
|
|
233
|
+
maxRetries?: number;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Options for confirming transactions
|
|
238
|
+
*/
|
|
239
|
+
export type ConfirmOptions = {
|
|
240
|
+
/** disable transaction verification step */
|
|
241
|
+
skipPreflight?: boolean;
|
|
242
|
+
/** desired commitment level */
|
|
243
|
+
commitment?: Commitment;
|
|
244
|
+
/** preflight commitment level */
|
|
245
|
+
preflightCommitment?: Commitment;
|
|
246
|
+
/** Maximum number of times for the RPC node to retry sending the transaction to the leader. */
|
|
247
|
+
maxRetries?: number;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Options for getConfirmedSignaturesForAddress2
|
|
252
|
+
*/
|
|
253
|
+
export type ConfirmedSignaturesForAddress2Options = {
|
|
254
|
+
/**
|
|
255
|
+
* Start searching backwards from this transaction signature.
|
|
256
|
+
* @remark If not provided the search starts from the highest max confirmed block.
|
|
257
|
+
*/
|
|
258
|
+
before?: TransactionSignature;
|
|
259
|
+
/** Search until this transaction signature is reached, if found before `limit`. */
|
|
260
|
+
until?: TransactionSignature;
|
|
261
|
+
/** Maximum transaction signatures to return (between 1 and 1,000, default: 1,000). */
|
|
262
|
+
limit?: number;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Options for getSignaturesForAddress
|
|
267
|
+
*/
|
|
268
|
+
export type SignaturesForAddressOptions = {
|
|
269
|
+
/**
|
|
270
|
+
* Start searching backwards from this transaction signature.
|
|
271
|
+
* @remark If not provided the search starts from the highest max confirmed block.
|
|
272
|
+
*/
|
|
273
|
+
before?: TransactionSignature;
|
|
274
|
+
/** Search until this transaction signature is reached, if found before `limit`. */
|
|
275
|
+
until?: TransactionSignature;
|
|
276
|
+
/** Maximum transaction signatures to return (between 1 and 1,000, default: 1,000). */
|
|
277
|
+
limit?: number;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* RPC Response with extra contextual information
|
|
282
|
+
*/
|
|
283
|
+
export type RpcResponseAndContext<T> = {
|
|
284
|
+
/** response context */
|
|
285
|
+
context: Context;
|
|
286
|
+
/** response value */
|
|
287
|
+
value: T;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @internal
|
|
292
|
+
*/
|
|
293
|
+
function createRpcResult<T, U>(result: Struct<T, U>) {
|
|
294
|
+
return union([
|
|
295
|
+
pick({
|
|
296
|
+
jsonrpc: literal('2.0'),
|
|
297
|
+
id: string(),
|
|
298
|
+
result,
|
|
299
|
+
}),
|
|
300
|
+
pick({
|
|
301
|
+
jsonrpc: literal('2.0'),
|
|
302
|
+
id: string(),
|
|
303
|
+
error: pick({
|
|
304
|
+
code: unknown(),
|
|
305
|
+
message: string(),
|
|
306
|
+
data: optional(any()),
|
|
307
|
+
}),
|
|
308
|
+
}),
|
|
309
|
+
]);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const UnknownRpcResult = createRpcResult(unknown());
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* @internal
|
|
316
|
+
*/
|
|
317
|
+
function jsonRpcResult<T, U>(schema: Struct<T, U>) {
|
|
318
|
+
return coerce(createRpcResult(schema), UnknownRpcResult, value => {
|
|
319
|
+
if ('error' in value) {
|
|
320
|
+
return value;
|
|
321
|
+
} else {
|
|
322
|
+
return {
|
|
323
|
+
...value,
|
|
324
|
+
result: create(value.result, schema),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @internal
|
|
332
|
+
*/
|
|
333
|
+
function jsonRpcResultAndContext<T, U>(value: Struct<T, U>) {
|
|
334
|
+
return jsonRpcResult(
|
|
335
|
+
pick({
|
|
336
|
+
context: pick({
|
|
337
|
+
slot: number(),
|
|
338
|
+
}),
|
|
339
|
+
value,
|
|
340
|
+
}),
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @internal
|
|
346
|
+
*/
|
|
347
|
+
function notificationResultAndContext<T, U>(value: Struct<T, U>) {
|
|
348
|
+
return pick({
|
|
349
|
+
context: pick({
|
|
350
|
+
slot: number(),
|
|
351
|
+
}),
|
|
352
|
+
value,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The level of commitment desired when querying state
|
|
358
|
+
* <pre>
|
|
359
|
+
* 'processed': Query the most recent block which has reached 1 confirmation by the connected node
|
|
360
|
+
* 'confirmed': Query the most recent block which has reached 1 confirmation by the cluster
|
|
361
|
+
* 'finalized': Query the most recent block which has been finalized by the cluster
|
|
362
|
+
* </pre>
|
|
363
|
+
*/
|
|
364
|
+
export type Commitment =
|
|
365
|
+
| 'processed'
|
|
366
|
+
| 'confirmed'
|
|
367
|
+
| 'finalized'
|
|
368
|
+
| 'recent' // Deprecated as of v1.5.5
|
|
369
|
+
| 'single' // Deprecated as of v1.5.5
|
|
370
|
+
| 'singleGossip' // Deprecated as of v1.5.5
|
|
371
|
+
| 'root' // Deprecated as of v1.5.5
|
|
372
|
+
| 'max'; // Deprecated as of v1.5.5
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* A subset of Commitment levels, which are at least optimistically confirmed
|
|
376
|
+
* <pre>
|
|
377
|
+
* 'confirmed': Query the most recent block which has reached 1 confirmation by the cluster
|
|
378
|
+
* 'finalized': Query the most recent block which has been finalized by the cluster
|
|
379
|
+
* </pre>
|
|
380
|
+
*/
|
|
381
|
+
export type Finality = 'confirmed' | 'finalized';
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Filter for largest accounts query
|
|
385
|
+
* <pre>
|
|
386
|
+
* 'circulating': Return the largest accounts that are part of the circulating supply
|
|
387
|
+
* 'nonCirculating': Return the largest accounts that are not part of the circulating supply
|
|
388
|
+
* </pre>
|
|
389
|
+
*/
|
|
390
|
+
export type LargestAccountsFilter = 'circulating' | 'nonCirculating';
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Configuration object for changing `getLargestAccounts` query behavior
|
|
394
|
+
*/
|
|
395
|
+
export type GetLargestAccountsConfig = {
|
|
396
|
+
/** The level of commitment desired */
|
|
397
|
+
commitment?: Commitment;
|
|
398
|
+
/** Filter largest accounts by whether they are part of the circulating supply */
|
|
399
|
+
filter?: LargestAccountsFilter;
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Configuration object for changing `getSupply` request behavior
|
|
404
|
+
*/
|
|
405
|
+
export type GetSupplyConfig = {
|
|
406
|
+
/** The level of commitment desired */
|
|
407
|
+
commitment?: Commitment;
|
|
408
|
+
/** Exclude non circulating accounts list from response */
|
|
409
|
+
excludeNonCirculatingAccountsList?: boolean;
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Configuration object for changing query behavior
|
|
414
|
+
*/
|
|
415
|
+
export type SignatureStatusConfig = {
|
|
416
|
+
/** enable searching status history, not needed for recent transactions */
|
|
417
|
+
searchTransactionHistory: boolean;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Information describing a cluster node
|
|
422
|
+
*/
|
|
423
|
+
export type ContactInfo = {
|
|
424
|
+
/** Identity public key of the node */
|
|
425
|
+
pubkey: string;
|
|
426
|
+
/** Gossip network address for the node */
|
|
427
|
+
gossip: string | null;
|
|
428
|
+
/** TPU network address for the node (null if not available) */
|
|
429
|
+
tpu: string | null;
|
|
430
|
+
/** JSON RPC network address for the node (null if not available) */
|
|
431
|
+
rpc: string | null;
|
|
432
|
+
/** Software version of the node (null if not available) */
|
|
433
|
+
version: string | null;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Information describing a vote account
|
|
438
|
+
*/
|
|
439
|
+
export type VoteAccountInfo = {
|
|
440
|
+
/** Public key of the vote account */
|
|
441
|
+
votePubkey: string;
|
|
442
|
+
/** Identity public key of the node voting with this account */
|
|
443
|
+
nodePubkey: string;
|
|
444
|
+
/** The stake, in lamports, delegated to this vote account and activated */
|
|
445
|
+
activatedStake: number;
|
|
446
|
+
/** Whether the vote account is staked for this epoch */
|
|
447
|
+
epochVoteAccount: boolean;
|
|
448
|
+
/** Recent epoch voting credit history for this voter */
|
|
449
|
+
epochCredits: Array<[number, number, number]>;
|
|
450
|
+
/** A percentage (0-100) of rewards payout owed to the voter */
|
|
451
|
+
commission: number;
|
|
452
|
+
/** Most recent slot voted on by this vote account */
|
|
453
|
+
lastVote: number;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* A collection of cluster vote accounts
|
|
458
|
+
*/
|
|
459
|
+
export type VoteAccountStatus = {
|
|
460
|
+
/** Active vote accounts */
|
|
461
|
+
current: Array<VoteAccountInfo>;
|
|
462
|
+
/** Inactive vote accounts */
|
|
463
|
+
delinquent: Array<VoteAccountInfo>;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Network Inflation
|
|
468
|
+
* (see https://docs.solana.com/implemented-proposals/ed_overview)
|
|
469
|
+
*/
|
|
470
|
+
export type InflationGovernor = {
|
|
471
|
+
foundation: number;
|
|
472
|
+
foundationTerm: number;
|
|
473
|
+
initial: number;
|
|
474
|
+
taper: number;
|
|
475
|
+
terminal: number;
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const GetInflationGovernorResult = pick({
|
|
479
|
+
foundation: number(),
|
|
480
|
+
foundationTerm: number(),
|
|
481
|
+
initial: number(),
|
|
482
|
+
taper: number(),
|
|
483
|
+
terminal: number(),
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* The inflation reward for an epoch
|
|
488
|
+
*/
|
|
489
|
+
export type InflationReward = {
|
|
490
|
+
/** epoch for which the reward occurs */
|
|
491
|
+
epoch: number;
|
|
492
|
+
/** the slot in which the rewards are effective */
|
|
493
|
+
effectiveSlot: number;
|
|
494
|
+
/** reward amount in lamports */
|
|
495
|
+
amount: number;
|
|
496
|
+
/** post balance of the account in lamports */
|
|
497
|
+
postBalance: number;
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Expected JSON RPC response for the "getInflationReward" message
|
|
502
|
+
*/
|
|
503
|
+
const GetInflationRewardResult = jsonRpcResult(
|
|
504
|
+
array(
|
|
505
|
+
nullable(
|
|
506
|
+
pick({
|
|
507
|
+
epoch: number(),
|
|
508
|
+
effectiveSlot: number(),
|
|
509
|
+
amount: number(),
|
|
510
|
+
postBalance: number(),
|
|
511
|
+
}),
|
|
512
|
+
),
|
|
513
|
+
),
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Information about the current epoch
|
|
518
|
+
*/
|
|
519
|
+
export type EpochInfo = {
|
|
520
|
+
epoch: number;
|
|
521
|
+
slotIndex: number;
|
|
522
|
+
slotsInEpoch: number;
|
|
523
|
+
absoluteSlot: number;
|
|
524
|
+
blockHeight?: number;
|
|
525
|
+
transactionCount?: number;
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const GetEpochInfoResult = pick({
|
|
529
|
+
epoch: number(),
|
|
530
|
+
slotIndex: number(),
|
|
531
|
+
slotsInEpoch: number(),
|
|
532
|
+
absoluteSlot: number(),
|
|
533
|
+
blockHeight: optional(number()),
|
|
534
|
+
transactionCount: optional(number()),
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const GetEpochScheduleResult = pick({
|
|
538
|
+
slotsPerEpoch: number(),
|
|
539
|
+
leaderScheduleSlotOffset: number(),
|
|
540
|
+
warmup: boolean(),
|
|
541
|
+
firstNormalEpoch: number(),
|
|
542
|
+
firstNormalSlot: number(),
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Leader schedule
|
|
547
|
+
* (see https://docs.solana.com/terminology#leader-schedule)
|
|
548
|
+
*/
|
|
549
|
+
export type LeaderSchedule = {
|
|
550
|
+
[address: string]: number[];
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const GetLeaderScheduleResult = record(string(), array(number()));
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Transaction error or null
|
|
557
|
+
*/
|
|
558
|
+
const TransactionErrorResult = nullable(union([pick({}), string()]));
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Signature status for a transaction
|
|
562
|
+
*/
|
|
563
|
+
const SignatureStatusResult = pick({
|
|
564
|
+
err: TransactionErrorResult,
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Transaction signature received notification
|
|
569
|
+
*/
|
|
570
|
+
const SignatureReceivedResult = literal('receivedSignature');
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Version info for a node
|
|
574
|
+
*/
|
|
575
|
+
export type Version = {
|
|
576
|
+
/** Version of solana-core */
|
|
577
|
+
'solana-core': string;
|
|
578
|
+
'feature-set'?: number;
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const VersionResult = pick({
|
|
582
|
+
'solana-core': string(),
|
|
583
|
+
'feature-set': optional(number()),
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
export type SimulatedTransactionAccountInfo = {
|
|
587
|
+
/** `true` if this account's data contains a loaded program */
|
|
588
|
+
executable: boolean;
|
|
589
|
+
/** Identifier of the program that owns the account */
|
|
590
|
+
owner: string;
|
|
591
|
+
/** Number of lamports assigned to the account */
|
|
592
|
+
lamports: number;
|
|
593
|
+
/** Optional data assigned to the account */
|
|
594
|
+
data: string[];
|
|
595
|
+
/** Optional rent epoch info for account */
|
|
596
|
+
rentEpoch?: number;
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
export type SimulatedTransactionResponse = {
|
|
600
|
+
err: TransactionError | string | null;
|
|
601
|
+
logs: Array<string> | null;
|
|
602
|
+
accounts?: (SimulatedTransactionAccountInfo | null)[] | null;
|
|
603
|
+
unitsConsumed?: number;
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const SimulatedTransactionResponseStruct = jsonRpcResultAndContext(
|
|
607
|
+
pick({
|
|
608
|
+
err: nullable(union([pick({}), string()])),
|
|
609
|
+
logs: nullable(array(string())),
|
|
610
|
+
accounts: optional(
|
|
611
|
+
nullable(
|
|
612
|
+
array(
|
|
613
|
+
nullable(
|
|
614
|
+
pick({
|
|
615
|
+
executable: boolean(),
|
|
616
|
+
owner: string(),
|
|
617
|
+
lamports: number(),
|
|
618
|
+
data: array(string()),
|
|
619
|
+
rentEpoch: optional(number()),
|
|
620
|
+
}),
|
|
621
|
+
),
|
|
622
|
+
),
|
|
623
|
+
),
|
|
624
|
+
),
|
|
625
|
+
unitsConsumed: optional(number()),
|
|
626
|
+
}),
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
export type ParsedInnerInstruction = {
|
|
630
|
+
index: number;
|
|
631
|
+
instructions: (ParsedInstruction | PartiallyDecodedInstruction)[];
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
export type TokenBalance = {
|
|
635
|
+
accountIndex: number;
|
|
636
|
+
mint: string;
|
|
637
|
+
owner?: string;
|
|
638
|
+
uiTokenAmount: TokenAmount;
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Metadata for a parsed confirmed transaction on the ledger
|
|
643
|
+
*
|
|
644
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link ParsedTransactionMeta} instead.
|
|
645
|
+
*/
|
|
646
|
+
export type ParsedConfirmedTransactionMeta = ParsedTransactionMeta;
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Metadata for a parsed transaction on the ledger
|
|
650
|
+
*/
|
|
651
|
+
export type ParsedTransactionMeta = {
|
|
652
|
+
/** The fee charged for processing the transaction */
|
|
653
|
+
fee: number;
|
|
654
|
+
/** An array of cross program invoked parsed instructions */
|
|
655
|
+
innerInstructions?: ParsedInnerInstruction[] | null;
|
|
656
|
+
/** The balances of the transaction accounts before processing */
|
|
657
|
+
preBalances: Array<number>;
|
|
658
|
+
/** The balances of the transaction accounts after processing */
|
|
659
|
+
postBalances: Array<number>;
|
|
660
|
+
/** An array of program log messages emitted during a transaction */
|
|
661
|
+
logMessages?: Array<string> | null;
|
|
662
|
+
/** The token balances of the transaction accounts before processing */
|
|
663
|
+
preTokenBalances?: Array<TokenBalance> | null;
|
|
664
|
+
/** The token balances of the transaction accounts after processing */
|
|
665
|
+
postTokenBalances?: Array<TokenBalance> | null;
|
|
666
|
+
/** The error result of transaction processing */
|
|
667
|
+
err: TransactionError | null;
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
export type CompiledInnerInstruction = {
|
|
671
|
+
index: number;
|
|
672
|
+
instructions: CompiledInstruction[];
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Metadata for a confirmed transaction on the ledger
|
|
677
|
+
*/
|
|
678
|
+
export type ConfirmedTransactionMeta = {
|
|
679
|
+
/** The fee charged for processing the transaction */
|
|
680
|
+
fee: number;
|
|
681
|
+
/** An array of cross program invoked instructions */
|
|
682
|
+
innerInstructions?: CompiledInnerInstruction[] | null;
|
|
683
|
+
/** The balances of the transaction accounts before processing */
|
|
684
|
+
preBalances: Array<number>;
|
|
685
|
+
/** The balances of the transaction accounts after processing */
|
|
686
|
+
postBalances: Array<number>;
|
|
687
|
+
/** An array of program log messages emitted during a transaction */
|
|
688
|
+
logMessages?: Array<string> | null;
|
|
689
|
+
/** The token balances of the transaction accounts before processing */
|
|
690
|
+
preTokenBalances?: Array<TokenBalance> | null;
|
|
691
|
+
/** The token balances of the transaction accounts after processing */
|
|
692
|
+
postTokenBalances?: Array<TokenBalance> | null;
|
|
693
|
+
/** The error result of transaction processing */
|
|
694
|
+
err: TransactionError | null;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* A processed transaction from the RPC API
|
|
699
|
+
*/
|
|
700
|
+
export type TransactionResponse = {
|
|
701
|
+
/** The slot during which the transaction was processed */
|
|
702
|
+
slot: number;
|
|
703
|
+
/** The transaction */
|
|
704
|
+
transaction: {
|
|
705
|
+
/** The transaction message */
|
|
706
|
+
message: Message;
|
|
707
|
+
/** The transaction signatures */
|
|
708
|
+
signatures: string[];
|
|
709
|
+
};
|
|
710
|
+
/** Metadata produced from the transaction */
|
|
711
|
+
meta: ConfirmedTransactionMeta | null;
|
|
712
|
+
/** The unix timestamp of when the transaction was processed */
|
|
713
|
+
blockTime?: number | null;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* A confirmed transaction on the ledger
|
|
718
|
+
*/
|
|
719
|
+
export type ConfirmedTransaction = {
|
|
720
|
+
/** The slot during which the transaction was processed */
|
|
721
|
+
slot: number;
|
|
722
|
+
/** The details of the transaction */
|
|
723
|
+
transaction: Transaction;
|
|
724
|
+
/** Metadata produced from the transaction */
|
|
725
|
+
meta: ConfirmedTransactionMeta | null;
|
|
726
|
+
/** The unix timestamp of when the transaction was processed */
|
|
727
|
+
blockTime?: number | null;
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* A partially decoded transaction instruction
|
|
732
|
+
*/
|
|
733
|
+
export type PartiallyDecodedInstruction = {
|
|
734
|
+
/** Program id called by this instruction */
|
|
735
|
+
programId: PublicKey;
|
|
736
|
+
/** Public keys of accounts passed to this instruction */
|
|
737
|
+
accounts: Array<PublicKey>;
|
|
738
|
+
/** Raw base-58 instruction data */
|
|
739
|
+
data: string;
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* A parsed transaction message account
|
|
744
|
+
*/
|
|
745
|
+
export type ParsedMessageAccount = {
|
|
746
|
+
/** Public key of the account */
|
|
747
|
+
pubkey: PublicKey;
|
|
748
|
+
/** Indicates if the account signed the transaction */
|
|
749
|
+
signer: boolean;
|
|
750
|
+
/** Indicates if the account is writable for this transaction */
|
|
751
|
+
writable: boolean;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* A parsed transaction instruction
|
|
756
|
+
*/
|
|
757
|
+
export type ParsedInstruction = {
|
|
758
|
+
/** Name of the program for this instruction */
|
|
759
|
+
program: string;
|
|
760
|
+
/** ID of the program for this instruction */
|
|
761
|
+
programId: PublicKey;
|
|
762
|
+
/** Parsed instruction info */
|
|
763
|
+
parsed: any;
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* A parsed transaction message
|
|
768
|
+
*/
|
|
769
|
+
export type ParsedMessage = {
|
|
770
|
+
/** Accounts used in the instructions */
|
|
771
|
+
accountKeys: ParsedMessageAccount[];
|
|
772
|
+
/** The atomically executed instructions for the transaction */
|
|
773
|
+
instructions: (ParsedInstruction | PartiallyDecodedInstruction)[];
|
|
774
|
+
/** Recent blockhash */
|
|
775
|
+
recentBlockhash: string;
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* A parsed transaction
|
|
780
|
+
*/
|
|
781
|
+
export type ParsedTransaction = {
|
|
782
|
+
/** Signatures for the transaction */
|
|
783
|
+
signatures: Array<string>;
|
|
784
|
+
/** Message of the transaction */
|
|
785
|
+
message: ParsedMessage;
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* A parsed and confirmed transaction on the ledger
|
|
790
|
+
*
|
|
791
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link ParsedTransactionWithMeta} instead.
|
|
792
|
+
*/
|
|
793
|
+
export type ParsedConfirmedTransaction = ParsedTransactionWithMeta;
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* A parsed transaction on the ledger with meta
|
|
797
|
+
*/
|
|
798
|
+
export type ParsedTransactionWithMeta = {
|
|
799
|
+
/** The slot during which the transaction was processed */
|
|
800
|
+
slot: number;
|
|
801
|
+
/** The details of the transaction */
|
|
802
|
+
transaction: ParsedTransaction;
|
|
803
|
+
/** Metadata produced from the transaction */
|
|
804
|
+
meta: ParsedTransactionMeta | null;
|
|
805
|
+
/** The unix timestamp of when the transaction was processed */
|
|
806
|
+
blockTime?: number | null;
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* A processed block fetched from the RPC API
|
|
811
|
+
*/
|
|
812
|
+
export type BlockResponse = {
|
|
813
|
+
/** Blockhash of this block */
|
|
814
|
+
blockhash: Blockhash;
|
|
815
|
+
/** Blockhash of this block's parent */
|
|
816
|
+
previousBlockhash: Blockhash;
|
|
817
|
+
/** Slot index of this block's parent */
|
|
818
|
+
parentSlot: number;
|
|
819
|
+
/** Vector of transactions with status meta and original message */
|
|
820
|
+
transactions: Array<{
|
|
821
|
+
/** The transaction */
|
|
822
|
+
transaction: {
|
|
823
|
+
/** The transaction message */
|
|
824
|
+
message: Message;
|
|
825
|
+
/** The transaction signatures */
|
|
826
|
+
signatures: string[];
|
|
827
|
+
};
|
|
828
|
+
/** Metadata produced from the transaction */
|
|
829
|
+
meta: ConfirmedTransactionMeta | null;
|
|
830
|
+
}>;
|
|
831
|
+
/** Vector of block rewards */
|
|
832
|
+
rewards?: Array<{
|
|
833
|
+
/** Public key of reward recipient */
|
|
834
|
+
pubkey: string;
|
|
835
|
+
/** Reward value in lamports */
|
|
836
|
+
lamports: number;
|
|
837
|
+
/** Account balance after reward is applied */
|
|
838
|
+
postBalance: number | null;
|
|
839
|
+
/** Type of reward received */
|
|
840
|
+
rewardType: string | null;
|
|
841
|
+
}>;
|
|
842
|
+
/** The unix timestamp of when the block was processed */
|
|
843
|
+
blockTime: number | null;
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* A ConfirmedBlock on the ledger
|
|
848
|
+
*/
|
|
849
|
+
export type ConfirmedBlock = {
|
|
850
|
+
/** Blockhash of this block */
|
|
851
|
+
blockhash: Blockhash;
|
|
852
|
+
/** Blockhash of this block's parent */
|
|
853
|
+
previousBlockhash: Blockhash;
|
|
854
|
+
/** Slot index of this block's parent */
|
|
855
|
+
parentSlot: number;
|
|
856
|
+
/** Vector of transactions and status metas */
|
|
857
|
+
transactions: Array<{
|
|
858
|
+
transaction: Transaction;
|
|
859
|
+
meta: ConfirmedTransactionMeta | null;
|
|
860
|
+
}>;
|
|
861
|
+
/** Vector of block rewards */
|
|
862
|
+
rewards?: Array<{
|
|
863
|
+
pubkey: string;
|
|
864
|
+
lamports: number;
|
|
865
|
+
postBalance: number | null;
|
|
866
|
+
rewardType: string | null;
|
|
867
|
+
}>;
|
|
868
|
+
/** The unix timestamp of when the block was processed */
|
|
869
|
+
blockTime: number | null;
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* A Block on the ledger with signatures only
|
|
874
|
+
*/
|
|
875
|
+
export type BlockSignatures = {
|
|
876
|
+
/** Blockhash of this block */
|
|
877
|
+
blockhash: Blockhash;
|
|
878
|
+
/** Blockhash of this block's parent */
|
|
879
|
+
previousBlockhash: Blockhash;
|
|
880
|
+
/** Slot index of this block's parent */
|
|
881
|
+
parentSlot: number;
|
|
882
|
+
/** Vector of signatures */
|
|
883
|
+
signatures: Array<string>;
|
|
884
|
+
/** The unix timestamp of when the block was processed */
|
|
885
|
+
blockTime: number | null;
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* recent block production information
|
|
890
|
+
*/
|
|
891
|
+
export type BlockProduction = Readonly<{
|
|
892
|
+
/** a dictionary of validator identities, as base-58 encoded strings. Value is a two element array containing the number of leader slots and the number of blocks produced */
|
|
893
|
+
byIdentity: Readonly<Record<string, ReadonlyArray<number>>>;
|
|
894
|
+
/** Block production slot range */
|
|
895
|
+
range: Readonly<{
|
|
896
|
+
/** first slot of the block production information (inclusive) */
|
|
897
|
+
firstSlot: number;
|
|
898
|
+
/** last slot of block production information (inclusive) */
|
|
899
|
+
lastSlot: number;
|
|
900
|
+
}>;
|
|
901
|
+
}>;
|
|
902
|
+
|
|
903
|
+
export type GetBlockProductionConfig = {
|
|
904
|
+
/** Optional commitment level */
|
|
905
|
+
commitment?: Commitment;
|
|
906
|
+
/** Slot range to return block production for. If parameter not provided, defaults to current epoch. */
|
|
907
|
+
range?: {
|
|
908
|
+
/** first slot to return block production information for (inclusive) */
|
|
909
|
+
firstSlot: number;
|
|
910
|
+
/** last slot to return block production information for (inclusive). If parameter not provided, defaults to the highest slot */
|
|
911
|
+
lastSlot?: number;
|
|
912
|
+
};
|
|
913
|
+
/** Only return results for this validator identity (base-58 encoded) */
|
|
914
|
+
identity?: string;
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Expected JSON RPC response for the "getBlockProduction" message
|
|
919
|
+
*/
|
|
920
|
+
const BlockProductionResponseStruct = jsonRpcResultAndContext(
|
|
921
|
+
pick({
|
|
922
|
+
byIdentity: record(string(), array(number())),
|
|
923
|
+
range: pick({
|
|
924
|
+
firstSlot: number(),
|
|
925
|
+
lastSlot: number(),
|
|
926
|
+
}),
|
|
927
|
+
}),
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* A performance sample
|
|
932
|
+
*/
|
|
933
|
+
export type PerfSample = {
|
|
934
|
+
/** Slot number of sample */
|
|
935
|
+
slot: number;
|
|
936
|
+
/** Number of transactions in a sample window */
|
|
937
|
+
numTransactions: number;
|
|
938
|
+
/** Number of slots in a sample window */
|
|
939
|
+
numSlots: number;
|
|
940
|
+
/** Sample window in seconds */
|
|
941
|
+
samplePeriodSecs: number;
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
function createRpcClient(
|
|
945
|
+
url: string,
|
|
946
|
+
useHttps: boolean,
|
|
947
|
+
httpHeaders?: HttpHeaders,
|
|
948
|
+
customFetch?: typeof crossFetch,
|
|
949
|
+
fetchMiddleware?: FetchMiddleware,
|
|
950
|
+
disableRetryOnRateLimit?: boolean,
|
|
951
|
+
): RpcClient {
|
|
952
|
+
const fetch = customFetch ? customFetch : crossFetch;
|
|
953
|
+
let agentManager: AgentManager | undefined;
|
|
954
|
+
if (!process.env.BROWSER) {
|
|
955
|
+
agentManager = new AgentManager(useHttps);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
let fetchWithMiddleware:
|
|
959
|
+
| ((url: string, options: any) => Promise<Response>)
|
|
960
|
+
| undefined;
|
|
961
|
+
|
|
962
|
+
if (fetchMiddleware) {
|
|
963
|
+
fetchWithMiddleware = async (url: string, options: any) => {
|
|
964
|
+
const modifiedFetchArgs = await new Promise<[string, any]>(
|
|
965
|
+
(resolve, reject) => {
|
|
966
|
+
try {
|
|
967
|
+
fetchMiddleware(url, options, (modifiedUrl, modifiedOptions) =>
|
|
968
|
+
resolve([modifiedUrl, modifiedOptions]),
|
|
969
|
+
);
|
|
970
|
+
} catch (error) {
|
|
971
|
+
reject(error);
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
);
|
|
975
|
+
return await fetch(...modifiedFetchArgs);
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const clientBrowser = new RpcClient(async (request, callback) => {
|
|
980
|
+
const agent = agentManager ? agentManager.requestStart() : undefined;
|
|
981
|
+
const options = {
|
|
982
|
+
method: 'POST',
|
|
983
|
+
body: request,
|
|
984
|
+
agent,
|
|
985
|
+
headers: Object.assign(
|
|
986
|
+
{
|
|
987
|
+
'Content-Type': 'application/json',
|
|
988
|
+
},
|
|
989
|
+
httpHeaders || {},
|
|
990
|
+
),
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
try {
|
|
994
|
+
let too_many_requests_retries = 5;
|
|
995
|
+
let res: Response;
|
|
996
|
+
let waitTime = 500;
|
|
997
|
+
for (;;) {
|
|
998
|
+
if (fetchWithMiddleware) {
|
|
999
|
+
res = await fetchWithMiddleware(url, options);
|
|
1000
|
+
} else {
|
|
1001
|
+
res = await fetch(url, options);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (res.status !== 429 /* Too many requests */) {
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
if (disableRetryOnRateLimit === true) {
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
too_many_requests_retries -= 1;
|
|
1011
|
+
if (too_many_requests_retries === 0) {
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
console.log(
|
|
1015
|
+
`Server responded with ${res.status} ${res.statusText}. Retrying after ${waitTime}ms delay...`,
|
|
1016
|
+
);
|
|
1017
|
+
await sleep(waitTime);
|
|
1018
|
+
waitTime *= 2;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const text = await res.text();
|
|
1022
|
+
if (res.ok) {
|
|
1023
|
+
callback(null, text);
|
|
1024
|
+
} else {
|
|
1025
|
+
callback(new Error(`${res.status} ${res.statusText}: ${text}`));
|
|
1026
|
+
}
|
|
1027
|
+
} catch (err) {
|
|
1028
|
+
if (err instanceof Error) callback(err);
|
|
1029
|
+
} finally {
|
|
1030
|
+
agentManager && agentManager.requestEnd();
|
|
1031
|
+
}
|
|
1032
|
+
}, {});
|
|
1033
|
+
|
|
1034
|
+
return clientBrowser;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function createRpcRequest(client: RpcClient): RpcRequest {
|
|
1038
|
+
return (method, args) => {
|
|
1039
|
+
return new Promise((resolve, reject) => {
|
|
1040
|
+
client.request(method, args, (err: any, response: any) => {
|
|
1041
|
+
if (err) {
|
|
1042
|
+
reject(err);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
resolve(response);
|
|
1046
|
+
});
|
|
1047
|
+
});
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function createRpcBatchRequest(client: RpcClient): RpcBatchRequest {
|
|
1052
|
+
return (requests: RpcParams[]) => {
|
|
1053
|
+
return new Promise((resolve, reject) => {
|
|
1054
|
+
// Do nothing if requests is empty
|
|
1055
|
+
if (requests.length === 0) resolve([]);
|
|
1056
|
+
|
|
1057
|
+
const batch = requests.map((params: RpcParams) => {
|
|
1058
|
+
return client.request(params.methodName, params.args);
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
client.request(batch, (err: any, response: any) => {
|
|
1062
|
+
if (err) {
|
|
1063
|
+
reject(err);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
resolve(response);
|
|
1067
|
+
});
|
|
1068
|
+
});
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Expected JSON RPC response for the "getInflationGovernor" message
|
|
1074
|
+
*/
|
|
1075
|
+
const GetInflationGovernorRpcResult = jsonRpcResult(GetInflationGovernorResult);
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Expected JSON RPC response for the "getEpochInfo" message
|
|
1079
|
+
*/
|
|
1080
|
+
const GetEpochInfoRpcResult = jsonRpcResult(GetEpochInfoResult);
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* Expected JSON RPC response for the "getEpochSchedule" message
|
|
1084
|
+
*/
|
|
1085
|
+
const GetEpochScheduleRpcResult = jsonRpcResult(GetEpochScheduleResult);
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Expected JSON RPC response for the "getLeaderSchedule" message
|
|
1089
|
+
*/
|
|
1090
|
+
const GetLeaderScheduleRpcResult = jsonRpcResult(GetLeaderScheduleResult);
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Expected JSON RPC response for the "minimumLedgerSlot" and "getFirstAvailableBlock" messages
|
|
1094
|
+
*/
|
|
1095
|
+
const SlotRpcResult = jsonRpcResult(number());
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Supply
|
|
1099
|
+
*/
|
|
1100
|
+
export type Supply = {
|
|
1101
|
+
/** Total supply in lamports */
|
|
1102
|
+
total: number;
|
|
1103
|
+
/** Circulating supply in lamports */
|
|
1104
|
+
circulating: number;
|
|
1105
|
+
/** Non-circulating supply in lamports */
|
|
1106
|
+
nonCirculating: number;
|
|
1107
|
+
/** List of non-circulating account addresses */
|
|
1108
|
+
nonCirculatingAccounts: Array<PublicKey>;
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Expected JSON RPC response for the "getSupply" message
|
|
1113
|
+
*/
|
|
1114
|
+
const GetSupplyRpcResult = jsonRpcResultAndContext(
|
|
1115
|
+
pick({
|
|
1116
|
+
total: number(),
|
|
1117
|
+
circulating: number(),
|
|
1118
|
+
nonCirculating: number(),
|
|
1119
|
+
nonCirculatingAccounts: array(PublicKeyFromString),
|
|
1120
|
+
}),
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Token amount object which returns a token amount in different formats
|
|
1125
|
+
* for various client use cases.
|
|
1126
|
+
*/
|
|
1127
|
+
export type TokenAmount = {
|
|
1128
|
+
/** Raw amount of tokens as string ignoring decimals */
|
|
1129
|
+
amount: string;
|
|
1130
|
+
/** Number of decimals configured for token's mint */
|
|
1131
|
+
decimals: number;
|
|
1132
|
+
/** Token amount as float, accounts for decimals */
|
|
1133
|
+
uiAmount: number | null;
|
|
1134
|
+
/** Token amount as string, accounts for decimals */
|
|
1135
|
+
uiAmountString?: string;
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Expected JSON RPC structure for token amounts
|
|
1140
|
+
*/
|
|
1141
|
+
const TokenAmountResult = pick({
|
|
1142
|
+
amount: string(),
|
|
1143
|
+
uiAmount: nullable(number()),
|
|
1144
|
+
decimals: number(),
|
|
1145
|
+
uiAmountString: optional(string()),
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Token address and balance.
|
|
1150
|
+
*/
|
|
1151
|
+
export type TokenAccountBalancePair = {
|
|
1152
|
+
/** Address of the token account */
|
|
1153
|
+
address: PublicKey;
|
|
1154
|
+
/** Raw amount of tokens as string ignoring decimals */
|
|
1155
|
+
amount: string;
|
|
1156
|
+
/** Number of decimals configured for token's mint */
|
|
1157
|
+
decimals: number;
|
|
1158
|
+
/** Token amount as float, accounts for decimals */
|
|
1159
|
+
uiAmount: number | null;
|
|
1160
|
+
/** Token amount as string, accounts for decimals */
|
|
1161
|
+
uiAmountString?: string;
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Expected JSON RPC response for the "getTokenLargestAccounts" message
|
|
1166
|
+
*/
|
|
1167
|
+
const GetTokenLargestAccountsResult = jsonRpcResultAndContext(
|
|
1168
|
+
array(
|
|
1169
|
+
pick({
|
|
1170
|
+
address: PublicKeyFromString,
|
|
1171
|
+
amount: string(),
|
|
1172
|
+
uiAmount: nullable(number()),
|
|
1173
|
+
decimals: number(),
|
|
1174
|
+
uiAmountString: optional(string()),
|
|
1175
|
+
}),
|
|
1176
|
+
),
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Expected JSON RPC response for the "getTokenAccountsByOwner" message
|
|
1181
|
+
*/
|
|
1182
|
+
const GetTokenAccountsByOwner = jsonRpcResultAndContext(
|
|
1183
|
+
array(
|
|
1184
|
+
pick({
|
|
1185
|
+
pubkey: PublicKeyFromString,
|
|
1186
|
+
account: pick({
|
|
1187
|
+
executable: boolean(),
|
|
1188
|
+
owner: PublicKeyFromString,
|
|
1189
|
+
lamports: number(),
|
|
1190
|
+
data: BufferFromRawAccountData,
|
|
1191
|
+
rentEpoch: number(),
|
|
1192
|
+
}),
|
|
1193
|
+
}),
|
|
1194
|
+
),
|
|
1195
|
+
);
|
|
1196
|
+
|
|
1197
|
+
const ParsedAccountDataResult = pick({
|
|
1198
|
+
program: string(),
|
|
1199
|
+
parsed: unknown(),
|
|
1200
|
+
space: number(),
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Expected JSON RPC response for the "getTokenAccountsByOwner" message with parsed data
|
|
1205
|
+
*/
|
|
1206
|
+
const GetParsedTokenAccountsByOwner = jsonRpcResultAndContext(
|
|
1207
|
+
array(
|
|
1208
|
+
pick({
|
|
1209
|
+
pubkey: PublicKeyFromString,
|
|
1210
|
+
account: pick({
|
|
1211
|
+
executable: boolean(),
|
|
1212
|
+
owner: PublicKeyFromString,
|
|
1213
|
+
lamports: number(),
|
|
1214
|
+
data: ParsedAccountDataResult,
|
|
1215
|
+
rentEpoch: number(),
|
|
1216
|
+
}),
|
|
1217
|
+
}),
|
|
1218
|
+
),
|
|
1219
|
+
);
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* Pair of an account address and its balance
|
|
1223
|
+
*/
|
|
1224
|
+
export type AccountBalancePair = {
|
|
1225
|
+
address: PublicKey;
|
|
1226
|
+
lamports: number;
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Expected JSON RPC response for the "getLargestAccounts" message
|
|
1231
|
+
*/
|
|
1232
|
+
const GetLargestAccountsRpcResult = jsonRpcResultAndContext(
|
|
1233
|
+
array(
|
|
1234
|
+
pick({
|
|
1235
|
+
lamports: number(),
|
|
1236
|
+
address: PublicKeyFromString,
|
|
1237
|
+
}),
|
|
1238
|
+
),
|
|
1239
|
+
);
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* @internal
|
|
1243
|
+
*/
|
|
1244
|
+
const AccountInfoResult = pick({
|
|
1245
|
+
executable: boolean(),
|
|
1246
|
+
owner: PublicKeyFromString,
|
|
1247
|
+
lamports: number(),
|
|
1248
|
+
data: BufferFromRawAccountData,
|
|
1249
|
+
rentEpoch: number(),
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* @internal
|
|
1254
|
+
*/
|
|
1255
|
+
const KeyedAccountInfoResult = pick({
|
|
1256
|
+
pubkey: PublicKeyFromString,
|
|
1257
|
+
account: AccountInfoResult,
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
const ParsedOrRawAccountData = coerce(
|
|
1261
|
+
union([instance(Buffer), ParsedAccountDataResult]),
|
|
1262
|
+
union([RawAccountDataResult, ParsedAccountDataResult]),
|
|
1263
|
+
value => {
|
|
1264
|
+
if (Array.isArray(value)) {
|
|
1265
|
+
return create(value, BufferFromRawAccountData);
|
|
1266
|
+
} else {
|
|
1267
|
+
return value;
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1270
|
+
);
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* @internal
|
|
1274
|
+
*/
|
|
1275
|
+
const ParsedAccountInfoResult = pick({
|
|
1276
|
+
executable: boolean(),
|
|
1277
|
+
owner: PublicKeyFromString,
|
|
1278
|
+
lamports: number(),
|
|
1279
|
+
data: ParsedOrRawAccountData,
|
|
1280
|
+
rentEpoch: number(),
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
const KeyedParsedAccountInfoResult = pick({
|
|
1284
|
+
pubkey: PublicKeyFromString,
|
|
1285
|
+
account: ParsedAccountInfoResult,
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* @internal
|
|
1290
|
+
*/
|
|
1291
|
+
const StakeActivationResult = pick({
|
|
1292
|
+
state: union([
|
|
1293
|
+
literal('active'),
|
|
1294
|
+
literal('inactive'),
|
|
1295
|
+
literal('activating'),
|
|
1296
|
+
literal('deactivating'),
|
|
1297
|
+
]),
|
|
1298
|
+
active: number(),
|
|
1299
|
+
inactive: number(),
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
/**
|
|
1303
|
+
* Expected JSON RPC response for the "getConfirmedSignaturesForAddress2" message
|
|
1304
|
+
*/
|
|
1305
|
+
|
|
1306
|
+
const GetConfirmedSignaturesForAddress2RpcResult = jsonRpcResult(
|
|
1307
|
+
array(
|
|
1308
|
+
pick({
|
|
1309
|
+
signature: string(),
|
|
1310
|
+
slot: number(),
|
|
1311
|
+
err: TransactionErrorResult,
|
|
1312
|
+
memo: nullable(string()),
|
|
1313
|
+
blockTime: optional(nullable(number())),
|
|
1314
|
+
}),
|
|
1315
|
+
),
|
|
1316
|
+
);
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Expected JSON RPC response for the "getSignaturesForAddress" message
|
|
1320
|
+
*/
|
|
1321
|
+
const GetSignaturesForAddressRpcResult = jsonRpcResult(
|
|
1322
|
+
array(
|
|
1323
|
+
pick({
|
|
1324
|
+
signature: string(),
|
|
1325
|
+
slot: number(),
|
|
1326
|
+
err: TransactionErrorResult,
|
|
1327
|
+
memo: nullable(string()),
|
|
1328
|
+
blockTime: optional(nullable(number())),
|
|
1329
|
+
}),
|
|
1330
|
+
),
|
|
1331
|
+
);
|
|
1332
|
+
|
|
1333
|
+
/***
|
|
1334
|
+
* Expected JSON RPC response for the "accountNotification" message
|
|
1335
|
+
*/
|
|
1336
|
+
const AccountNotificationResult = pick({
|
|
1337
|
+
subscription: number(),
|
|
1338
|
+
result: notificationResultAndContext(AccountInfoResult),
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* @internal
|
|
1343
|
+
*/
|
|
1344
|
+
const ProgramAccountInfoResult = pick({
|
|
1345
|
+
pubkey: PublicKeyFromString,
|
|
1346
|
+
account: AccountInfoResult,
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
/***
|
|
1350
|
+
* Expected JSON RPC response for the "programNotification" message
|
|
1351
|
+
*/
|
|
1352
|
+
const ProgramAccountNotificationResult = pick({
|
|
1353
|
+
subscription: number(),
|
|
1354
|
+
result: notificationResultAndContext(ProgramAccountInfoResult),
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* @internal
|
|
1359
|
+
*/
|
|
1360
|
+
const SlotInfoResult = pick({
|
|
1361
|
+
parent: number(),
|
|
1362
|
+
slot: number(),
|
|
1363
|
+
root: number(),
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Expected JSON RPC response for the "slotNotification" message
|
|
1368
|
+
*/
|
|
1369
|
+
const SlotNotificationResult = pick({
|
|
1370
|
+
subscription: number(),
|
|
1371
|
+
result: SlotInfoResult,
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* Slot updates which can be used for tracking the live progress of a cluster.
|
|
1376
|
+
* - `"firstShredReceived"`: connected node received the first shred of a block.
|
|
1377
|
+
* Indicates that a new block that is being produced.
|
|
1378
|
+
* - `"completed"`: connected node has received all shreds of a block. Indicates
|
|
1379
|
+
* a block was recently produced.
|
|
1380
|
+
* - `"optimisticConfirmation"`: block was optimistically confirmed by the
|
|
1381
|
+
* cluster. It is not guaranteed that an optimistic confirmation notification
|
|
1382
|
+
* will be sent for every finalized blocks.
|
|
1383
|
+
* - `"root"`: the connected node rooted this block.
|
|
1384
|
+
* - `"createdBank"`: the connected node has started validating this block.
|
|
1385
|
+
* - `"frozen"`: the connected node has validated this block.
|
|
1386
|
+
* - `"dead"`: the connected node failed to validate this block.
|
|
1387
|
+
*/
|
|
1388
|
+
export type SlotUpdate =
|
|
1389
|
+
| {
|
|
1390
|
+
type: 'firstShredReceived';
|
|
1391
|
+
slot: number;
|
|
1392
|
+
timestamp: number;
|
|
1393
|
+
}
|
|
1394
|
+
| {
|
|
1395
|
+
type: 'completed';
|
|
1396
|
+
slot: number;
|
|
1397
|
+
timestamp: number;
|
|
1398
|
+
}
|
|
1399
|
+
| {
|
|
1400
|
+
type: 'createdBank';
|
|
1401
|
+
slot: number;
|
|
1402
|
+
timestamp: number;
|
|
1403
|
+
parent: number;
|
|
1404
|
+
}
|
|
1405
|
+
| {
|
|
1406
|
+
type: 'frozen';
|
|
1407
|
+
slot: number;
|
|
1408
|
+
timestamp: number;
|
|
1409
|
+
stats: {
|
|
1410
|
+
numTransactionEntries: number;
|
|
1411
|
+
numSuccessfulTransactions: number;
|
|
1412
|
+
numFailedTransactions: number;
|
|
1413
|
+
maxTransactionsPerEntry: number;
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
| {
|
|
1417
|
+
type: 'dead';
|
|
1418
|
+
slot: number;
|
|
1419
|
+
timestamp: number;
|
|
1420
|
+
err: string;
|
|
1421
|
+
}
|
|
1422
|
+
| {
|
|
1423
|
+
type: 'optimisticConfirmation';
|
|
1424
|
+
slot: number;
|
|
1425
|
+
timestamp: number;
|
|
1426
|
+
}
|
|
1427
|
+
| {
|
|
1428
|
+
type: 'root';
|
|
1429
|
+
slot: number;
|
|
1430
|
+
timestamp: number;
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* @internal
|
|
1435
|
+
*/
|
|
1436
|
+
const SlotUpdateResult = union([
|
|
1437
|
+
pick({
|
|
1438
|
+
type: union([
|
|
1439
|
+
literal('firstShredReceived'),
|
|
1440
|
+
literal('completed'),
|
|
1441
|
+
literal('optimisticConfirmation'),
|
|
1442
|
+
literal('root'),
|
|
1443
|
+
]),
|
|
1444
|
+
slot: number(),
|
|
1445
|
+
timestamp: number(),
|
|
1446
|
+
}),
|
|
1447
|
+
pick({
|
|
1448
|
+
type: literal('createdBank'),
|
|
1449
|
+
parent: number(),
|
|
1450
|
+
slot: number(),
|
|
1451
|
+
timestamp: number(),
|
|
1452
|
+
}),
|
|
1453
|
+
pick({
|
|
1454
|
+
type: literal('frozen'),
|
|
1455
|
+
slot: number(),
|
|
1456
|
+
timestamp: number(),
|
|
1457
|
+
stats: pick({
|
|
1458
|
+
numTransactionEntries: number(),
|
|
1459
|
+
numSuccessfulTransactions: number(),
|
|
1460
|
+
numFailedTransactions: number(),
|
|
1461
|
+
maxTransactionsPerEntry: number(),
|
|
1462
|
+
}),
|
|
1463
|
+
}),
|
|
1464
|
+
pick({
|
|
1465
|
+
type: literal('dead'),
|
|
1466
|
+
slot: number(),
|
|
1467
|
+
timestamp: number(),
|
|
1468
|
+
err: string(),
|
|
1469
|
+
}),
|
|
1470
|
+
]);
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* Expected JSON RPC response for the "slotsUpdatesNotification" message
|
|
1474
|
+
*/
|
|
1475
|
+
const SlotUpdateNotificationResult = pick({
|
|
1476
|
+
subscription: number(),
|
|
1477
|
+
result: SlotUpdateResult,
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
/**
|
|
1481
|
+
* Expected JSON RPC response for the "signatureNotification" message
|
|
1482
|
+
*/
|
|
1483
|
+
const SignatureNotificationResult = pick({
|
|
1484
|
+
subscription: number(),
|
|
1485
|
+
result: notificationResultAndContext(
|
|
1486
|
+
union([SignatureStatusResult, SignatureReceivedResult]),
|
|
1487
|
+
),
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* Expected JSON RPC response for the "rootNotification" message
|
|
1492
|
+
*/
|
|
1493
|
+
const RootNotificationResult = pick({
|
|
1494
|
+
subscription: number(),
|
|
1495
|
+
result: number(),
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
const ContactInfoResult = pick({
|
|
1499
|
+
pubkey: string(),
|
|
1500
|
+
gossip: nullable(string()),
|
|
1501
|
+
tpu: nullable(string()),
|
|
1502
|
+
rpc: nullable(string()),
|
|
1503
|
+
version: nullable(string()),
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
const VoteAccountInfoResult = pick({
|
|
1507
|
+
votePubkey: string(),
|
|
1508
|
+
nodePubkey: string(),
|
|
1509
|
+
activatedStake: number(),
|
|
1510
|
+
epochVoteAccount: boolean(),
|
|
1511
|
+
epochCredits: array(tuple([number(), number(), number()])),
|
|
1512
|
+
commission: number(),
|
|
1513
|
+
lastVote: number(),
|
|
1514
|
+
rootSlot: nullable(number()),
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
/**
|
|
1518
|
+
* Expected JSON RPC response for the "getVoteAccounts" message
|
|
1519
|
+
*/
|
|
1520
|
+
const GetVoteAccounts = jsonRpcResult(
|
|
1521
|
+
pick({
|
|
1522
|
+
current: array(VoteAccountInfoResult),
|
|
1523
|
+
delinquent: array(VoteAccountInfoResult),
|
|
1524
|
+
}),
|
|
1525
|
+
);
|
|
1526
|
+
|
|
1527
|
+
const ConfirmationStatus = union([
|
|
1528
|
+
literal('processed'),
|
|
1529
|
+
literal('confirmed'),
|
|
1530
|
+
literal('finalized'),
|
|
1531
|
+
]);
|
|
1532
|
+
|
|
1533
|
+
const SignatureStatusResponse = pick({
|
|
1534
|
+
slot: number(),
|
|
1535
|
+
confirmations: nullable(number()),
|
|
1536
|
+
err: TransactionErrorResult,
|
|
1537
|
+
confirmationStatus: optional(ConfirmationStatus),
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1540
|
+
/**
|
|
1541
|
+
* Expected JSON RPC response for the "getSignatureStatuses" message
|
|
1542
|
+
*/
|
|
1543
|
+
const GetSignatureStatusesRpcResult = jsonRpcResultAndContext(
|
|
1544
|
+
array(nullable(SignatureStatusResponse)),
|
|
1545
|
+
);
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Expected JSON RPC response for the "getMinimumBalanceForRentExemption" message
|
|
1549
|
+
*/
|
|
1550
|
+
const GetMinimumBalanceForRentExemptionRpcResult = jsonRpcResult(number());
|
|
1551
|
+
|
|
1552
|
+
const ConfirmedTransactionResult = pick({
|
|
1553
|
+
signatures: array(string()),
|
|
1554
|
+
message: pick({
|
|
1555
|
+
accountKeys: array(string()),
|
|
1556
|
+
header: pick({
|
|
1557
|
+
numRequiredSignatures: number(),
|
|
1558
|
+
numReadonlySignedAccounts: number(),
|
|
1559
|
+
numReadonlyUnsignedAccounts: number(),
|
|
1560
|
+
}),
|
|
1561
|
+
instructions: array(
|
|
1562
|
+
pick({
|
|
1563
|
+
accounts: array(number()),
|
|
1564
|
+
data: string(),
|
|
1565
|
+
programIdIndex: number(),
|
|
1566
|
+
}),
|
|
1567
|
+
),
|
|
1568
|
+
recentBlockhash: string(),
|
|
1569
|
+
}),
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
const ParsedInstructionResult = pick({
|
|
1573
|
+
parsed: unknown(),
|
|
1574
|
+
program: string(),
|
|
1575
|
+
programId: PublicKeyFromString,
|
|
1576
|
+
});
|
|
1577
|
+
|
|
1578
|
+
const RawInstructionResult = pick({
|
|
1579
|
+
accounts: array(PublicKeyFromString),
|
|
1580
|
+
data: string(),
|
|
1581
|
+
programId: PublicKeyFromString,
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
const InstructionResult = union([
|
|
1585
|
+
RawInstructionResult,
|
|
1586
|
+
ParsedInstructionResult,
|
|
1587
|
+
]);
|
|
1588
|
+
|
|
1589
|
+
const UnknownInstructionResult = union([
|
|
1590
|
+
pick({
|
|
1591
|
+
parsed: unknown(),
|
|
1592
|
+
program: string(),
|
|
1593
|
+
programId: string(),
|
|
1594
|
+
}),
|
|
1595
|
+
pick({
|
|
1596
|
+
accounts: array(string()),
|
|
1597
|
+
data: string(),
|
|
1598
|
+
programId: string(),
|
|
1599
|
+
}),
|
|
1600
|
+
]);
|
|
1601
|
+
|
|
1602
|
+
const ParsedOrRawInstruction = coerce(
|
|
1603
|
+
InstructionResult,
|
|
1604
|
+
UnknownInstructionResult,
|
|
1605
|
+
value => {
|
|
1606
|
+
if ('accounts' in value) {
|
|
1607
|
+
return create(value, RawInstructionResult);
|
|
1608
|
+
} else {
|
|
1609
|
+
return create(value, ParsedInstructionResult);
|
|
1610
|
+
}
|
|
1611
|
+
},
|
|
1612
|
+
);
|
|
1613
|
+
|
|
1614
|
+
/**
|
|
1615
|
+
* @internal
|
|
1616
|
+
*/
|
|
1617
|
+
const ParsedConfirmedTransactionResult = pick({
|
|
1618
|
+
signatures: array(string()),
|
|
1619
|
+
message: pick({
|
|
1620
|
+
accountKeys: array(
|
|
1621
|
+
pick({
|
|
1622
|
+
pubkey: PublicKeyFromString,
|
|
1623
|
+
signer: boolean(),
|
|
1624
|
+
writable: boolean(),
|
|
1625
|
+
}),
|
|
1626
|
+
),
|
|
1627
|
+
instructions: array(ParsedOrRawInstruction),
|
|
1628
|
+
recentBlockhash: string(),
|
|
1629
|
+
}),
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
const TokenBalanceResult = pick({
|
|
1633
|
+
accountIndex: number(),
|
|
1634
|
+
mint: string(),
|
|
1635
|
+
owner: optional(string()),
|
|
1636
|
+
uiTokenAmount: TokenAmountResult,
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
/**
|
|
1640
|
+
* @internal
|
|
1641
|
+
*/
|
|
1642
|
+
const ConfirmedTransactionMetaResult = pick({
|
|
1643
|
+
err: TransactionErrorResult,
|
|
1644
|
+
fee: number(),
|
|
1645
|
+
innerInstructions: optional(
|
|
1646
|
+
nullable(
|
|
1647
|
+
array(
|
|
1648
|
+
pick({
|
|
1649
|
+
index: number(),
|
|
1650
|
+
instructions: array(
|
|
1651
|
+
pick({
|
|
1652
|
+
accounts: array(number()),
|
|
1653
|
+
data: string(),
|
|
1654
|
+
programIdIndex: number(),
|
|
1655
|
+
}),
|
|
1656
|
+
),
|
|
1657
|
+
}),
|
|
1658
|
+
),
|
|
1659
|
+
),
|
|
1660
|
+
),
|
|
1661
|
+
preBalances: array(number()),
|
|
1662
|
+
postBalances: array(number()),
|
|
1663
|
+
logMessages: optional(nullable(array(string()))),
|
|
1664
|
+
preTokenBalances: optional(nullable(array(TokenBalanceResult))),
|
|
1665
|
+
postTokenBalances: optional(nullable(array(TokenBalanceResult))),
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
/**
|
|
1669
|
+
* @internal
|
|
1670
|
+
*/
|
|
1671
|
+
const ParsedConfirmedTransactionMetaResult = pick({
|
|
1672
|
+
err: TransactionErrorResult,
|
|
1673
|
+
fee: number(),
|
|
1674
|
+
innerInstructions: optional(
|
|
1675
|
+
nullable(
|
|
1676
|
+
array(
|
|
1677
|
+
pick({
|
|
1678
|
+
index: number(),
|
|
1679
|
+
instructions: array(ParsedOrRawInstruction),
|
|
1680
|
+
}),
|
|
1681
|
+
),
|
|
1682
|
+
),
|
|
1683
|
+
),
|
|
1684
|
+
preBalances: array(number()),
|
|
1685
|
+
postBalances: array(number()),
|
|
1686
|
+
logMessages: optional(nullable(array(string()))),
|
|
1687
|
+
preTokenBalances: optional(nullable(array(TokenBalanceResult))),
|
|
1688
|
+
postTokenBalances: optional(nullable(array(TokenBalanceResult))),
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
/**
|
|
1692
|
+
* Expected JSON RPC response for the "getBlock" message
|
|
1693
|
+
*/
|
|
1694
|
+
const GetBlockRpcResult = jsonRpcResult(
|
|
1695
|
+
nullable(
|
|
1696
|
+
pick({
|
|
1697
|
+
blockhash: string(),
|
|
1698
|
+
previousBlockhash: string(),
|
|
1699
|
+
parentSlot: number(),
|
|
1700
|
+
transactions: array(
|
|
1701
|
+
pick({
|
|
1702
|
+
transaction: ConfirmedTransactionResult,
|
|
1703
|
+
meta: nullable(ConfirmedTransactionMetaResult),
|
|
1704
|
+
}),
|
|
1705
|
+
),
|
|
1706
|
+
rewards: optional(
|
|
1707
|
+
array(
|
|
1708
|
+
pick({
|
|
1709
|
+
pubkey: string(),
|
|
1710
|
+
lamports: number(),
|
|
1711
|
+
postBalance: nullable(number()),
|
|
1712
|
+
rewardType: nullable(string()),
|
|
1713
|
+
}),
|
|
1714
|
+
),
|
|
1715
|
+
),
|
|
1716
|
+
blockTime: nullable(number()),
|
|
1717
|
+
blockHeight: nullable(number()),
|
|
1718
|
+
}),
|
|
1719
|
+
),
|
|
1720
|
+
);
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* Expected JSON RPC response for the "getConfirmedBlock" message
|
|
1724
|
+
*
|
|
1725
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link GetBlockRpcResult} instead.
|
|
1726
|
+
*/
|
|
1727
|
+
const GetConfirmedBlockRpcResult = jsonRpcResult(
|
|
1728
|
+
nullable(
|
|
1729
|
+
pick({
|
|
1730
|
+
blockhash: string(),
|
|
1731
|
+
previousBlockhash: string(),
|
|
1732
|
+
parentSlot: number(),
|
|
1733
|
+
transactions: array(
|
|
1734
|
+
pick({
|
|
1735
|
+
transaction: ConfirmedTransactionResult,
|
|
1736
|
+
meta: nullable(ConfirmedTransactionMetaResult),
|
|
1737
|
+
}),
|
|
1738
|
+
),
|
|
1739
|
+
rewards: optional(
|
|
1740
|
+
array(
|
|
1741
|
+
pick({
|
|
1742
|
+
pubkey: string(),
|
|
1743
|
+
lamports: number(),
|
|
1744
|
+
postBalance: nullable(number()),
|
|
1745
|
+
rewardType: nullable(string()),
|
|
1746
|
+
}),
|
|
1747
|
+
),
|
|
1748
|
+
),
|
|
1749
|
+
blockTime: nullable(number()),
|
|
1750
|
+
}),
|
|
1751
|
+
),
|
|
1752
|
+
);
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* Expected JSON RPC response for the "getBlock" message
|
|
1756
|
+
*/
|
|
1757
|
+
const GetBlockSignaturesRpcResult = jsonRpcResult(
|
|
1758
|
+
nullable(
|
|
1759
|
+
pick({
|
|
1760
|
+
blockhash: string(),
|
|
1761
|
+
previousBlockhash: string(),
|
|
1762
|
+
parentSlot: number(),
|
|
1763
|
+
signatures: array(string()),
|
|
1764
|
+
blockTime: nullable(number()),
|
|
1765
|
+
}),
|
|
1766
|
+
),
|
|
1767
|
+
);
|
|
1768
|
+
|
|
1769
|
+
/**
|
|
1770
|
+
* Expected JSON RPC response for the "getTransaction" message
|
|
1771
|
+
*/
|
|
1772
|
+
const GetTransactionRpcResult = jsonRpcResult(
|
|
1773
|
+
nullable(
|
|
1774
|
+
pick({
|
|
1775
|
+
slot: number(),
|
|
1776
|
+
meta: ConfirmedTransactionMetaResult,
|
|
1777
|
+
blockTime: optional(nullable(number())),
|
|
1778
|
+
transaction: ConfirmedTransactionResult,
|
|
1779
|
+
}),
|
|
1780
|
+
),
|
|
1781
|
+
);
|
|
1782
|
+
|
|
1783
|
+
/**
|
|
1784
|
+
* Expected parsed JSON RPC response for the "getTransaction" message
|
|
1785
|
+
*/
|
|
1786
|
+
const GetParsedTransactionRpcResult = jsonRpcResult(
|
|
1787
|
+
nullable(
|
|
1788
|
+
pick({
|
|
1789
|
+
slot: number(),
|
|
1790
|
+
transaction: ParsedConfirmedTransactionResult,
|
|
1791
|
+
meta: nullable(ParsedConfirmedTransactionMetaResult),
|
|
1792
|
+
blockTime: optional(nullable(number())),
|
|
1793
|
+
}),
|
|
1794
|
+
),
|
|
1795
|
+
);
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* Expected JSON RPC response for the "getRecentBlockhash" message
|
|
1799
|
+
*
|
|
1800
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link GetLatestBlockhashRpcResult} instead.
|
|
1801
|
+
*/
|
|
1802
|
+
const GetRecentBlockhashAndContextRpcResult = jsonRpcResultAndContext(
|
|
1803
|
+
pick({
|
|
1804
|
+
blockhash: string(),
|
|
1805
|
+
feeCalculator: pick({
|
|
1806
|
+
lamportsPerSignature: number(),
|
|
1807
|
+
}),
|
|
1808
|
+
}),
|
|
1809
|
+
);
|
|
1810
|
+
|
|
1811
|
+
/**
|
|
1812
|
+
* Expected JSON RPC response for the "getLatestBlockhash" message
|
|
1813
|
+
*/
|
|
1814
|
+
const GetLatestBlockhashRpcResult = jsonRpcResultAndContext(
|
|
1815
|
+
pick({
|
|
1816
|
+
blockhash: string(),
|
|
1817
|
+
lastValidBlockHeight: number(),
|
|
1818
|
+
}),
|
|
1819
|
+
);
|
|
1820
|
+
|
|
1821
|
+
const PerfSampleResult = pick({
|
|
1822
|
+
slot: number(),
|
|
1823
|
+
numTransactions: number(),
|
|
1824
|
+
numSlots: number(),
|
|
1825
|
+
samplePeriodSecs: number(),
|
|
1826
|
+
});
|
|
1827
|
+
|
|
1828
|
+
/*
|
|
1829
|
+
* Expected JSON RPC response for "getRecentPerformanceSamples" message
|
|
1830
|
+
*/
|
|
1831
|
+
const GetRecentPerformanceSamplesRpcResult = jsonRpcResult(
|
|
1832
|
+
array(PerfSampleResult),
|
|
1833
|
+
);
|
|
1834
|
+
|
|
1835
|
+
/**
|
|
1836
|
+
* Expected JSON RPC response for the "getFeeCalculatorForBlockhash" message
|
|
1837
|
+
*/
|
|
1838
|
+
const GetFeeCalculatorRpcResult = jsonRpcResultAndContext(
|
|
1839
|
+
nullable(
|
|
1840
|
+
pick({
|
|
1841
|
+
feeCalculator: pick({
|
|
1842
|
+
lamportsPerSignature: number(),
|
|
1843
|
+
}),
|
|
1844
|
+
}),
|
|
1845
|
+
),
|
|
1846
|
+
);
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* Expected JSON RPC response for the "requestAirdrop" message
|
|
1850
|
+
*/
|
|
1851
|
+
const RequestAirdropRpcResult = jsonRpcResult(string());
|
|
1852
|
+
|
|
1853
|
+
/**
|
|
1854
|
+
* Expected JSON RPC response for the "sendTransaction" message
|
|
1855
|
+
*/
|
|
1856
|
+
const SendTransactionRpcResult = jsonRpcResult(string());
|
|
1857
|
+
|
|
1858
|
+
/**
|
|
1859
|
+
* Information about the latest slot being processed by a node
|
|
1860
|
+
*/
|
|
1861
|
+
export type SlotInfo = {
|
|
1862
|
+
/** Currently processing slot */
|
|
1863
|
+
slot: number;
|
|
1864
|
+
/** Parent of the current slot */
|
|
1865
|
+
parent: number;
|
|
1866
|
+
/** The root block of the current slot's fork */
|
|
1867
|
+
root: number;
|
|
1868
|
+
};
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* Parsed account data
|
|
1872
|
+
*/
|
|
1873
|
+
export type ParsedAccountData = {
|
|
1874
|
+
/** Name of the program that owns this account */
|
|
1875
|
+
program: string;
|
|
1876
|
+
/** Parsed account data */
|
|
1877
|
+
parsed: any;
|
|
1878
|
+
/** Space used by account data */
|
|
1879
|
+
space: number;
|
|
1880
|
+
};
|
|
1881
|
+
|
|
1882
|
+
/**
|
|
1883
|
+
* Stake Activation data
|
|
1884
|
+
*/
|
|
1885
|
+
export type StakeActivationData = {
|
|
1886
|
+
/** the stake account's activation state */
|
|
1887
|
+
state: 'active' | 'inactive' | 'activating' | 'deactivating';
|
|
1888
|
+
/** stake active during the epoch */
|
|
1889
|
+
active: number;
|
|
1890
|
+
/** stake inactive during the epoch */
|
|
1891
|
+
inactive: number;
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1894
|
+
/**
|
|
1895
|
+
* Data slice argument for getProgramAccounts
|
|
1896
|
+
*/
|
|
1897
|
+
export type DataSlice = {
|
|
1898
|
+
/** offset of data slice */
|
|
1899
|
+
offset: number;
|
|
1900
|
+
/** length of data slice */
|
|
1901
|
+
length: number;
|
|
1902
|
+
};
|
|
1903
|
+
|
|
1904
|
+
/**
|
|
1905
|
+
* Memory comparison filter for getProgramAccounts
|
|
1906
|
+
*/
|
|
1907
|
+
export type MemcmpFilter = {
|
|
1908
|
+
memcmp: {
|
|
1909
|
+
/** offset into program account data to start comparison */
|
|
1910
|
+
offset: number;
|
|
1911
|
+
/** data to match, as base-58 encoded string and limited to less than 129 bytes */
|
|
1912
|
+
bytes: string;
|
|
1913
|
+
};
|
|
1914
|
+
};
|
|
1915
|
+
|
|
1916
|
+
/**
|
|
1917
|
+
* Data size comparison filter for getProgramAccounts
|
|
1918
|
+
*/
|
|
1919
|
+
export type DataSizeFilter = {
|
|
1920
|
+
/** Size of data for program account data length comparison */
|
|
1921
|
+
dataSize: number;
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
/**
|
|
1925
|
+
* A filter object for getProgramAccounts
|
|
1926
|
+
*/
|
|
1927
|
+
export type GetProgramAccountsFilter = MemcmpFilter | DataSizeFilter;
|
|
1928
|
+
|
|
1929
|
+
/**
|
|
1930
|
+
* Configuration object for getProgramAccounts requests
|
|
1931
|
+
*/
|
|
1932
|
+
export type GetProgramAccountsConfig = {
|
|
1933
|
+
/** Optional commitment level */
|
|
1934
|
+
commitment?: Commitment;
|
|
1935
|
+
/** Optional encoding for account data (default base64)
|
|
1936
|
+
* To use "jsonParsed" encoding, please refer to `getParsedProgramAccounts` in connection.ts
|
|
1937
|
+
* */
|
|
1938
|
+
encoding?: 'base64';
|
|
1939
|
+
/** Optional data slice to limit the returned account data */
|
|
1940
|
+
dataSlice?: DataSlice;
|
|
1941
|
+
/** Optional array of filters to apply to accounts */
|
|
1942
|
+
filters?: GetProgramAccountsFilter[];
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
/**
|
|
1946
|
+
* Configuration object for getParsedProgramAccounts
|
|
1947
|
+
*/
|
|
1948
|
+
export type GetParsedProgramAccountsConfig = {
|
|
1949
|
+
/** Optional commitment level */
|
|
1950
|
+
commitment?: Commitment;
|
|
1951
|
+
/** Optional array of filters to apply to accounts */
|
|
1952
|
+
filters?: GetProgramAccountsFilter[];
|
|
1953
|
+
};
|
|
1954
|
+
|
|
1955
|
+
/**
|
|
1956
|
+
* Configuration object for getMultipleAccounts
|
|
1957
|
+
*/
|
|
1958
|
+
export type GetMultipleAccountsConfig = {
|
|
1959
|
+
/** Optional commitment level */
|
|
1960
|
+
commitment?: Commitment;
|
|
1961
|
+
/** Optional encoding for account data (default base64) */
|
|
1962
|
+
encoding?: 'base64' | 'jsonParsed';
|
|
1963
|
+
};
|
|
1964
|
+
|
|
1965
|
+
/**
|
|
1966
|
+
* Information describing an account
|
|
1967
|
+
*/
|
|
1968
|
+
export type AccountInfo<T> = {
|
|
1969
|
+
/** `true` if this account's data contains a loaded program */
|
|
1970
|
+
executable: boolean;
|
|
1971
|
+
/** Identifier of the program that owns the account */
|
|
1972
|
+
owner: PublicKey;
|
|
1973
|
+
/** Number of lamports assigned to the account */
|
|
1974
|
+
lamports: number;
|
|
1975
|
+
/** Optional data assigned to the account */
|
|
1976
|
+
data: T;
|
|
1977
|
+
/** Optional rent epoch info for account */
|
|
1978
|
+
rentEpoch?: number;
|
|
1979
|
+
};
|
|
1980
|
+
|
|
1981
|
+
/**
|
|
1982
|
+
* Account information identified by pubkey
|
|
1983
|
+
*/
|
|
1984
|
+
export type KeyedAccountInfo = {
|
|
1985
|
+
accountId: PublicKey;
|
|
1986
|
+
accountInfo: AccountInfo<Buffer>;
|
|
1987
|
+
};
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* Callback function for account change notifications
|
|
1991
|
+
*/
|
|
1992
|
+
export type AccountChangeCallback = (
|
|
1993
|
+
accountInfo: AccountInfo<Buffer>,
|
|
1994
|
+
context: Context,
|
|
1995
|
+
) => void;
|
|
1996
|
+
|
|
1997
|
+
/**
|
|
1998
|
+
* Callback function for program account change notifications
|
|
1999
|
+
*/
|
|
2000
|
+
export type ProgramAccountChangeCallback = (
|
|
2001
|
+
keyedAccountInfo: KeyedAccountInfo,
|
|
2002
|
+
context: Context,
|
|
2003
|
+
) => void;
|
|
2004
|
+
|
|
2005
|
+
/**
|
|
2006
|
+
* Callback function for slot change notifications
|
|
2007
|
+
*/
|
|
2008
|
+
export type SlotChangeCallback = (slotInfo: SlotInfo) => void;
|
|
2009
|
+
|
|
2010
|
+
/**
|
|
2011
|
+
* Callback function for slot update notifications
|
|
2012
|
+
*/
|
|
2013
|
+
export type SlotUpdateCallback = (slotUpdate: SlotUpdate) => void;
|
|
2014
|
+
|
|
2015
|
+
/**
|
|
2016
|
+
* Callback function for signature status notifications
|
|
2017
|
+
*/
|
|
2018
|
+
export type SignatureResultCallback = (
|
|
2019
|
+
signatureResult: SignatureResult,
|
|
2020
|
+
context: Context,
|
|
2021
|
+
) => void;
|
|
2022
|
+
|
|
2023
|
+
/**
|
|
2024
|
+
* Signature status notification with transaction result
|
|
2025
|
+
*/
|
|
2026
|
+
export type SignatureStatusNotification = {
|
|
2027
|
+
type: 'status';
|
|
2028
|
+
result: SignatureResult;
|
|
2029
|
+
};
|
|
2030
|
+
|
|
2031
|
+
/**
|
|
2032
|
+
* Signature received notification
|
|
2033
|
+
*/
|
|
2034
|
+
export type SignatureReceivedNotification = {
|
|
2035
|
+
type: 'received';
|
|
2036
|
+
};
|
|
2037
|
+
|
|
2038
|
+
/**
|
|
2039
|
+
* Callback function for signature notifications
|
|
2040
|
+
*/
|
|
2041
|
+
export type SignatureSubscriptionCallback = (
|
|
2042
|
+
notification: SignatureStatusNotification | SignatureReceivedNotification,
|
|
2043
|
+
context: Context,
|
|
2044
|
+
) => void;
|
|
2045
|
+
|
|
2046
|
+
/**
|
|
2047
|
+
* Signature subscription options
|
|
2048
|
+
*/
|
|
2049
|
+
export type SignatureSubscriptionOptions = {
|
|
2050
|
+
commitment?: Commitment; // Must default to 'finalized'
|
|
2051
|
+
enableReceivedNotification?: boolean;
|
|
2052
|
+
};
|
|
2053
|
+
|
|
2054
|
+
/**
|
|
2055
|
+
* Callback function for root change notifications
|
|
2056
|
+
*/
|
|
2057
|
+
export type RootChangeCallback = (root: number) => void;
|
|
2058
|
+
|
|
2059
|
+
/**
|
|
2060
|
+
* @internal
|
|
2061
|
+
*/
|
|
2062
|
+
const LogsResult = pick({
|
|
2063
|
+
err: TransactionErrorResult,
|
|
2064
|
+
logs: array(string()),
|
|
2065
|
+
signature: string(),
|
|
2066
|
+
});
|
|
2067
|
+
|
|
2068
|
+
/**
|
|
2069
|
+
* Logs result.
|
|
2070
|
+
*/
|
|
2071
|
+
export type Logs = {
|
|
2072
|
+
err: TransactionError | null;
|
|
2073
|
+
logs: string[];
|
|
2074
|
+
signature: string;
|
|
2075
|
+
};
|
|
2076
|
+
|
|
2077
|
+
/**
|
|
2078
|
+
* Expected JSON RPC response for the "logsNotification" message.
|
|
2079
|
+
*/
|
|
2080
|
+
const LogsNotificationResult = pick({
|
|
2081
|
+
result: notificationResultAndContext(LogsResult),
|
|
2082
|
+
subscription: number(),
|
|
2083
|
+
});
|
|
2084
|
+
|
|
2085
|
+
/**
|
|
2086
|
+
* Filter for log subscriptions.
|
|
2087
|
+
*/
|
|
2088
|
+
export type LogsFilter = PublicKey | 'all' | 'allWithVotes';
|
|
2089
|
+
|
|
2090
|
+
/**
|
|
2091
|
+
* Callback function for log notifications.
|
|
2092
|
+
*/
|
|
2093
|
+
export type LogsCallback = (logs: Logs, ctx: Context) => void;
|
|
2094
|
+
|
|
2095
|
+
/**
|
|
2096
|
+
* Signature result
|
|
2097
|
+
*/
|
|
2098
|
+
export type SignatureResult = {
|
|
2099
|
+
err: TransactionError | null;
|
|
2100
|
+
};
|
|
2101
|
+
|
|
2102
|
+
/**
|
|
2103
|
+
* Transaction error
|
|
2104
|
+
*/
|
|
2105
|
+
export type TransactionError = {} | string;
|
|
2106
|
+
|
|
2107
|
+
/**
|
|
2108
|
+
* Transaction confirmation status
|
|
2109
|
+
* <pre>
|
|
2110
|
+
* 'processed': Transaction landed in a block which has reached 1 confirmation by the connected node
|
|
2111
|
+
* 'confirmed': Transaction landed in a block which has reached 1 confirmation by the cluster
|
|
2112
|
+
* 'finalized': Transaction landed in a block which has been finalized by the cluster
|
|
2113
|
+
* </pre>
|
|
2114
|
+
*/
|
|
2115
|
+
export type TransactionConfirmationStatus =
|
|
2116
|
+
| 'processed'
|
|
2117
|
+
| 'confirmed'
|
|
2118
|
+
| 'finalized';
|
|
2119
|
+
|
|
2120
|
+
/**
|
|
2121
|
+
* Signature status
|
|
2122
|
+
*/
|
|
2123
|
+
export type SignatureStatus = {
|
|
2124
|
+
/** when the transaction was processed */
|
|
2125
|
+
slot: number;
|
|
2126
|
+
/** the number of blocks that have been confirmed and voted on in the fork containing `slot` */
|
|
2127
|
+
confirmations: number | null;
|
|
2128
|
+
/** transaction error, if any */
|
|
2129
|
+
err: TransactionError | null;
|
|
2130
|
+
/** cluster confirmation status, if data available. Possible responses: `processed`, `confirmed`, `finalized` */
|
|
2131
|
+
confirmationStatus?: TransactionConfirmationStatus;
|
|
2132
|
+
};
|
|
2133
|
+
|
|
2134
|
+
/**
|
|
2135
|
+
* A confirmed signature with its status
|
|
2136
|
+
*/
|
|
2137
|
+
export type ConfirmedSignatureInfo = {
|
|
2138
|
+
/** the transaction signature */
|
|
2139
|
+
signature: string;
|
|
2140
|
+
/** when the transaction was processed */
|
|
2141
|
+
slot: number;
|
|
2142
|
+
/** error, if any */
|
|
2143
|
+
err: TransactionError | null;
|
|
2144
|
+
/** memo associated with the transaction, if any */
|
|
2145
|
+
memo: string | null;
|
|
2146
|
+
/** The unix timestamp of when the transaction was processed */
|
|
2147
|
+
blockTime?: number | null;
|
|
2148
|
+
};
|
|
2149
|
+
|
|
2150
|
+
/**
|
|
2151
|
+
* An object defining headers to be passed to the RPC server
|
|
2152
|
+
*/
|
|
2153
|
+
export type HttpHeaders = {[header: string]: string};
|
|
2154
|
+
|
|
2155
|
+
/**
|
|
2156
|
+
* A callback used to augment the outgoing HTTP request
|
|
2157
|
+
*/
|
|
2158
|
+
export type FetchMiddleware = (
|
|
2159
|
+
url: string,
|
|
2160
|
+
options: any,
|
|
2161
|
+
fetch: (modifiedUrl: string, modifiedOptions: any) => void,
|
|
2162
|
+
) => void;
|
|
2163
|
+
|
|
2164
|
+
/**
|
|
2165
|
+
* Configuration for instantiating a Connection
|
|
2166
|
+
*/
|
|
2167
|
+
export type ConnectionConfig = {
|
|
2168
|
+
/** Optional commitment level */
|
|
2169
|
+
commitment?: Commitment;
|
|
2170
|
+
/** Optional endpoint URL to the fullnode JSON RPC PubSub WebSocket Endpoint */
|
|
2171
|
+
wsEndpoint?: string;
|
|
2172
|
+
/** Optional HTTP headers object */
|
|
2173
|
+
httpHeaders?: HttpHeaders;
|
|
2174
|
+
/** Optional custom fetch function */
|
|
2175
|
+
fetch?: typeof crossFetch;
|
|
2176
|
+
/** Optional fetch middleware callback */
|
|
2177
|
+
fetchMiddleware?: FetchMiddleware;
|
|
2178
|
+
/** Optional Disable retrying calls when server responds with HTTP 429 (Too Many Requests) */
|
|
2179
|
+
disableRetryOnRateLimit?: boolean;
|
|
2180
|
+
/** time to allow for the server to initially process a transaction (in milliseconds) */
|
|
2181
|
+
confirmTransactionInitialTimeout?: number;
|
|
2182
|
+
};
|
|
2183
|
+
|
|
2184
|
+
/**
|
|
2185
|
+
* A connection to a fullnode JSON RPC endpoint
|
|
2186
|
+
*/
|
|
2187
|
+
export class Connection {
|
|
2188
|
+
/** @internal */ _commitment?: Commitment;
|
|
2189
|
+
/** @internal */ _confirmTransactionInitialTimeout?: number;
|
|
2190
|
+
/** @internal */ _rpcEndpoint: string;
|
|
2191
|
+
/** @internal */ _rpcWsEndpoint: string;
|
|
2192
|
+
/** @internal */ _rpcClient: RpcClient;
|
|
2193
|
+
/** @internal */ _rpcRequest: RpcRequest;
|
|
2194
|
+
/** @internal */ _rpcBatchRequest: RpcBatchRequest;
|
|
2195
|
+
/** @internal */ _rpcWebSocket: RpcWebSocketClient;
|
|
2196
|
+
/** @internal */ _rpcWebSocketConnected: boolean = false;
|
|
2197
|
+
/** @internal */ _rpcWebSocketHeartbeat: ReturnType<
|
|
2198
|
+
typeof setInterval
|
|
2199
|
+
> | null = null;
|
|
2200
|
+
/** @internal */ _rpcWebSocketIdleTimeout: ReturnType<
|
|
2201
|
+
typeof setTimeout
|
|
2202
|
+
> | null = null;
|
|
2203
|
+
|
|
2204
|
+
/** @internal */ _disableBlockhashCaching: boolean = false;
|
|
2205
|
+
/** @internal */ _pollingBlockhash: boolean = false;
|
|
2206
|
+
/** @internal */ _blockhashInfo: {
|
|
2207
|
+
recentBlockhash: Blockhash | null;
|
|
2208
|
+
lastFetch: number;
|
|
2209
|
+
simulatedSignatures: Array<string>;
|
|
2210
|
+
transactionSignatures: Array<string>;
|
|
2211
|
+
} = {
|
|
2212
|
+
recentBlockhash: null,
|
|
2213
|
+
lastFetch: 0,
|
|
2214
|
+
transactionSignatures: [],
|
|
2215
|
+
simulatedSignatures: [],
|
|
2216
|
+
};
|
|
2217
|
+
|
|
2218
|
+
/** @internal */ _nextClientSubscriptionId: ClientSubscriptionId = 0;
|
|
2219
|
+
/** @internal */ _subscriptionDisposeFunctionsByClientSubscriptionId: {
|
|
2220
|
+
[clientSubscriptionId: ClientSubscriptionId]:
|
|
2221
|
+
| SubscriptionDisposeFn
|
|
2222
|
+
| undefined;
|
|
2223
|
+
} = {};
|
|
2224
|
+
/** @internal */ _subscriptionCallbacksByServerSubscriptionId: {
|
|
2225
|
+
[serverSubscriptionId: ServerSubscriptionId]:
|
|
2226
|
+
| Set<SubscriptionConfig['callback']>
|
|
2227
|
+
| undefined;
|
|
2228
|
+
} = {};
|
|
2229
|
+
/** @internal */ _subscriptionsByHash: {
|
|
2230
|
+
[hash: SubscriptionConfigHash]: Subscription | undefined;
|
|
2231
|
+
} = {};
|
|
2232
|
+
/**
|
|
2233
|
+
* Special case.
|
|
2234
|
+
* After a signature is processed, RPCs automatically dispose of the
|
|
2235
|
+
* subscription on the server side. We need to track which of these
|
|
2236
|
+
* subscriptions have been disposed in such a way, so that we know
|
|
2237
|
+
* whether the client is dealing with a not-yet-processed signature
|
|
2238
|
+
* (in which case we must tear down the server subscription) or an
|
|
2239
|
+
* already-processed signature (in which case the client can simply
|
|
2240
|
+
* clear out the subscription locally without telling the server).
|
|
2241
|
+
*
|
|
2242
|
+
* NOTE: There is a proposal to eliminate this special case, here:
|
|
2243
|
+
* https://github.com/solana-labs/solana/issues/18892
|
|
2244
|
+
*/
|
|
2245
|
+
/** @internal */ _subscriptionsAutoDisposedByRpc: Set<ServerSubscriptionId> =
|
|
2246
|
+
new Set();
|
|
2247
|
+
|
|
2248
|
+
/**
|
|
2249
|
+
* Establish a JSON RPC connection
|
|
2250
|
+
*
|
|
2251
|
+
* @param endpoint URL to the fullnode JSON RPC endpoint
|
|
2252
|
+
* @param commitmentOrConfig optional default commitment level or optional ConnectionConfig configuration object
|
|
2253
|
+
*/
|
|
2254
|
+
constructor(
|
|
2255
|
+
endpoint: string,
|
|
2256
|
+
commitmentOrConfig?: Commitment | ConnectionConfig,
|
|
2257
|
+
) {
|
|
2258
|
+
let url = new URL(endpoint);
|
|
2259
|
+
const useHttps = url.protocol === 'https:';
|
|
2260
|
+
|
|
2261
|
+
let wsEndpoint;
|
|
2262
|
+
let httpHeaders;
|
|
2263
|
+
let fetch;
|
|
2264
|
+
let fetchMiddleware;
|
|
2265
|
+
let disableRetryOnRateLimit;
|
|
2266
|
+
if (commitmentOrConfig && typeof commitmentOrConfig === 'string') {
|
|
2267
|
+
this._commitment = commitmentOrConfig;
|
|
2268
|
+
} else if (commitmentOrConfig) {
|
|
2269
|
+
this._commitment = commitmentOrConfig.commitment;
|
|
2270
|
+
this._confirmTransactionInitialTimeout =
|
|
2271
|
+
commitmentOrConfig.confirmTransactionInitialTimeout;
|
|
2272
|
+
wsEndpoint = commitmentOrConfig.wsEndpoint;
|
|
2273
|
+
httpHeaders = commitmentOrConfig.httpHeaders;
|
|
2274
|
+
fetch = commitmentOrConfig.fetch;
|
|
2275
|
+
fetchMiddleware = commitmentOrConfig.fetchMiddleware;
|
|
2276
|
+
disableRetryOnRateLimit = commitmentOrConfig.disableRetryOnRateLimit;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
this._rpcEndpoint = endpoint;
|
|
2280
|
+
this._rpcWsEndpoint = wsEndpoint || makeWebsocketUrl(endpoint);
|
|
2281
|
+
|
|
2282
|
+
this._rpcClient = createRpcClient(
|
|
2283
|
+
url.toString(),
|
|
2284
|
+
useHttps,
|
|
2285
|
+
httpHeaders,
|
|
2286
|
+
fetch,
|
|
2287
|
+
fetchMiddleware,
|
|
2288
|
+
disableRetryOnRateLimit,
|
|
2289
|
+
);
|
|
2290
|
+
this._rpcRequest = createRpcRequest(this._rpcClient);
|
|
2291
|
+
this._rpcBatchRequest = createRpcBatchRequest(this._rpcClient);
|
|
2292
|
+
|
|
2293
|
+
this._rpcWebSocket = new RpcWebSocketClient(this._rpcWsEndpoint, {
|
|
2294
|
+
autoconnect: false,
|
|
2295
|
+
max_reconnects: Infinity,
|
|
2296
|
+
});
|
|
2297
|
+
this._rpcWebSocket.on('open', this._wsOnOpen.bind(this));
|
|
2298
|
+
this._rpcWebSocket.on('error', this._wsOnError.bind(this));
|
|
2299
|
+
this._rpcWebSocket.on('close', this._wsOnClose.bind(this));
|
|
2300
|
+
this._rpcWebSocket.on(
|
|
2301
|
+
'accountNotification',
|
|
2302
|
+
this._wsOnAccountNotification.bind(this),
|
|
2303
|
+
);
|
|
2304
|
+
this._rpcWebSocket.on(
|
|
2305
|
+
'programNotification',
|
|
2306
|
+
this._wsOnProgramAccountNotification.bind(this),
|
|
2307
|
+
);
|
|
2308
|
+
this._rpcWebSocket.on(
|
|
2309
|
+
'slotNotification',
|
|
2310
|
+
this._wsOnSlotNotification.bind(this),
|
|
2311
|
+
);
|
|
2312
|
+
this._rpcWebSocket.on(
|
|
2313
|
+
'slotsUpdatesNotification',
|
|
2314
|
+
this._wsOnSlotUpdatesNotification.bind(this),
|
|
2315
|
+
);
|
|
2316
|
+
this._rpcWebSocket.on(
|
|
2317
|
+
'signatureNotification',
|
|
2318
|
+
this._wsOnSignatureNotification.bind(this),
|
|
2319
|
+
);
|
|
2320
|
+
this._rpcWebSocket.on(
|
|
2321
|
+
'rootNotification',
|
|
2322
|
+
this._wsOnRootNotification.bind(this),
|
|
2323
|
+
);
|
|
2324
|
+
this._rpcWebSocket.on(
|
|
2325
|
+
'logsNotification',
|
|
2326
|
+
this._wsOnLogsNotification.bind(this),
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
/**
|
|
2331
|
+
* The default commitment used for requests
|
|
2332
|
+
*/
|
|
2333
|
+
get commitment(): Commitment | undefined {
|
|
2334
|
+
return this._commitment;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
/**
|
|
2338
|
+
* The RPC endpoint
|
|
2339
|
+
*/
|
|
2340
|
+
get rpcEndpoint(): string {
|
|
2341
|
+
return this._rpcEndpoint;
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
/**
|
|
2345
|
+
* Fetch the balance for the specified public key, return with context
|
|
2346
|
+
*/
|
|
2347
|
+
async getBalanceAndContext(
|
|
2348
|
+
publicKey: PublicKey,
|
|
2349
|
+
commitment?: Commitment,
|
|
2350
|
+
): Promise<RpcResponseAndContext<number>> {
|
|
2351
|
+
const args = this._buildArgs([publicKey.toBase58()], commitment);
|
|
2352
|
+
const unsafeRes = await this._rpcRequest('getBalance', args);
|
|
2353
|
+
const res = create(unsafeRes, jsonRpcResultAndContext(number()));
|
|
2354
|
+
if ('error' in res) {
|
|
2355
|
+
throw new Error(
|
|
2356
|
+
'failed to get balance for ' +
|
|
2357
|
+
publicKey.toBase58() +
|
|
2358
|
+
': ' +
|
|
2359
|
+
res.error.message,
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
return res.result;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
/**
|
|
2366
|
+
* Fetch the balance for the specified public key
|
|
2367
|
+
*/
|
|
2368
|
+
async getBalance(
|
|
2369
|
+
publicKey: PublicKey,
|
|
2370
|
+
commitment?: Commitment,
|
|
2371
|
+
): Promise<number> {
|
|
2372
|
+
return await this.getBalanceAndContext(publicKey, commitment)
|
|
2373
|
+
.then(x => x.value)
|
|
2374
|
+
.catch(e => {
|
|
2375
|
+
throw new Error(
|
|
2376
|
+
'failed to get balance of account ' + publicKey.toBase58() + ': ' + e,
|
|
2377
|
+
);
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
/**
|
|
2382
|
+
* Fetch the estimated production time of a block
|
|
2383
|
+
*/
|
|
2384
|
+
async getBlockTime(slot: number): Promise<number | null> {
|
|
2385
|
+
const unsafeRes = await this._rpcRequest('getBlockTime', [slot]);
|
|
2386
|
+
const res = create(unsafeRes, jsonRpcResult(nullable(number())));
|
|
2387
|
+
if ('error' in res) {
|
|
2388
|
+
throw new Error(
|
|
2389
|
+
'failed to get block time for slot ' + slot + ': ' + res.error.message,
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
return res.result;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
/**
|
|
2396
|
+
* Fetch the lowest slot that the node has information about in its ledger.
|
|
2397
|
+
* This value may increase over time if the node is configured to purge older ledger data
|
|
2398
|
+
*/
|
|
2399
|
+
async getMinimumLedgerSlot(): Promise<number> {
|
|
2400
|
+
const unsafeRes = await this._rpcRequest('minimumLedgerSlot', []);
|
|
2401
|
+
const res = create(unsafeRes, jsonRpcResult(number()));
|
|
2402
|
+
if ('error' in res) {
|
|
2403
|
+
throw new Error(
|
|
2404
|
+
'failed to get minimum ledger slot: ' + res.error.message,
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
return res.result;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
/**
|
|
2411
|
+
* Fetch the slot of the lowest confirmed block that has not been purged from the ledger
|
|
2412
|
+
*/
|
|
2413
|
+
async getFirstAvailableBlock(): Promise<number> {
|
|
2414
|
+
const unsafeRes = await this._rpcRequest('getFirstAvailableBlock', []);
|
|
2415
|
+
const res = create(unsafeRes, SlotRpcResult);
|
|
2416
|
+
if ('error' in res) {
|
|
2417
|
+
throw new Error(
|
|
2418
|
+
'failed to get first available block: ' + res.error.message,
|
|
2419
|
+
);
|
|
2420
|
+
}
|
|
2421
|
+
return res.result;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
/**
|
|
2425
|
+
* Fetch information about the current supply
|
|
2426
|
+
*/
|
|
2427
|
+
async getSupply(
|
|
2428
|
+
config?: GetSupplyConfig | Commitment,
|
|
2429
|
+
): Promise<RpcResponseAndContext<Supply>> {
|
|
2430
|
+
let configArg: GetSupplyConfig = {};
|
|
2431
|
+
if (typeof config === 'string') {
|
|
2432
|
+
configArg = {commitment: config};
|
|
2433
|
+
} else if (config) {
|
|
2434
|
+
configArg = {
|
|
2435
|
+
...config,
|
|
2436
|
+
commitment: (config && config.commitment) || this.commitment,
|
|
2437
|
+
};
|
|
2438
|
+
} else {
|
|
2439
|
+
configArg = {
|
|
2440
|
+
commitment: this.commitment,
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
const unsafeRes = await this._rpcRequest('getSupply', [configArg]);
|
|
2445
|
+
const res = create(unsafeRes, GetSupplyRpcResult);
|
|
2446
|
+
if ('error' in res) {
|
|
2447
|
+
throw new Error('failed to get supply: ' + res.error.message);
|
|
2448
|
+
}
|
|
2449
|
+
return res.result;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
/**
|
|
2453
|
+
* Fetch the current supply of a token mint
|
|
2454
|
+
*/
|
|
2455
|
+
async getTokenSupply(
|
|
2456
|
+
tokenMintAddress: PublicKey,
|
|
2457
|
+
commitment?: Commitment,
|
|
2458
|
+
): Promise<RpcResponseAndContext<TokenAmount>> {
|
|
2459
|
+
const args = this._buildArgs([tokenMintAddress.toBase58()], commitment);
|
|
2460
|
+
const unsafeRes = await this._rpcRequest('getTokenSupply', args);
|
|
2461
|
+
const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult));
|
|
2462
|
+
if ('error' in res) {
|
|
2463
|
+
throw new Error('failed to get token supply: ' + res.error.message);
|
|
2464
|
+
}
|
|
2465
|
+
return res.result;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
/**
|
|
2469
|
+
* Fetch the current balance of a token account
|
|
2470
|
+
*/
|
|
2471
|
+
async getTokenAccountBalance(
|
|
2472
|
+
tokenAddress: PublicKey,
|
|
2473
|
+
commitment?: Commitment,
|
|
2474
|
+
): Promise<RpcResponseAndContext<TokenAmount>> {
|
|
2475
|
+
const args = this._buildArgs([tokenAddress.toBase58()], commitment);
|
|
2476
|
+
const unsafeRes = await this._rpcRequest('getTokenAccountBalance', args);
|
|
2477
|
+
const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult));
|
|
2478
|
+
if ('error' in res) {
|
|
2479
|
+
throw new Error(
|
|
2480
|
+
'failed to get token account balance: ' + res.error.message,
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
return res.result;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
/**
|
|
2487
|
+
* Fetch all the token accounts owned by the specified account
|
|
2488
|
+
*
|
|
2489
|
+
* @return {Promise<RpcResponseAndContext<Array<{pubkey: PublicKey, account: AccountInfo<Buffer>}>>>}
|
|
2490
|
+
*/
|
|
2491
|
+
async getTokenAccountsByOwner(
|
|
2492
|
+
ownerAddress: PublicKey,
|
|
2493
|
+
filter: TokenAccountsFilter,
|
|
2494
|
+
commitment?: Commitment,
|
|
2495
|
+
): Promise<
|
|
2496
|
+
RpcResponseAndContext<
|
|
2497
|
+
Array<{pubkey: PublicKey; account: AccountInfo<Buffer>}>
|
|
2498
|
+
>
|
|
2499
|
+
> {
|
|
2500
|
+
let _args: any[] = [ownerAddress.toBase58()];
|
|
2501
|
+
if ('mint' in filter) {
|
|
2502
|
+
_args.push({mint: filter.mint.toBase58()});
|
|
2503
|
+
} else {
|
|
2504
|
+
_args.push({programId: filter.programId.toBase58()});
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
const args = this._buildArgs(_args, commitment, 'base64');
|
|
2508
|
+
const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args);
|
|
2509
|
+
const res = create(unsafeRes, GetTokenAccountsByOwner);
|
|
2510
|
+
if ('error' in res) {
|
|
2511
|
+
throw new Error(
|
|
2512
|
+
'failed to get token accounts owned by account ' +
|
|
2513
|
+
ownerAddress.toBase58() +
|
|
2514
|
+
': ' +
|
|
2515
|
+
res.error.message,
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
return res.result;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
/**
|
|
2522
|
+
* Fetch parsed token accounts owned by the specified account
|
|
2523
|
+
*
|
|
2524
|
+
* @return {Promise<RpcResponseAndContext<Array<{pubkey: PublicKey, account: AccountInfo<ParsedAccountData>}>>>}
|
|
2525
|
+
*/
|
|
2526
|
+
async getParsedTokenAccountsByOwner(
|
|
2527
|
+
ownerAddress: PublicKey,
|
|
2528
|
+
filter: TokenAccountsFilter,
|
|
2529
|
+
commitment?: Commitment,
|
|
2530
|
+
): Promise<
|
|
2531
|
+
RpcResponseAndContext<
|
|
2532
|
+
Array<{pubkey: PublicKey; account: AccountInfo<ParsedAccountData>}>
|
|
2533
|
+
>
|
|
2534
|
+
> {
|
|
2535
|
+
let _args: any[] = [ownerAddress.toBase58()];
|
|
2536
|
+
if ('mint' in filter) {
|
|
2537
|
+
_args.push({mint: filter.mint.toBase58()});
|
|
2538
|
+
} else {
|
|
2539
|
+
_args.push({programId: filter.programId.toBase58()});
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
const args = this._buildArgs(_args, commitment, 'jsonParsed');
|
|
2543
|
+
const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args);
|
|
2544
|
+
const res = create(unsafeRes, GetParsedTokenAccountsByOwner);
|
|
2545
|
+
if ('error' in res) {
|
|
2546
|
+
throw new Error(
|
|
2547
|
+
'failed to get token accounts owned by account ' +
|
|
2548
|
+
ownerAddress.toBase58() +
|
|
2549
|
+
': ' +
|
|
2550
|
+
res.error.message,
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
return res.result;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
/**
|
|
2557
|
+
* Fetch the 20 largest accounts with their current balances
|
|
2558
|
+
*/
|
|
2559
|
+
async getLargestAccounts(
|
|
2560
|
+
config?: GetLargestAccountsConfig,
|
|
2561
|
+
): Promise<RpcResponseAndContext<Array<AccountBalancePair>>> {
|
|
2562
|
+
const arg = {
|
|
2563
|
+
...config,
|
|
2564
|
+
commitment: (config && config.commitment) || this.commitment,
|
|
2565
|
+
};
|
|
2566
|
+
const args = arg.filter || arg.commitment ? [arg] : [];
|
|
2567
|
+
const unsafeRes = await this._rpcRequest('getLargestAccounts', args);
|
|
2568
|
+
const res = create(unsafeRes, GetLargestAccountsRpcResult);
|
|
2569
|
+
if ('error' in res) {
|
|
2570
|
+
throw new Error('failed to get largest accounts: ' + res.error.message);
|
|
2571
|
+
}
|
|
2572
|
+
return res.result;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
/**
|
|
2576
|
+
* Fetch the 20 largest token accounts with their current balances
|
|
2577
|
+
* for a given mint.
|
|
2578
|
+
*/
|
|
2579
|
+
async getTokenLargestAccounts(
|
|
2580
|
+
mintAddress: PublicKey,
|
|
2581
|
+
commitment?: Commitment,
|
|
2582
|
+
): Promise<RpcResponseAndContext<Array<TokenAccountBalancePair>>> {
|
|
2583
|
+
const args = this._buildArgs([mintAddress.toBase58()], commitment);
|
|
2584
|
+
const unsafeRes = await this._rpcRequest('getTokenLargestAccounts', args);
|
|
2585
|
+
const res = create(unsafeRes, GetTokenLargestAccountsResult);
|
|
2586
|
+
if ('error' in res) {
|
|
2587
|
+
throw new Error(
|
|
2588
|
+
'failed to get token largest accounts: ' + res.error.message,
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
return res.result;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* Fetch all the account info for the specified public key, return with context
|
|
2596
|
+
*/
|
|
2597
|
+
async getAccountInfoAndContext(
|
|
2598
|
+
publicKey: PublicKey,
|
|
2599
|
+
commitment?: Commitment,
|
|
2600
|
+
): Promise<RpcResponseAndContext<AccountInfo<Buffer> | null>> {
|
|
2601
|
+
const args = this._buildArgs([publicKey.toBase58()], commitment, 'base64');
|
|
2602
|
+
const unsafeRes = await this._rpcRequest('getAccountInfo', args);
|
|
2603
|
+
const res = create(
|
|
2604
|
+
unsafeRes,
|
|
2605
|
+
jsonRpcResultAndContext(nullable(AccountInfoResult)),
|
|
2606
|
+
);
|
|
2607
|
+
if ('error' in res) {
|
|
2608
|
+
throw new Error(
|
|
2609
|
+
'failed to get info about account ' +
|
|
2610
|
+
publicKey.toBase58() +
|
|
2611
|
+
': ' +
|
|
2612
|
+
res.error.message,
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
return res.result;
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
/**
|
|
2619
|
+
* Fetch parsed account info for the specified public key
|
|
2620
|
+
*/
|
|
2621
|
+
async getParsedAccountInfo(
|
|
2622
|
+
publicKey: PublicKey,
|
|
2623
|
+
commitment?: Commitment,
|
|
2624
|
+
): Promise<
|
|
2625
|
+
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>
|
|
2626
|
+
> {
|
|
2627
|
+
const args = this._buildArgs(
|
|
2628
|
+
[publicKey.toBase58()],
|
|
2629
|
+
commitment,
|
|
2630
|
+
'jsonParsed',
|
|
2631
|
+
);
|
|
2632
|
+
const unsafeRes = await this._rpcRequest('getAccountInfo', args);
|
|
2633
|
+
const res = create(
|
|
2634
|
+
unsafeRes,
|
|
2635
|
+
jsonRpcResultAndContext(nullable(ParsedAccountInfoResult)),
|
|
2636
|
+
);
|
|
2637
|
+
if ('error' in res) {
|
|
2638
|
+
throw new Error(
|
|
2639
|
+
'failed to get info about account ' +
|
|
2640
|
+
publicKey.toBase58() +
|
|
2641
|
+
': ' +
|
|
2642
|
+
res.error.message,
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
return res.result;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
/**
|
|
2649
|
+
* Fetch all the account info for the specified public key
|
|
2650
|
+
*/
|
|
2651
|
+
async getAccountInfo(
|
|
2652
|
+
publicKey: PublicKey,
|
|
2653
|
+
commitment?: Commitment,
|
|
2654
|
+
): Promise<AccountInfo<Buffer> | null> {
|
|
2655
|
+
try {
|
|
2656
|
+
const res = await this.getAccountInfoAndContext(publicKey, commitment);
|
|
2657
|
+
return res.value;
|
|
2658
|
+
} catch (e) {
|
|
2659
|
+
throw new Error(
|
|
2660
|
+
'failed to get info about account ' + publicKey.toBase58() + ': ' + e,
|
|
2661
|
+
);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
/**
|
|
2666
|
+
* Fetch all the account info for multiple accounts specified by an array of public keys, return with context
|
|
2667
|
+
*/
|
|
2668
|
+
async getMultipleAccountsInfoAndContext(
|
|
2669
|
+
publicKeys: PublicKey[],
|
|
2670
|
+
commitment?: Commitment,
|
|
2671
|
+
): Promise<RpcResponseAndContext<(AccountInfo<Buffer> | null)[]>> {
|
|
2672
|
+
const keys = publicKeys.map(key => key.toBase58());
|
|
2673
|
+
const args = this._buildArgs([keys], commitment, 'base64');
|
|
2674
|
+
const unsafeRes = await this._rpcRequest('getMultipleAccounts', args);
|
|
2675
|
+
const res = create(
|
|
2676
|
+
unsafeRes,
|
|
2677
|
+
jsonRpcResultAndContext(array(nullable(AccountInfoResult))),
|
|
2678
|
+
);
|
|
2679
|
+
if ('error' in res) {
|
|
2680
|
+
throw new Error(
|
|
2681
|
+
'failed to get info for accounts ' + keys + ': ' + res.error.message,
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
return res.result;
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
/**
|
|
2688
|
+
* Fetch all the account info for multiple accounts specified by an array of public keys
|
|
2689
|
+
*/
|
|
2690
|
+
async getMultipleAccountsInfo(
|
|
2691
|
+
publicKeys: PublicKey[],
|
|
2692
|
+
commitment?: Commitment,
|
|
2693
|
+
): Promise<(AccountInfo<Buffer> | null)[]> {
|
|
2694
|
+
const res = await this.getMultipleAccountsInfoAndContext(
|
|
2695
|
+
publicKeys,
|
|
2696
|
+
commitment,
|
|
2697
|
+
);
|
|
2698
|
+
return res.value;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
/**
|
|
2702
|
+
* Returns epoch activation information for a stake account that has been delegated
|
|
2703
|
+
*/
|
|
2704
|
+
async getStakeActivation(
|
|
2705
|
+
publicKey: PublicKey,
|
|
2706
|
+
commitment?: Commitment,
|
|
2707
|
+
epoch?: number,
|
|
2708
|
+
): Promise<StakeActivationData> {
|
|
2709
|
+
const args = this._buildArgs(
|
|
2710
|
+
[publicKey.toBase58()],
|
|
2711
|
+
commitment,
|
|
2712
|
+
undefined,
|
|
2713
|
+
epoch !== undefined ? {epoch} : undefined,
|
|
2714
|
+
);
|
|
2715
|
+
|
|
2716
|
+
const unsafeRes = await this._rpcRequest('getStakeActivation', args);
|
|
2717
|
+
const res = create(unsafeRes, jsonRpcResult(StakeActivationResult));
|
|
2718
|
+
if ('error' in res) {
|
|
2719
|
+
throw new Error(
|
|
2720
|
+
`failed to get Stake Activation ${publicKey.toBase58()}: ${
|
|
2721
|
+
res.error.message
|
|
2722
|
+
}`,
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
2725
|
+
return res.result;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
/**
|
|
2729
|
+
* Fetch all the accounts owned by the specified program id
|
|
2730
|
+
*
|
|
2731
|
+
* @return {Promise<Array<{pubkey: PublicKey, account: AccountInfo<Buffer>}>>}
|
|
2732
|
+
*/
|
|
2733
|
+
async getProgramAccounts(
|
|
2734
|
+
programId: PublicKey,
|
|
2735
|
+
configOrCommitment?: GetProgramAccountsConfig | Commitment,
|
|
2736
|
+
): Promise<Array<{pubkey: PublicKey; account: AccountInfo<Buffer>}>> {
|
|
2737
|
+
const extra: Pick<GetProgramAccountsConfig, 'dataSlice' | 'filters'> = {};
|
|
2738
|
+
|
|
2739
|
+
let commitment;
|
|
2740
|
+
let encoding;
|
|
2741
|
+
if (configOrCommitment) {
|
|
2742
|
+
if (typeof configOrCommitment === 'string') {
|
|
2743
|
+
commitment = configOrCommitment;
|
|
2744
|
+
} else {
|
|
2745
|
+
commitment = configOrCommitment.commitment;
|
|
2746
|
+
encoding = configOrCommitment.encoding;
|
|
2747
|
+
|
|
2748
|
+
if (configOrCommitment.dataSlice) {
|
|
2749
|
+
extra.dataSlice = configOrCommitment.dataSlice;
|
|
2750
|
+
}
|
|
2751
|
+
if (configOrCommitment.filters) {
|
|
2752
|
+
extra.filters = configOrCommitment.filters;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
const args = this._buildArgs(
|
|
2758
|
+
[programId.toBase58()],
|
|
2759
|
+
commitment,
|
|
2760
|
+
encoding || 'base64',
|
|
2761
|
+
extra,
|
|
2762
|
+
);
|
|
2763
|
+
const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
|
|
2764
|
+
const res = create(unsafeRes, jsonRpcResult(array(KeyedAccountInfoResult)));
|
|
2765
|
+
if ('error' in res) {
|
|
2766
|
+
throw new Error(
|
|
2767
|
+
'failed to get accounts owned by program ' +
|
|
2768
|
+
programId.toBase58() +
|
|
2769
|
+
': ' +
|
|
2770
|
+
res.error.message,
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2773
|
+
return res.result;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
/**
|
|
2777
|
+
* Fetch and parse all the accounts owned by the specified program id
|
|
2778
|
+
*
|
|
2779
|
+
* @return {Promise<Array<{pubkey: PublicKey, account: AccountInfo<Buffer | ParsedAccountData>}>>}
|
|
2780
|
+
*/
|
|
2781
|
+
async getParsedProgramAccounts(
|
|
2782
|
+
programId: PublicKey,
|
|
2783
|
+
configOrCommitment?: GetParsedProgramAccountsConfig | Commitment,
|
|
2784
|
+
): Promise<
|
|
2785
|
+
Array<{
|
|
2786
|
+
pubkey: PublicKey;
|
|
2787
|
+
account: AccountInfo<Buffer | ParsedAccountData>;
|
|
2788
|
+
}>
|
|
2789
|
+
> {
|
|
2790
|
+
const extra: Pick<GetParsedProgramAccountsConfig, 'filters'> = {};
|
|
2791
|
+
|
|
2792
|
+
let commitment;
|
|
2793
|
+
if (configOrCommitment) {
|
|
2794
|
+
if (typeof configOrCommitment === 'string') {
|
|
2795
|
+
commitment = configOrCommitment;
|
|
2796
|
+
} else {
|
|
2797
|
+
commitment = configOrCommitment.commitment;
|
|
2798
|
+
|
|
2799
|
+
if (configOrCommitment.filters) {
|
|
2800
|
+
extra.filters = configOrCommitment.filters;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
const args = this._buildArgs(
|
|
2806
|
+
[programId.toBase58()],
|
|
2807
|
+
commitment,
|
|
2808
|
+
'jsonParsed',
|
|
2809
|
+
extra,
|
|
2810
|
+
);
|
|
2811
|
+
const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
|
|
2812
|
+
const res = create(
|
|
2813
|
+
unsafeRes,
|
|
2814
|
+
jsonRpcResult(array(KeyedParsedAccountInfoResult)),
|
|
2815
|
+
);
|
|
2816
|
+
if ('error' in res) {
|
|
2817
|
+
throw new Error(
|
|
2818
|
+
'failed to get accounts owned by program ' +
|
|
2819
|
+
programId.toBase58() +
|
|
2820
|
+
': ' +
|
|
2821
|
+
res.error.message,
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2824
|
+
return res.result;
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
/**
|
|
2828
|
+
* Confirm the transaction identified by the specified signature.
|
|
2829
|
+
*/
|
|
2830
|
+
async confirmTransaction(
|
|
2831
|
+
signature: TransactionSignature,
|
|
2832
|
+
commitment?: Commitment,
|
|
2833
|
+
): Promise<RpcResponseAndContext<SignatureResult>> {
|
|
2834
|
+
let decodedSignature;
|
|
2835
|
+
try {
|
|
2836
|
+
decodedSignature = bs58.decode(signature);
|
|
2837
|
+
} catch (err) {
|
|
2838
|
+
throw new Error('signature must be base58 encoded: ' + signature);
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
assert(decodedSignature.length === 64, 'signature has invalid length');
|
|
2842
|
+
|
|
2843
|
+
const start = Date.now();
|
|
2844
|
+
const subscriptionCommitment = commitment || this.commitment;
|
|
2845
|
+
|
|
2846
|
+
let subscriptionId;
|
|
2847
|
+
let response: RpcResponseAndContext<SignatureResult> | null = null;
|
|
2848
|
+
const confirmPromise = new Promise((resolve, reject) => {
|
|
2849
|
+
try {
|
|
2850
|
+
subscriptionId = this.onSignature(
|
|
2851
|
+
signature,
|
|
2852
|
+
(result: SignatureResult, context: Context) => {
|
|
2853
|
+
subscriptionId = undefined;
|
|
2854
|
+
response = {
|
|
2855
|
+
context,
|
|
2856
|
+
value: result,
|
|
2857
|
+
};
|
|
2858
|
+
resolve(null);
|
|
2859
|
+
},
|
|
2860
|
+
subscriptionCommitment,
|
|
2861
|
+
);
|
|
2862
|
+
} catch (err) {
|
|
2863
|
+
reject(err);
|
|
2864
|
+
}
|
|
2865
|
+
});
|
|
2866
|
+
|
|
2867
|
+
let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
|
|
2868
|
+
switch (subscriptionCommitment) {
|
|
2869
|
+
case 'processed':
|
|
2870
|
+
case 'recent':
|
|
2871
|
+
case 'single':
|
|
2872
|
+
case 'confirmed':
|
|
2873
|
+
case 'singleGossip': {
|
|
2874
|
+
timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000;
|
|
2875
|
+
break;
|
|
2876
|
+
}
|
|
2877
|
+
// exhaust enums to ensure full coverage
|
|
2878
|
+
case 'finalized':
|
|
2879
|
+
case 'max':
|
|
2880
|
+
case 'root':
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
try {
|
|
2884
|
+
await promiseTimeout(confirmPromise, timeoutMs);
|
|
2885
|
+
} finally {
|
|
2886
|
+
if (subscriptionId) {
|
|
2887
|
+
this.removeSignatureListener(subscriptionId);
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
if (response === null) {
|
|
2892
|
+
const duration = (Date.now() - start) / 1000;
|
|
2893
|
+
throw new Error(
|
|
2894
|
+
`Transaction was not confirmed in ${duration.toFixed(
|
|
2895
|
+
2,
|
|
2896
|
+
)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`,
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
return response;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
/**
|
|
2904
|
+
* Return the list of nodes that are currently participating in the cluster
|
|
2905
|
+
*/
|
|
2906
|
+
async getClusterNodes(): Promise<Array<ContactInfo>> {
|
|
2907
|
+
const unsafeRes = await this._rpcRequest('getClusterNodes', []);
|
|
2908
|
+
const res = create(unsafeRes, jsonRpcResult(array(ContactInfoResult)));
|
|
2909
|
+
if ('error' in res) {
|
|
2910
|
+
throw new Error('failed to get cluster nodes: ' + res.error.message);
|
|
2911
|
+
}
|
|
2912
|
+
return res.result;
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
/**
|
|
2916
|
+
* Return the list of nodes that are currently participating in the cluster
|
|
2917
|
+
*/
|
|
2918
|
+
async getVoteAccounts(commitment?: Commitment): Promise<VoteAccountStatus> {
|
|
2919
|
+
const args = this._buildArgs([], commitment);
|
|
2920
|
+
const unsafeRes = await this._rpcRequest('getVoteAccounts', args);
|
|
2921
|
+
const res = create(unsafeRes, GetVoteAccounts);
|
|
2922
|
+
if ('error' in res) {
|
|
2923
|
+
throw new Error('failed to get vote accounts: ' + res.error.message);
|
|
2924
|
+
}
|
|
2925
|
+
return res.result;
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
/**
|
|
2929
|
+
* Fetch the current slot that the node is processing
|
|
2930
|
+
*/
|
|
2931
|
+
async getSlot(commitment?: Commitment): Promise<number> {
|
|
2932
|
+
const args = this._buildArgs([], commitment);
|
|
2933
|
+
const unsafeRes = await this._rpcRequest('getSlot', args);
|
|
2934
|
+
const res = create(unsafeRes, jsonRpcResult(number()));
|
|
2935
|
+
if ('error' in res) {
|
|
2936
|
+
throw new Error('failed to get slot: ' + res.error.message);
|
|
2937
|
+
}
|
|
2938
|
+
return res.result;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
/**
|
|
2942
|
+
* Fetch the current slot leader of the cluster
|
|
2943
|
+
*/
|
|
2944
|
+
async getSlotLeader(commitment?: Commitment): Promise<string> {
|
|
2945
|
+
const args = this._buildArgs([], commitment);
|
|
2946
|
+
const unsafeRes = await this._rpcRequest('getSlotLeader', args);
|
|
2947
|
+
const res = create(unsafeRes, jsonRpcResult(string()));
|
|
2948
|
+
if ('error' in res) {
|
|
2949
|
+
throw new Error('failed to get slot leader: ' + res.error.message);
|
|
2950
|
+
}
|
|
2951
|
+
return res.result;
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
/**
|
|
2955
|
+
* Fetch `limit` number of slot leaders starting from `startSlot`
|
|
2956
|
+
*
|
|
2957
|
+
* @param startSlot fetch slot leaders starting from this slot
|
|
2958
|
+
* @param limit number of slot leaders to return
|
|
2959
|
+
*/
|
|
2960
|
+
async getSlotLeaders(
|
|
2961
|
+
startSlot: number,
|
|
2962
|
+
limit: number,
|
|
2963
|
+
): Promise<Array<PublicKey>> {
|
|
2964
|
+
const args = [startSlot, limit];
|
|
2965
|
+
const unsafeRes = await this._rpcRequest('getSlotLeaders', args);
|
|
2966
|
+
const res = create(unsafeRes, jsonRpcResult(array(PublicKeyFromString)));
|
|
2967
|
+
if ('error' in res) {
|
|
2968
|
+
throw new Error('failed to get slot leaders: ' + res.error.message);
|
|
2969
|
+
}
|
|
2970
|
+
return res.result;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
/**
|
|
2974
|
+
* Fetch the current status of a signature
|
|
2975
|
+
*/
|
|
2976
|
+
async getSignatureStatus(
|
|
2977
|
+
signature: TransactionSignature,
|
|
2978
|
+
config?: SignatureStatusConfig,
|
|
2979
|
+
): Promise<RpcResponseAndContext<SignatureStatus | null>> {
|
|
2980
|
+
const {context, value: values} = await this.getSignatureStatuses(
|
|
2981
|
+
[signature],
|
|
2982
|
+
config,
|
|
2983
|
+
);
|
|
2984
|
+
assert(values.length === 1);
|
|
2985
|
+
const value = values[0];
|
|
2986
|
+
return {context, value};
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
/**
|
|
2990
|
+
* Fetch the current statuses of a batch of signatures
|
|
2991
|
+
*/
|
|
2992
|
+
async getSignatureStatuses(
|
|
2993
|
+
signatures: Array<TransactionSignature>,
|
|
2994
|
+
config?: SignatureStatusConfig,
|
|
2995
|
+
): Promise<RpcResponseAndContext<Array<SignatureStatus | null>>> {
|
|
2996
|
+
const params: any[] = [signatures];
|
|
2997
|
+
if (config) {
|
|
2998
|
+
params.push(config);
|
|
2999
|
+
}
|
|
3000
|
+
const unsafeRes = await this._rpcRequest('getSignatureStatuses', params);
|
|
3001
|
+
const res = create(unsafeRes, GetSignatureStatusesRpcResult);
|
|
3002
|
+
if ('error' in res) {
|
|
3003
|
+
throw new Error('failed to get signature status: ' + res.error.message);
|
|
3004
|
+
}
|
|
3005
|
+
return res.result;
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
/**
|
|
3009
|
+
* Fetch the current transaction count of the cluster
|
|
3010
|
+
*/
|
|
3011
|
+
async getTransactionCount(commitment?: Commitment): Promise<number> {
|
|
3012
|
+
const args = this._buildArgs([], commitment);
|
|
3013
|
+
const unsafeRes = await this._rpcRequest('getTransactionCount', args);
|
|
3014
|
+
const res = create(unsafeRes, jsonRpcResult(number()));
|
|
3015
|
+
if ('error' in res) {
|
|
3016
|
+
throw new Error('failed to get transaction count: ' + res.error.message);
|
|
3017
|
+
}
|
|
3018
|
+
return res.result;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
/**
|
|
3022
|
+
* Fetch the current total currency supply of the cluster in lamports
|
|
3023
|
+
*
|
|
3024
|
+
* @deprecated Deprecated since v1.2.8. Please use {@link getSupply} instead.
|
|
3025
|
+
*/
|
|
3026
|
+
async getTotalSupply(commitment?: Commitment): Promise<number> {
|
|
3027
|
+
const result = await this.getSupply({
|
|
3028
|
+
commitment,
|
|
3029
|
+
excludeNonCirculatingAccountsList: true,
|
|
3030
|
+
});
|
|
3031
|
+
return result.value.total;
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
/**
|
|
3035
|
+
* Fetch the cluster InflationGovernor parameters
|
|
3036
|
+
*/
|
|
3037
|
+
async getInflationGovernor(
|
|
3038
|
+
commitment?: Commitment,
|
|
3039
|
+
): Promise<InflationGovernor> {
|
|
3040
|
+
const args = this._buildArgs([], commitment);
|
|
3041
|
+
const unsafeRes = await this._rpcRequest('getInflationGovernor', args);
|
|
3042
|
+
const res = create(unsafeRes, GetInflationGovernorRpcResult);
|
|
3043
|
+
if ('error' in res) {
|
|
3044
|
+
throw new Error('failed to get inflation: ' + res.error.message);
|
|
3045
|
+
}
|
|
3046
|
+
return res.result;
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
/**
|
|
3050
|
+
* Fetch the inflation reward for a list of addresses for an epoch
|
|
3051
|
+
*/
|
|
3052
|
+
async getInflationReward(
|
|
3053
|
+
addresses: PublicKey[],
|
|
3054
|
+
epoch?: number,
|
|
3055
|
+
commitment?: Commitment,
|
|
3056
|
+
): Promise<(InflationReward | null)[]> {
|
|
3057
|
+
const args = this._buildArgs(
|
|
3058
|
+
[addresses.map(pubkey => pubkey.toBase58())],
|
|
3059
|
+
commitment,
|
|
3060
|
+
undefined,
|
|
3061
|
+
{
|
|
3062
|
+
epoch,
|
|
3063
|
+
},
|
|
3064
|
+
);
|
|
3065
|
+
const unsafeRes = await this._rpcRequest('getInflationReward', args);
|
|
3066
|
+
const res = create(unsafeRes, GetInflationRewardResult);
|
|
3067
|
+
if ('error' in res) {
|
|
3068
|
+
throw new Error('failed to get inflation reward: ' + res.error.message);
|
|
3069
|
+
}
|
|
3070
|
+
return res.result;
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
/**
|
|
3074
|
+
* Fetch the Epoch Info parameters
|
|
3075
|
+
*/
|
|
3076
|
+
async getEpochInfo(commitment?: Commitment): Promise<EpochInfo> {
|
|
3077
|
+
const args = this._buildArgs([], commitment);
|
|
3078
|
+
const unsafeRes = await this._rpcRequest('getEpochInfo', args);
|
|
3079
|
+
const res = create(unsafeRes, GetEpochInfoRpcResult);
|
|
3080
|
+
if ('error' in res) {
|
|
3081
|
+
throw new Error('failed to get epoch info: ' + res.error.message);
|
|
3082
|
+
}
|
|
3083
|
+
return res.result;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
/**
|
|
3087
|
+
* Fetch the Epoch Schedule parameters
|
|
3088
|
+
*/
|
|
3089
|
+
async getEpochSchedule(): Promise<EpochSchedule> {
|
|
3090
|
+
const unsafeRes = await this._rpcRequest('getEpochSchedule', []);
|
|
3091
|
+
const res = create(unsafeRes, GetEpochScheduleRpcResult);
|
|
3092
|
+
if ('error' in res) {
|
|
3093
|
+
throw new Error('failed to get epoch schedule: ' + res.error.message);
|
|
3094
|
+
}
|
|
3095
|
+
const epochSchedule = res.result;
|
|
3096
|
+
return new EpochSchedule(
|
|
3097
|
+
epochSchedule.slotsPerEpoch,
|
|
3098
|
+
epochSchedule.leaderScheduleSlotOffset,
|
|
3099
|
+
epochSchedule.warmup,
|
|
3100
|
+
epochSchedule.firstNormalEpoch,
|
|
3101
|
+
epochSchedule.firstNormalSlot,
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
/**
|
|
3106
|
+
* Fetch the leader schedule for the current epoch
|
|
3107
|
+
* @return {Promise<RpcResponseAndContext<LeaderSchedule>>}
|
|
3108
|
+
*/
|
|
3109
|
+
async getLeaderSchedule(): Promise<LeaderSchedule> {
|
|
3110
|
+
const unsafeRes = await this._rpcRequest('getLeaderSchedule', []);
|
|
3111
|
+
const res = create(unsafeRes, GetLeaderScheduleRpcResult);
|
|
3112
|
+
if ('error' in res) {
|
|
3113
|
+
throw new Error('failed to get leader schedule: ' + res.error.message);
|
|
3114
|
+
}
|
|
3115
|
+
return res.result;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
/**
|
|
3119
|
+
* Fetch the minimum balance needed to exempt an account of `dataLength`
|
|
3120
|
+
* size from rent
|
|
3121
|
+
*/
|
|
3122
|
+
async getMinimumBalanceForRentExemption(
|
|
3123
|
+
dataLength: number,
|
|
3124
|
+
commitment?: Commitment,
|
|
3125
|
+
): Promise<number> {
|
|
3126
|
+
const args = this._buildArgs([dataLength], commitment);
|
|
3127
|
+
const unsafeRes = await this._rpcRequest(
|
|
3128
|
+
'getMinimumBalanceForRentExemption',
|
|
3129
|
+
args,
|
|
3130
|
+
);
|
|
3131
|
+
const res = create(unsafeRes, GetMinimumBalanceForRentExemptionRpcResult);
|
|
3132
|
+
if ('error' in res) {
|
|
3133
|
+
console.warn('Unable to fetch minimum balance for rent exemption');
|
|
3134
|
+
return 0;
|
|
3135
|
+
}
|
|
3136
|
+
return res.result;
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
/**
|
|
3140
|
+
* Fetch a recent blockhash from the cluster, return with context
|
|
3141
|
+
* @return {Promise<RpcResponseAndContext<{blockhash: Blockhash, feeCalculator: FeeCalculator}>>}
|
|
3142
|
+
*
|
|
3143
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link getLatestBlockhash} instead.
|
|
3144
|
+
*/
|
|
3145
|
+
async getRecentBlockhashAndContext(
|
|
3146
|
+
commitment?: Commitment,
|
|
3147
|
+
): Promise<
|
|
3148
|
+
RpcResponseAndContext<{blockhash: Blockhash; feeCalculator: FeeCalculator}>
|
|
3149
|
+
> {
|
|
3150
|
+
const args = this._buildArgs([], commitment);
|
|
3151
|
+
const unsafeRes = await this._rpcRequest('getRecentBlockhash', args);
|
|
3152
|
+
const res = create(unsafeRes, GetRecentBlockhashAndContextRpcResult);
|
|
3153
|
+
if ('error' in res) {
|
|
3154
|
+
throw new Error('failed to get recent blockhash: ' + res.error.message);
|
|
3155
|
+
}
|
|
3156
|
+
return res.result;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
/**
|
|
3160
|
+
* Fetch recent performance samples
|
|
3161
|
+
* @return {Promise<Array<PerfSample>>}
|
|
3162
|
+
*/
|
|
3163
|
+
async getRecentPerformanceSamples(
|
|
3164
|
+
limit?: number,
|
|
3165
|
+
): Promise<Array<PerfSample>> {
|
|
3166
|
+
const args = this._buildArgs(limit ? [limit] : []);
|
|
3167
|
+
const unsafeRes = await this._rpcRequest(
|
|
3168
|
+
'getRecentPerformanceSamples',
|
|
3169
|
+
args,
|
|
3170
|
+
);
|
|
3171
|
+
const res = create(unsafeRes, GetRecentPerformanceSamplesRpcResult);
|
|
3172
|
+
if ('error' in res) {
|
|
3173
|
+
throw new Error(
|
|
3174
|
+
'failed to get recent performance samples: ' + res.error.message,
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
return res.result;
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
/**
|
|
3182
|
+
* Fetch the fee calculator for a recent blockhash from the cluster, return with context
|
|
3183
|
+
*
|
|
3184
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link getFeeForMessage} instead.
|
|
3185
|
+
*/
|
|
3186
|
+
async getFeeCalculatorForBlockhash(
|
|
3187
|
+
blockhash: Blockhash,
|
|
3188
|
+
commitment?: Commitment,
|
|
3189
|
+
): Promise<RpcResponseAndContext<FeeCalculator | null>> {
|
|
3190
|
+
const args = this._buildArgs([blockhash], commitment);
|
|
3191
|
+
const unsafeRes = await this._rpcRequest(
|
|
3192
|
+
'getFeeCalculatorForBlockhash',
|
|
3193
|
+
args,
|
|
3194
|
+
);
|
|
3195
|
+
|
|
3196
|
+
const res = create(unsafeRes, GetFeeCalculatorRpcResult);
|
|
3197
|
+
if ('error' in res) {
|
|
3198
|
+
throw new Error('failed to get fee calculator: ' + res.error.message);
|
|
3199
|
+
}
|
|
3200
|
+
const {context, value} = res.result;
|
|
3201
|
+
return {
|
|
3202
|
+
context,
|
|
3203
|
+
value: value !== null ? value.feeCalculator : null,
|
|
3204
|
+
};
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
/**
|
|
3208
|
+
* Fetch the fee for a message from the cluster, return with context
|
|
3209
|
+
*/
|
|
3210
|
+
async getFeeForMessage(
|
|
3211
|
+
message: Message,
|
|
3212
|
+
commitment?: Commitment,
|
|
3213
|
+
): Promise<RpcResponseAndContext<number>> {
|
|
3214
|
+
const wireMessage = message.serialize().toString('base64');
|
|
3215
|
+
const args = this._buildArgs([wireMessage], commitment);
|
|
3216
|
+
const unsafeRes = await this._rpcRequest('getFeeForMessage', args);
|
|
3217
|
+
|
|
3218
|
+
const res = create(unsafeRes, jsonRpcResultAndContext(nullable(number())));
|
|
3219
|
+
if ('error' in res) {
|
|
3220
|
+
throw new Error('failed to get slot: ' + res.error.message);
|
|
3221
|
+
}
|
|
3222
|
+
if (res.result === null) {
|
|
3223
|
+
throw new Error('invalid blockhash');
|
|
3224
|
+
}
|
|
3225
|
+
return res.result as unknown as RpcResponseAndContext<number>;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
/**
|
|
3229
|
+
* Fetch a recent blockhash from the cluster
|
|
3230
|
+
* @return {Promise<{blockhash: Blockhash, feeCalculator: FeeCalculator}>}
|
|
3231
|
+
*
|
|
3232
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link getLatestBlockhash} instead.
|
|
3233
|
+
*/
|
|
3234
|
+
async getRecentBlockhash(
|
|
3235
|
+
commitment?: Commitment,
|
|
3236
|
+
): Promise<{blockhash: Blockhash; feeCalculator: FeeCalculator}> {
|
|
3237
|
+
try {
|
|
3238
|
+
const res = await this.getRecentBlockhashAndContext(commitment);
|
|
3239
|
+
return res.value;
|
|
3240
|
+
} catch (e) {
|
|
3241
|
+
throw new Error('failed to get recent blockhash: ' + e);
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
/**
|
|
3246
|
+
* Fetch the latest blockhash from the cluster
|
|
3247
|
+
* @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>}
|
|
3248
|
+
*/
|
|
3249
|
+
async getLatestBlockhash(
|
|
3250
|
+
commitment?: Commitment,
|
|
3251
|
+
): Promise<{blockhash: Blockhash; lastValidBlockHeight: number}> {
|
|
3252
|
+
try {
|
|
3253
|
+
const res = await this.getLatestBlockhashAndContext(commitment);
|
|
3254
|
+
return res.value;
|
|
3255
|
+
} catch (e) {
|
|
3256
|
+
throw new Error('failed to get recent blockhash: ' + e);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
/**
|
|
3261
|
+
* Fetch the latest blockhash from the cluster
|
|
3262
|
+
* @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>}
|
|
3263
|
+
*/
|
|
3264
|
+
async getLatestBlockhashAndContext(
|
|
3265
|
+
commitment?: Commitment,
|
|
3266
|
+
): Promise<
|
|
3267
|
+
RpcResponseAndContext<{blockhash: Blockhash; lastValidBlockHeight: number}>
|
|
3268
|
+
> {
|
|
3269
|
+
const args = this._buildArgs([], commitment);
|
|
3270
|
+
const unsafeRes = await this._rpcRequest('getLatestBlockhash', args);
|
|
3271
|
+
const res = create(unsafeRes, GetLatestBlockhashRpcResult);
|
|
3272
|
+
if ('error' in res) {
|
|
3273
|
+
throw new Error('failed to get latest blockhash: ' + res.error.message);
|
|
3274
|
+
}
|
|
3275
|
+
return res.result;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
/**
|
|
3279
|
+
* Fetch the node version
|
|
3280
|
+
*/
|
|
3281
|
+
async getVersion(): Promise<Version> {
|
|
3282
|
+
const unsafeRes = await this._rpcRequest('getVersion', []);
|
|
3283
|
+
const res = create(unsafeRes, jsonRpcResult(VersionResult));
|
|
3284
|
+
if ('error' in res) {
|
|
3285
|
+
throw new Error('failed to get version: ' + res.error.message);
|
|
3286
|
+
}
|
|
3287
|
+
return res.result;
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
/**
|
|
3291
|
+
* Fetch the genesis hash
|
|
3292
|
+
*/
|
|
3293
|
+
async getGenesisHash(): Promise<string> {
|
|
3294
|
+
const unsafeRes = await this._rpcRequest('getGenesisHash', []);
|
|
3295
|
+
const res = create(unsafeRes, jsonRpcResult(string()));
|
|
3296
|
+
if ('error' in res) {
|
|
3297
|
+
throw new Error('failed to get genesis hash: ' + res.error.message);
|
|
3298
|
+
}
|
|
3299
|
+
return res.result;
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
/**
|
|
3303
|
+
* Fetch a processed block from the cluster.
|
|
3304
|
+
*/
|
|
3305
|
+
async getBlock(
|
|
3306
|
+
slot: number,
|
|
3307
|
+
opts?: {commitment?: Finality},
|
|
3308
|
+
): Promise<BlockResponse | null> {
|
|
3309
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3310
|
+
[slot],
|
|
3311
|
+
opts && opts.commitment,
|
|
3312
|
+
);
|
|
3313
|
+
const unsafeRes = await this._rpcRequest('getBlock', args);
|
|
3314
|
+
const res = create(unsafeRes, GetBlockRpcResult);
|
|
3315
|
+
|
|
3316
|
+
if ('error' in res) {
|
|
3317
|
+
throw new Error('failed to get confirmed block: ' + res.error.message);
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
const result = res.result;
|
|
3321
|
+
if (!result) return result;
|
|
3322
|
+
|
|
3323
|
+
return {
|
|
3324
|
+
...result,
|
|
3325
|
+
transactions: result.transactions.map(({transaction, meta}) => {
|
|
3326
|
+
const message = new Message(transaction.message);
|
|
3327
|
+
return {
|
|
3328
|
+
meta,
|
|
3329
|
+
transaction: {
|
|
3330
|
+
...transaction,
|
|
3331
|
+
message,
|
|
3332
|
+
},
|
|
3333
|
+
};
|
|
3334
|
+
}),
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
/*
|
|
3339
|
+
* Returns the current block height of the node
|
|
3340
|
+
*/
|
|
3341
|
+
async getBlockHeight(commitment?: Commitment): Promise<number> {
|
|
3342
|
+
const args = this._buildArgs([], commitment);
|
|
3343
|
+
const unsafeRes = await this._rpcRequest('getBlockHeight', args);
|
|
3344
|
+
const res = create(unsafeRes, jsonRpcResult(number()));
|
|
3345
|
+
if ('error' in res) {
|
|
3346
|
+
throw new Error(
|
|
3347
|
+
'failed to get block height information: ' + res.error.message,
|
|
3348
|
+
);
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
return res.result;
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
/*
|
|
3355
|
+
* Returns recent block production information from the current or previous epoch
|
|
3356
|
+
*/
|
|
3357
|
+
async getBlockProduction(
|
|
3358
|
+
configOrCommitment?: GetBlockProductionConfig | Commitment,
|
|
3359
|
+
): Promise<RpcResponseAndContext<BlockProduction>> {
|
|
3360
|
+
let extra: Omit<GetBlockProductionConfig, 'commitment'> | undefined;
|
|
3361
|
+
let commitment: Commitment | undefined;
|
|
3362
|
+
|
|
3363
|
+
if (typeof configOrCommitment === 'string') {
|
|
3364
|
+
commitment = configOrCommitment;
|
|
3365
|
+
} else if (configOrCommitment) {
|
|
3366
|
+
const {commitment: c, ...rest} = configOrCommitment;
|
|
3367
|
+
commitment = c;
|
|
3368
|
+
extra = rest;
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
const args = this._buildArgs([], commitment, 'base64', extra);
|
|
3372
|
+
const unsafeRes = await this._rpcRequest('getBlockProduction', args);
|
|
3373
|
+
const res = create(unsafeRes, BlockProductionResponseStruct);
|
|
3374
|
+
if ('error' in res) {
|
|
3375
|
+
throw new Error(
|
|
3376
|
+
'failed to get block production information: ' + res.error.message,
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
return res.result;
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
/**
|
|
3384
|
+
* Fetch a confirmed or finalized transaction from the cluster.
|
|
3385
|
+
*/
|
|
3386
|
+
async getTransaction(
|
|
3387
|
+
signature: string,
|
|
3388
|
+
opts?: {commitment?: Finality},
|
|
3389
|
+
): Promise<TransactionResponse | null> {
|
|
3390
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3391
|
+
[signature],
|
|
3392
|
+
opts && opts.commitment,
|
|
3393
|
+
);
|
|
3394
|
+
const unsafeRes = await this._rpcRequest('getTransaction', args);
|
|
3395
|
+
const res = create(unsafeRes, GetTransactionRpcResult);
|
|
3396
|
+
if ('error' in res) {
|
|
3397
|
+
throw new Error('failed to get transaction: ' + res.error.message);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
const result = res.result;
|
|
3401
|
+
if (!result) return result;
|
|
3402
|
+
|
|
3403
|
+
return {
|
|
3404
|
+
...result,
|
|
3405
|
+
transaction: {
|
|
3406
|
+
...result.transaction,
|
|
3407
|
+
message: new Message(result.transaction.message),
|
|
3408
|
+
},
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
/**
|
|
3413
|
+
* Fetch parsed transaction details for a confirmed or finalized transaction
|
|
3414
|
+
*/
|
|
3415
|
+
async getParsedTransaction(
|
|
3416
|
+
signature: TransactionSignature,
|
|
3417
|
+
commitment?: Finality,
|
|
3418
|
+
): Promise<ParsedConfirmedTransaction | null> {
|
|
3419
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3420
|
+
[signature],
|
|
3421
|
+
commitment,
|
|
3422
|
+
'jsonParsed',
|
|
3423
|
+
);
|
|
3424
|
+
const unsafeRes = await this._rpcRequest('getTransaction', args);
|
|
3425
|
+
const res = create(unsafeRes, GetParsedTransactionRpcResult);
|
|
3426
|
+
if ('error' in res) {
|
|
3427
|
+
throw new Error('failed to get transaction: ' + res.error.message);
|
|
3428
|
+
}
|
|
3429
|
+
return res.result;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
/**
|
|
3433
|
+
* Fetch parsed transaction details for a batch of confirmed transactions
|
|
3434
|
+
*/
|
|
3435
|
+
async getParsedTransactions(
|
|
3436
|
+
signatures: TransactionSignature[],
|
|
3437
|
+
commitment?: Finality,
|
|
3438
|
+
): Promise<(ParsedConfirmedTransaction | null)[]> {
|
|
3439
|
+
const batch = signatures.map(signature => {
|
|
3440
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3441
|
+
[signature],
|
|
3442
|
+
commitment,
|
|
3443
|
+
'jsonParsed',
|
|
3444
|
+
);
|
|
3445
|
+
return {
|
|
3446
|
+
methodName: 'getTransaction',
|
|
3447
|
+
args,
|
|
3448
|
+
};
|
|
3449
|
+
});
|
|
3450
|
+
|
|
3451
|
+
const unsafeRes = await this._rpcBatchRequest(batch);
|
|
3452
|
+
const res = unsafeRes.map((unsafeRes: any) => {
|
|
3453
|
+
const res = create(unsafeRes, GetParsedTransactionRpcResult);
|
|
3454
|
+
if ('error' in res) {
|
|
3455
|
+
throw new Error('failed to get transactions: ' + res.error.message);
|
|
3456
|
+
}
|
|
3457
|
+
return res.result;
|
|
3458
|
+
});
|
|
3459
|
+
|
|
3460
|
+
return res;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
/**
|
|
3464
|
+
* Fetch a list of Transactions and transaction statuses from the cluster
|
|
3465
|
+
* for a confirmed block.
|
|
3466
|
+
*
|
|
3467
|
+
* @deprecated Deprecated since v1.13.0. Please use {@link getBlock} instead.
|
|
3468
|
+
*/
|
|
3469
|
+
async getConfirmedBlock(
|
|
3470
|
+
slot: number,
|
|
3471
|
+
commitment?: Finality,
|
|
3472
|
+
): Promise<ConfirmedBlock> {
|
|
3473
|
+
const args = this._buildArgsAtLeastConfirmed([slot], commitment);
|
|
3474
|
+
const unsafeRes = await this._rpcRequest('getConfirmedBlock', args);
|
|
3475
|
+
const res = create(unsafeRes, GetConfirmedBlockRpcResult);
|
|
3476
|
+
|
|
3477
|
+
if ('error' in res) {
|
|
3478
|
+
throw new Error('failed to get confirmed block: ' + res.error.message);
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
const result = res.result;
|
|
3482
|
+
if (!result) {
|
|
3483
|
+
throw new Error('Confirmed block ' + slot + ' not found');
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
const block = {
|
|
3487
|
+
...result,
|
|
3488
|
+
transactions: result.transactions.map(({transaction, meta}) => {
|
|
3489
|
+
const message = new Message(transaction.message);
|
|
3490
|
+
return {
|
|
3491
|
+
meta,
|
|
3492
|
+
transaction: {
|
|
3493
|
+
...transaction,
|
|
3494
|
+
message,
|
|
3495
|
+
},
|
|
3496
|
+
};
|
|
3497
|
+
}),
|
|
3498
|
+
};
|
|
3499
|
+
|
|
3500
|
+
return {
|
|
3501
|
+
...block,
|
|
3502
|
+
transactions: block.transactions.map(({transaction, meta}) => {
|
|
3503
|
+
return {
|
|
3504
|
+
meta,
|
|
3505
|
+
transaction: Transaction.populate(
|
|
3506
|
+
transaction.message,
|
|
3507
|
+
transaction.signatures,
|
|
3508
|
+
),
|
|
3509
|
+
};
|
|
3510
|
+
}),
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
/**
|
|
3515
|
+
* Fetch confirmed blocks between two slots
|
|
3516
|
+
*/
|
|
3517
|
+
async getBlocks(
|
|
3518
|
+
startSlot: number,
|
|
3519
|
+
endSlot?: number,
|
|
3520
|
+
commitment?: Finality,
|
|
3521
|
+
): Promise<Array<number>> {
|
|
3522
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3523
|
+
endSlot !== undefined ? [startSlot, endSlot] : [startSlot],
|
|
3524
|
+
commitment,
|
|
3525
|
+
);
|
|
3526
|
+
const unsafeRes = await this._rpcRequest('getBlocks', args);
|
|
3527
|
+
const res = create(unsafeRes, jsonRpcResult(array(number())));
|
|
3528
|
+
if ('error' in res) {
|
|
3529
|
+
throw new Error('failed to get blocks: ' + res.error.message);
|
|
3530
|
+
}
|
|
3531
|
+
return res.result;
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
/**
|
|
3535
|
+
* Fetch a list of Signatures from the cluster for a block, excluding rewards
|
|
3536
|
+
*/
|
|
3537
|
+
async getBlockSignatures(
|
|
3538
|
+
slot: number,
|
|
3539
|
+
commitment?: Finality,
|
|
3540
|
+
): Promise<BlockSignatures> {
|
|
3541
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3542
|
+
[slot],
|
|
3543
|
+
commitment,
|
|
3544
|
+
undefined,
|
|
3545
|
+
{
|
|
3546
|
+
transactionDetails: 'signatures',
|
|
3547
|
+
rewards: false,
|
|
3548
|
+
},
|
|
3549
|
+
);
|
|
3550
|
+
const unsafeRes = await this._rpcRequest('getBlock', args);
|
|
3551
|
+
const res = create(unsafeRes, GetBlockSignaturesRpcResult);
|
|
3552
|
+
if ('error' in res) {
|
|
3553
|
+
throw new Error('failed to get block: ' + res.error.message);
|
|
3554
|
+
}
|
|
3555
|
+
const result = res.result;
|
|
3556
|
+
if (!result) {
|
|
3557
|
+
throw new Error('Block ' + slot + ' not found');
|
|
3558
|
+
}
|
|
3559
|
+
return result;
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
/**
|
|
3563
|
+
* Fetch a list of Signatures from the cluster for a confirmed block, excluding rewards
|
|
3564
|
+
*
|
|
3565
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link getBlockSignatures} instead.
|
|
3566
|
+
*/
|
|
3567
|
+
async getConfirmedBlockSignatures(
|
|
3568
|
+
slot: number,
|
|
3569
|
+
commitment?: Finality,
|
|
3570
|
+
): Promise<BlockSignatures> {
|
|
3571
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3572
|
+
[slot],
|
|
3573
|
+
commitment,
|
|
3574
|
+
undefined,
|
|
3575
|
+
{
|
|
3576
|
+
transactionDetails: 'signatures',
|
|
3577
|
+
rewards: false,
|
|
3578
|
+
},
|
|
3579
|
+
);
|
|
3580
|
+
const unsafeRes = await this._rpcRequest('getConfirmedBlock', args);
|
|
3581
|
+
const res = create(unsafeRes, GetBlockSignaturesRpcResult);
|
|
3582
|
+
if ('error' in res) {
|
|
3583
|
+
throw new Error('failed to get confirmed block: ' + res.error.message);
|
|
3584
|
+
}
|
|
3585
|
+
const result = res.result;
|
|
3586
|
+
if (!result) {
|
|
3587
|
+
throw new Error('Confirmed block ' + slot + ' not found');
|
|
3588
|
+
}
|
|
3589
|
+
return result;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
/**
|
|
3593
|
+
* Fetch a transaction details for a confirmed transaction
|
|
3594
|
+
*
|
|
3595
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link getTransaction} instead.
|
|
3596
|
+
*/
|
|
3597
|
+
async getConfirmedTransaction(
|
|
3598
|
+
signature: TransactionSignature,
|
|
3599
|
+
commitment?: Finality,
|
|
3600
|
+
): Promise<ConfirmedTransaction | null> {
|
|
3601
|
+
const args = this._buildArgsAtLeastConfirmed([signature], commitment);
|
|
3602
|
+
const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args);
|
|
3603
|
+
const res = create(unsafeRes, GetTransactionRpcResult);
|
|
3604
|
+
if ('error' in res) {
|
|
3605
|
+
throw new Error('failed to get transaction: ' + res.error.message);
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
const result = res.result;
|
|
3609
|
+
if (!result) return result;
|
|
3610
|
+
|
|
3611
|
+
const message = new Message(result.transaction.message);
|
|
3612
|
+
const signatures = result.transaction.signatures;
|
|
3613
|
+
return {
|
|
3614
|
+
...result,
|
|
3615
|
+
transaction: Transaction.populate(message, signatures),
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
/**
|
|
3620
|
+
* Fetch parsed transaction details for a confirmed transaction
|
|
3621
|
+
*
|
|
3622
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link getParsedTransaction} instead.
|
|
3623
|
+
*/
|
|
3624
|
+
async getParsedConfirmedTransaction(
|
|
3625
|
+
signature: TransactionSignature,
|
|
3626
|
+
commitment?: Finality,
|
|
3627
|
+
): Promise<ParsedConfirmedTransaction | null> {
|
|
3628
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3629
|
+
[signature],
|
|
3630
|
+
commitment,
|
|
3631
|
+
'jsonParsed',
|
|
3632
|
+
);
|
|
3633
|
+
const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args);
|
|
3634
|
+
const res = create(unsafeRes, GetParsedTransactionRpcResult);
|
|
3635
|
+
if ('error' in res) {
|
|
3636
|
+
throw new Error(
|
|
3637
|
+
'failed to get confirmed transaction: ' + res.error.message,
|
|
3638
|
+
);
|
|
3639
|
+
}
|
|
3640
|
+
return res.result;
|
|
3641
|
+
}
|
|
3642
|
+
|
|
3643
|
+
/**
|
|
3644
|
+
* Fetch parsed transaction details for a batch of confirmed transactions
|
|
3645
|
+
*
|
|
3646
|
+
* @deprecated Deprecated since Solana v1.8.0. Please use {@link getParsedTransactions} instead.
|
|
3647
|
+
*/
|
|
3648
|
+
async getParsedConfirmedTransactions(
|
|
3649
|
+
signatures: TransactionSignature[],
|
|
3650
|
+
commitment?: Finality,
|
|
3651
|
+
): Promise<(ParsedConfirmedTransaction | null)[]> {
|
|
3652
|
+
const batch = signatures.map(signature => {
|
|
3653
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3654
|
+
[signature],
|
|
3655
|
+
commitment,
|
|
3656
|
+
'jsonParsed',
|
|
3657
|
+
);
|
|
3658
|
+
return {
|
|
3659
|
+
methodName: 'getConfirmedTransaction',
|
|
3660
|
+
args,
|
|
3661
|
+
};
|
|
3662
|
+
});
|
|
3663
|
+
|
|
3664
|
+
const unsafeRes = await this._rpcBatchRequest(batch);
|
|
3665
|
+
const res = unsafeRes.map((unsafeRes: any) => {
|
|
3666
|
+
const res = create(unsafeRes, GetParsedTransactionRpcResult);
|
|
3667
|
+
if ('error' in res) {
|
|
3668
|
+
throw new Error(
|
|
3669
|
+
'failed to get confirmed transactions: ' + res.error.message,
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
return res.result;
|
|
3673
|
+
});
|
|
3674
|
+
|
|
3675
|
+
return res;
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
/**
|
|
3679
|
+
* Fetch a list of all the confirmed signatures for transactions involving an address
|
|
3680
|
+
* within a specified slot range. Max range allowed is 10,000 slots.
|
|
3681
|
+
*
|
|
3682
|
+
* @deprecated Deprecated since v1.3. Please use {@link getConfirmedSignaturesForAddress2} instead.
|
|
3683
|
+
*
|
|
3684
|
+
* @param address queried address
|
|
3685
|
+
* @param startSlot start slot, inclusive
|
|
3686
|
+
* @param endSlot end slot, inclusive
|
|
3687
|
+
*/
|
|
3688
|
+
async getConfirmedSignaturesForAddress(
|
|
3689
|
+
address: PublicKey,
|
|
3690
|
+
startSlot: number,
|
|
3691
|
+
endSlot: number,
|
|
3692
|
+
): Promise<Array<TransactionSignature>> {
|
|
3693
|
+
let options: any = {};
|
|
3694
|
+
|
|
3695
|
+
let firstAvailableBlock = await this.getFirstAvailableBlock();
|
|
3696
|
+
while (!('until' in options)) {
|
|
3697
|
+
startSlot--;
|
|
3698
|
+
if (startSlot <= 0 || startSlot < firstAvailableBlock) {
|
|
3699
|
+
break;
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
try {
|
|
3703
|
+
const block = await this.getConfirmedBlockSignatures(
|
|
3704
|
+
startSlot,
|
|
3705
|
+
'finalized',
|
|
3706
|
+
);
|
|
3707
|
+
if (block.signatures.length > 0) {
|
|
3708
|
+
options.until =
|
|
3709
|
+
block.signatures[block.signatures.length - 1].toString();
|
|
3710
|
+
}
|
|
3711
|
+
} catch (err) {
|
|
3712
|
+
if (err instanceof Error && err.message.includes('skipped')) {
|
|
3713
|
+
continue;
|
|
3714
|
+
} else {
|
|
3715
|
+
throw err;
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
let highestConfirmedRoot = await this.getSlot('finalized');
|
|
3721
|
+
while (!('before' in options)) {
|
|
3722
|
+
endSlot++;
|
|
3723
|
+
if (endSlot > highestConfirmedRoot) {
|
|
3724
|
+
break;
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
try {
|
|
3728
|
+
const block = await this.getConfirmedBlockSignatures(endSlot);
|
|
3729
|
+
if (block.signatures.length > 0) {
|
|
3730
|
+
options.before =
|
|
3731
|
+
block.signatures[block.signatures.length - 1].toString();
|
|
3732
|
+
}
|
|
3733
|
+
} catch (err) {
|
|
3734
|
+
if (err instanceof Error && err.message.includes('skipped')) {
|
|
3735
|
+
continue;
|
|
3736
|
+
} else {
|
|
3737
|
+
throw err;
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
|
|
3742
|
+
const confirmedSignatureInfo = await this.getConfirmedSignaturesForAddress2(
|
|
3743
|
+
address,
|
|
3744
|
+
options,
|
|
3745
|
+
);
|
|
3746
|
+
return confirmedSignatureInfo.map(info => info.signature);
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
/**
|
|
3750
|
+
* Returns confirmed signatures for transactions involving an
|
|
3751
|
+
* address backwards in time from the provided signature or most recent confirmed block
|
|
3752
|
+
*
|
|
3753
|
+
*
|
|
3754
|
+
* @param address queried address
|
|
3755
|
+
* @param options
|
|
3756
|
+
*/
|
|
3757
|
+
async getConfirmedSignaturesForAddress2(
|
|
3758
|
+
address: PublicKey,
|
|
3759
|
+
options?: ConfirmedSignaturesForAddress2Options,
|
|
3760
|
+
commitment?: Finality,
|
|
3761
|
+
): Promise<Array<ConfirmedSignatureInfo>> {
|
|
3762
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3763
|
+
[address.toBase58()],
|
|
3764
|
+
commitment,
|
|
3765
|
+
undefined,
|
|
3766
|
+
options,
|
|
3767
|
+
);
|
|
3768
|
+
const unsafeRes = await this._rpcRequest(
|
|
3769
|
+
'getConfirmedSignaturesForAddress2',
|
|
3770
|
+
args,
|
|
3771
|
+
);
|
|
3772
|
+
const res = create(unsafeRes, GetConfirmedSignaturesForAddress2RpcResult);
|
|
3773
|
+
if ('error' in res) {
|
|
3774
|
+
throw new Error(
|
|
3775
|
+
'failed to get confirmed signatures for address: ' + res.error.message,
|
|
3776
|
+
);
|
|
3777
|
+
}
|
|
3778
|
+
return res.result;
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
/**
|
|
3782
|
+
* Returns confirmed signatures for transactions involving an
|
|
3783
|
+
* address backwards in time from the provided signature or most recent confirmed block
|
|
3784
|
+
*
|
|
3785
|
+
*
|
|
3786
|
+
* @param address queried address
|
|
3787
|
+
* @param options
|
|
3788
|
+
*/
|
|
3789
|
+
async getSignaturesForAddress(
|
|
3790
|
+
address: PublicKey,
|
|
3791
|
+
options?: SignaturesForAddressOptions,
|
|
3792
|
+
commitment?: Finality,
|
|
3793
|
+
): Promise<Array<ConfirmedSignatureInfo>> {
|
|
3794
|
+
const args = this._buildArgsAtLeastConfirmed(
|
|
3795
|
+
[address.toBase58()],
|
|
3796
|
+
commitment,
|
|
3797
|
+
undefined,
|
|
3798
|
+
options,
|
|
3799
|
+
);
|
|
3800
|
+
const unsafeRes = await this._rpcRequest('getSignaturesForAddress', args);
|
|
3801
|
+
const res = create(unsafeRes, GetSignaturesForAddressRpcResult);
|
|
3802
|
+
if ('error' in res) {
|
|
3803
|
+
throw new Error(
|
|
3804
|
+
'failed to get signatures for address: ' + res.error.message,
|
|
3805
|
+
);
|
|
3806
|
+
}
|
|
3807
|
+
return res.result;
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
/**
|
|
3811
|
+
* Fetch the contents of a Nonce account from the cluster, return with context
|
|
3812
|
+
*/
|
|
3813
|
+
async getNonceAndContext(
|
|
3814
|
+
nonceAccount: PublicKey,
|
|
3815
|
+
commitment?: Commitment,
|
|
3816
|
+
): Promise<RpcResponseAndContext<NonceAccount | null>> {
|
|
3817
|
+
const {context, value: accountInfo} = await this.getAccountInfoAndContext(
|
|
3818
|
+
nonceAccount,
|
|
3819
|
+
commitment,
|
|
3820
|
+
);
|
|
3821
|
+
|
|
3822
|
+
let value = null;
|
|
3823
|
+
if (accountInfo !== null) {
|
|
3824
|
+
value = NonceAccount.fromAccountData(accountInfo.data);
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
return {
|
|
3828
|
+
context,
|
|
3829
|
+
value,
|
|
3830
|
+
};
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
/**
|
|
3834
|
+
* Fetch the contents of a Nonce account from the cluster
|
|
3835
|
+
*/
|
|
3836
|
+
async getNonce(
|
|
3837
|
+
nonceAccount: PublicKey,
|
|
3838
|
+
commitment?: Commitment,
|
|
3839
|
+
): Promise<NonceAccount | null> {
|
|
3840
|
+
return await this.getNonceAndContext(nonceAccount, commitment)
|
|
3841
|
+
.then(x => x.value)
|
|
3842
|
+
.catch(e => {
|
|
3843
|
+
throw new Error(
|
|
3844
|
+
'failed to get nonce for account ' +
|
|
3845
|
+
nonceAccount.toBase58() +
|
|
3846
|
+
': ' +
|
|
3847
|
+
e,
|
|
3848
|
+
);
|
|
3849
|
+
});
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
/**
|
|
3853
|
+
* Request an allocation of lamports to the specified address
|
|
3854
|
+
*
|
|
3855
|
+
* ```typescript
|
|
3856
|
+
* import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
|
|
3857
|
+
*
|
|
3858
|
+
* (async () => {
|
|
3859
|
+
* const connection = new Connection("https://api.testnet.solana.com", "confirmed");
|
|
3860
|
+
* const myAddress = new PublicKey("2nr1bHFT86W9tGnyvmYW4vcHKsQB3sVQfnddasz4kExM");
|
|
3861
|
+
* const signature = await connection.requestAirdrop(myAddress, LAMPORTS_PER_SOL);
|
|
3862
|
+
* await connection.confirmTransaction(signature);
|
|
3863
|
+
* })();
|
|
3864
|
+
* ```
|
|
3865
|
+
*/
|
|
3866
|
+
async requestAirdrop(
|
|
3867
|
+
to: PublicKey,
|
|
3868
|
+
lamports: number,
|
|
3869
|
+
): Promise<TransactionSignature> {
|
|
3870
|
+
const unsafeRes = await this._rpcRequest('requestAirdrop', [
|
|
3871
|
+
to.toBase58(),
|
|
3872
|
+
lamports,
|
|
3873
|
+
]);
|
|
3874
|
+
const res = create(unsafeRes, RequestAirdropRpcResult);
|
|
3875
|
+
if ('error' in res) {
|
|
3876
|
+
throw new Error(
|
|
3877
|
+
'airdrop to ' + to.toBase58() + ' failed: ' + res.error.message,
|
|
3878
|
+
);
|
|
3879
|
+
}
|
|
3880
|
+
return res.result;
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3883
|
+
/**
|
|
3884
|
+
* @internal
|
|
3885
|
+
*/
|
|
3886
|
+
async _recentBlockhash(disableCache: boolean): Promise<Blockhash> {
|
|
3887
|
+
if (!disableCache) {
|
|
3888
|
+
// Wait for polling to finish
|
|
3889
|
+
while (this._pollingBlockhash) {
|
|
3890
|
+
await sleep(100);
|
|
3891
|
+
}
|
|
3892
|
+
const timeSinceFetch = Date.now() - this._blockhashInfo.lastFetch;
|
|
3893
|
+
const expired = timeSinceFetch >= BLOCKHASH_CACHE_TIMEOUT_MS;
|
|
3894
|
+
if (this._blockhashInfo.recentBlockhash !== null && !expired) {
|
|
3895
|
+
return this._blockhashInfo.recentBlockhash;
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
return await this._pollNewBlockhash();
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
/**
|
|
3903
|
+
* @internal
|
|
3904
|
+
*/
|
|
3905
|
+
async _pollNewBlockhash(): Promise<Blockhash> {
|
|
3906
|
+
this._pollingBlockhash = true;
|
|
3907
|
+
try {
|
|
3908
|
+
const startTime = Date.now();
|
|
3909
|
+
for (let i = 0; i < 50; i++) {
|
|
3910
|
+
const {blockhash} = await this.getRecentBlockhash('finalized');
|
|
3911
|
+
|
|
3912
|
+
if (this._blockhashInfo.recentBlockhash != blockhash) {
|
|
3913
|
+
this._blockhashInfo = {
|
|
3914
|
+
recentBlockhash: blockhash,
|
|
3915
|
+
lastFetch: Date.now(),
|
|
3916
|
+
transactionSignatures: [],
|
|
3917
|
+
simulatedSignatures: [],
|
|
3918
|
+
};
|
|
3919
|
+
return blockhash;
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
// Sleep for approximately half a slot
|
|
3923
|
+
await sleep(MS_PER_SLOT / 2);
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
throw new Error(
|
|
3927
|
+
`Unable to obtain a new blockhash after ${Date.now() - startTime}ms`,
|
|
3928
|
+
);
|
|
3929
|
+
} finally {
|
|
3930
|
+
this._pollingBlockhash = false;
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
/**
|
|
3935
|
+
* Simulate a transaction
|
|
3936
|
+
*/
|
|
3937
|
+
async simulateTransaction(
|
|
3938
|
+
transactionOrMessage: Transaction | Message,
|
|
3939
|
+
signers?: Array<Signer>,
|
|
3940
|
+
includeAccounts?: boolean | Array<PublicKey>,
|
|
3941
|
+
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
|
|
3942
|
+
let transaction;
|
|
3943
|
+
if (transactionOrMessage instanceof Transaction) {
|
|
3944
|
+
let originalTx: Transaction = transactionOrMessage;
|
|
3945
|
+
transaction = new Transaction({
|
|
3946
|
+
recentBlockhash: originalTx.recentBlockhash,
|
|
3947
|
+
nonceInfo: originalTx.nonceInfo,
|
|
3948
|
+
feePayer: originalTx.feePayer,
|
|
3949
|
+
signatures: [...originalTx.signatures],
|
|
3950
|
+
});
|
|
3951
|
+
transaction.instructions = transactionOrMessage.instructions;
|
|
3952
|
+
} else {
|
|
3953
|
+
transaction = Transaction.populate(transactionOrMessage);
|
|
3954
|
+
// HACK: this function relies on mutating the populated transaction
|
|
3955
|
+
transaction._message = transaction._json = undefined;
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
if (transaction.nonceInfo && signers) {
|
|
3959
|
+
transaction.sign(...signers);
|
|
3960
|
+
} else {
|
|
3961
|
+
let disableCache = this._disableBlockhashCaching;
|
|
3962
|
+
for (;;) {
|
|
3963
|
+
transaction.recentBlockhash = await this._recentBlockhash(disableCache);
|
|
3964
|
+
|
|
3965
|
+
if (!signers) break;
|
|
3966
|
+
|
|
3967
|
+
transaction.sign(...signers);
|
|
3968
|
+
if (!transaction.signature) {
|
|
3969
|
+
throw new Error('!signature'); // should never happen
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
const signature = transaction.signature.toString('base64');
|
|
3973
|
+
if (
|
|
3974
|
+
!this._blockhashInfo.simulatedSignatures.includes(signature) &&
|
|
3975
|
+
!this._blockhashInfo.transactionSignatures.includes(signature)
|
|
3976
|
+
) {
|
|
3977
|
+
// The signature of this transaction has not been seen before with the
|
|
3978
|
+
// current recentBlockhash, all done. Let's break
|
|
3979
|
+
this._blockhashInfo.simulatedSignatures.push(signature);
|
|
3980
|
+
break;
|
|
3981
|
+
} else {
|
|
3982
|
+
// This transaction would be treated as duplicate (its derived signature
|
|
3983
|
+
// matched to one of already recorded signatures).
|
|
3984
|
+
// So, we must fetch a new blockhash for a different signature by disabling
|
|
3985
|
+
// our cache not to wait for the cache expiration (BLOCKHASH_CACHE_TIMEOUT_MS).
|
|
3986
|
+
disableCache = true;
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3991
|
+
const message = transaction._compile();
|
|
3992
|
+
const signData = message.serialize();
|
|
3993
|
+
const wireTransaction = transaction._serialize(signData);
|
|
3994
|
+
const encodedTransaction = wireTransaction.toString('base64');
|
|
3995
|
+
const config: any = {
|
|
3996
|
+
encoding: 'base64',
|
|
3997
|
+
commitment: this.commitment,
|
|
3998
|
+
};
|
|
3999
|
+
|
|
4000
|
+
if (includeAccounts) {
|
|
4001
|
+
const addresses = (
|
|
4002
|
+
Array.isArray(includeAccounts)
|
|
4003
|
+
? includeAccounts
|
|
4004
|
+
: message.nonProgramIds()
|
|
4005
|
+
).map(key => key.toBase58());
|
|
4006
|
+
|
|
4007
|
+
config['accounts'] = {
|
|
4008
|
+
encoding: 'base64',
|
|
4009
|
+
addresses,
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
if (signers) {
|
|
4014
|
+
config.sigVerify = true;
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
const args = [encodedTransaction, config];
|
|
4018
|
+
const unsafeRes = await this._rpcRequest('simulateTransaction', args);
|
|
4019
|
+
const res = create(unsafeRes, SimulatedTransactionResponseStruct);
|
|
4020
|
+
if ('error' in res) {
|
|
4021
|
+
let logs;
|
|
4022
|
+
if ('data' in res.error) {
|
|
4023
|
+
logs = res.error.data.logs;
|
|
4024
|
+
if (logs && Array.isArray(logs)) {
|
|
4025
|
+
const traceIndent = '\n ';
|
|
4026
|
+
const logTrace = traceIndent + logs.join(traceIndent);
|
|
4027
|
+
console.error(res.error.message, logTrace);
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
throw new SendTransactionError(
|
|
4031
|
+
'failed to simulate transaction: ' + res.error.message,
|
|
4032
|
+
logs,
|
|
4033
|
+
);
|
|
4034
|
+
}
|
|
4035
|
+
return res.result;
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
/**
|
|
4039
|
+
* Sign and send a transaction
|
|
4040
|
+
*/
|
|
4041
|
+
async sendTransaction(
|
|
4042
|
+
transaction: Transaction,
|
|
4043
|
+
signers: Array<Signer>,
|
|
4044
|
+
options?: SendOptions,
|
|
4045
|
+
): Promise<TransactionSignature> {
|
|
4046
|
+
if (transaction.nonceInfo) {
|
|
4047
|
+
transaction.sign(...signers);
|
|
4048
|
+
} else {
|
|
4049
|
+
let disableCache = this._disableBlockhashCaching;
|
|
4050
|
+
for (;;) {
|
|
4051
|
+
transaction.recentBlockhash = await this._recentBlockhash(disableCache);
|
|
4052
|
+
transaction.sign(...signers);
|
|
4053
|
+
if (!transaction.signature) {
|
|
4054
|
+
throw new Error('!signature'); // should never happen
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
const signature = transaction.signature.toString('base64');
|
|
4058
|
+
if (!this._blockhashInfo.transactionSignatures.includes(signature)) {
|
|
4059
|
+
// The signature of this transaction has not been seen before with the
|
|
4060
|
+
// current recentBlockhash, all done. Let's break
|
|
4061
|
+
this._blockhashInfo.transactionSignatures.push(signature);
|
|
4062
|
+
break;
|
|
4063
|
+
} else {
|
|
4064
|
+
// This transaction would be treated as duplicate (its derived signature
|
|
4065
|
+
// matched to one of already recorded signatures).
|
|
4066
|
+
// So, we must fetch a new blockhash for a different signature by disabling
|
|
4067
|
+
// our cache not to wait for the cache expiration (BLOCKHASH_CACHE_TIMEOUT_MS).
|
|
4068
|
+
disableCache = true;
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
|
|
4073
|
+
const wireTransaction = transaction.serialize();
|
|
4074
|
+
return await this.sendRawTransaction(wireTransaction, options);
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
/**
|
|
4078
|
+
* Send a transaction that has already been signed and serialized into the
|
|
4079
|
+
* wire format
|
|
4080
|
+
*/
|
|
4081
|
+
async sendRawTransaction(
|
|
4082
|
+
rawTransaction: Buffer | Uint8Array | Array<number>,
|
|
4083
|
+
options?: SendOptions,
|
|
4084
|
+
): Promise<TransactionSignature> {
|
|
4085
|
+
const encodedTransaction = toBuffer(rawTransaction).toString('base64');
|
|
4086
|
+
const result = await this.sendEncodedTransaction(
|
|
4087
|
+
encodedTransaction,
|
|
4088
|
+
options,
|
|
4089
|
+
);
|
|
4090
|
+
return result;
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
/**
|
|
4094
|
+
* Send a transaction that has already been signed, serialized into the
|
|
4095
|
+
* wire format, and encoded as a base64 string
|
|
4096
|
+
*/
|
|
4097
|
+
async sendEncodedTransaction(
|
|
4098
|
+
encodedTransaction: string,
|
|
4099
|
+
options?: SendOptions,
|
|
4100
|
+
): Promise<TransactionSignature> {
|
|
4101
|
+
const config: any = {encoding: 'base64'};
|
|
4102
|
+
const skipPreflight = options && options.skipPreflight;
|
|
4103
|
+
const preflightCommitment =
|
|
4104
|
+
(options && options.preflightCommitment) || this.commitment;
|
|
4105
|
+
|
|
4106
|
+
if (options && options.maxRetries) {
|
|
4107
|
+
config.maxRetries = options.maxRetries;
|
|
4108
|
+
}
|
|
4109
|
+
if (skipPreflight) {
|
|
4110
|
+
config.skipPreflight = skipPreflight;
|
|
4111
|
+
}
|
|
4112
|
+
if (preflightCommitment) {
|
|
4113
|
+
config.preflightCommitment = preflightCommitment;
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
const args = [encodedTransaction, config];
|
|
4117
|
+
const unsafeRes = await this._rpcRequest('sendTransaction', args);
|
|
4118
|
+
const res = create(unsafeRes, SendTransactionRpcResult);
|
|
4119
|
+
if ('error' in res) {
|
|
4120
|
+
let logs;
|
|
4121
|
+
if ('data' in res.error) {
|
|
4122
|
+
logs = res.error.data.logs;
|
|
4123
|
+
}
|
|
4124
|
+
throw new SendTransactionError(
|
|
4125
|
+
'failed to send transaction: ' + res.error.message,
|
|
4126
|
+
logs,
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
4129
|
+
return res.result;
|
|
4130
|
+
}
|
|
4131
|
+
|
|
4132
|
+
/**
|
|
4133
|
+
* @internal
|
|
4134
|
+
*/
|
|
4135
|
+
_wsOnOpen() {
|
|
4136
|
+
this._rpcWebSocketConnected = true;
|
|
4137
|
+
this._rpcWebSocketHeartbeat = setInterval(() => {
|
|
4138
|
+
// Ping server every 5s to prevent idle timeouts
|
|
4139
|
+
this._rpcWebSocket.notify('ping').catch(() => {});
|
|
4140
|
+
}, 5000);
|
|
4141
|
+
this._updateSubscriptions();
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
/**
|
|
4145
|
+
* @internal
|
|
4146
|
+
*/
|
|
4147
|
+
_wsOnError(err: Error) {
|
|
4148
|
+
console.error('ws error:', err.message);
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
/**
|
|
4152
|
+
* @internal
|
|
4153
|
+
*/
|
|
4154
|
+
_wsOnClose(code: number) {
|
|
4155
|
+
if (this._rpcWebSocketHeartbeat) {
|
|
4156
|
+
clearInterval(this._rpcWebSocketHeartbeat);
|
|
4157
|
+
this._rpcWebSocketHeartbeat = null;
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
if (code === 1000) {
|
|
4161
|
+
// explicit close, check if any subscriptions have been made since close
|
|
4162
|
+
this._updateSubscriptions();
|
|
4163
|
+
return;
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
// implicit close, prepare subscriptions for auto-reconnect
|
|
4167
|
+
this._subscriptionCallbacksByServerSubscriptionId = {};
|
|
4168
|
+
Object.entries(
|
|
4169
|
+
this._subscriptionsByHash as Record<SubscriptionConfigHash, Subscription>,
|
|
4170
|
+
).forEach(([hash, subscription]) => {
|
|
4171
|
+
this._subscriptionsByHash[hash] = {
|
|
4172
|
+
...subscription,
|
|
4173
|
+
state: 'pending',
|
|
4174
|
+
};
|
|
4175
|
+
});
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
/**
|
|
4179
|
+
* @internal
|
|
4180
|
+
*/
|
|
4181
|
+
async _updateSubscriptions() {
|
|
4182
|
+
if (Object.keys(this._subscriptionsByHash).length === 0) {
|
|
4183
|
+
if (this._rpcWebSocketConnected) {
|
|
4184
|
+
this._rpcWebSocketConnected = false;
|
|
4185
|
+
this._rpcWebSocketIdleTimeout = setTimeout(() => {
|
|
4186
|
+
this._rpcWebSocketIdleTimeout = null;
|
|
4187
|
+
try {
|
|
4188
|
+
this._rpcWebSocket.close();
|
|
4189
|
+
} catch (err) {
|
|
4190
|
+
// swallow error if socket has already been closed.
|
|
4191
|
+
if (err instanceof Error) {
|
|
4192
|
+
console.log(
|
|
4193
|
+
`Error when closing socket connection: ${err.message}`,
|
|
4194
|
+
);
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
}, 500);
|
|
4198
|
+
}
|
|
4199
|
+
return;
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
if (this._rpcWebSocketIdleTimeout !== null) {
|
|
4203
|
+
clearTimeout(this._rpcWebSocketIdleTimeout);
|
|
4204
|
+
this._rpcWebSocketIdleTimeout = null;
|
|
4205
|
+
this._rpcWebSocketConnected = true;
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
if (!this._rpcWebSocketConnected) {
|
|
4209
|
+
this._rpcWebSocket.connect();
|
|
4210
|
+
return;
|
|
4211
|
+
}
|
|
4212
|
+
|
|
4213
|
+
await Promise.all(
|
|
4214
|
+
Object.entries(
|
|
4215
|
+
this._subscriptionsByHash as Record<
|
|
4216
|
+
SubscriptionConfigHash,
|
|
4217
|
+
Subscription
|
|
4218
|
+
>,
|
|
4219
|
+
).map(async ([hash, subscription]) => {
|
|
4220
|
+
switch (subscription.state) {
|
|
4221
|
+
case 'pending':
|
|
4222
|
+
case 'unsubscribed':
|
|
4223
|
+
if (subscription.callbacks.size === 0) {
|
|
4224
|
+
/**
|
|
4225
|
+
* You can end up here when:
|
|
4226
|
+
*
|
|
4227
|
+
* - a subscription has recently unsubscribed
|
|
4228
|
+
* without having new callbacks added to it
|
|
4229
|
+
* while the unsubscribe was in flight, or
|
|
4230
|
+
* - when a pending subscription has its
|
|
4231
|
+
* listeners removed before a request was
|
|
4232
|
+
* sent to the server.
|
|
4233
|
+
*
|
|
4234
|
+
* Being that nobody is interested in this
|
|
4235
|
+
* subscription any longer, delete it.
|
|
4236
|
+
*/
|
|
4237
|
+
delete this._subscriptionsByHash[hash];
|
|
4238
|
+
if (subscription.state === 'unsubscribed') {
|
|
4239
|
+
delete this._subscriptionCallbacksByServerSubscriptionId[
|
|
4240
|
+
subscription.serverSubscriptionId
|
|
4241
|
+
];
|
|
4242
|
+
}
|
|
4243
|
+
await this._updateSubscriptions();
|
|
4244
|
+
return;
|
|
4245
|
+
}
|
|
4246
|
+
await (async () => {
|
|
4247
|
+
const {method, params} = subscription;
|
|
4248
|
+
let args: IWSRequestParams;
|
|
4249
|
+
switch (method) {
|
|
4250
|
+
case 'accountSubscribe':
|
|
4251
|
+
args = this._buildArgs(
|
|
4252
|
+
[params.publicKey],
|
|
4253
|
+
params.commitment,
|
|
4254
|
+
'base64',
|
|
4255
|
+
);
|
|
4256
|
+
break;
|
|
4257
|
+
case 'logsSubscribe':
|
|
4258
|
+
args = this._buildArgs(
|
|
4259
|
+
[
|
|
4260
|
+
typeof params.filter === 'object'
|
|
4261
|
+
? {mentions: [params.filter.toString()]}
|
|
4262
|
+
: params.filter,
|
|
4263
|
+
],
|
|
4264
|
+
params.commitment,
|
|
4265
|
+
);
|
|
4266
|
+
break;
|
|
4267
|
+
case 'programSubscribe':
|
|
4268
|
+
args = this._buildArgs(
|
|
4269
|
+
[params.programId],
|
|
4270
|
+
params.commitment,
|
|
4271
|
+
'base64',
|
|
4272
|
+
params.filters
|
|
4273
|
+
? {
|
|
4274
|
+
filters: params.filters,
|
|
4275
|
+
}
|
|
4276
|
+
: undefined,
|
|
4277
|
+
);
|
|
4278
|
+
break;
|
|
4279
|
+
case 'signatureSubscribe':
|
|
4280
|
+
args = [params.signature, params.options].filter(Boolean);
|
|
4281
|
+
break;
|
|
4282
|
+
case 'rootSubscribe':
|
|
4283
|
+
case 'slotSubscribe':
|
|
4284
|
+
case 'slotsUpdatesSubscribe':
|
|
4285
|
+
args = [];
|
|
4286
|
+
break;
|
|
4287
|
+
}
|
|
4288
|
+
try {
|
|
4289
|
+
this._subscriptionsByHash[hash] = {
|
|
4290
|
+
...subscription,
|
|
4291
|
+
state: 'subscribing',
|
|
4292
|
+
};
|
|
4293
|
+
const serverSubscriptionId: ServerSubscriptionId =
|
|
4294
|
+
(await this._rpcWebSocket.call(method, args)) as number;
|
|
4295
|
+
this._subscriptionsByHash[hash] = {
|
|
4296
|
+
...subscription,
|
|
4297
|
+
serverSubscriptionId,
|
|
4298
|
+
state: 'subscribed',
|
|
4299
|
+
};
|
|
4300
|
+
this._subscriptionCallbacksByServerSubscriptionId[
|
|
4301
|
+
serverSubscriptionId
|
|
4302
|
+
] = subscription.callbacks;
|
|
4303
|
+
} catch (e) {
|
|
4304
|
+
if (e instanceof Error) {
|
|
4305
|
+
console.error(
|
|
4306
|
+
`${method} error for argument`,
|
|
4307
|
+
args,
|
|
4308
|
+
e.message,
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4311
|
+
// TODO: Maybe add an 'errored' state or a retry limit?
|
|
4312
|
+
this._subscriptionsByHash[hash] = {
|
|
4313
|
+
...subscription,
|
|
4314
|
+
state: 'pending',
|
|
4315
|
+
};
|
|
4316
|
+
} finally {
|
|
4317
|
+
await this._updateSubscriptions();
|
|
4318
|
+
}
|
|
4319
|
+
})();
|
|
4320
|
+
break;
|
|
4321
|
+
case 'subscribed':
|
|
4322
|
+
if (subscription.callbacks.size === 0) {
|
|
4323
|
+
// By the time we successfully set up a subscription
|
|
4324
|
+
// with the server, the client stopped caring about it.
|
|
4325
|
+
// Tear it down now.
|
|
4326
|
+
await (async () => {
|
|
4327
|
+
const {serverSubscriptionId, unsubscribeMethod} = subscription;
|
|
4328
|
+
if (
|
|
4329
|
+
this._subscriptionsAutoDisposedByRpc.has(serverSubscriptionId)
|
|
4330
|
+
) {
|
|
4331
|
+
/**
|
|
4332
|
+
* Special case.
|
|
4333
|
+
* If we're dealing with a subscription that has been auto-
|
|
4334
|
+
* disposed by the RPC, then we can skip the RPC call to
|
|
4335
|
+
* tear down the subscription here.
|
|
4336
|
+
*
|
|
4337
|
+
* NOTE: There is a proposal to eliminate this special case, here:
|
|
4338
|
+
* https://github.com/solana-labs/solana/issues/18892
|
|
4339
|
+
*/
|
|
4340
|
+
this._subscriptionsAutoDisposedByRpc.delete(
|
|
4341
|
+
serverSubscriptionId,
|
|
4342
|
+
);
|
|
4343
|
+
} else {
|
|
4344
|
+
this._subscriptionsByHash[hash] = {
|
|
4345
|
+
...subscription,
|
|
4346
|
+
state: 'unsubscribing',
|
|
4347
|
+
};
|
|
4348
|
+
try {
|
|
4349
|
+
await this._rpcWebSocket.call(unsubscribeMethod, [
|
|
4350
|
+
serverSubscriptionId,
|
|
4351
|
+
]);
|
|
4352
|
+
} catch (e) {
|
|
4353
|
+
if (e instanceof Error) {
|
|
4354
|
+
console.error(`${unsubscribeMethod} error:`, e.message);
|
|
4355
|
+
}
|
|
4356
|
+
// TODO: Maybe add an 'errored' state or a retry limit?
|
|
4357
|
+
this._subscriptionsByHash[hash] = {
|
|
4358
|
+
...subscription,
|
|
4359
|
+
state: 'subscribed',
|
|
4360
|
+
};
|
|
4361
|
+
await this._updateSubscriptions();
|
|
4362
|
+
return;
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
this._subscriptionsByHash[hash] = {
|
|
4366
|
+
...subscription,
|
|
4367
|
+
state: 'unsubscribed',
|
|
4368
|
+
};
|
|
4369
|
+
await this._updateSubscriptions();
|
|
4370
|
+
})();
|
|
4371
|
+
}
|
|
4372
|
+
break;
|
|
4373
|
+
case 'subscribing':
|
|
4374
|
+
case 'unsubscribing':
|
|
4375
|
+
break;
|
|
4376
|
+
}
|
|
4377
|
+
}),
|
|
4378
|
+
);
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
/**
|
|
4382
|
+
*
|
|
4383
|
+
* @internal
|
|
4384
|
+
*/
|
|
4385
|
+
_publishNotification<TCallback extends SubscriptionConfig['callback']>(
|
|
4386
|
+
serverSubscriptionId: ServerSubscriptionId,
|
|
4387
|
+
publishArgs: Parameters<TCallback>,
|
|
4388
|
+
): void {
|
|
4389
|
+
const callbacks =
|
|
4390
|
+
this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId];
|
|
4391
|
+
if (callbacks == null) {
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
4394
|
+
callbacks.forEach(cb => {
|
|
4395
|
+
try {
|
|
4396
|
+
cb(...(publishArgs as [any, any]));
|
|
4397
|
+
} catch (e) {
|
|
4398
|
+
console.error(e);
|
|
4399
|
+
}
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4403
|
+
/**
|
|
4404
|
+
* @internal
|
|
4405
|
+
*/
|
|
4406
|
+
_wsOnAccountNotification(notification: object) {
|
|
4407
|
+
const {result, subscription} = create(
|
|
4408
|
+
notification,
|
|
4409
|
+
AccountNotificationResult,
|
|
4410
|
+
);
|
|
4411
|
+
this._publishNotification<AccountChangeCallback>(subscription, [
|
|
4412
|
+
result.value,
|
|
4413
|
+
result.context,
|
|
4414
|
+
]);
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
/**
|
|
4418
|
+
* @private
|
|
4419
|
+
*/
|
|
4420
|
+
_makeSubscription(
|
|
4421
|
+
// The subscription config, before applying defaults
|
|
4422
|
+
rawConfig: SubscriptionConfig,
|
|
4423
|
+
): ClientSubscriptionId {
|
|
4424
|
+
const clientSubscriptionId = this._nextClientSubscriptionId++;
|
|
4425
|
+
let subscriptionConfig: SubscriptionConfig;
|
|
4426
|
+
// Apply defaults.
|
|
4427
|
+
switch (rawConfig.method) {
|
|
4428
|
+
case 'accountSubscribe':
|
|
4429
|
+
case 'logsSubscribe':
|
|
4430
|
+
case 'programSubscribe':
|
|
4431
|
+
if (rawConfig.params.commitment === undefined) {
|
|
4432
|
+
subscriptionConfig = {
|
|
4433
|
+
...rawConfig,
|
|
4434
|
+
params: {
|
|
4435
|
+
...rawConfig.params,
|
|
4436
|
+
commitment: 'finalized',
|
|
4437
|
+
},
|
|
4438
|
+
} as SubscriptionConfig;
|
|
4439
|
+
} else {
|
|
4440
|
+
subscriptionConfig = rawConfig;
|
|
4441
|
+
}
|
|
4442
|
+
break;
|
|
4443
|
+
case 'signatureSubscribe':
|
|
4444
|
+
if (
|
|
4445
|
+
rawConfig.params.options &&
|
|
4446
|
+
rawConfig.params.options.commitment === undefined
|
|
4447
|
+
) {
|
|
4448
|
+
subscriptionConfig = {
|
|
4449
|
+
...rawConfig,
|
|
4450
|
+
params: {
|
|
4451
|
+
...rawConfig.params,
|
|
4452
|
+
options: {
|
|
4453
|
+
...rawConfig.params.options,
|
|
4454
|
+
commitment: 'finalized',
|
|
4455
|
+
},
|
|
4456
|
+
},
|
|
4457
|
+
};
|
|
4458
|
+
} else {
|
|
4459
|
+
subscriptionConfig = rawConfig;
|
|
4460
|
+
}
|
|
4461
|
+
break;
|
|
4462
|
+
default:
|
|
4463
|
+
subscriptionConfig = rawConfig;
|
|
4464
|
+
}
|
|
4465
|
+
const hash = hashSubscriptionConfig(subscriptionConfig);
|
|
4466
|
+
const existingSubscription = this._subscriptionsByHash[hash];
|
|
4467
|
+
if (existingSubscription == null) {
|
|
4468
|
+
this._subscriptionsByHash[hash] = {
|
|
4469
|
+
...subscriptionConfig,
|
|
4470
|
+
callbacks: new Set([subscriptionConfig.callback]),
|
|
4471
|
+
state: 'pending',
|
|
4472
|
+
};
|
|
4473
|
+
} else {
|
|
4474
|
+
existingSubscription.callbacks.add(subscriptionConfig.callback);
|
|
4475
|
+
}
|
|
4476
|
+
let unsubscribed = false;
|
|
4477
|
+
this._subscriptionDisposeFunctionsByClientSubscriptionId[
|
|
4478
|
+
clientSubscriptionId
|
|
4479
|
+
] = async () => {
|
|
4480
|
+
if (unsubscribed) {
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
unsubscribed = true;
|
|
4484
|
+
delete this._subscriptionDisposeFunctionsByClientSubscriptionId[
|
|
4485
|
+
clientSubscriptionId
|
|
4486
|
+
];
|
|
4487
|
+
const subscription = this._subscriptionsByHash[hash];
|
|
4488
|
+
assert(
|
|
4489
|
+
subscription,
|
|
4490
|
+
`Could not find a \`Subscription\` when tearing down client subscription #${clientSubscriptionId}`,
|
|
4491
|
+
);
|
|
4492
|
+
subscription.callbacks.delete(subscriptionConfig.callback);
|
|
4493
|
+
await this._updateSubscriptions();
|
|
4494
|
+
};
|
|
4495
|
+
this._updateSubscriptions();
|
|
4496
|
+
return clientSubscriptionId;
|
|
4497
|
+
}
|
|
4498
|
+
|
|
4499
|
+
/**
|
|
4500
|
+
* Register a callback to be invoked whenever the specified account changes
|
|
4501
|
+
*
|
|
4502
|
+
* @param publicKey Public key of the account to monitor
|
|
4503
|
+
* @param callback Function to invoke whenever the account is changed
|
|
4504
|
+
* @param commitment Specify the commitment level account changes must reach before notification
|
|
4505
|
+
* @return subscription id
|
|
4506
|
+
*/
|
|
4507
|
+
onAccountChange(
|
|
4508
|
+
publicKey: PublicKey,
|
|
4509
|
+
callback: AccountChangeCallback,
|
|
4510
|
+
commitment?: Commitment,
|
|
4511
|
+
): ClientSubscriptionId {
|
|
4512
|
+
return this._makeSubscription({
|
|
4513
|
+
callback,
|
|
4514
|
+
method: 'accountSubscribe',
|
|
4515
|
+
params: {
|
|
4516
|
+
commitment,
|
|
4517
|
+
publicKey: publicKey.toBase58(),
|
|
4518
|
+
},
|
|
4519
|
+
unsubscribeMethod: 'accountUnsubscribe',
|
|
4520
|
+
});
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
/**
|
|
4524
|
+
* Deregister an account notification callback
|
|
4525
|
+
*
|
|
4526
|
+
* @param id client subscription id to deregister
|
|
4527
|
+
*/
|
|
4528
|
+
async removeAccountChangeListener(
|
|
4529
|
+
clientSubscriptionId: ClientSubscriptionId,
|
|
4530
|
+
): Promise<void> {
|
|
4531
|
+
await this._unsubscribeClientSubscription(
|
|
4532
|
+
clientSubscriptionId,
|
|
4533
|
+
'account change',
|
|
4534
|
+
);
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
/**
|
|
4538
|
+
* @internal
|
|
4539
|
+
*/
|
|
4540
|
+
_wsOnProgramAccountNotification(notification: Object) {
|
|
4541
|
+
const {result, subscription} = create(
|
|
4542
|
+
notification,
|
|
4543
|
+
ProgramAccountNotificationResult,
|
|
4544
|
+
);
|
|
4545
|
+
this._publishNotification<ProgramAccountChangeCallback>(subscription, [
|
|
4546
|
+
{
|
|
4547
|
+
accountId: result.value.pubkey,
|
|
4548
|
+
accountInfo: result.value.account,
|
|
4549
|
+
},
|
|
4550
|
+
result.context,
|
|
4551
|
+
]);
|
|
4552
|
+
}
|
|
4553
|
+
|
|
4554
|
+
/**
|
|
4555
|
+
* Register a callback to be invoked whenever accounts owned by the
|
|
4556
|
+
* specified program change
|
|
4557
|
+
*
|
|
4558
|
+
* @param programId Public key of the program to monitor
|
|
4559
|
+
* @param callback Function to invoke whenever the account is changed
|
|
4560
|
+
* @param commitment Specify the commitment level account changes must reach before notification
|
|
4561
|
+
* @param filters The program account filters to pass into the RPC method
|
|
4562
|
+
* @return subscription id
|
|
4563
|
+
*/
|
|
4564
|
+
onProgramAccountChange(
|
|
4565
|
+
programId: PublicKey,
|
|
4566
|
+
callback: ProgramAccountChangeCallback,
|
|
4567
|
+
commitment?: Commitment,
|
|
4568
|
+
filters?: GetProgramAccountsFilter[],
|
|
4569
|
+
): ClientSubscriptionId {
|
|
4570
|
+
return this._makeSubscription({
|
|
4571
|
+
callback,
|
|
4572
|
+
method: 'programSubscribe',
|
|
4573
|
+
params: {
|
|
4574
|
+
commitment,
|
|
4575
|
+
filters,
|
|
4576
|
+
programId: programId.toBase58(),
|
|
4577
|
+
},
|
|
4578
|
+
unsubscribeMethod: 'programUnsubscribe',
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
/**
|
|
4583
|
+
* Deregister an account notification callback
|
|
4584
|
+
*
|
|
4585
|
+
* @param id client subscription id to deregister
|
|
4586
|
+
*/
|
|
4587
|
+
async removeProgramAccountChangeListener(
|
|
4588
|
+
clientSubscriptionId: ClientSubscriptionId,
|
|
4589
|
+
): Promise<void> {
|
|
4590
|
+
await this._unsubscribeClientSubscription(
|
|
4591
|
+
clientSubscriptionId,
|
|
4592
|
+
'program account change',
|
|
4593
|
+
);
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
/**
|
|
4597
|
+
* Registers a callback to be invoked whenever logs are emitted.
|
|
4598
|
+
*/
|
|
4599
|
+
onLogs(
|
|
4600
|
+
filter: LogsFilter,
|
|
4601
|
+
callback: LogsCallback,
|
|
4602
|
+
commitment?: Commitment,
|
|
4603
|
+
): ClientSubscriptionId {
|
|
4604
|
+
return this._makeSubscription({
|
|
4605
|
+
callback,
|
|
4606
|
+
method: 'logsSubscribe',
|
|
4607
|
+
params: {
|
|
4608
|
+
commitment,
|
|
4609
|
+
filter,
|
|
4610
|
+
},
|
|
4611
|
+
unsubscribeMethod: 'logsUnsubscribe',
|
|
4612
|
+
});
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4615
|
+
/**
|
|
4616
|
+
* Deregister a logs callback.
|
|
4617
|
+
*
|
|
4618
|
+
* @param id client subscription id to deregister.
|
|
4619
|
+
*/
|
|
4620
|
+
async removeOnLogsListener(
|
|
4621
|
+
clientSubscriptionId: ClientSubscriptionId,
|
|
4622
|
+
): Promise<void> {
|
|
4623
|
+
await this._unsubscribeClientSubscription(clientSubscriptionId, 'logs');
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4626
|
+
/**
|
|
4627
|
+
* @internal
|
|
4628
|
+
*/
|
|
4629
|
+
_wsOnLogsNotification(notification: Object) {
|
|
4630
|
+
const {result, subscription} = create(notification, LogsNotificationResult);
|
|
4631
|
+
this._publishNotification<LogsCallback>(subscription, [
|
|
4632
|
+
result.value,
|
|
4633
|
+
result.context,
|
|
4634
|
+
]);
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
/**
|
|
4638
|
+
* @internal
|
|
4639
|
+
*/
|
|
4640
|
+
_wsOnSlotNotification(notification: Object) {
|
|
4641
|
+
const {result, subscription} = create(notification, SlotNotificationResult);
|
|
4642
|
+
this._publishNotification<SlotChangeCallback>(subscription, [result]);
|
|
4643
|
+
}
|
|
4644
|
+
|
|
4645
|
+
/**
|
|
4646
|
+
* Register a callback to be invoked upon slot changes
|
|
4647
|
+
*
|
|
4648
|
+
* @param callback Function to invoke whenever the slot changes
|
|
4649
|
+
* @return subscription id
|
|
4650
|
+
*/
|
|
4651
|
+
onSlotChange(callback: SlotChangeCallback): ClientSubscriptionId {
|
|
4652
|
+
return this._makeSubscription({
|
|
4653
|
+
callback,
|
|
4654
|
+
method: 'slotSubscribe',
|
|
4655
|
+
params: undefined,
|
|
4656
|
+
unsubscribeMethod: 'slotUnsubscribe',
|
|
4657
|
+
});
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4660
|
+
/**
|
|
4661
|
+
* Deregister a slot notification callback
|
|
4662
|
+
*
|
|
4663
|
+
* @param id client subscription id to deregister
|
|
4664
|
+
*/
|
|
4665
|
+
async removeSlotChangeListener(
|
|
4666
|
+
clientSubscriptionId: ClientSubscriptionId,
|
|
4667
|
+
): Promise<void> {
|
|
4668
|
+
await this._unsubscribeClientSubscription(
|
|
4669
|
+
clientSubscriptionId,
|
|
4670
|
+
'slot change',
|
|
4671
|
+
);
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
/**
|
|
4675
|
+
* @internal
|
|
4676
|
+
*/
|
|
4677
|
+
_wsOnSlotUpdatesNotification(notification: Object) {
|
|
4678
|
+
const {result, subscription} = create(
|
|
4679
|
+
notification,
|
|
4680
|
+
SlotUpdateNotificationResult,
|
|
4681
|
+
);
|
|
4682
|
+
this._publishNotification<SlotUpdateCallback>(subscription, [result]);
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
/**
|
|
4686
|
+
* Register a callback to be invoked upon slot updates. {@link SlotUpdate}'s
|
|
4687
|
+
* may be useful to track live progress of a cluster.
|
|
4688
|
+
*
|
|
4689
|
+
* @param callback Function to invoke whenever the slot updates
|
|
4690
|
+
* @return subscription id
|
|
4691
|
+
*/
|
|
4692
|
+
onSlotUpdate(callback: SlotUpdateCallback): ClientSubscriptionId {
|
|
4693
|
+
return this._makeSubscription({
|
|
4694
|
+
callback,
|
|
4695
|
+
method: 'slotsUpdatesSubscribe',
|
|
4696
|
+
params: undefined,
|
|
4697
|
+
unsubscribeMethod: 'slotsUpdatesUnsubscribe',
|
|
4698
|
+
});
|
|
4699
|
+
}
|
|
4700
|
+
|
|
4701
|
+
/**
|
|
4702
|
+
* Deregister a slot update notification callback
|
|
4703
|
+
*
|
|
4704
|
+
* @param id client subscription id to deregister
|
|
4705
|
+
*/
|
|
4706
|
+
async removeSlotUpdateListener(
|
|
4707
|
+
clientSubscriptionId: ClientSubscriptionId,
|
|
4708
|
+
): Promise<void> {
|
|
4709
|
+
await this._unsubscribeClientSubscription(
|
|
4710
|
+
clientSubscriptionId,
|
|
4711
|
+
'slot update',
|
|
4712
|
+
);
|
|
4713
|
+
}
|
|
4714
|
+
|
|
4715
|
+
/**
|
|
4716
|
+
* @internal
|
|
4717
|
+
*/
|
|
4718
|
+
|
|
4719
|
+
async _unsubscribeClientSubscription(
|
|
4720
|
+
clientSubscriptionId: ClientSubscriptionId,
|
|
4721
|
+
subscriptionName: string,
|
|
4722
|
+
) {
|
|
4723
|
+
const dispose =
|
|
4724
|
+
this._subscriptionDisposeFunctionsByClientSubscriptionId[
|
|
4725
|
+
clientSubscriptionId
|
|
4726
|
+
];
|
|
4727
|
+
if (dispose) {
|
|
4728
|
+
await dispose();
|
|
4729
|
+
} else {
|
|
4730
|
+
throw new Error(
|
|
4731
|
+
`Unknown ${subscriptionName} client subscription id: ${clientSubscriptionId}`,
|
|
4732
|
+
);
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4736
|
+
_buildArgs(
|
|
4737
|
+
args: Array<any>,
|
|
4738
|
+
override?: Commitment,
|
|
4739
|
+
encoding?: 'jsonParsed' | 'base64',
|
|
4740
|
+
extra?: any,
|
|
4741
|
+
): Array<any> {
|
|
4742
|
+
const commitment = override || this._commitment;
|
|
4743
|
+
if (commitment || encoding || extra) {
|
|
4744
|
+
let options: any = {};
|
|
4745
|
+
if (encoding) {
|
|
4746
|
+
options.encoding = encoding;
|
|
4747
|
+
}
|
|
4748
|
+
if (commitment) {
|
|
4749
|
+
options.commitment = commitment;
|
|
4750
|
+
}
|
|
4751
|
+
if (extra) {
|
|
4752
|
+
options = Object.assign(options, extra);
|
|
4753
|
+
}
|
|
4754
|
+
args.push(options);
|
|
4755
|
+
}
|
|
4756
|
+
return args;
|
|
4757
|
+
}
|
|
4758
|
+
|
|
4759
|
+
/**
|
|
4760
|
+
* @internal
|
|
4761
|
+
*/
|
|
4762
|
+
_buildArgsAtLeastConfirmed(
|
|
4763
|
+
args: Array<any>,
|
|
4764
|
+
override?: Finality,
|
|
4765
|
+
encoding?: 'jsonParsed' | 'base64',
|
|
4766
|
+
extra?: any,
|
|
4767
|
+
): Array<any> {
|
|
4768
|
+
const commitment = override || this._commitment;
|
|
4769
|
+
if (commitment && !['confirmed', 'finalized'].includes(commitment)) {
|
|
4770
|
+
throw new Error(
|
|
4771
|
+
'Using Connection with default commitment: `' +
|
|
4772
|
+
this._commitment +
|
|
4773
|
+
'`, but method requires at least `confirmed`',
|
|
4774
|
+
);
|
|
4775
|
+
}
|
|
4776
|
+
return this._buildArgs(args, override, encoding, extra);
|
|
4777
|
+
}
|
|
4778
|
+
|
|
4779
|
+
/**
|
|
4780
|
+
* @internal
|
|
4781
|
+
*/
|
|
4782
|
+
_wsOnSignatureNotification(notification: Object) {
|
|
4783
|
+
const {result, subscription} = create(
|
|
4784
|
+
notification,
|
|
4785
|
+
SignatureNotificationResult,
|
|
4786
|
+
);
|
|
4787
|
+
if (result.value !== 'receivedSignature') {
|
|
4788
|
+
/**
|
|
4789
|
+
* Special case.
|
|
4790
|
+
* After a signature is processed, RPCs automatically dispose of the
|
|
4791
|
+
* subscription on the server side. We need to track which of these
|
|
4792
|
+
* subscriptions have been disposed in such a way, so that we know
|
|
4793
|
+
* whether the client is dealing with a not-yet-processed signature
|
|
4794
|
+
* (in which case we must tear down the server subscription) or an
|
|
4795
|
+
* already-processed signature (in which case the client can simply
|
|
4796
|
+
* clear out the subscription locally without telling the server).
|
|
4797
|
+
*
|
|
4798
|
+
* NOTE: There is a proposal to eliminate this special case, here:
|
|
4799
|
+
* https://github.com/solana-labs/solana/issues/18892
|
|
4800
|
+
*/
|
|
4801
|
+
this._subscriptionsAutoDisposedByRpc.add(subscription);
|
|
4802
|
+
}
|
|
4803
|
+
this._publishNotification<SignatureSubscriptionCallback>(
|
|
4804
|
+
subscription,
|
|
4805
|
+
result.value === 'receivedSignature'
|
|
4806
|
+
? [{type: 'received'}, result.context]
|
|
4807
|
+
: [{type: 'status', result: result.value}, result.context],
|
|
4808
|
+
);
|
|
4809
|
+
}
|
|
4810
|
+
|
|
4811
|
+
/**
|
|
4812
|
+
* Register a callback to be invoked upon signature updates
|
|
4813
|
+
*
|
|
4814
|
+
* @param signature Transaction signature string in base 58
|
|
4815
|
+
* @param callback Function to invoke on signature notifications
|
|
4816
|
+
* @param commitment Specify the commitment level signature must reach before notification
|
|
4817
|
+
* @return subscription id
|
|
4818
|
+
*/
|
|
4819
|
+
onSignature(
|
|
4820
|
+
signature: TransactionSignature,
|
|
4821
|
+
callback: SignatureResultCallback,
|
|
4822
|
+
commitment?: Commitment,
|
|
4823
|
+
): ClientSubscriptionId {
|
|
4824
|
+
const clientSubscriptionId = this._makeSubscription({
|
|
4825
|
+
callback: (notification, context) => {
|
|
4826
|
+
if (notification.type === 'status') {
|
|
4827
|
+
callback(notification.result, context);
|
|
4828
|
+
// Signatures subscriptions are auto-removed by the RPC service
|
|
4829
|
+
// so no need to explicitly send an unsubscribe message.
|
|
4830
|
+
try {
|
|
4831
|
+
this.removeSignatureListener(clientSubscriptionId);
|
|
4832
|
+
// eslint-disable-next-line no-empty
|
|
4833
|
+
} catch {
|
|
4834
|
+
// Already removed.
|
|
4835
|
+
}
|
|
4836
|
+
}
|
|
4837
|
+
},
|
|
4838
|
+
method: 'signatureSubscribe',
|
|
4839
|
+
params: {
|
|
4840
|
+
options: {commitment},
|
|
4841
|
+
signature,
|
|
4842
|
+
},
|
|
4843
|
+
unsubscribeMethod: 'signatureUnsubscribe',
|
|
4844
|
+
});
|
|
4845
|
+
return clientSubscriptionId;
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
/**
|
|
4849
|
+
* Register a callback to be invoked when a transaction is
|
|
4850
|
+
* received and/or processed.
|
|
4851
|
+
*
|
|
4852
|
+
* @param signature Transaction signature string in base 58
|
|
4853
|
+
* @param callback Function to invoke on signature notifications
|
|
4854
|
+
* @param options Enable received notifications and set the commitment
|
|
4855
|
+
* level that signature must reach before notification
|
|
4856
|
+
* @return subscription id
|
|
4857
|
+
*/
|
|
4858
|
+
onSignatureWithOptions(
|
|
4859
|
+
signature: TransactionSignature,
|
|
4860
|
+
callback: SignatureSubscriptionCallback,
|
|
4861
|
+
options?: SignatureSubscriptionOptions,
|
|
4862
|
+
): ClientSubscriptionId {
|
|
4863
|
+
const clientSubscriptionId = this._makeSubscription({
|
|
4864
|
+
callback: (notification, context) => {
|
|
4865
|
+
callback(notification, context);
|
|
4866
|
+
// Signatures subscriptions are auto-removed by the RPC service
|
|
4867
|
+
// so no need to explicitly send an unsubscribe message.
|
|
4868
|
+
try {
|
|
4869
|
+
this.removeSignatureListener(clientSubscriptionId);
|
|
4870
|
+
// eslint-disable-next-line no-empty
|
|
4871
|
+
} catch {
|
|
4872
|
+
// Already removed.
|
|
4873
|
+
}
|
|
4874
|
+
},
|
|
4875
|
+
method: 'signatureSubscribe',
|
|
4876
|
+
params: {
|
|
4877
|
+
options,
|
|
4878
|
+
signature,
|
|
4879
|
+
},
|
|
4880
|
+
unsubscribeMethod: 'signatureUnsubscribe',
|
|
4881
|
+
});
|
|
4882
|
+
return clientSubscriptionId;
|
|
4883
|
+
}
|
|
4884
|
+
|
|
4885
|
+
/**
|
|
4886
|
+
* Deregister a signature notification callback
|
|
4887
|
+
*
|
|
4888
|
+
* @param id client subscription id to deregister
|
|
4889
|
+
*/
|
|
4890
|
+
async removeSignatureListener(
|
|
4891
|
+
clientSubscriptionId: ClientSubscriptionId,
|
|
4892
|
+
): Promise<void> {
|
|
4893
|
+
await this._unsubscribeClientSubscription(
|
|
4894
|
+
clientSubscriptionId,
|
|
4895
|
+
'signature result',
|
|
4896
|
+
);
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4899
|
+
/**
|
|
4900
|
+
* @internal
|
|
4901
|
+
*/
|
|
4902
|
+
_wsOnRootNotification(notification: Object) {
|
|
4903
|
+
const {result, subscription} = create(notification, RootNotificationResult);
|
|
4904
|
+
this._publishNotification<RootChangeCallback>(subscription, [result]);
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
/**
|
|
4908
|
+
* Register a callback to be invoked upon root changes
|
|
4909
|
+
*
|
|
4910
|
+
* @param callback Function to invoke whenever the root changes
|
|
4911
|
+
* @return subscription id
|
|
4912
|
+
*/
|
|
4913
|
+
onRootChange(callback: RootChangeCallback): ClientSubscriptionId {
|
|
4914
|
+
return this._makeSubscription({
|
|
4915
|
+
callback,
|
|
4916
|
+
method: 'rootSubscribe',
|
|
4917
|
+
params: undefined,
|
|
4918
|
+
unsubscribeMethod: 'rootUnsubscribe',
|
|
4919
|
+
});
|
|
4920
|
+
}
|
|
4921
|
+
|
|
4922
|
+
/**
|
|
4923
|
+
* Deregister a root notification callback
|
|
4924
|
+
*
|
|
4925
|
+
* @param id client subscription id to deregister
|
|
4926
|
+
*/
|
|
4927
|
+
async removeRootChangeListener(
|
|
4928
|
+
clientSubscriptionId: ClientSubscriptionId,
|
|
4929
|
+
): Promise<void> {
|
|
4930
|
+
await this._unsubscribeClientSubscription(
|
|
4931
|
+
clientSubscriptionId,
|
|
4932
|
+
'root change',
|
|
4933
|
+
);
|
|
4934
|
+
}
|
|
4935
|
+
}
|