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

feat: add rippled API v2 support and use as default #2656

Merged
merged 57 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
9fed915
add apiVersion support to requests and AccountInfoResponse v1/v2 types
khancode Mar 4, 2024
e35fb0d
fix submitAndWait signature
khancode Mar 4, 2024
0fe7543
Merge branch 'main' into rippled-v2-account-info-response
khancode Mar 4, 2024
82ce7dd
Merge branch 'main' into rippled-v2-account-info-response
khancode Mar 21, 2024
84b861b
update docker container README
khancode Mar 21, 2024
a7139fe
update tests
khancode Mar 21, 2024
a98e59f
Merge branch 'main' into rippled-v2-account-info-response
khancode Mar 25, 2024
dcf0d17
fix apiVersion param in wrong position of Client.request
khancode Mar 25, 2024
b5b42cd
add integ tests
khancode Mar 25, 2024
6230780
update HISTORY.md
khancode Mar 25, 2024
884c104
fix request.api_version
khancode Mar 25, 2024
7e2b125
update RIPPLED_DOCKER_IMAGE to use v2.1.0
khancode Mar 25, 2024
f19c814
Merge branch 'main' into rippled-v2-account-info-response
khancode Mar 27, 2024
d8134db
refactor Client.request signature
khancode Mar 27, 2024
a712043
update rippled docker image
khancode Mar 27, 2024
7440975
fix Client.requestAll
khancode Mar 27, 2024
aa30786
update rippled docker image to use v2.1.1
khancode Mar 27, 2024
4d319e9
update README
khancode Mar 27, 2024
88d961e
Merge branch 'main' into rippled-v2-account-info-response
khancode Apr 1, 2024
0894d32
Merge branch 'main' into rippled-v2-account-info-response
khancode Apr 21, 2024
720d5e8
use import type
khancode Apr 23, 2024
4c4499e
fix faucet; unrelated to PR
khancode Apr 29, 2024
755f32a
Merge branch 'main' into rippled-v2-account-info-response
khancode Jun 4, 2024
ffc5962
Merge branch 'main' into rippled-v2-account-info-response
khancode Jun 6, 2024
09e71a0
add api_version v2 support and set as default while providing support…
khancode Jun 13, 2024
c389749
Merge branch 'main' into rippled-v2-account-info-response
khancode Jun 13, 2024
90d326f
refactor: add apiVersion to Client
khancode Jun 14, 2024
6e663c6
resolve errors
khancode Jun 14, 2024
4e7d1be
use DeliverMax for isPartialPayment check
khancode Jun 15, 2024
d515058
update fixtures
khancode Jun 15, 2024
684a279
resolve lint errors
khancode Jun 15, 2024
3f0b803
add API v1 support for isPartialPayment
khancode Jun 15, 2024
05a7470
update CONTRIBUTING
khancode Jun 18, 2024
3d80faf
update accountTx JSDoc
khancode Jun 18, 2024
96b650d
revert deleted JSDoc comments in accountTx
khancode Jun 18, 2024
ad5b2c6
update JSDoc for account_info response
khancode Jun 20, 2024
e9b67c7
Merge branch 'main' into rippled-v2-account-info-response
khancode Jun 21, 2024
082cd10
only use client.apiVersion in Client.request()
khancode Jun 21, 2024
886f65b
add ledger_hash
khancode Jun 21, 2024
35d096a
remove API v1 comment from v2 model
khancode Jun 21, 2024
4319f76
update meta_blob JSDoc
khancode Jun 21, 2024
7e514d6
delete second AccountTxRequest matching
khancode Jun 21, 2024
d9afe5b
add close_time_iso
khancode Jun 24, 2024
31abffb
set close_time_iso as optional field
khancode Jun 24, 2024
89794c6
add meta_blob to BaseResponse
khancode Jun 24, 2024
f76c906
Revert "add meta_blob to BaseResponse"
khancode Jun 24, 2024
c6d4725
Merge branch 'main' into rippled-v2-account-info-response
khancode Jun 25, 2024
29cbfff
use DEFAULT_API_VERSION throughout call stack
khancode Jun 25, 2024
0e6b0f4
improve JSDoc explanation of ledger_index
khancode Jun 27, 2024
f9500ac
remove this.apiVersion from getLedgerIndex
khancode Jun 27, 2024
ed1e8ff
refactor Client.request()
khancode Jun 27, 2024
ff066b7
refactor RequestManger.resolve()
khancode Jun 27, 2024
8261a98
add TODO to fix TxResponse type assertion
khancode Jun 27, 2024
d05ad82
use @category ResponsesV1 for API v1 types
khancode Jun 27, 2024
fff23c8
Merge branch 'main' into rippled-v2-account-info-response
khancode Jun 27, 2024
eb33765
refactor accountTxHasPartialPayment()
khancode Jun 27, 2024
219e0f7
remove TODO
khancode Jun 28, 2024
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
4 changes: 4 additions & 0 deletions packages/xrpl/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
## Unreleased
* Remove references to the Hooks testnet faucet in the xrpl.js code repository.

### BREAKING CHANGES
* Use rippled api_version v2 as default while maintaining support for v1.

### Added
* Add `nfts_by_issuer` clio-only API definition

## 3.1.0 (2024-06-03)

### BREAKING CHANGES
Expand Down
2 changes: 1 addition & 1 deletion packages/xrpl/snippets/src/claimPayChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async function claimPayChannel(): Promise<void> {
Channel: hashes.hashPaymentChannel(
wallet1.classicAddress,
wallet2.classicAddress,
paymentChannelResponse.result.Sequence ?? 0,
paymentChannelResponse.result.tx_json.Sequence ?? 0,
),
Amount: '100',
}
Expand Down
2 changes: 1 addition & 1 deletion packages/xrpl/snippets/src/sendEscrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async function sendEscrow(): Promise<void> {
TransactionType: 'EscrowFinish',
Account: wallet1.classicAddress,
Owner: wallet1.classicAddress,
OfferSequence: Number(createEscrowResponse.result.Sequence),
OfferSequence: Number(createEscrowResponse.result.tx_json.Sequence),
}

await client.submit(finishTx, {
Expand Down
25 changes: 14 additions & 11 deletions packages/xrpl/src/client/RequestManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
TimeoutError,
XrplError,
} from '../errors'
import type { APIVersion } from '../models'
import { Response, RequestResponseMap } from '../models/methods'
import { BaseRequest, ErrorResponse } from '../models/methods/baseMethod'

Expand Down Expand Up @@ -35,10 +36,10 @@ export default class RequestManager {
* @param timer - The timer associated with the promise.
* @returns A promise that resolves to the specified generic type.
*/
public async addPromise<R extends BaseRequest, T = RequestResponseMap<R>>(
newId: string | number,
timer: ReturnType<typeof setTimeout>,
): Promise<T> {
public async addPromise<
R extends BaseRequest,
T = RequestResponseMap<R, APIVersion>,
ckeshava marked this conversation as resolved.
Show resolved Hide resolved
>(newId: string | number, timer: ReturnType<typeof setTimeout>): Promise<T> {
return new Promise<T>((resolve, reject) => {
this.promisesAwaitingResponse.set(newId, {
resolve,
Expand All @@ -55,7 +56,7 @@ export default class RequestManager {
* @param response - Response to return.
* @throws Error if no existing promise with the given ID.
*/
public resolve(id: string | number, response: Response): void {
public resolve(id: string | number, response: Response<APIVersion>): void {
const promise = this.promisesAwaitingResponse.get(id)
if (promise == null) {
throw new XrplError(`No existing promise with id ${id}`, {
Expand Down Expand Up @@ -111,10 +112,10 @@ export default class RequestManager {
* @returns Request ID, new request form, and the promise for resolving the request.
* @throws XrplError if request with the same ID is already pending.
*/
public createRequest<R extends BaseRequest, T = RequestResponseMap<R>>(
request: R,
timeout: number,
): [string | number, string, Promise<T>] {
public createRequest<
R extends BaseRequest,
T = RequestResponseMap<R, APIVersion>,
>(request: R, timeout: number): [string | number, string, Promise<T>] {
let newId: string | number
if (request.id == null) {
newId = this.nextId
Expand Down Expand Up @@ -171,7 +172,9 @@ export default class RequestManager {
* @param response - The response to handle.
* @throws ResponseFormatError if the response format is invalid, RippledError if rippled returns an error.
*/
public handleResponse(response: Partial<Response | ErrorResponse>): void {
public handleResponse(
response: Partial<Response<APIVersion> | ErrorResponse>,
): void {
if (
response.id == null ||
!(typeof response.id === 'string' || typeof response.id === 'number')
Expand Down Expand Up @@ -206,7 +209,7 @@ export default class RequestManager {
// status no longer needed because error is thrown if status is not "success"
delete response.status
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Must be a valid Response here
this.resolve(response.id, response as unknown as Response)
this.resolve(response.id, response as unknown as Response<APIVersion>)
khancode marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
12 changes: 7 additions & 5 deletions packages/xrpl/src/client/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ConnectionError,
XrplError,
} from '../errors'
import type { RequestResponseMap } from '../models'
import type { APIVersion, RequestResponseMap } from '../models'
import { BaseRequest } from '../models/methods/baseMethod'

import ConnectionManager from './ConnectionManager'
Expand Down Expand Up @@ -267,6 +267,7 @@ export class Connection extends EventEmitter {

/**
* Disconnect the websocket, then connect again.
*
*/
public async reconnect(): Promise<void> {
/*
Expand All @@ -287,10 +288,10 @@ export class Connection extends EventEmitter {
* @returns The response from the rippled server.
* @throws NotConnectedError if the Connection isn't connected to a server.
*/
public async request<R extends BaseRequest, T = RequestResponseMap<R>>(
request: R,
timeout?: number,
): Promise<T> {
public async request<
R extends BaseRequest,
T = RequestResponseMap<R, APIVersion>,
>(request: R, timeout?: number): Promise<T> {
if (!this.shouldBeConnected || this.ws == null) {
throw new NotConnectedError(JSON.stringify(request), request)
}
Expand Down Expand Up @@ -468,6 +469,7 @@ export class Connection extends EventEmitter {

/**
* Starts a heartbeat to check the connection with the server.
*
*/
private startHeartbeatInterval(): void {
this.clearHeartbeatInterval()
Expand Down
40 changes: 30 additions & 10 deletions packages/xrpl/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import {
ValidationError,
XrplError,
} from '../errors'
import type { LedgerIndex, Balance } from '../models/common'
import {
APIVersion,
LedgerIndex,
Balance,
DEFAULT_API_VERSION,
} from '../models/common'
import {
Request,
// account methods
Expand All @@ -28,6 +33,7 @@ import {
LedgerDataRequest,
LedgerDataResponse,
TxResponse,
LedgerRequest,
} from '../models/methods'
import type {
RequestResponseMap,
Expand Down Expand Up @@ -213,6 +219,12 @@ class Client extends EventEmitter<EventTypes> {
*/
public buildVersion: string | undefined

/**
* API Version used by the server this client is connected to
*
*/
public apiVersion: APIVersion = DEFAULT_API_VERSION
ckeshava marked this conversation as resolved.
Show resolved Hide resolved

/**
* Creates a new Client with a websocket connection to a rippled server.
*
Expand Down Expand Up @@ -306,7 +318,6 @@ class Client extends EventEmitter<EventTypes> {
* additional request body parameters.
*
* @category Network
*
* @param req - Request to send to the server.
* @returns The response from the server.
*
Expand All @@ -319,16 +330,20 @@ class Client extends EventEmitter<EventTypes> {
* console.log(response)
* ```
*/
public async request<R extends Request, T = RequestResponseMap<R>>(
req: R,
): Promise<T> {
const response = await this.connection.request<R, T>({
public async request<
R extends Request,
V extends APIVersion = typeof DEFAULT_API_VERSION,
ckeshava marked this conversation as resolved.
Show resolved Hide resolved
T = RequestResponseMap<R, V>,
>(req: R): Promise<T> {
const request = {
...req,
account: req.account
khancode marked this conversation as resolved.
Show resolved Hide resolved
? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Must be string
ensureClassicAddress(req.account as string)
: undefined,
})
api_version: req.api_version ?? this.apiVersion,
}
const response = await this.connection.request<R, T>(request)

// mutates `response` to add warnings
handlePartialPayment(req.command, response)
Expand Down Expand Up @@ -437,9 +452,10 @@ class Client extends EventEmitter<EventTypes> {
* const allResponses = await client.requestAll({ command: 'transaction_data' });
* console.log(allResponses);
*/

public async requestAll<
T extends MarkerRequest,
U = RequestAllResponseMap<T>,
U = RequestAllResponseMap<T, APIVersion>,
>(request: T, collect?: string): Promise<U[]> {
/*
* The data under collection is keyed based on the command. Fail if command
Expand Down Expand Up @@ -467,7 +483,7 @@ 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).result
const singleResult = (singleResponse as MarkerResponse<APIVersion>).result
if (!(collectKey in singleResult)) {
throw new XrplError(`${collectKey} not in result`)
}
Expand Down Expand Up @@ -1037,9 +1053,13 @@ class Client extends EventEmitter<EventTypes> {
* ```
*/
public async getLedgerIndex(): Promise<number> {
const ledgerResponse = await this.request({
const ledgerResponse = await this.request<
LedgerRequest,
typeof this.apiVersion
khancode marked this conversation as resolved.
Show resolved Hide resolved
>({
command: 'ledger',
ledger_index: 'validated',
api_version: this.apiVersion,
})
return ledgerResponse.result.ledger_index
}
Expand Down
24 changes: 15 additions & 9 deletions packages/xrpl/src/client/partialPayment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
TransactionStream,
TxResponse,
} from '..'
import type { Amount } from '../models/common'
import type { Amount, APIVersion } from '../models/common'
import type { RequestResponseMap } from '../models/methods'
import { BaseRequest, BaseResponse } from '../models/methods/baseMethod'
import { PaymentFlags, Transaction } from '../models/transactions'
Expand Down Expand Up @@ -63,7 +63,10 @@ function isPartialPayment(
}

const delivered = meta.delivered_amount
const amount = tx.Amount
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- DeliverMax is a valid field on Payment response
khancode marked this conversation as resolved.
Show resolved Hide resolved
// @ts-expect-error -- DeliverMax is a valid field on Payment response
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- DeliverMax is a valid field on Payment response
const amount = tx.DeliverMax

if (delivered === undefined) {
return false
Expand All @@ -73,7 +76,7 @@ function isPartialPayment(
}

function txHasPartialPayment(response: TxResponse): boolean {
return isPartialPayment(response.result, response.result.meta)
return isPartialPayment(response.result.tx_json, response.result.meta)
}

function txEntryHasPartialPayment(response: TransactionEntryResponse): boolean {
Expand All @@ -82,14 +85,17 @@ function txEntryHasPartialPayment(response: TransactionEntryResponse): boolean {

function accountTxHasPartialPayment(response: AccountTxResponse): boolean {
const { transactions } = response.result
const foo = transactions.some((tx) => isPartialPayment(tx.tx, tx.meta))
const foo = transactions.some((tx) =>
// API v2 returns tx_json, API v1 returns tx
isPartialPayment(tx.tx_json ?? tx.tx, tx.meta),
justinr1234 marked this conversation as resolved.
Show resolved Hide resolved
)
return foo
}

function hasPartialPayment<R extends BaseRequest, T = RequestResponseMap<R>>(
command: string,
response: T,
): boolean {
function hasPartialPayment<
R extends BaseRequest,
T = RequestResponseMap<R, APIVersion>,
>(command: string, response: T): boolean {
/* eslint-disable @typescript-eslint/consistent-type-assertions -- Request type is known at runtime from command */
switch (command) {
case 'tx':
Expand All @@ -112,7 +118,7 @@ function hasPartialPayment<R extends BaseRequest, T = RequestResponseMap<R>>(
*/
export function handlePartialPayment<
R extends BaseRequest,
T = RequestResponseMap<R>,
T = RequestResponseMap<R, APIVersion>,
>(command: string, response: T): void {
if (hasPartialPayment(command, response)) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- We are checking dynamically and safely.
Expand Down
8 changes: 8 additions & 0 deletions packages/xrpl/src/models/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export const RIPPLED_API_V1 = 1
export const RIPPLED_API_V2 = 2
export const DEFAULT_API_VERSION = RIPPLED_API_V2
export type APIVersion = typeof RIPPLED_API_V1 | typeof RIPPLED_API_V2
export type LedgerIndex = number | ('validated' | 'closed' | 'current')

export interface XRP {
Expand Down Expand Up @@ -104,6 +108,10 @@ export interface ResponseOnlyTxInfo {
* The sequence number of the ledger that included this transaction.
*/
ledger_index?: number
/**
* The hash of the ledger included this transaction.
*/
ledger_hash?: string
/**
* @deprecated Alias for ledger_index.
*/
Expand Down
47 changes: 39 additions & 8 deletions packages/xrpl/src/models/ledger/Ledger.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { APIVersion, RIPPLED_API_V1 } from '../common'
import { Transaction, TransactionMetadata } from '../transactions'

import { LedgerEntry } from './LedgerEntry'

/**
* A ledger is a block of transactions and shared state data. It has a unique
* header that describes its contents using cryptographic hashes.
* Common properties for ledger entries.
*
* @category Ledger Entries
*/
export default interface Ledger {
interface BaseLedger {
/** The SHA-512Half of this ledger's state tree information. */
account_hash: string
/** All the state information in this ledger. Admin only. */
Expand Down Expand Up @@ -38,11 +38,6 @@ export default interface Ledger {
* for this ledger and all its contents.
*/
ledger_hash: string
/**
* The ledger index of the ledger. Some API methods display this as a quoted
* integer; some display it as a native JSON number.
*/
ledger_index: string
/** The approximate time at which the previous ledger was closed. */
parent_close_time: number
/**
Expand All @@ -63,3 +58,39 @@ export default interface Ledger {
*/
transactions?: Array<Transaction & { metaData?: TransactionMetadata }>
}

/**
* A ledger is a block of transactions and shared state data. It has a unique
* header that describes its contents using cryptographic hashes.
*
* @category Ledger Entries
*/
export interface Ledger extends BaseLedger {
/**
* The ledger index of the ledger. Represented as a native JSON number.
khancode marked this conversation as resolved.
Show resolved Hide resolved
*/
ledger_index: number
ckeshava marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* A ledger is a block of transactions and shared state data. It has a unique
* header that describes its contents using cryptographic hashes. This is used
* in api_version 1.
*
* @category Ledger Entries
*/
export interface LedgerV1 extends BaseLedger {
/**
* The ledger index of the ledger. Some API methods display this as a quoted
* integer; some display it as a native JSON number.
*/
ledger_index: string
}

/**
* Type to map between the API version and the Ledger type.
*
* @category Responses
*/
export type LedgerVersionMap<Version extends APIVersion> =
Version extends typeof RIPPLED_API_V1 ? LedgerV1 : Ledger
Loading
Loading