@bbachain/web3.js
Version:
BBAChain Javascript API
1,730 lines (1,601 loc) • 195 kB
text/typescript
import HttpKeepAliveAgent, {
HttpsAgent as HttpsKeepAliveAgent,
} from 'agentkeepalive';
import bs58 from 'bs58';
import {Buffer} from 'buffer';
// @ts-ignore
import fastStableStringify from 'fast-stable-stringify';
import type {Agent as NodeHttpAgent} from 'http';
import {Agent as NodeHttpsAgent} from 'https';
import {
type as pick,
number,
string,
array,
boolean,
literal,
record,
union,
optional,
nullable,
coerce,
instance,
create,
tuple,
unknown,
any,
} from 'superstruct';
import type {Struct} from 'superstruct';
import RpcClient from 'jayson/lib/client/browser';
import {JSONRPCError} from 'jayson';
import {EpochSchedule} from './epoch-schedule';
import {SendTransactionError, BBAChainJSONRPCError} from './errors';
import fetchImpl, {Response} from './fetch-impl';
import {DurableNonce, NonceAccount} from './nonce-account';
import {PublicKey} from './publickey';
import {Signer} from './keypair';
import RpcWebSocketClient from './rpc-websocket';
import {MS_PER_SLOT} from './timing';
import {
Transaction,
TransactionStatus,
TransactionVersion,
VersionedTransaction,
} from './transaction';
import {Message, MessageHeader, MessageV0, VersionedMessage} from './message';
import {AddressLookupTableAccount} from './programs/address-lookup-table/state';
import assert from './utils/assert';
import {sleep} from './utils/sleep';
import {toBuffer} from './utils/to-buffer';
import {
TransactionExpiredBlockheightExceededError,
TransactionExpiredNonceInvalidError,
TransactionExpiredTimeoutError,
} from './transaction/expiry-custom-errors';
import {makeWebsocketUrl} from './utils/makeWebsocketUrl';
import type {Blockhash} from './blockhash';
import type {FeeCalculator} from './fee-calculator';
import type {TransactionSignature} from './transaction';
import type {CompiledInstruction} from './message';
const PublicKeyFromString = coerce(
instance(PublicKey),
string(),
value => new PublicKey(value),
);
const RawAccountDataResult = tuple([string(), literal('base64')]);
const BufferFromRawAccountData = coerce(
instance(Buffer),
RawAccountDataResult,
value => Buffer.from(value[0], 'base64'),
);
/**
* Attempt to use a recent blockhash for up to 30 seconds
* @internal
*/
export const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
/**
* HACK.
* Copied from rpc-websockets/dist/lib/client.
* Otherwise, `yarn build` fails with:
* https://gist.github.com/steveluscher/c057eca81d479ef705cdb53162f9971d
*/
interface IWSRequestParams {
[x: string]: any;
[x: number]: any;
}
type ClientSubscriptionId = number;
/** @internal */ type ServerSubscriptionId = number;
/** @internal */ type SubscriptionConfigHash = string;
/** @internal */ type SubscriptionDisposeFn = () => Promise<void>;
/** @internal */ type SubscriptionStateChangeCallback = (
nextState: StatefulSubscription['state'],
) => void;
/** @internal */ type SubscriptionStateChangeDisposeFn = () => void;
/**
* @internal
* Every subscription contains the args used to open the subscription with
* the server, and a list of callers interested in notifications.
*/
type BaseSubscription<TMethod = SubscriptionConfig['method']> = Readonly<{
args: IWSRequestParams;
callbacks: Set<Extract<SubscriptionConfig, {method: TMethod}>['callback']>;
}>;
/**
* @internal
* A subscription may be in various states of connectedness. Only when it is
* fully connected will it have a server subscription id associated with it.
* This id can be returned to the server to unsubscribe the client entirely.
*/
type StatefulSubscription = Readonly<
// New subscriptions that have not yet been
// sent to the server start in this state.
| {
state: 'pending';
}
// These subscriptions have been sent to the server
// and are waiting for the server to acknowledge them.
| {
state: 'subscribing';
}
// These subscriptions have been acknowledged by the
// server and have been assigned server subscription ids.
| {
serverSubscriptionId: ServerSubscriptionId;
state: 'subscribed';
}
// These subscriptions are intended to be torn down and
// are waiting on an acknowledgement from the server.
| {
serverSubscriptionId: ServerSubscriptionId;
state: 'unsubscribing';
}
// The request to tear down these subscriptions has been
// acknowledged by the server. The `serverSubscriptionId`
// is the id of the now-dead subscription.
| {
serverSubscriptionId: ServerSubscriptionId;
state: 'unsubscribed';
}
>;
/**
* A type that encapsulates a subscription's RPC method
* names and notification (callback) signature.
*/
type SubscriptionConfig = Readonly<
| {
callback: AccountChangeCallback;
method: 'accountSubscribe';
unsubscribeMethod: 'accountUnsubscribe';
}
| {
callback: LogsCallback;
method: 'logsSubscribe';
unsubscribeMethod: 'logsUnsubscribe';
}
| {
callback: ProgramAccountChangeCallback;
method: 'programSubscribe';
unsubscribeMethod: 'programUnsubscribe';
}
| {
callback: RootChangeCallback;
method: 'rootSubscribe';
unsubscribeMethod: 'rootUnsubscribe';
}
| {
callback: SignatureSubscriptionCallback;
method: 'signatureSubscribe';
unsubscribeMethod: 'signatureUnsubscribe';
}
| {
callback: SlotChangeCallback;
method: 'slotSubscribe';
unsubscribeMethod: 'slotUnsubscribe';
}
| {
callback: SlotUpdateCallback;
method: 'slotsUpdatesSubscribe';
unsubscribeMethod: 'slotsUpdatesUnsubscribe';
}
>;
/**
* @internal
* Utility type that keeps tagged unions intact while omitting properties.
*/
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
? Omit<T, K>
: never;
/**
* @internal
* This type represents a single subscribable 'topic.' It's made up of:
*
* - The args used to open the subscription with the server,
* - The state of the subscription, in terms of its connectedness, and
* - The set of callbacks to call when the server publishes notifications
*
* This record gets indexed by `SubscriptionConfigHash` and is used to
* set up subscriptions, fan out notifications, and track subscription state.
*/
type Subscription = BaseSubscription &
StatefulSubscription &
DistributiveOmit<SubscriptionConfig, 'callback'>;
type RpcRequest = (methodName: string, args: Array<any>) => Promise<any>;
type RpcBatchRequest = (requests: RpcParams[]) => Promise<any[]>;
/**
* @internal
*/
export type RpcParams = {
methodName: string;
args: Array<any>;
};
export type TokenAccountsFilter =
| {
mint: PublicKey;
}
| {
programId: PublicKey;
};
/**
* Extra contextual information for RPC responses
*/
export type Context = {
slot: number;
};
/**
* Options for sending transactions
*/
export type SendOptions = {
/** disable transaction verification step */
skipPreflight?: boolean;
/** preflight commitment level */
preflightCommitment?: Commitment;
/** Maximum number of times for the RPC node to retry sending the transaction to the leader. */
maxRetries?: number;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* Options for confirming transactions
*/
export type ConfirmOptions = {
/** disable transaction verification step */
skipPreflight?: boolean;
/** desired commitment level */
commitment?: Commitment;
/** preflight commitment level */
preflightCommitment?: Commitment;
/** Maximum number of times for the RPC node to retry sending the transaction to the leader. */
maxRetries?: number;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* Options for getConfirmedSignaturesForAddress2
*/
export type ConfirmedSignaturesForAddress2Options = {
/**
* Start searching backwards from this transaction signature.
* @remark If not provided the search starts from the highest max confirmed block.
*/
before?: TransactionSignature;
/** Search until this transaction signature is reached, if found before `limit`. */
until?: TransactionSignature;
/** Maximum transaction signatures to return (between 1 and 1,000, default: 1,000). */
limit?: number;
};
/**
* Options for getSignaturesForAddress
*/
export type SignaturesForAddressOptions = {
/**
* Start searching backwards from this transaction signature.
* @remark If not provided the search starts from the highest max confirmed block.
*/
before?: TransactionSignature;
/** Search until this transaction signature is reached, if found before `limit`. */
until?: TransactionSignature;
/** Maximum transaction signatures to return (between 1 and 1,000, default: 1,000). */
limit?: number;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* RPC Response with extra contextual information
*/
export type RpcResponseAndContext<T> = {
/** response context */
context: Context;
/** response value */
value: T;
};
export type BlockhashWithExpiryBlockHeight = Readonly<{
blockhash: Blockhash;
lastValidBlockHeight: number;
}>;
/**
* A strategy for confirming transactions that uses the last valid
* block height for a given blockhash to check for transaction expiration.
*/
export type BlockheightBasedTransactionConfirmationStrategy =
BaseTransactionConfirmationStrategy & BlockhashWithExpiryBlockHeight;
/**
* A strategy for confirming durable nonce transactions.
*/
export type DurableNonceTransactionConfirmationStrategy =
BaseTransactionConfirmationStrategy & {
/**
* The lowest slot at which to fetch the nonce value from the
* nonce account. This should be no lower than the slot at
* which the last-known value of the nonce was fetched.
*/
minContextSlot: number;
/**
* The account where the current value of the nonce is stored.
*/
nonceAccountPubkey: PublicKey;
/**
* The nonce value that was used to sign the transaction
* for which confirmation is being sought.
*/
nonceValue: DurableNonce;
};
/**
* Properties shared by all transaction confirmation strategies
*/
export type BaseTransactionConfirmationStrategy = Readonly<{
/** A signal that, when aborted, cancels any outstanding transaction confirmation operations */
abortSignal?: AbortSignal;
signature: TransactionSignature;
}>;
/**
* This type represents all transaction confirmation strategies
*/
export type TransactionConfirmationStrategy =
| BlockheightBasedTransactionConfirmationStrategy
| DurableNonceTransactionConfirmationStrategy;
/* @internal */
function assertEndpointUrl(putativeUrl: string) {
if (/^https?:/.test(putativeUrl) === false) {
throw new TypeError('Endpoint URL must start with `http:` or `https:`.');
}
return putativeUrl;
}
/** @internal */
function extractCommitmentFromConfig<TConfig>(
commitmentOrConfig?: Commitment | ({commitment?: Commitment} & TConfig),
) {
let commitment: Commitment | undefined;
let config: Omit<TConfig, 'commitment'> | undefined;
if (typeof commitmentOrConfig === 'string') {
commitment = commitmentOrConfig;
} else if (commitmentOrConfig) {
const {commitment: specifiedCommitment, ...specifiedConfig} =
commitmentOrConfig;
commitment = specifiedCommitment;
config = specifiedConfig;
}
return {commitment, config};
}
/**
* @internal
*/
function createRpcResult<T, U>(result: Struct<T, U>) {
return union([
pick({
jsonrpc: literal('2.0'),
id: string(),
result,
}),
pick({
jsonrpc: literal('2.0'),
id: string(),
error: pick({
code: unknown(),
message: string(),
data: optional(any()),
}),
}),
]);
}
const UnknownRpcResult = createRpcResult(unknown());
/**
* @internal
*/
function jsonRpcResult<T, U>(schema: Struct<T, U>) {
return coerce(createRpcResult(schema), UnknownRpcResult, value => {
if ('error' in value) {
return value;
} else {
return {
...value,
result: create(value.result, schema),
};
}
});
}
/**
* @internal
*/
function jsonRpcResultAndContext<T, U>(value: Struct<T, U>) {
return jsonRpcResult(
pick({
context: pick({
slot: number(),
}),
value,
}),
);
}
/**
* @internal
*/
function notificationResultAndContext<T, U>(value: Struct<T, U>) {
return pick({
context: pick({
slot: number(),
}),
value,
});
}
/**
* @internal
*/
function versionedMessageFromResponse(
version: TransactionVersion | undefined,
response: MessageResponse,
): VersionedMessage {
if (version === 0) {
return new MessageV0({
header: response.header,
staticAccountKeys: response.accountKeys.map(
accountKey => new PublicKey(accountKey),
),
recentBlockhash: response.recentBlockhash,
compiledInstructions: response.instructions.map(ix => ({
programIdIndex: ix.programIdIndex,
accountKeyIndexes: ix.accounts,
data: bs58.decode(ix.data),
})),
addressTableLookups: response.addressTableLookups!,
});
} else {
return new Message(response);
}
}
/**
* The level of commitment desired when querying state
* <pre>
* 'processed': Query the most recent block which has reached 1 confirmation by the connected node
* 'confirmed': Query the most recent block which has reached 1 confirmation by the cluster
* 'finalized': Query the most recent block which has been finalized by the cluster
* </pre>
*/
export type Commitment =
| 'processed'
| 'confirmed'
| 'finalized'
| 'recent' // Deprecated as of v1.5.5
| 'single' // Deprecated as of v1.5.5
| 'singleGossip' // Deprecated as of v1.5.5
| 'root' // Deprecated as of v1.5.5
| 'max'; // Deprecated as of v1.5.5
/**
* A subset of Commitment levels, which are at least optimistically confirmed
* <pre>
* 'confirmed': Query the most recent block which has reached 1 confirmation by the cluster
* 'finalized': Query the most recent block which has been finalized by the cluster
* </pre>
*/
export type Finality = 'confirmed' | 'finalized';
/**
* Filter for largest accounts query
* <pre>
* 'circulating': Return the largest accounts that are part of the circulating supply
* 'nonCirculating': Return the largest accounts that are not part of the circulating supply
* </pre>
*/
export type LargestAccountsFilter = 'circulating' | 'nonCirculating';
/**
* Configuration object for changing `getAccountInfo` query behavior
*/
export type GetAccountInfoConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
/** Optional data slice to limit the returned account data */
dataSlice?: DataSlice;
};
/**
* Configuration object for changing `getBalance` query behavior
*/
export type GetBalanceConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* Configuration object for changing `getBlock` query behavior
*/
export type GetBlockConfig = {
/** The level of finality desired */
commitment?: Finality;
/**
* Whether to populate the rewards array. If parameter not provided, the default includes rewards.
*/
rewards?: boolean;
/**
* Level of transaction detail to return, either "full", "accounts", "signatures", or "none". If
* parameter not provided, the default detail level is "full". If "accounts" are requested,
* transaction details only include signatures and an annotated list of accounts in each
* transaction. Transaction metadata is limited to only: fee, err, pre_balances, post_balances,
* pre_token_balances, and post_token_balances.
*/
transactionDetails?: 'accounts' | 'full' | 'none' | 'signatures';
};
/**
* Configuration object for changing `getBlock` query behavior
*/
export type GetVersionedBlockConfig = {
/** The level of finality desired */
commitment?: Finality;
/** The max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned */
maxSupportedTransactionVersion?: number;
/**
* Whether to populate the rewards array. If parameter not provided, the default includes rewards.
*/
rewards?: boolean;
/**
* Level of transaction detail to return, either "full", "accounts", "signatures", or "none". If
* parameter not provided, the default detail level is "full". If "accounts" are requested,
* transaction details only include signatures and an annotated list of accounts in each
* transaction. Transaction metadata is limited to only: fee, err, pre_balances, post_balances,
* pre_token_balances, and post_token_balances.
*/
transactionDetails?: 'accounts' | 'full' | 'none' | 'signatures';
};
/**
* Configuration object for changing `getStakeMinimumDelegation` query behavior
*/
export type GetStakeMinimumDelegationConfig = {
/** The level of commitment desired */
commitment?: Commitment;
};
/**
* Configuration object for changing `getBlockHeight` query behavior
*/
export type GetBlockHeightConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* Configuration object for changing `getEpochInfo` query behavior
*/
export type GetEpochInfoConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* Configuration object for changing `getInflationReward` query behavior
*/
export type GetInflationRewardConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** An epoch for which the reward occurs. If omitted, the previous epoch will be used */
epoch?: number;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* Configuration object for changing `getLatestBlockhash` query behavior
*/
export type GetLatestBlockhashConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* Configuration object for changing `getSlot` query behavior
*/
export type GetSlotConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* Configuration object for changing `getSlotLeader` query behavior
*/
export type GetSlotLeaderConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** The minimum slot that the request can be evaluated at */
minContextSlot?: number;
};
/**
* Configuration object for changing `getTransaction` query behavior
*/
export type GetTransactionConfig = {
/** The level of finality desired */
commitment?: Finality;
};
/**
* Configuration object for changing `getTransaction` query behavior
*/
export type GetVersionedTransactionConfig = {
/** The level of finality desired */
commitment?: Finality;
/** The max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned */
maxSupportedTransactionVersion?: number;
};
/**
* Configuration object for changing `getLargestAccounts` query behavior
*/
export type GetLargestAccountsConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** Filter largest accounts by whether they are part of the circulating supply */
filter?: LargestAccountsFilter;
};
/**
* Configuration object for changing `getSupply` request behavior
*/
export type GetSupplyConfig = {
/** The level of commitment desired */
commitment?: Commitment;
/** Exclude non circulating accounts list from response */
excludeNonCirculatingAccountsList?: boolean;
};
/**
* Configuration object for changing query behavior
*/
export type SignatureStatusConfig = {
/** enable searching status history, not needed for recent transactions */
searchTransactionHistory: boolean;
};
/**
* Information describing a cluster node
*/
export type ContactInfo = {
/** Identity public key of the node */
pubkey: string;
/** Gossip network address for the node */
gossip: string | null;
/** TPU network address for the node (null if not available) */
tpu: string | null;
/** JSON RPC network address for the node (null if not available) */
rpc: string | null;
/** Software version of the node (null if not available) */
version: string | null;
};
/**
* Information describing a vote account
*/
export type VoteAccountInfo = {
/** Public key of the vote account */
votePubkey: string;
/** Identity public key of the node voting with this account */
nodePubkey: string;
/** The stake, in daltons, delegated to this vote account and activated */
activatedStake: number;
/** Whether the vote account is staked for this epoch */
epochVoteAccount: boolean;
/** Recent epoch voting credit history for this voter */
epochCredits: Array<[number, number, number]>;
/** A percentage (0-100) of rewards payout owed to the voter */
commission: number;
/** Most recent slot voted on by this vote account */
lastVote: number;
};
/**
* A collection of cluster vote accounts
*/
export type VoteAccountStatus = {
/** Active vote accounts */
current: Array<VoteAccountInfo>;
/** Inactive vote accounts */
delinquent: Array<VoteAccountInfo>;
};
/**
* Network Inflation
* (see https://docs.bbachain.com/implemented-proposals/ed_overview)
*/
export type InflationGovernor = {
foundation: number;
foundationTerm: number;
initial: number;
taper: number;
terminal: number;
};
const GetInflationGovernorResult = pick({
foundation: number(),
foundationTerm: number(),
initial: number(),
taper: number(),
terminal: number(),
});
/**
* The inflation reward for an epoch
*/
export type InflationReward = {
/** epoch for which the reward occurs */
epoch: number;
/** the slot in which the rewards are effective */
effectiveSlot: number;
/** reward amount in daltons */
amount: number;
/** post balance of the account in daltons */
postBalance: number;
/** vote account commission when the reward was credited */
commission?: number | null;
};
/**
* Expected JSON RPC response for the "getInflationReward" message
*/
const GetInflationRewardResult = jsonRpcResult(
array(
nullable(
pick({
epoch: number(),
effectiveSlot: number(),
amount: number(),
postBalance: number(),
commission: optional(nullable(number())),
}),
),
),
);
export type InflationRate = {
/** total inflation */
total: number;
/** inflation allocated to validators */
validator: number;
/** inflation allocated to the foundation */
foundation: number;
/** epoch for which these values are valid */
epoch: number;
};
/**
* Expected JSON RPC response for the "getInflationRate" message
*/
const GetInflationRateResult = pick({
total: number(),
validator: number(),
foundation: number(),
epoch: number(),
});
/**
* Information about the current epoch
*/
export type EpochInfo = {
epoch: number;
slotIndex: number;
slotsInEpoch: number;
absoluteSlot: number;
blockHeight?: number;
transactionCount?: number;
};
const GetEpochInfoResult = pick({
epoch: number(),
slotIndex: number(),
slotsInEpoch: number(),
absoluteSlot: number(),
blockHeight: optional(number()),
transactionCount: optional(number()),
});
const GetEpochScheduleResult = pick({
slotsPerEpoch: number(),
leaderScheduleSlotOffset: number(),
warmup: boolean(),
firstNormalEpoch: number(),
firstNormalSlot: number(),
});
/**
* Leader schedule
* (see https://docs.bbachain.com/terminology#leader-schedule)
*/
export type LeaderSchedule = {
[address: string]: number[];
};
const GetLeaderScheduleResult = record(string(), array(number()));
/**
* Transaction error or null
*/
const TransactionErrorResult = nullable(union([pick({}), string()]));
/**
* Signature status for a transaction
*/
const SignatureStatusResult = pick({
err: TransactionErrorResult,
});
/**
* Transaction signature received notification
*/
const SignatureReceivedResult = literal('receivedSignature');
/**
* Version info for a node
*/
export type Version = {
/** Version of solana-core */
'solana-core': string;
'feature-set'?: number;
};
const VersionResult = pick({
'solana-core': string(),
'feature-set': optional(number()),
});
export type SimulatedTransactionAccountInfo = {
/** `true` if this account's data contains a loaded program */
executable: boolean;
/** Identifier of the program that owns the account */
owner: string;
/** Number of daltons assigned to the account */
daltons: number;
/** Optional data assigned to the account */
data: string[];
/** Optional rent epoch info for account */
rentEpoch?: number;
};
export type TransactionReturnDataEncoding = 'base64';
export type TransactionReturnData = {
programId: string;
data: [string, TransactionReturnDataEncoding];
};
export type SimulateTransactionConfig = {
/** Optional parameter used to enable signature verification before simulation */
sigVerify?: boolean;
/** Optional parameter used to replace the simulated transaction's recent blockhash with the latest blockhash */
replaceRecentBlockhash?: boolean;
/** Optional parameter used to set the commitment level when selecting the latest block */
commitment?: Commitment;
/** Optional parameter used to specify a list of account addresses to return post simulation state for */
accounts?: {
encoding: 'base64';
addresses: string[];
};
/** Optional parameter used to specify the minimum block slot that can be used for simulation */
minContextSlot?: number;
};
export type SimulatedTransactionResponse = {
err: TransactionError | string | null;
logs: Array<string> | null;
accounts?: (SimulatedTransactionAccountInfo | null)[] | null;
unitsConsumed?: number;
returnData?: TransactionReturnData | null;
};
const SimulatedTransactionResponseStruct = jsonRpcResultAndContext(
pick({
err: nullable(union([pick({}), string()])),
logs: nullable(array(string())),
accounts: optional(
nullable(
array(
nullable(
pick({
executable: boolean(),
owner: string(),
daltons: number(),
data: array(string()),
rentEpoch: optional(number()),
}),
),
),
),
),
unitsConsumed: optional(number()),
returnData: optional(
nullable(
pick({
programId: string(),
data: tuple([string(), literal('base64')]),
}),
),
),
}),
);
export type ParsedInnerInstruction = {
index: number;
instructions: (ParsedInstruction | PartiallyDecodedInstruction)[];
};
export type TokenBalance = {
accountIndex: number;
mint: string;
owner?: string;
uiTokenAmount: TokenAmount;
};
/**
* Metadata for a parsed confirmed transaction on the ledger
*
* @deprecated Deprecated since BBAChain v1.8.0. Please use {@link ParsedTransactionMeta} instead.
*/
export type ParsedConfirmedTransactionMeta = ParsedTransactionMeta;
/**
* Collection of addresses loaded by a transaction using address table lookups
*/
export type LoadedAddresses = {
writable: Array<PublicKey>;
readonly: Array<PublicKey>;
};
/**
* Metadata for a parsed transaction on the ledger
*/
export type ParsedTransactionMeta = {
/** The fee charged for processing the transaction */
fee: number;
/** An array of cross program invoked parsed instructions */
innerInstructions?: ParsedInnerInstruction[] | null;
/** The balances of the transaction accounts before processing */
preBalances: Array<number>;
/** The balances of the transaction accounts after processing */
postBalances: Array<number>;
/** An array of program log messages emitted during a transaction */
logMessages?: Array<string> | null;
/** The token balances of the transaction accounts before processing */
preTokenBalances?: Array<TokenBalance> | null;
/** The token balances of the transaction accounts after processing */
postTokenBalances?: Array<TokenBalance> | null;
/** The error result of transaction processing */
err: TransactionError | null;
/** The collection of addresses loaded using address lookup tables */
loadedAddresses?: LoadedAddresses;
/** The compute units consumed after processing the transaction */
computeUnitsConsumed?: number;
};
export type CompiledInnerInstruction = {
index: number;
instructions: CompiledInstruction[];
};
/**
* Metadata for a confirmed transaction on the ledger
*/
export type ConfirmedTransactionMeta = {
/** The fee charged for processing the transaction */
fee: number;
/** An array of cross program invoked instructions */
innerInstructions?: CompiledInnerInstruction[] | null;
/** The balances of the transaction accounts before processing */
preBalances: Array<number>;
/** The balances of the transaction accounts after processing */
postBalances: Array<number>;
/** An array of program log messages emitted during a transaction */
logMessages?: Array<string> | null;
/** The token balances of the transaction accounts before processing */
preTokenBalances?: Array<TokenBalance> | null;
/** The token balances of the transaction accounts after processing */
postTokenBalances?: Array<TokenBalance> | null;
/** The error result of transaction processing */
err: TransactionError | null;
/** The collection of addresses loaded using address lookup tables */
loadedAddresses?: LoadedAddresses;
/** The compute units consumed after processing the transaction */
computeUnitsConsumed?: number;
};
/**
* A processed transaction from the RPC API
*/
export type TransactionResponse = {
/** The slot during which the transaction was processed */
slot: number;
/** The transaction */
transaction: {
/** The transaction message */
message: Message;
/** The transaction signatures */
signatures: string[];
};
/** Metadata produced from the transaction */
meta: ConfirmedTransactionMeta | null;
/** The unix timestamp of when the transaction was processed */
blockTime?: number | null;
};
/**
* A processed transaction from the RPC API
*/
export type VersionedTransactionResponse = {
/** The slot during which the transaction was processed */
slot: number;
/** The transaction */
transaction: {
/** The transaction message */
message: VersionedMessage;
/** The transaction signatures */
signatures: string[];
};
/** Metadata produced from the transaction */
meta: ConfirmedTransactionMeta | null;
/** The unix timestamp of when the transaction was processed */
blockTime?: number | null;
/** The transaction version */
version?: TransactionVersion;
};
/**
* A processed transaction message from the RPC API
*/
type MessageResponse = {
accountKeys: string[];
header: MessageHeader;
instructions: CompiledInstruction[];
recentBlockhash: string;
addressTableLookups?: ParsedAddressTableLookup[];
};
/**
* A confirmed transaction on the ledger
*
* @deprecated Deprecated since BBAChain v1.8.0.
*/
export type ConfirmedTransaction = {
/** The slot during which the transaction was processed */
slot: number;
/** The details of the transaction */
transaction: Transaction;
/** Metadata produced from the transaction */
meta: ConfirmedTransactionMeta | null;
/** The unix timestamp of when the transaction was processed */
blockTime?: number | null;
};
/**
* A partially decoded transaction instruction
*/
export type PartiallyDecodedInstruction = {
/** Program id called by this instruction */
programId: PublicKey;
/** Public keys of accounts passed to this instruction */
accounts: Array<PublicKey>;
/** Raw base-58 instruction data */
data: string;
};
/**
* A parsed transaction message account
*/
export type ParsedMessageAccount = {
/** Public key of the account */
pubkey: PublicKey;
/** Indicates if the account signed the transaction */
signer: boolean;
/** Indicates if the account is writable for this transaction */
writable: boolean;
/** Indicates if the account key came from the transaction or a lookup table */
source?: 'transaction' | 'lookupTable';
};
/**
* A parsed transaction instruction
*/
export type ParsedInstruction = {
/** Name of the program for this instruction */
program: string;
/** ID of the program for this instruction */
programId: PublicKey;
/** Parsed instruction info */
parsed: any;
};
/**
* A parsed address table lookup
*/
export type ParsedAddressTableLookup = {
/** Address lookup table account key */
accountKey: PublicKey;
/** Parsed instruction info */
writableIndexes: number[];
/** Parsed instruction info */
readonlyIndexes: number[];
};
/**
* A parsed transaction message
*/
export type ParsedMessage = {
/** Accounts used in the instructions */
accountKeys: ParsedMessageAccount[];
/** The atomically executed instructions for the transaction */
instructions: (ParsedInstruction | PartiallyDecodedInstruction)[];
/** Recent blockhash */
recentBlockhash: string;
/** Address table lookups used to load additional accounts */
addressTableLookups?: ParsedAddressTableLookup[] | null;
};
/**
* A parsed transaction
*/
export type ParsedTransaction = {
/** Signatures for the transaction */
signatures: Array<string>;
/** Message of the transaction */
message: ParsedMessage;
};
/**
* A parsed and confirmed transaction on the ledger
*
* @deprecated Deprecated since BBAChain v1.8.0. Please use {@link ParsedTransactionWithMeta} instead.
*/
export type ParsedConfirmedTransaction = ParsedTransactionWithMeta;
/**
* A parsed transaction on the ledger with meta
*/
export type ParsedTransactionWithMeta = {
/** The slot during which the transaction was processed */
slot: number;
/** The details of the transaction */
transaction: ParsedTransaction;
/** Metadata produced from the transaction */
meta: ParsedTransactionMeta | null;
/** The unix timestamp of when the transaction was processed */
blockTime?: number | null;
/** The version of the transaction message */
version?: TransactionVersion;
};
/**
* A processed block fetched from the RPC API
*/
export type BlockResponse = {
/** Blockhash of this block */
blockhash: Blockhash;
/** Blockhash of this block's parent */
previousBlockhash: Blockhash;
/** Slot index of this block's parent */
parentSlot: number;
/** Vector of transactions with status meta and original message */
transactions: Array<{
/** The transaction */
transaction: {
/** The transaction message */
message: Message;
/** The transaction signatures */
signatures: string[];
};
/** Metadata produced from the transaction */
meta: ConfirmedTransactionMeta | null;
/** The transaction version */
version?: TransactionVersion;
}>;
/** Vector of block rewards */
rewards?: Array<{
/** Public key of reward recipient */
pubkey: string;
/** Reward value in daltons */
daltons: number;
/** Account balance after reward is applied */
postBalance: number | null;
/** Type of reward received */
rewardType: string | null;
/** Vote account commission when the reward was credited, only present for voting and staking rewards */
commission?: number | null;
}>;
/** The unix timestamp of when the block was processed */
blockTime: number | null;
};
/**
* A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts`
*/
export type AccountsModeBlockResponse = VersionedAccountsModeBlockResponse;
/**
* A processed block fetched from the RPC API where the `transactionDetails` mode is `none`
*/
export type NoneModeBlockResponse = VersionedNoneModeBlockResponse;
/**
* A block with parsed transactions
*/
export type ParsedBlockResponse = {
/** Blockhash of this block */
blockhash: Blockhash;
/** Blockhash of this block's parent */
previousBlockhash: Blockhash;
/** Slot index of this block's parent */
parentSlot: number;
/** Vector of transactions with status meta and original message */
transactions: Array<{
/** The details of the transaction */
transaction: ParsedTransaction;
/** Metadata produced from the transaction */
meta: ParsedTransactionMeta | null;
/** The transaction version */
version?: TransactionVersion;
}>;
/** Vector of block rewards */
rewards?: Array<{
/** Public key of reward recipient */
pubkey: string;
/** Reward value in daltons */
daltons: number;
/** Account balance after reward is applied */
postBalance: number | null;
/** Type of reward received */
rewardType: string | null;
/** Vote account commission when the reward was credited, only present for voting and staking rewards */
commission?: number | null;
}>;
/** The unix timestamp of when the block was processed */
blockTime: number | null;
/** The number of blocks beneath this block */
blockHeight: number | null;
};
/**
* A block with parsed transactions where the `transactionDetails` mode is `accounts`
*/
export type ParsedAccountsModeBlockResponse = Omit<
ParsedBlockResponse,
'transactions'
> & {
transactions: Array<
Omit<ParsedBlockResponse['transactions'][number], 'transaction'> & {
transaction: Pick<
ParsedBlockResponse['transactions'][number]['transaction'],
'signatures'
> & {
accountKeys: ParsedMessageAccount[];
};
}
>;
};
/**
* A block with parsed transactions where the `transactionDetails` mode is `none`
*/
export type ParsedNoneModeBlockResponse = Omit<
ParsedBlockResponse,
'transactions'
>;
/**
* A processed block fetched from the RPC API
*/
export type VersionedBlockResponse = {
/** Blockhash of this block */
blockhash: Blockhash;
/** Blockhash of this block's parent */
previousBlockhash: Blockhash;
/** Slot index of this block's parent */
parentSlot: number;
/** Vector of transactions with status meta and original message */
transactions: Array<{
/** The transaction */
transaction: {
/** The transaction message */
message: VersionedMessage;
/** The transaction signatures */
signatures: string[];
};
/** Metadata produced from the transaction */
meta: ConfirmedTransactionMeta | null;
/** The transaction version */
version?: TransactionVersion;
}>;
/** Vector of block rewards */
rewards?: Array<{
/** Public key of reward recipient */
pubkey: string;
/** Reward value in daltons */
daltons: number;
/** Account balance after reward is applied */
postBalance: number | null;
/** Type of reward received */
rewardType: string | null;
/** Vote account commission when the reward was credited, only present for voting and staking rewards */
commission?: number | null;
}>;
/** The unix timestamp of when the block was processed */
blockTime: number | null;
};
/**
* A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts`
*/
export type VersionedAccountsModeBlockResponse = Omit<
VersionedBlockResponse,
'transactions'
> & {
transactions: Array<
Omit<VersionedBlockResponse['transactions'][number], 'transaction'> & {
transaction: Pick<
VersionedBlockResponse['transactions'][number]['transaction'],
'signatures'
> & {
accountKeys: ParsedMessageAccount[];
};
}
>;
};
/**
* A processed block fetched from the RPC API where the `transactionDetails` mode is `none`
*/
export type VersionedNoneModeBlockResponse = Omit<
VersionedBlockResponse,
'transactions'
>;
/**
* A confirmed block on the ledger
*
* @deprecated Deprecated since BBAChain v1.8.0.
*/
export type ConfirmedBlock = {
/** Blockhash of this block */
blockhash: Blockhash;
/** Blockhash of this block's parent */
previousBlockhash: Blockhash;
/** Slot index of this block's parent */
parentSlot: number;
/** Vector of transactions and status metas */
transactions: Array<{
transaction: Transaction;
meta: ConfirmedTransactionMeta | null;
}>;
/** Vector of block rewards */
rewards?: Array<{
pubkey: string;
daltons: number;
postBalance: number | null;
rewardType: string | null;
commission?: number | null;
}>;
/** The unix timestamp of when the block was processed */
blockTime: number | null;
};
/**
* A Block on the ledger with signatures only
*/
export type BlockSignatures = {
/** Blockhash of this block */
blockhash: Blockhash;
/** Blockhash of this block's parent */
previousBlockhash: Blockhash;
/** Slot index of this block's parent */
parentSlot: number;
/** Vector of signatures */
signatures: Array<string>;
/** The unix timestamp of when the block was processed */
blockTime: number | null;
};
/**
* recent block production information
*/
export type BlockProduction = Readonly<{
/** 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 */
byIdentity: Readonly<Record<string, ReadonlyArray<number>>>;
/** Block production slot range */
range: Readonly<{
/** first slot of the block production information (inclusive) */
firstSlot: number;
/** last slot of block production information (inclusive) */
lastSlot: number;
}>;
}>;
export type GetBlockProductionConfig = {
/** Optional commitment level */
commitment?: Commitment;
/** Slot range to return block production for. If parameter not provided, defaults to current epoch. */
range?: {
/** first slot to return block production information for (inclusive) */
firstSlot: number;
/** last slot to return block production information for (inclusive). If parameter not provided, defaults to the highest slot */
lastSlot?: number;
};
/** Only return results for this validator identity (base-58 encoded) */
identity?: string;
};
/**
* Expected JSON RPC response for the "getBlockProduction" message
*/
const BlockProductionResponseStruct = jsonRpcResultAndContext(
pick({
byIdentity: record(string(), array(number())),
range: pick({
firstSlot: number(),
lastSlot: number(),
}),
}),
);
/**
* A performance sample
*/
export type PerfSample = {
/** Slot number of sample */
slot: number;
/** Number of transactions in a sample window */
numTransactions: number;
/** Number of slots in a sample window */
numSlots: number;
/** Sample window in seconds */
samplePeriodSecs: number;
};
function createRpcClient(
url: string,
httpHeaders?: HttpHeaders,
customFetch?: FetchFn,
fetchMiddleware?: FetchMiddleware,
disableRetryOnRateLimit?: boolean,
httpAgent?: NodeHttpAgent | NodeHttpsAgent | false,
): RpcClient {
const fetch = customFetch ? customFetch : fetchImpl;
let agent: NodeHttpAgent | NodeHttpsAgent | undefined;
if (process.env.BROWSER) {
if (httpAgent != null) {
console.warn(
'You have supplied an `httpAgent` when creating a `Connection` in a browser environment.' +
'It has been ignored; `httpAgent` is only used in Node environments.',
);
}
} else {
if (httpAgent == null) {
if (process.env.NODE_ENV !== 'test') {
const agentOptions = {
// One second fewer than the BBAChain RPC's keepalive timeout.
// Read more: https://github.com/solana-labs/solana/issues/27859#issuecomment-1340097889
freeSocketTimeout: 19000,
keepAlive: true,
maxSockets: 25,
};
if (url.startsWith('https:')) {
agent = new HttpsKeepAliveAgent(agentOptions);
} else {
agent = new HttpKeepAliveAgent(agentOptions);
}
}
} else {
if (httpAgent !== false) {
const isHttps = url.startsWith('https:');
if (isHttps && !(httpAgent instanceof NodeHttpsAgent)) {
throw new Error(
'The endpoint `' +
url +
'` can only be paired with an `https.Agent`. You have, instead, supplied an ' +
'`http.Agent` through `httpAgent`.',
);
} else if (!isHttps && httpAgent instanceof NodeHttpsAgent) {
throw new Error(
'The endpoint `' +
url +
'` can only be paired with an `http.Agent`. You have, instead, supplied an ' +
'`https.Agent` through `httpAgent`.',
);
}
agent = httpAgent;
}
}
}
let fetchWithMiddleware: FetchFn | undefined;
if (fetchMiddleware) {
fetchWithMiddleware = async (info, init) => {
const modifiedFetchArgs = await new Promise<Parameters<FetchFn>>(
(resolve, reject) => {
try {
fetchMiddleware(info, init, (modifiedInfo, modifiedInit) =>
resolve([modifiedInfo, modifiedInit]),
);
} catch (error) {
reject(error);
}
},
);
return await fetch(...modifiedFetchArgs);
};
}
const clientBrowser = new RpcClient(async (request, callback) => {
const options = {
method: 'POST',
body: request,
agent,
headers: Object.assign(
{
'Content-Type': 'application/json',
},
httpHeaders || {},
COMMON_HTTP_HEADERS,
),
};
try {
let too_many_requests_retries = 5;
let res: Response;
let waitTime = 500;
for (;;) {
if (fetchWithMiddleware) {
res = await fetchWithMiddleware(url, options);
} else {
res = await fetch(url, options);
}
if (res.status !== 429 /* Too many requests */) {
break;
}
if (disableRetryOnRateLimit === true) {
break;
}
too_many_requests_retries -= 1;
if (too_many_requests_retries === 0) {
break;
}
console.log(
`Server responded with ${res.status} ${res.statusText}. Retrying after ${waitTime}ms delay...`,
);
await sleep(waitTime);
waitTime *= 2;
}
const text = await res.text();
if (res.ok) {
callback(null, text);
} else {
callback(new Error(`${res.status} ${res.statusText}: ${text}`));
}
} catch (err) {
if (err instanceof Error) callback(err);
}
}, {});
return clientBrowser;
}
function createRpcRequest(client: RpcClient): RpcRequest {
return (method, args) => {
return new Promise((resolve, reject) => {
client.request(method, args, (err: any, response: any) => {
if (err) {
reject(err);
return;
}
resolve(response);
});
});
};
}
function createRpcBatchRequest(client: RpcClient): RpcBatchRequest {
return (requests: RpcParams[]) => {
return new Promise((resolve, reject) => {
// Do nothing if requests is empty
if (requests.length === 0) resolve([]);
const batch = requests.map((params: RpcParams) => {
return client.request(params.methodName, params.args);
});
client.request(batch, (err: any, response: any) => {
if (err) {
reject(err);
return;
}
resolve(response);
});
});
};
}
/**
* Expected JSON RPC response for the "getInflationGovernor" message
*/
const GetInflationGovernorRpcResult = jsonRpcResult(GetInflationGovernorResult);
/**
* Expected JSON RPC response for the "getInflationRate" message
*/
const GetInflationRateRpcResult = jsonRpcResult(GetInflationRateResult);
/**
* Expected JSON RPC response for the "getEpochInfo" message
*/
const GetEpochInfoRpcResult = jsonRpcResult(GetEpochInfoResult);
/**
* Expected JSON RPC response for the "getEpochSchedule" message
*/
const GetEpochScheduleRpcResult = jsonRpcResult(GetEpochScheduleResult);
/**
* Expected JSON RPC response for the "getLeaderSchedule" message
*/
const GetLeaderScheduleRpcResult = jsonRpcResult(GetLeaderScheduleResult);
/**
* Expected JSON RPC response for the "minimumLedgerSlot" and "getFirstAvailableBlock" messages
*/
const SlotRpcResult = jsonRpcResult(number());
/**
* Supply
*/
export type Supply = {
/** Total supply in daltons */
total: number;
/** Circulating supply in daltons */
circulating: number;
/** Non-circulating supply in daltons */
nonCirculating: number;
/** List of non-circulating account addresses */
nonCirculatingAccounts: Array<PublicKey>;
};
/**
* Expected JSON RPC response for the "getSupply" message
*/
const GetSupplyRpcResult = jsonRpcResultAndContext(
pick({
total: number(),
circulating: number(),
nonCirculating: number(),
nonCirculatingAccounts: array(PublicKeyFromString),
}),
);
/**
* Token amount object which returns a token amount in different formats
* for various client use cases.
*/
export type TokenAmount = {
/** Raw amount of tokens as string ignoring decimals */
amount: string;
/** Number of decimals configured for token's mint */
decimals: number;
/** Token amount as float, accounts for decimals */
uiAmount: number | null;
/** Token amount as string, accounts for decimals */
uiAmountString?: string;
};
/**
* Expected JSON RPC structure for token amounts
*/
const TokenAmountResult = pick({
amount: