diff --git a/web3.js/src/connection.ts b/web3.js/src/connection.ts index 50fc8c22016af4..0ae37b094df2a0 100644 --- a/web3.js/src/connection.ts +++ b/web3.js/src/connection.ts @@ -228,6 +228,8 @@ export type SendOptions = { 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; }; /** @@ -242,6 +244,8 @@ export type ConfirmOptions = { 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; }; /** @@ -272,6 +276,8 @@ export type SignaturesForAddressOptions = { 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; }; /** @@ -297,6 +303,23 @@ export type BlockheightBasedTransactionConfirmationStrategy = { signature: TransactionSignature; } & BlockhashWithExpiryBlockHeight; +/** @internal */ +function extractCommitmentFromConfig( + commitmentOrConfig?: Commitment | ({commitment?: Commitment} & TConfig), +) { + let commitment: Commitment | undefined; + let config: Omit | undefined; + if (typeof commitmentOrConfig === 'string') { + commitment = commitmentOrConfig; + } else if (commitmentOrConfig) { + const {commitment: specifiedCommitment, ...specifiedConfig} = + commitmentOrConfig; + commitment = specifiedCommitment; + config = specifiedConfig; + } + return {commitment, config}; +} + /** * @internal */ @@ -399,6 +422,88 @@ export type Finality = 'confirmed' | 'finalized'; */ 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; +}; + +/** + * 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 `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 `getLargestAccounts` query behavior */ @@ -1948,6 +2053,8 @@ export type GetProgramAccountsConfig = { dataSlice?: DataSlice; /** Optional array of filters to apply to accounts */ filters?: GetProgramAccountsFilter[]; + /** The minimum slot that the request can be evaluated at */ + minContextSlot?: number; }; /** @@ -1958,6 +2065,8 @@ export type GetParsedProgramAccountsConfig = { commitment?: Commitment; /** Optional array of filters to apply to accounts */ filters?: GetProgramAccountsFilter[]; + /** The minimum slot that the request can be evaluated at */ + minContextSlot?: number; }; /** @@ -1966,8 +2075,40 @@ export type GetParsedProgramAccountsConfig = { export type GetMultipleAccountsConfig = { /** Optional commitment level */ commitment?: Commitment; - /** Optional encoding for account data (default base64) */ - encoding?: 'base64' | 'jsonParsed'; + /** The minimum slot that the request can be evaluated at */ + minContextSlot?: number; +}; + +/** + * Configuration object for `getStakeActivation` + */ +export type GetStakeActivationConfig = { + /** Optional commitment level */ + commitment?: Commitment; + /** Epoch for which to calculate activation details. If parameter not provided, defaults to current epoch */ + epoch?: number; + /** The minimum slot that the request can be evaluated at */ + minContextSlot?: number; +}; + +/** + * Configuration object for `getStakeActivation` + */ +export type GetTokenAccountsByOwnerConfig = { + /** Optional commitment level */ + commitment?: Commitment; + /** The minimum slot that the request can be evaluated at */ + minContextSlot?: number; +}; + +/** + * Configuration object for `getStakeActivation` + */ +export type GetTransactionCountConfig = { + /** Optional commitment level */ + commitment?: Commitment; + /** The minimum slot that the request can be evaluated at */ + minContextSlot?: number; }; /** @@ -2366,9 +2507,17 @@ export class Connection { */ async getBalanceAndContext( publicKey: PublicKey, - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetBalanceConfig, ): Promise> { - const args = this._buildArgs([publicKey.toBase58()], commitment); + /** @internal */ + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs( + [publicKey.toBase58()], + commitment, + undefined /* encoding */, + config, + ); const unsafeRes = await this._rpcRequest('getBalance', args); const res = create(unsafeRes, jsonRpcResultAndContext(number())); if ('error' in res) { @@ -2387,9 +2536,9 @@ export class Connection { */ async getBalance( publicKey: PublicKey, - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetBalanceConfig, ): Promise { - return await this.getBalanceAndContext(publicKey, commitment) + return await this.getBalanceAndContext(publicKey, commitmentOrConfig) .then(x => x.value) .catch(e => { throw new Error( @@ -2511,12 +2660,14 @@ export class Connection { async getTokenAccountsByOwner( ownerAddress: PublicKey, filter: TokenAccountsFilter, - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetTokenAccountsByOwnerConfig, ): Promise< RpcResponseAndContext< Array<{pubkey: PublicKey; account: AccountInfo}> > > { + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); let _args: any[] = [ownerAddress.toBase58()]; if ('mint' in filter) { _args.push({mint: filter.mint.toBase58()}); @@ -2524,7 +2675,7 @@ export class Connection { _args.push({programId: filter.programId.toBase58()}); } - const args = this._buildArgs(_args, commitment, 'base64'); + const args = this._buildArgs(_args, commitment, 'base64', config); const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args); const res = create(unsafeRes, GetTokenAccountsByOwner); if ('error' in res) { @@ -2616,9 +2767,16 @@ export class Connection { */ async getAccountInfoAndContext( publicKey: PublicKey, - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetAccountInfoConfig, ): Promise | null>> { - const args = this._buildArgs([publicKey.toBase58()], commitment, 'base64'); + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs( + [publicKey.toBase58()], + commitment, + 'base64', + config, + ); const unsafeRes = await this._rpcRequest('getAccountInfo', args); const res = create( unsafeRes, @@ -2670,10 +2828,13 @@ export class Connection { */ async getAccountInfo( publicKey: PublicKey, - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetAccountInfoConfig, ): Promise | null> { try { - const res = await this.getAccountInfoAndContext(publicKey, commitment); + const res = await this.getAccountInfoAndContext( + publicKey, + commitmentOrConfig, + ); return res.value; } catch (e) { throw new Error( @@ -2687,10 +2848,12 @@ export class Connection { */ async getMultipleAccountsInfoAndContext( publicKeys: PublicKey[], - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetMultipleAccountsConfig, ): Promise | null)[]>> { + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); const keys = publicKeys.map(key => key.toBase58()); - const args = this._buildArgs([keys], commitment, 'base64'); + const args = this._buildArgs([keys], commitment, 'base64', config); const unsafeRes = await this._rpcRequest('getMultipleAccounts', args); const res = create( unsafeRes, @@ -2709,11 +2872,11 @@ export class Connection { */ async getMultipleAccountsInfo( publicKeys: PublicKey[], - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetMultipleAccountsConfig, ): Promise<(AccountInfo | null)[]> { const res = await this.getMultipleAccountsInfoAndContext( publicKeys, - commitment, + commitmentOrConfig, ); return res.value; } @@ -2723,14 +2886,19 @@ export class Connection { */ async getStakeActivation( publicKey: PublicKey, - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetStakeActivationConfig, epoch?: number, ): Promise { + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); const args = this._buildArgs( [publicKey.toBase58()], commitment, - undefined, - epoch !== undefined ? {epoch} : undefined, + undefined /* encoding */, + { + ...config, + epoch: epoch != null ? epoch : config?.epoch, + }, ); const unsafeRes = await this._rpcRequest('getStakeActivation', args); @@ -2754,31 +2922,14 @@ export class Connection { programId: PublicKey, configOrCommitment?: GetProgramAccountsConfig | Commitment, ): Promise}>> { - const extra: Pick = {}; - - let commitment; - let encoding; - if (configOrCommitment) { - if (typeof configOrCommitment === 'string') { - commitment = configOrCommitment; - } else { - commitment = configOrCommitment.commitment; - encoding = configOrCommitment.encoding; - - if (configOrCommitment.dataSlice) { - extra.dataSlice = configOrCommitment.dataSlice; - } - if (configOrCommitment.filters) { - extra.filters = configOrCommitment.filters; - } - } - } - + const {commitment, config} = + extractCommitmentFromConfig(configOrCommitment); + const {encoding, ...configWithoutEncoding} = config || {}; const args = this._buildArgs( [programId.toBase58()], commitment, encoding || 'base64', - extra, + configWithoutEncoding, ); const unsafeRes = await this._rpcRequest('getProgramAccounts', args); const res = create(unsafeRes, jsonRpcResult(array(KeyedAccountInfoResult))); @@ -2807,26 +2958,13 @@ export class Connection { account: AccountInfo; }> > { - const extra: Pick = {}; - - let commitment; - if (configOrCommitment) { - if (typeof configOrCommitment === 'string') { - commitment = configOrCommitment; - } else { - commitment = configOrCommitment.commitment; - - if (configOrCommitment.filters) { - extra.filters = configOrCommitment.filters; - } - } - } - + const {commitment, config} = + extractCommitmentFromConfig(configOrCommitment); const args = this._buildArgs( [programId.toBase58()], commitment, 'jsonParsed', - extra, + config, ); const unsafeRes = await this._rpcRequest('getProgramAccounts', args); const res = create( @@ -3014,8 +3152,17 @@ export class Connection { /** * Fetch the current slot that the node is processing */ - async getSlot(commitment?: Commitment): Promise { - const args = this._buildArgs([], commitment); + async getSlot( + commitmentOrConfig?: Commitment | GetSlotConfig, + ): Promise { + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs( + [], + commitment, + undefined /* encoding */, + config, + ); const unsafeRes = await this._rpcRequest('getSlot', args); const res = create(unsafeRes, jsonRpcResult(number())); if ('error' in res) { @@ -3027,8 +3174,17 @@ export class Connection { /** * Fetch the current slot leader of the cluster */ - async getSlotLeader(commitment?: Commitment): Promise { - const args = this._buildArgs([], commitment); + async getSlotLeader( + commitmentOrConfig?: Commitment | GetSlotLeaderConfig, + ): Promise { + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs( + [], + commitment, + undefined /* encoding */, + config, + ); const unsafeRes = await this._rpcRequest('getSlotLeader', args); const res = create(unsafeRes, jsonRpcResult(string())); if ('error' in res) { @@ -3094,8 +3250,17 @@ export class Connection { /** * Fetch the current transaction count of the cluster */ - async getTransactionCount(commitment?: Commitment): Promise { - const args = this._buildArgs([], commitment); + async getTransactionCount( + commitmentOrConfig?: Commitment | GetTransactionCountConfig, + ): Promise { + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs( + [], + commitment, + undefined /* encoding */, + config, + ); const unsafeRes = await this._rpcRequest('getTransactionCount', args); const res = create(unsafeRes, jsonRpcResult(number())); if ('error' in res) { @@ -3138,14 +3303,17 @@ export class Connection { async getInflationReward( addresses: PublicKey[], epoch?: number, - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetInflationRewardConfig, ): Promise<(InflationReward | null)[]> { + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); const args = this._buildArgs( [addresses.map(pubkey => pubkey.toBase58())], commitment, - undefined, + undefined /* encoding */, { - epoch, + ...config, + epoch: epoch != null ? epoch : config?.epoch, }, ); const unsafeRes = await this._rpcRequest('getInflationReward', args); @@ -3159,8 +3327,17 @@ export class Connection { /** * Fetch the Epoch Info parameters */ - async getEpochInfo(commitment?: Commitment): Promise { - const args = this._buildArgs([], commitment); + async getEpochInfo( + commitmentOrConfig?: Commitment | GetEpochInfoConfig, + ): Promise { + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs( + [], + commitment, + undefined /* encoding */, + config, + ); const unsafeRes = await this._rpcRequest('getEpochInfo', args); const res = create(unsafeRes, GetEpochInfoRpcResult); if ('error' in res) { @@ -3333,10 +3510,10 @@ export class Connection { * @return {Promise} */ async getLatestBlockhash( - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetLatestBlockhashConfig, ): Promise { try { - const res = await this.getLatestBlockhashAndContext(commitment); + const res = await this.getLatestBlockhashAndContext(commitmentOrConfig); return res.value; } catch (e) { throw new Error('failed to get recent blockhash: ' + e); @@ -3348,9 +3525,16 @@ export class Connection { * @return {Promise} */ async getLatestBlockhashAndContext( - commitment?: Commitment, + commitmentOrConfig?: Commitment | GetLatestBlockhashConfig, ): Promise> { - const args = this._buildArgs([], commitment); + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs( + [], + commitment, + undefined /* encoding */, + config, + ); const unsafeRes = await this._rpcRequest('getLatestBlockhash', args); const res = create(unsafeRes, GetLatestBlockhashRpcResult); if ('error' in res) { @@ -3422,8 +3606,17 @@ export class Connection { /* * Returns the current block height of the node */ - async getBlockHeight(commitment?: Commitment): Promise { - const args = this._buildArgs([], commitment); + async getBlockHeight( + commitmentOrConfig?: Commitment | GetBlockHeightConfig, + ): Promise { + const {commitment, config} = + extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs( + [], + commitment, + undefined /* encoding */, + config, + ); const unsafeRes = await this._rpcRequest('getBlockHeight', args); const res = create(unsafeRes, jsonRpcResult(number())); if ('error' in res) { @@ -4239,6 +4432,9 @@ export class Connection { if (options && options.maxRetries) { config.maxRetries = options.maxRetries; } + if (options && options.minContextSlot != null) { + config.minContextSlot = options.minContextSlot; + } if (skipPreflight) { config.skipPreflight = skipPreflight; } diff --git a/web3.js/src/util/send-and-confirm-raw-transaction.ts b/web3.js/src/util/send-and-confirm-raw-transaction.ts index ed9de3a90b08d1..1b5c98fc98f503 100644 --- a/web3.js/src/util/send-and-confirm-raw-transaction.ts +++ b/web3.js/src/util/send-and-confirm-raw-transaction.ts @@ -68,6 +68,7 @@ export async function sendAndConfirmRawTransaction( const sendOptions = options && { skipPreflight: options.skipPreflight, preflightCommitment: options.preflightCommitment || options.commitment, + minContextSlot: options.minContextSlot, }; const signature = await connection.sendRawTransaction( diff --git a/web3.js/src/util/send-and-confirm-transaction.ts b/web3.js/src/util/send-and-confirm-transaction.ts index bbfcf7cee77f67..46abc5002a8b62 100644 --- a/web3.js/src/util/send-and-confirm-transaction.ts +++ b/web3.js/src/util/send-and-confirm-transaction.ts @@ -25,6 +25,7 @@ export async function sendAndConfirmTransaction( skipPreflight: options.skipPreflight, preflightCommitment: options.preflightCommitment || options.commitment, maxRetries: options.maxRetries, + minContextSlot: options.minContextSlot, }; const signature = await connection.sendTransaction(