Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add apiVersion option to Client constructor #2877

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/xrpl/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
### Changed
* Deprecated `setTransactionFlagsToNumber`. Start using convertTxFlagsToNumber instead

### Fixed
* `Client` supports `apiVersion` option on constructor

## 4.1.0 (2024-12-23)

### Added
Expand Down
51 changes: 37 additions & 14 deletions packages/xrpl/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
AccountOffersRequest,
AccountOffersResponse,
AccountTxRequest,
AccountTxResponse,
// ledger methods
LedgerDataRequest,
LedgerDataResponse,
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Comment on lines +96 to +98
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this typing change is needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this, Client class will not be able to recognize the API version set in the constructor, and the response of request() and requestAll() will only be the types of APIv2 (DEFAULT_API_VERSION).

/**
* 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.
Expand All @@ -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
Expand All @@ -122,7 +131,10 @@ type RequestNextPageType =
| AccountTxRequest
| LedgerDataRequest

type RequestNextPageReturnMap<T> = T extends AccountChannelsRequest
type RequestNextPageReturnMap<
T extends BaseRequest,
V extends APIVersion = typeof DEFAULT_API_VERSION,
> = T extends AccountChannelsRequest
? AccountChannelsResponse
: T extends AccountLinesRequest
? AccountLinesResponse
Expand All @@ -131,7 +143,7 @@ type RequestNextPageReturnMap<T> = T extends AccountChannelsRequest
: T extends AccountOffersRequest
? AccountOffersResponse
: T extends AccountTxRequest
? AccountTxResponse
? AccountTxResponseBase<V>
: T extends LedgerDataRequest
? LedgerDataResponse
: never
Expand Down Expand Up @@ -184,7 +196,9 @@ const NORMAL_DISCONNECT_CODE = 1000
*
* @category Clients
*/
class Client extends EventEmitter<EventTypes> {
class Client<
ClientAPIVersion extends APIVersion = typeof DEFAULT_API_VERSION,
> extends EventEmitter<EventTypes> {
/*
* Underlying connection to rippled.
*/
Expand Down Expand Up @@ -222,7 +236,7 @@ class Client extends EventEmitter<EventTypes> {
* API Version used by the server this client is connected to
*
*/
public apiVersion: APIVersion = DEFAULT_API_VERSION
public apiVersion: APIVersion
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Type of apiVersion property should match the generic parameter.

Currently, apiVersion is typed as APIVersion. To ensure consistency and leverage the benefits of the generic parameter, consider typing it as ClientAPIVersion.

Apply this diff to update the type:

-public apiVersion: APIVersion
+public apiVersion: ClientAPIVersion

Committable suggestion skipped: line range outside the PR's diff.


/**
* Creates a new Client with a websocket connection to a rippled server.
Expand All @@ -238,7 +252,10 @@ class Client extends EventEmitter<EventTypes> {
* ```
*/
/* 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<ClientAPIVersion> = {},
) {
super()
if (typeof server !== 'string' || !/wss?(?:\+unix)?:\/\//u.exec(server)) {
throw new ValidationError(
Expand All @@ -249,6 +266,8 @@ class Client extends EventEmitter<EventTypes> {
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) => {
Expand Down Expand Up @@ -332,7 +351,7 @@ class Client extends EventEmitter<EventTypes> {
*/
public async request<
R extends Request,
V extends APIVersion = typeof DEFAULT_API_VERSION,
V extends APIVersion = ClientAPIVersion,
T = RequestResponseMap<R, V>,
>(req: R): Promise<T> {
const request = {
Expand Down Expand Up @@ -377,8 +396,8 @@ class Client extends EventEmitter<EventTypes> {
*/
public async requestNextPage<
T extends RequestNextPageType,
U extends RequestNextPageReturnMap<T>,
>(req: T, resp: U): Promise<RequestNextPageReturnMap<T>> {
U extends RequestNextPageReturnMap<T, ClientAPIVersion>,
>(req: T, resp: U): Promise<U> {
if (!resp.result.marker) {
return Promise.reject(
new NotFoundError('response does not have a next page'),
Expand Down Expand Up @@ -417,7 +436,10 @@ class Client extends EventEmitter<EventTypes> {
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<T>,
U extends (...args: any[]) => void = OnEventToListenerMap<
T,
ClientAPIVersion
>,
>(eventName: T, listener: U): this {
return super.on(eventName, listener)
}
Expand Down Expand Up @@ -455,7 +477,7 @@ class Client extends EventEmitter<EventTypes> {

public async requestAll<
T extends MarkerRequest,
U = RequestAllResponseMap<T, APIVersion>,
U = RequestAllResponseMap<T, ClientAPIVersion>,
>(request: T, collect?: string): Promise<U[]> {
/*
* The data under collection is keyed based on the command. Fail if command
Expand Down Expand Up @@ -483,7 +505,8 @@ class Client extends EventEmitter<EventTypes> {
// 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<APIVersion>).result
const singleResult = (singleResponse as MarkerResponse<ClientAPIVersion>)
.result
if (!(collectKey in singleResult)) {
throw new XrplError(`${collectKey} not in result`)
}
Expand Down Expand Up @@ -833,7 +856,7 @@ class Client extends EventEmitter<EventTypes> {
// A wallet to sign a transaction. It must be provided when submitting an unsigned transaction.
wallet?: Wallet
},
): Promise<TxResponse<T>> {
): Promise<TxResponse<T, ClientAPIVersion>> {
const signedTx = await getSignedTx(this, transaction, opts)

const lastLedger = getLastLedgerSequence(signedTx)
Expand Down
2 changes: 1 addition & 1 deletion packages/xrpl/src/models/methods/accountTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
7 changes: 5 additions & 2 deletions packages/xrpl/src/models/methods/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,10 @@ export type EventTypes =
| 'path_find'
| 'error'

export type OnEventToListenerMap<T extends EventTypes> = 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
Expand All @@ -487,7 +490,7 @@ export type OnEventToListenerMap<T extends EventTypes> = T extends 'connected'
: T extends 'validationReceived'
? (validation: ValidationStream) => void
: T extends 'transaction'
? (transaction: TransactionStream) => void
? (transaction: TransactionStreamBase<V>) => void
: T extends 'peerStatusChange'
? (peerStatus: PeerStatusStream) => void
: T extends 'consensusPhase'
Expand Down
8 changes: 5 additions & 3 deletions packages/xrpl/src/models/methods/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ interface BaseTxResult<
*
* @category Responses
*/
export interface TxResponse<T extends BaseTransaction = Transaction>
extends BaseResponse {
result: BaseTxResult<typeof RIPPLED_API_V2, T> & { tx_json: T }
export interface TxResponse<
T extends BaseTransaction = Transaction,
V extends APIVersion = typeof DEFAULT_API_VERSION,
> extends BaseResponse {
result: BaseTxResult<V, T> & { 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
Expand Down
38 changes: 19 additions & 19 deletions packages/xrpl/src/sugar/autofill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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<V>): boolean {
if (
client.networkID !== undefined &&
client.networkID > RESTRICTED_NETWORKS
Expand Down Expand Up @@ -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<void> {
export async function setNextValidSequenceNumber<
V extends APIVersion = typeof DEFAULT_API_VERSION,
>(client: Client<V>, tx: Transaction): Promise<void> {
const request: AccountInfoRequest = {
command: 'account_info',
account: tx.Account,
Expand All @@ -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<BigNumber> {
async function fetchAccountDeleteFee<
V extends APIVersion = typeof DEFAULT_API_VERSION,
>(client: Client<V>): Promise<BigNumber> {
const response = await client.request({ command: 'server_state' })
const fee = response.result.state.validated_ledger?.reserve_inc

Expand All @@ -255,11 +259,9 @@ async function fetchAccountDeleteFee(client: Client): Promise<BigNumber> {
* @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<void> {
export async function calculateFeePerTransactionType<
V extends APIVersion = typeof DEFAULT_API_VERSION,
>(client: Client<V>, tx: Transaction, signersCount = 0): Promise<void> {
// netFee is usually 0.00001 XRP (10 drops)
const netFeeXRP = await getFeeXrp(client)
const netFeeDrops = xrpToDrops(netFeeXRP)
Expand Down Expand Up @@ -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<void> {
export async function setLatestValidatedLedgerSequence<
V extends APIVersion = typeof DEFAULT_API_VERSION,
>(client: Client<V>, tx: Transaction): Promise<void> {
const ledgerSequence = await client.getLedgerIndex()
// eslint-disable-next-line no-param-reassign -- param reassign is safe
tx.LastLedgerSequence = ledgerSequence + LEDGER_OFFSET
Expand All @@ -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<void> {
export async function checkAccountDeleteBlockers<
V extends APIVersion = typeof DEFAULT_API_VERSION,
>(client: Client<V>, tx: Transaction): Promise<void> {
const request: AccountObjectsRequest = {
command: 'account_objects',
account: tx.Account,
Expand Down
8 changes: 4 additions & 4 deletions packages/xrpl/src/sugar/getFeeXrp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<string> {
export default async function getFeeXrp<
V extends APIVersion = typeof DEFAULT_API_VERSION,
>(client: Client<V>, cushion?: number): Promise<string> {
const feeCushion = cushion ?? client.feeCushion

const serverInfo = (
Expand Down
9 changes: 4 additions & 5 deletions packages/xrpl/src/sugar/getOrderbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<BookOfferResult[]> {
export async function requestAllOffers<
V extends APIVersion = typeof DEFAULT_API_VERSION,
>(client: Client<V>, request: BookOffersRequest): Promise<BookOfferResult[]> {
const results = await client.requestAll(request)
return results.map((result) => result.result.offers)
}
Expand Down
Loading
Loading