diff --git a/packages/xrpl/HISTORY.md b/packages/xrpl/HISTORY.md index d41e1c537b..1540509dba 100644 --- a/packages/xrpl/HISTORY.md +++ b/packages/xrpl/HISTORY.md @@ -6,6 +6,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr ### Added * Adds utility function `convertTxFlagsToNumber` +* Adds `apiVersion` option on `Client` constructor ### Changed * Deprecated `setTransactionFlagsToNumber`. Start using convertTxFlagsToNumber instead diff --git a/packages/xrpl/src/client/index.ts b/packages/xrpl/src/client/index.ts index 0afaa29b72..8d5fe5fa98 100644 --- a/packages/xrpl/src/client/index.ts +++ b/packages/xrpl/src/client/index.ts @@ -28,7 +28,6 @@ import { AccountOffersRequest, AccountOffersResponse, AccountTxRequest, - AccountTxResponse, // ledger methods LedgerDataRequest, LedgerDataResponse, @@ -40,7 +39,9 @@ import type { MarkerRequest, MarkerResponse, SubmitResponse, + BaseRequest, } from '../models/methods' +import { AccountTxResponseBase } from '../models/methods/accountTx' import type { BookOffer, BookOfferCurrency } from '../models/methods/bookOffers' import type { EventTypes, @@ -92,7 +93,9 @@ import { handleStreamPartialPayment, } from './partialPayment' -export interface ClientOptions extends ConnectionUserOptions { +export interface ClientOptions< + ClientAPIVersion extends APIVersion = typeof DEFAULT_API_VERSION, +> extends ConnectionUserOptions { /** * Multiplication factor to multiply estimated fee by to provide a cushion in case the * required fee rises during submission of a transaction. Defaults to 1.2. @@ -111,6 +114,12 @@ export interface ClientOptions extends ConnectionUserOptions { * Duration to wait for a request to timeout. */ timeout?: number + /** + * API Version to use for requests. + * + * @default DEFAULT_API_VERSION + */ + apiVersion?: ClientAPIVersion } // Make sure to update both this and `RequestNextPageReturnMap` at the same time @@ -122,7 +131,10 @@ type RequestNextPageType = | AccountTxRequest | LedgerDataRequest -type RequestNextPageReturnMap = T extends AccountChannelsRequest +type RequestNextPageReturnMap< + T extends BaseRequest, + V extends APIVersion = typeof DEFAULT_API_VERSION, +> = T extends AccountChannelsRequest ? AccountChannelsResponse : T extends AccountLinesRequest ? AccountLinesResponse @@ -131,7 +143,7 @@ type RequestNextPageReturnMap = T extends AccountChannelsRequest : T extends AccountOffersRequest ? AccountOffersResponse : T extends AccountTxRequest - ? AccountTxResponse + ? AccountTxResponseBase : T extends LedgerDataRequest ? LedgerDataResponse : never @@ -184,7 +196,9 @@ const NORMAL_DISCONNECT_CODE = 1000 * * @category Clients */ -class Client extends EventEmitter { +class Client< + ClientAPIVersion extends APIVersion = typeof DEFAULT_API_VERSION, +> extends EventEmitter { /* * Underlying connection to rippled. */ @@ -222,7 +236,7 @@ class Client extends EventEmitter { * API Version used by the server this client is connected to * */ - public apiVersion: APIVersion = DEFAULT_API_VERSION + public apiVersion: APIVersion /** * Creates a new Client with a websocket connection to a rippled server. @@ -238,7 +252,10 @@ class Client extends EventEmitter { * ``` */ /* eslint-disable max-lines-per-function -- the constructor requires more lines to implement the logic */ - public constructor(server: string, options: ClientOptions = {}) { + public constructor( + server: string, + options: ClientOptions = {}, + ) { super() if (typeof server !== 'string' || !/wss?(?:\+unix)?:\/\//u.exec(server)) { throw new ValidationError( @@ -249,6 +266,8 @@ class Client extends EventEmitter { this.feeCushion = options.feeCushion ?? DEFAULT_FEE_CUSHION this.maxFeeXRP = options.maxFeeXRP ?? DEFAULT_MAX_FEE_XRP + this.apiVersion = options.apiVersion ?? DEFAULT_API_VERSION + this.connection = new Connection(server, options) this.connection.on('error', (errorCode, errorMessage, data) => { @@ -332,7 +351,7 @@ class Client extends EventEmitter { */ public async request< R extends Request, - V extends APIVersion = typeof DEFAULT_API_VERSION, + V extends APIVersion = ClientAPIVersion, T = RequestResponseMap, >(req: R): Promise { const request = { @@ -377,8 +396,8 @@ class Client extends EventEmitter { */ public async requestNextPage< T extends RequestNextPageType, - U extends RequestNextPageReturnMap, - >(req: T, resp: U): Promise> { + U extends RequestNextPageReturnMap, + >(req: T, resp: U): Promise { if (!resp.result.marker) { return Promise.reject( new NotFoundError('response does not have a next page'), @@ -417,7 +436,10 @@ class Client extends EventEmitter { public on< T extends EventTypes, // eslint-disable-next-line @typescript-eslint/no-explicit-any -- needs to be any for overload - U extends (...args: any[]) => void = OnEventToListenerMap, + U extends (...args: any[]) => void = OnEventToListenerMap< + T, + ClientAPIVersion + >, >(eventName: T, listener: U): this { return super.on(eventName, listener) } @@ -455,7 +477,7 @@ class Client extends EventEmitter { public async requestAll< T extends MarkerRequest, - U = RequestAllResponseMap, + U = RequestAllResponseMap, >(request: T, collect?: string): Promise { /* * The data under collection is keyed based on the command. Fail if command @@ -483,7 +505,8 @@ class Client extends EventEmitter { // eslint-disable-next-line no-await-in-loop -- Necessary for this, it really has to wait const singleResponse = await this.connection.request(repeatProps) // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true - const singleResult = (singleResponse as MarkerResponse).result + const singleResult = (singleResponse as MarkerResponse) + .result if (!(collectKey in singleResult)) { throw new XrplError(`${collectKey} not in result`) } @@ -833,7 +856,7 @@ class Client extends EventEmitter { // A wallet to sign a transaction. It must be provided when submitting an unsigned transaction. wallet?: Wallet }, - ): Promise> { + ): Promise> { const signedTx = await getSignedTx(this, transaction, opts) const lastLedger = getLastLedgerSequence(signedTx) diff --git a/packages/xrpl/src/models/methods/accountTx.ts b/packages/xrpl/src/models/methods/accountTx.ts index ccff2279eb..09a655570e 100644 --- a/packages/xrpl/src/models/methods/accountTx.ts +++ b/packages/xrpl/src/models/methods/accountTx.ts @@ -87,7 +87,7 @@ export interface AccountTxTransaction< /** * Base interface for account transaction responses. */ -interface AccountTxResponseBase< +export interface AccountTxResponseBase< Version extends APIVersion = typeof DEFAULT_API_VERSION, > extends BaseResponse { result: { diff --git a/packages/xrpl/src/models/methods/subscribe.ts b/packages/xrpl/src/models/methods/subscribe.ts index a0bd7cdf86..f465f536a0 100644 --- a/packages/xrpl/src/models/methods/subscribe.ts +++ b/packages/xrpl/src/models/methods/subscribe.ts @@ -478,7 +478,10 @@ export type EventTypes = | 'path_find' | 'error' -export type OnEventToListenerMap = T extends 'connected' +export type OnEventToListenerMap< + T extends EventTypes, + V extends APIVersion = typeof DEFAULT_API_VERSION, +> = T extends 'connected' ? () => void : T extends 'disconnected' ? (code: number) => void @@ -487,7 +490,7 @@ export type OnEventToListenerMap = T extends 'connected' : T extends 'validationReceived' ? (validation: ValidationStream) => void : T extends 'transaction' - ? (transaction: TransactionStream) => void + ? (transaction: TransactionStreamBase) => void : T extends 'peerStatusChange' ? (peerStatus: PeerStatusStream) => void : T extends 'consensusPhase' diff --git a/packages/xrpl/src/models/methods/tx.ts b/packages/xrpl/src/models/methods/tx.ts index c8df683fb2..0878cba434 100644 --- a/packages/xrpl/src/models/methods/tx.ts +++ b/packages/xrpl/src/models/methods/tx.ts @@ -93,9 +93,11 @@ interface BaseTxResult< * * @category Responses */ -export interface TxResponse - extends BaseResponse { - result: BaseTxResult & { tx_json: T } +export interface TxResponse< + T extends BaseTransaction = Transaction, + V extends APIVersion = typeof DEFAULT_API_VERSION, +> extends BaseResponse { + result: BaseTxResult & { tx_json: T } /** * If true, the server was able to search all of the specified ledger * versions, and the transaction was in none of them. If false, the server did diff --git a/packages/xrpl/src/sugar/autofill.ts b/packages/xrpl/src/sugar/autofill.ts index ca0757611d..0e37df710a 100644 --- a/packages/xrpl/src/sugar/autofill.ts +++ b/packages/xrpl/src/sugar/autofill.ts @@ -3,6 +3,7 @@ import { xAddressToClassicAddress, isValidXAddress } from 'ripple-address-codec' import { type Client } from '..' import { ValidationError, XrplError } from '../errors' +import { APIVersion, DEFAULT_API_VERSION } from '../models/common' import { AccountInfoRequest, AccountObjectsRequest } from '../models/methods' import { Transaction } from '../models/transactions' import { xrpToDrops } from '../utils' @@ -91,7 +92,9 @@ function isNotLaterRippledVersion(source: string, target: string): boolean { * @param client -- The connected client. * @returns True if required networkID, false otherwise. */ -export function txNeedsNetworkID(client: Client): boolean { +export function txNeedsNetworkID< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>(client: Client): boolean { if ( client.networkID !== undefined && client.networkID > RESTRICTED_NETWORKS @@ -215,10 +218,9 @@ function convertToClassicAddress(tx: Transaction, fieldName: string): void { * @returns A Promise that resolves when the sequence number is set. * @throws {Error} If there is an error retrieving the account information. */ -export async function setNextValidSequenceNumber( - client: Client, - tx: Transaction, -): Promise { +export async function setNextValidSequenceNumber< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>(client: Client, tx: Transaction): Promise { const request: AccountInfoRequest = { command: 'account_info', account: tx.Account, @@ -236,7 +238,9 @@ export async function setNextValidSequenceNumber( * @returns A Promise that resolves to the account deletion fee as a BigNumber. * @throws {Error} Throws an error if the account deletion fee cannot be fetched. */ -async function fetchAccountDeleteFee(client: Client): Promise { +async function fetchAccountDeleteFee< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>(client: Client): Promise { const response = await client.request({ command: 'server_state' }) const fee = response.result.state.validated_ledger?.reserve_inc @@ -255,11 +259,9 @@ async function fetchAccountDeleteFee(client: Client): Promise { * @param [signersCount=0] - The number of signers (default is 0). Only used for multisigning. * @returns A promise that resolves with void. Modifies the `tx` parameter to give it the calculated fee. */ -export async function calculateFeePerTransactionType( - client: Client, - tx: Transaction, - signersCount = 0, -): Promise { +export async function calculateFeePerTransactionType< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>(client: Client, tx: Transaction, signersCount = 0): Promise { // netFee is usually 0.00001 XRP (10 drops) const netFeeXRP = await getFeeXrp(client) const netFeeDrops = xrpToDrops(netFeeXRP) @@ -320,10 +322,9 @@ function scaleValue(value, multiplier): string { * @param tx - The transaction object. * @returns A promise that resolves with void. Modifies the `tx` parameter setting `LastLedgerSequence`. */ -export async function setLatestValidatedLedgerSequence( - client: Client, - tx: Transaction, -): Promise { +export async function setLatestValidatedLedgerSequence< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>(client: Client, tx: Transaction): Promise { const ledgerSequence = await client.getLedgerIndex() // eslint-disable-next-line no-param-reassign -- param reassign is safe tx.LastLedgerSequence = ledgerSequence + LEDGER_OFFSET @@ -336,10 +337,9 @@ export async function setLatestValidatedLedgerSequence( * @param tx - The transaction object. * @returns A promise that resolves with void if there are no blockers, or rejects with an XrplError if there are blockers. */ -export async function checkAccountDeleteBlockers( - client: Client, - tx: Transaction, -): Promise { +export async function checkAccountDeleteBlockers< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>(client: Client, tx: Transaction): Promise { const request: AccountObjectsRequest = { command: 'account_objects', account: tx.Account, diff --git a/packages/xrpl/src/sugar/getFeeXrp.ts b/packages/xrpl/src/sugar/getFeeXrp.ts index 24c4c99a9f..e011c6b7ee 100644 --- a/packages/xrpl/src/sugar/getFeeXrp.ts +++ b/packages/xrpl/src/sugar/getFeeXrp.ts @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js' import { type Client } from '..' import { XrplError } from '../errors' +import { APIVersion, DEFAULT_API_VERSION } from '../models/common' const NUM_DECIMAL_PLACES = 6 const BASE_10 = 10 @@ -14,10 +15,9 @@ const BASE_10 = 10 * @param cushion - The fee cushion to use. * @returns The transaction fee. */ -export default async function getFeeXrp( - client: Client, - cushion?: number, -): Promise { +export default async function getFeeXrp< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>(client: Client, cushion?: number): Promise { const feeCushion = cushion ?? client.feeCushion const serverInfo = ( diff --git a/packages/xrpl/src/sugar/getOrderbook.ts b/packages/xrpl/src/sugar/getOrderbook.ts index 285a9d8f28..b90a9295ad 100644 --- a/packages/xrpl/src/sugar/getOrderbook.ts +++ b/packages/xrpl/src/sugar/getOrderbook.ts @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js' import type { Client } from '../client' import { ValidationError } from '../errors' -import { LedgerIndex } from '../models/common' +import { APIVersion, DEFAULT_API_VERSION, LedgerIndex } from '../models/common' import { OfferFlags } from '../models/ledger/Offer' import { BookOffer, @@ -142,10 +142,9 @@ type BookOfferResult = BookOffer[] * @param request - The request object. * @returns The array of book offer results. */ -export async function requestAllOffers( - client: Client, - request: BookOffersRequest, -): Promise { +export async function requestAllOffers< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>(client: Client, request: BookOffersRequest): Promise { const results = await client.requestAll(request) return results.map((result) => result.result.offers) } diff --git a/packages/xrpl/src/sugar/submit.ts b/packages/xrpl/src/sugar/submit.ts index 423a863cf8..603b93b398 100644 --- a/packages/xrpl/src/sugar/submit.ts +++ b/packages/xrpl/src/sugar/submit.ts @@ -1,7 +1,9 @@ import { decode, encode } from 'ripple-binary-codec' import type { + APIVersion, Client, + DEFAULT_API_VERSION, SubmitRequest, SubmitResponse, SubmittableTransaction, @@ -46,8 +48,10 @@ async function sleep(ms: number): Promise { * const signedTransactionString = encode(signedTransaction); * const response2 = await submitRequest(client, signedTransactionString, true); */ -export async function submitRequest( - client: Client, +export async function submitRequest< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>( + client: Client, signedTransaction: SubmittableTransaction | string, failHard = false, ): Promise { @@ -111,12 +115,13 @@ export async function submitRequest( // eslint-disable-next-line max-params, max-lines-per-function -- this function needs to display and do with more information. export async function waitForFinalTransactionOutcome< T extends BaseTransaction = SubmittableTransaction, + V extends APIVersion = typeof DEFAULT_API_VERSION, >( - client: Client, + client: Client, txHash: string, lastLedger: number, submissionResult: string, -): Promise> { +): Promise> { await sleep(LEDGER_CLOSE_TIME) const latestLedger = await client.getLedgerIndex() @@ -138,7 +143,7 @@ export async function waitForFinalTransactionOutcome< // eslint-disable-next-line @typescript-eslint/consistent-type-assertions,@typescript-eslint/no-unsafe-member-access -- ^ const message = error?.data?.error as string if (message === 'txnNotFound') { - return waitForFinalTransactionOutcome( + return waitForFinalTransactionOutcome( client, txHash, lastLedger, @@ -155,10 +160,10 @@ export async function waitForFinalTransactionOutcome< if (txResponse.result.validated) { // TODO: resolve the type assertion below // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- we know that txResponse is of type TxResponse - return txResponse as TxResponse + return txResponse as TxResponse } - return waitForFinalTransactionOutcome( + return waitForFinalTransactionOutcome( client, txHash, lastLedger, @@ -224,8 +229,10 @@ function isSigned(transaction: SubmittableTransaction | string): boolean { * // Example 2: Retrieving a string representation of the signed transaction * const signedTxString = await getSignedTx(client, encode(transaction), options); */ -export async function getSignedTx( - client: Client, +export async function getSignedTx< + V extends APIVersion = typeof DEFAULT_API_VERSION, +>( + client: Client, transaction: SubmittableTransaction | string, { autofill = true,