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

* add apiVersion support to requests and AccountInfoResponse v1/v2 types

* fix submitAndWait signature

* update docker container README

* update tests

* fix apiVersion param in wrong position of Client.request

* add integ tests

* update HISTORY.md

* fix request.api_version

* update RIPPLED_DOCKER_IMAGE to use v2.1.0

* refactor Client.request signature

* update rippled docker image

* fix Client.requestAll

* update rippled docker image to use v2.1.1

* update README

* use import type

* fix faucet; unrelated to PR

* add api_version v2 support and set as default while providing support for v1

* refactor: add apiVersion to Client

* resolve errors

* use DeliverMax for isPartialPayment check

* update fixtures

* resolve lint errors

* add API v1 support for isPartialPayment

* update CONTRIBUTING

* update accountTx JSDoc

* revert deleted JSDoc comments in accountTx

* update JSDoc for account_info response

* only use client.apiVersion in Client.request()

* add ledger_hash

* remove API v1 comment from v2 model

* update meta_blob JSDoc

* delete second AccountTxRequest matching

* add close_time_iso

* set close_time_iso as optional field

* add meta_blob to BaseResponse

* Revert "add meta_blob to BaseResponse"

This reverts commit 89794c629dc515915e28752d7c2552bfeab266a3.

* use DEFAULT_API_VERSION throughout call stack

* improve JSDoc explanation of ledger_index

* remove this.apiVersion from getLedgerIndex

* refactor Client.request()

* refactor RequestManger.resolve()

* add TODO to fix TxResponse type assertion

* use @category ResponsesV1 for API v1 types

* refactor accountTxHasPartialPayment()

* remove TODO
This commit is contained in:
Omar Khan
2024-06-28 08:26:21 -04:00
committed by GitHub
parent 39fed49654
commit 8e2aba3b78
48 changed files with 977 additions and 243 deletions

View File

@@ -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

View File

@@ -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',
}

View File

@@ -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, {

View File

@@ -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'
@@ -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>,
>(newId: string | number, timer: ReturnType<typeof setTimeout>): Promise<T> {
return new Promise<T>((resolve, reject) => {
this.promisesAwaitingResponse.set(newId, {
resolve,
@@ -55,7 +56,10 @@ 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: Partial<Response<APIVersion>>,
): void {
const promise = this.promisesAwaitingResponse.get(id)
if (promise == null) {
throw new XrplError(`No existing promise with id ${id}`, {
@@ -111,10 +115,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
@@ -171,7 +175,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')
@@ -205,8 +211,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)
}
/**

View File

@@ -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'
@@ -267,6 +267,7 @@ export class Connection extends EventEmitter {
/**
* Disconnect the websocket, then connect again.
*
*/
public async reconnect(): Promise<void> {
/*
@@ -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)
}
@@ -468,6 +469,7 @@ export class Connection extends EventEmitter {
/**
* Starts a heartbeat to check the connection with the server.
*
*/
private startHeartbeatInterval(): void {
this.clearHeartbeatInterval()

View File

@@ -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
@@ -213,6 +218,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
/**
* Creates a new Client with a websocket connection to a rippled server.
*
@@ -307,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.
*
@@ -320,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,
T = RequestResponseMap<R, V>,
>(req: R): Promise<T> {
const request = {
...req,
account: req.account
? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Must be string
ensureClassicAddress(req.account as string)
: undefined,
})
account:
typeof req.account === 'string'
? ensureClassicAddress(req.account)
: 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)
@@ -438,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
@@ -468,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`)
}

View File

@@ -2,13 +2,16 @@ import BigNumber from 'bignumber.js'
import { decode } from 'ripple-binary-codec'
import type {
AccountTxResponse,
TransactionEntryResponse,
TransactionStream,
TxResponse,
} from '..'
import type { Amount } from '../models/common'
import type { RequestResponseMap } from '../models/methods'
import type { Amount, APIVersion, DEFAULT_API_VERSION } from '../models/common'
import type {
AccountTxTransaction,
RequestResponseMap,
} from '../models/methods'
import { AccountTxVersionResponseMap } from '../models/methods/accountTx'
import { BaseRequest, BaseResponse } from '../models/methods/baseMethod'
import { PaymentFlags, Transaction } from '../models/transactions'
import type { TransactionMetadata } from '../models/transactions/metadata'
@@ -63,7 +66,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
// @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
@@ -73,23 +79,36 @@ 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 {
return isPartialPayment(response.result.tx_json, response.result.metadata)
}
function accountTxHasPartialPayment(response: AccountTxResponse): boolean {
function accountTxHasPartialPayment<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
>(response: AccountTxVersionResponseMap<Version>): boolean {
const { transactions } = response.result
const foo = transactions.some((tx) => isPartialPayment(tx.tx, tx.meta))
const foo = transactions.some((tx) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- required to check API version model
if (tx.tx_json != null) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- use API v2 model
const transaction = tx as AccountTxTransaction
return isPartialPayment(transaction.tx_json, transaction.meta)
}
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- use API v1 model
const transaction = tx as AccountTxTransaction<1>
return isPartialPayment(transaction.tx, transaction.meta)
})
return foo
}
function hasPartialPayment<R extends BaseRequest, T = RequestResponseMap<R>>(
command: string,
response: T,
): boolean {
function hasPartialPayment<
R extends BaseRequest,
V extends APIVersion = typeof DEFAULT_API_VERSION,
T = RequestResponseMap<R, V>,
>(command: string, response: T): boolean {
/* eslint-disable @typescript-eslint/consistent-type-assertions -- Request type is known at runtime from command */
switch (command) {
case 'tx':
@@ -97,7 +116,9 @@ function hasPartialPayment<R extends BaseRequest, T = RequestResponseMap<R>>(
case 'transaction_entry':
return txEntryHasPartialPayment(response as TransactionEntryResponse)
case 'account_tx':
return accountTxHasPartialPayment(response as AccountTxResponse)
return accountTxHasPartialPayment(
response as AccountTxVersionResponseMap<V>,
)
default:
return false
}
@@ -112,7 +133,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.

View File

@@ -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 {
@@ -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.
*/

View File

@@ -1,14 +1,14 @@
import { APIVersion, DEFAULT_API_VERSION, 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. */
@@ -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
/**
@@ -63,3 +58,40 @@ 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 number.
*/
ledger_index: number
}
/**
* 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 number.
*/
ledger_index: string
}
/**
* Type to map between the API version and the Ledger type.
*
* @category Responses
*/
export type LedgerVersionMap<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> = Version extends typeof RIPPLED_API_V1 ? LedgerV1 : Ledger

View File

@@ -15,7 +15,7 @@ import FeeSettings, {
FeeSettingsPostAmendmentFields,
FEE_SETTINGS_ID,
} from './FeeSettings'
import Ledger from './Ledger'
import { Ledger, LedgerV1 } from './Ledger'
import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry'
import LedgerHashes from './LedgerHashes'
import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL'
@@ -48,6 +48,7 @@ export {
FeeSettingsPreAmendmentFields,
FeeSettingsPostAmendmentFields,
Ledger,
LedgerV1,
LedgerEntryFilter,
LedgerEntry,
LedgerHashes,

View File

@@ -1,3 +1,4 @@
import { APIVersion, DEFAULT_API_VERSION, RIPPLED_API_V1 } from '../common'
import { AccountRoot, SignerList } from '../ledger'
import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod'
@@ -133,23 +134,13 @@ export interface AccountInfoAccountFlags {
allowTrustLineClawback: boolean
}
/**
* Response expected from an {@link AccountInfoRequest}.
*
* @category Responses
*/
export interface AccountInfoResponse extends BaseResponse {
interface BaseAccountInfoResponse extends BaseResponse {
result: {
/**
* The AccountRoot ledger object with this account's information, as stored
* in the ledger.
* If requested, also includes Array of SignerList ledger objects
* associated with this account for Multi-Signing. Since an account can own
* at most one SignerList, this array must have exactly one member if it is
* present.
*/
account_data: AccountRoot & { signer_lists?: SignerList[] }
account_data: AccountRoot
/**
* A map of account flags parsed out. This will only be available for rippled nodes 1.11.0 and higher.
*/
@@ -180,3 +171,58 @@ export interface AccountInfoResponse extends BaseResponse {
validated?: boolean
}
}
/**
* Response expected from a {@link AccountInfoRequest}.
*
* @category Responses
*/
export interface AccountInfoResponse extends BaseAccountInfoResponse {
result: BaseAccountInfoResponse['result'] & {
/**
* If requested, array of SignerList ledger objects associated with this account for Multi-Signing.
* Since an account can own at most one SignerList, this array must have exactly one
* member if it is present.
*/
signer_lists?: SignerList[]
}
}
/**
* Response expected from a {@link AccountInfoRequest} using API version 1.
*
* @category ResponsesV1
*/
export interface AccountInfoV1Response extends BaseAccountInfoResponse {
result: BaseAccountInfoResponse['result'] & {
/**
* The AccountRoot ledger object with this account's information, as stored
* in the ledger.
* If requested, also includes Array of SignerList ledger objects
* associated with this account for Multi-Signing. Since an account can own
* at most one SignerList, this array must have exactly one member if it is
* present.
*/
account_data: BaseAccountInfoResponse['result']['account_data'] & {
/**
* Array of SignerList ledger objects associated with this account for Multi-Signing.
* Since an account can own at most one SignerList, this array must have exactly one
* member if it is present.
* Quirk: In API version 1, this field is nested under account_data. For this method,
* Clio implements the API version 2 behavior where is field is not nested under account_data.
*/
signer_lists?: SignerList[]
}
}
}
/**
* Type to map between the API version and the response type.
*
* @category Responses
*/
export type AccountInfoVersionResponseMap<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> = Version extends typeof RIPPLED_API_V1
? AccountInfoV1Response
: AccountInfoResponse

View File

@@ -1,4 +1,10 @@
import { ResponseOnlyTxInfo } from '../common'
import {
APIVersion,
DEFAULT_API_VERSION,
RIPPLED_API_V1,
RIPPLED_API_V2,
ResponseOnlyTxInfo,
} from '../common'
import { Transaction, TransactionMetadata } from '../transactions'
import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod'
@@ -49,7 +55,9 @@ export interface AccountTxRequest extends BaseRequest, LookupByLedgerRequest {
marker?: unknown
}
export interface AccountTxTransaction {
export interface AccountTxTransaction<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> {
/** The ledger index of the ledger version that included this transaction. */
ledger_index: number
/**
@@ -58,7 +66,15 @@ export interface AccountTxTransaction {
*/
meta: string | TransactionMetadata
/** JSON object defining the transaction. */
tx?: Transaction & ResponseOnlyTxInfo
tx_json?: Version extends typeof RIPPLED_API_V2
? Transaction & ResponseOnlyTxInfo
: never
/** JSON object defining the transaction in rippled API v1. */
tx?: Version extends typeof RIPPLED_API_V1
? Transaction & ResponseOnlyTxInfo
: never
/** The hash of the transaction. */
hash?: Version extends typeof RIPPLED_API_V2 ? string : never
/** Unique hashed String representing the transaction. */
tx_blob?: string
/**
@@ -69,11 +85,11 @@ export interface AccountTxTransaction {
}
/**
* Expected response from an {@link AccountTxRequest}.
*
* @category Responses
* Base interface for account transaction responses.
*/
export interface AccountTxResponse extends BaseResponse {
interface AccountTxResponseBase<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> extends BaseResponse {
result: {
/** Unique Address identifying the related account. */
account: string
@@ -98,7 +114,7 @@ export interface AccountTxResponse extends BaseResponse {
* Array of transactions matching the request's criteria, as explained
* below.
*/
transactions: AccountTxTransaction[]
transactions: Array<AccountTxTransaction<Version>>
/**
* If included and set to true, the information in this response comes from
* a validated ledger version. Otherwise, the information is subject to
@@ -107,3 +123,28 @@ export interface AccountTxResponse extends BaseResponse {
validated?: boolean
}
}
/**
* Expected response from an {@link AccountTxRequest}.
*
* @category Responses
*/
export type AccountTxResponse = AccountTxResponseBase
/**
* Expected response from an {@link AccountTxRequest} with `api_version` set to 1.
*
* @category ResponsesV1
*/
export type AccountTxV1Response = AccountTxResponseBase<typeof RIPPLED_API_V1>
/**
* Type to map between the API version and the response type.
*
* @category Responses
*/
export type AccountTxVersionResponseMap<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> = Version extends typeof RIPPLED_API_V1
? AccountTxV1Response
: AccountTxResponse

View File

@@ -1,5 +1,7 @@
/* eslint-disable no-inline-comments -- Necessary for important note */
/* eslint-disable max-lines -- There is a lot to export */
import type { APIVersion, DEFAULT_API_VERSION } from '../common'
import {
AccountChannelsRequest,
AccountChannelsResponse,
@@ -13,6 +15,8 @@ import {
AccountInfoAccountFlags,
AccountInfoRequest,
AccountInfoResponse,
AccountInfoV1Response,
AccountInfoVersionResponseMap,
AccountQueueData,
AccountQueueTransaction,
} from './accountInfo'
@@ -40,6 +44,8 @@ import {
import {
AccountTxRequest,
AccountTxResponse,
AccountTxV1Response,
AccountTxVersionResponseMap,
AccountTxTransaction,
} from './accountTx'
import { AMMInfoRequest, AMMInfoResponse } from './ammInfo'
@@ -76,11 +82,13 @@ import {
LedgerQueueData,
LedgerRequest,
LedgerResponse,
LedgerV1Response,
LedgerRequestExpandedTransactionsOnly,
LedgerResponseExpanded,
LedgerRequestExpandedAccountsAndTransactions,
LedgerRequestExpandedAccountsOnly,
LedgerRequestExpandedTransactionsBinary,
LedgerVersionResponseMap,
} from './ledger'
import { LedgerClosedRequest, LedgerClosedResponse } from './ledgerClosed'
import { LedgerCurrentRequest, LedgerCurrentResponse } from './ledgerCurrent'
@@ -136,6 +144,8 @@ import { SubmitRequest, SubmitResponse } from './submit'
import {
SubmitMultisignedRequest,
SubmitMultisignedResponse,
SubmitMultisignedV1Response,
SubmitMultisignedVersionResponseMap,
} from './submitMultisigned'
import {
BooksSnapshot,
@@ -156,7 +166,7 @@ import {
TransactionEntryRequest,
TransactionEntryResponse,
} from './transactionEntry'
import { TxRequest, TxResponse } from './tx'
import { TxRequest, TxResponse, TxV1Response, TxVersionResponseMap } from './tx'
import {
UnsubscribeBook,
UnsubscribeRequest,
@@ -222,27 +232,27 @@ type Request =
/**
* @category Responses
*/
type Response =
type Response<Version extends APIVersion = typeof DEFAULT_API_VERSION> =
// account methods
| AccountChannelsResponse
| AccountCurrenciesResponse
| AccountInfoResponse
| AccountInfoVersionResponseMap<Version>
| AccountLinesResponse
| AccountNFTsResponse
| AccountObjectsResponse
| AccountOffersResponse
| AccountTxResponse
| AccountTxVersionResponseMap<Version>
| GatewayBalancesResponse
| NoRippleCheckResponse
// ledger methods
| LedgerResponse
| LedgerVersionResponseMap<Version>
| LedgerClosedResponse
| LedgerCurrentResponse
| LedgerDataResponse
| LedgerEntryResponse
// transaction methods
| SubmitResponse
| SubmitMultisignedResponse
| SubmitMultisignedVersionResponseMap<Version>
| TransactionEntryResponse
| TxResponse
// path and order book methods
@@ -276,12 +286,15 @@ type Response =
// Price Oracle methods
| GetAggregatePriceResponse
export type RequestResponseMap<T> = T extends AccountChannelsRequest
export type RequestResponseMap<
T,
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> = T extends AccountChannelsRequest
? AccountChannelsResponse
: T extends AccountCurrenciesRequest
? AccountCurrenciesResponse
: T extends AccountInfoRequest
? AccountInfoResponse
? AccountInfoVersionResponseMap<Version>
: T extends AccountLinesRequest
? AccountLinesResponse
: T extends AccountNFTsRequest
@@ -291,7 +304,7 @@ export type RequestResponseMap<T> = T extends AccountChannelsRequest
: T extends AccountOffersRequest
? AccountOffersResponse
: T extends AccountTxRequest
? AccountTxResponse
? AccountTxVersionResponseMap<Version>
: T extends AMMInfoRequest
? AMMInfoResponse
: T extends GatewayBalancesRequest
@@ -357,15 +370,15 @@ export type RequestResponseMap<T> = T extends AccountChannelsRequest
// then we'd get the wrong response type, LedgerResponse, instead of
// LedgerResponseExpanded.
T extends LedgerRequestExpandedTransactionsBinary
? LedgerResponse
? LedgerVersionResponseMap<Version>
: T extends LedgerRequestExpandedAccountsAndTransactions
? LedgerResponseExpanded
? LedgerResponseExpanded<Version>
: T extends LedgerRequestExpandedTransactionsOnly
? LedgerResponseExpanded
? LedgerResponseExpanded<Version>
: T extends LedgerRequestExpandedAccountsOnly
? LedgerResponseExpanded
? LedgerResponseExpanded<Version>
: T extends LedgerRequest
? LedgerResponse
? LedgerVersionResponseMap<Version>
: T extends LedgerClosedRequest
? LedgerClosedResponse
: T extends LedgerCurrentRequest
@@ -377,11 +390,11 @@ export type RequestResponseMap<T> = T extends AccountChannelsRequest
: T extends SubmitRequest
? SubmitResponse
: T extends SubmitMultisignedRequest
? SubmitMultisignedResponse
? SubmitMultisignedVersionResponseMap<Version>
: T extends TransactionEntryRequest
? TransactionEntryResponse
: T extends TxRequest
? TxResponse
? TxVersionResponseMap<Version>
: T extends BookOffersRequest
? BookOffersResponse
: T extends DepositAuthorizedRequest
@@ -420,20 +433,25 @@ export type RequestResponseMap<T> = T extends AccountChannelsRequest
? NFTsByIssuerResponse
: T extends NFTHistoryRequest
? NFTHistoryResponse
: Response
: Response<Version>
export type MarkerRequest = Request & {
limit?: number
marker?: unknown
}
export type MarkerResponse = Response & {
export type MarkerResponse<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> = Response<Version> & {
result: {
marker?: unknown
}
}
export type RequestAllResponseMap<T> = T extends AccountChannelsRequest
export type RequestAllResponseMap<
T,
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> = T extends AccountChannelsRequest
? AccountChannelsResponse
: T extends AccountLinesRequest
? AccountLinesResponse
@@ -442,14 +460,12 @@ export type RequestAllResponseMap<T> = T extends AccountChannelsRequest
: T extends AccountOffersRequest
? AccountOffersResponse
: T extends AccountTxRequest
? AccountTxResponse
? AccountTxVersionResponseMap<Version>
: T extends LedgerDataRequest
? LedgerDataResponse
: T extends AccountTxRequest
? AccountTxResponse
: T extends BookOffersRequest
? BookOffersResponse
: MarkerResponse
: MarkerResponse<Version>
export {
// Allow users to define their own requests and responses. This is useful for releasing experimental versions
@@ -467,6 +483,7 @@ export {
AccountInfoAccountFlags,
AccountInfoRequest,
AccountInfoResponse,
AccountInfoV1Response,
AccountQueueData,
AccountQueueTransaction,
AccountLinesRequest,
@@ -484,6 +501,7 @@ export {
AccountOffersResponse,
AccountTxRequest,
AccountTxResponse,
AccountTxV1Response,
AccountTxTransaction,
GatewayBalance,
GatewayBalancesRequest,
@@ -495,6 +513,7 @@ export {
// ledger methods
LedgerRequest,
LedgerResponse,
LedgerV1Response,
LedgerQueueData,
LedgerBinary,
LedgerModifiedOfferCreateTransaction,
@@ -514,10 +533,12 @@ export {
SubmitResponse,
SubmitMultisignedRequest,
SubmitMultisignedResponse,
SubmitMultisignedV1Response,
TransactionEntryRequest,
TransactionEntryResponse,
TxRequest,
TxResponse,
TxV1Response,
// path and order book methods with types
BookOffersRequest,
BookOffer,

View File

@@ -1,4 +1,5 @@
import { Ledger } from '../ledger'
import { APIVersion, DEFAULT_API_VERSION, RIPPLED_API_V1 } from '../common'
import { Ledger, LedgerV1, LedgerVersionMap } from '../ledger/Ledger'
import { LedgerEntryFilter } from '../ledger/LedgerEntry'
import { Transaction, TransactionAndMetadata } from '../transactions'
import { TransactionMetadata } from '../transactions/metadata'
@@ -207,6 +208,12 @@ export interface LedgerBinary
transactions?: string[]
}
export interface LedgerBinaryV1
extends Omit<Omit<LedgerV1, 'transactions'>, 'accountState'> {
accountState?: string[]
transactions?: string[]
}
interface LedgerResponseBase {
/** Unique identifying hash of the entire ledger. */
ledger_hash: string
@@ -231,6 +238,11 @@ interface LedgerResponseResult extends LedgerResponseBase {
ledger: LedgerBinary
}
interface LedgerV1ResponseResult extends LedgerResponseBase {
/** The complete header data of this {@link Ledger}. */
ledger: LedgerBinaryV1
}
/**
* Response expected from a {@link LedgerRequest}.
* This is the default request response, triggered when `expand` and `binary` are both false.
@@ -241,9 +253,31 @@ export interface LedgerResponse extends BaseResponse {
result: LedgerResponseResult
}
interface LedgerResponseExpandedResult extends LedgerResponseBase {
/**
* Response expected from a {@link LedgerRequest}.
* This is the default request response, triggered when `expand` and `binary` are both false.
* This is the response for API version 1.
*
* @category ResponsesV1
*/
export interface LedgerV1Response extends BaseResponse {
result: LedgerV1ResponseResult
}
/**
* Type to map between the API version and the response type.
*
* @category Responses
*/
export type LedgerVersionResponseMap<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> = Version extends typeof RIPPLED_API_V1 ? LedgerV1Response : LedgerResponse
interface LedgerResponseExpandedResult<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> extends LedgerResponseBase {
/** The complete header data of this {@link Ledger}. */
ledger: Ledger
ledger: LedgerVersionMap<Version>
}
/**
@@ -254,6 +288,8 @@ interface LedgerResponseExpandedResult extends LedgerResponseBase {
*
* @category Responses
*/
export interface LedgerResponseExpanded extends BaseResponse {
result: LedgerResponseExpandedResult
export interface LedgerResponseExpanded<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> extends BaseResponse {
result: LedgerResponseExpandedResult<Version>
}

View File

@@ -1,3 +1,4 @@
import { APIVersion, DEFAULT_API_VERSION, RIPPLED_API_V1 } from '../common'
import { Transaction } from '../transactions'
import { BaseRequest, BaseResponse } from './baseMethod'
@@ -24,28 +25,59 @@ export interface SubmitMultisignedRequest extends BaseRequest {
fail_hard?: boolean
}
/**
* Common properties for multisigned transaction responses.
*
* @category Responses
*/
interface BaseSubmitMultisignedResult {
/**
* Code indicating the preliminary result of the transaction, for example.
* `tesSUCCESS`.
*/
engine_result: string
/**
* Numeric code indicating the preliminary result of the transaction,
* directly correlated to `engine_result`.
*/
engine_result_code: number
/** Human-readable explanation of the preliminary transaction result. */
engine_result_message: string
/** The complete transaction in hex string format. */
tx_blob: string
/** The complete transaction in JSON format. */
tx_json: Transaction
}
/**
* Response expected from a {@link SubmitMultisignedRequest}.
*
* @category Responses
*/
export interface SubmitMultisignedResponse extends BaseResponse {
result: {
/**
* Code indicating the preliminary result of the transaction, for example.
* `tesSUCCESS` .
*/
engine_result: string
/**
* Numeric code indicating the preliminary result of the transaction,
* directly correlated to `engine_result`.
*/
engine_result_code: number
/** Human-readable explanation of the preliminary transaction result. */
engine_result_message: string
/** The complete transaction in hex string format. */
tx_blob: string
/** The complete transaction in JSON format. */
result: BaseSubmitMultisignedResult & {
hash?: string
}
}
/**
* Response expected from a {@link SubmitMultisignedRequest} using api_version 1.
*
* @category ResponsesV1
*/
export interface SubmitMultisignedV1Response extends BaseResponse {
result: BaseSubmitMultisignedResult & {
tx_json: Transaction & { hash?: string }
}
}
/**
* Type to map between the API version and the response type.
*
* @category Responses
*/
export type SubmitMultisignedVersionResponseMap<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> = Version extends typeof RIPPLED_API_V1
? SubmitMultisignedV1Response
: SubmitMultisignedResponse

View File

@@ -1,3 +1,9 @@
import {
APIVersion,
DEFAULT_API_VERSION,
RIPPLED_API_V1,
RIPPLED_API_V2,
} from '../common'
import { Transaction, TransactionMetadata } from '../transactions'
import { BaseTransaction } from '../transactions/common'
@@ -41,6 +47,47 @@ export interface TxRequest extends BaseRequest {
max_ledger?: number
}
/**
* Common properties of transaction responses.
*
* @category Responses
*/
interface BaseTxResult<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
T extends BaseTransaction = Transaction,
> {
/** The SHA-512 hash of the transaction. */
hash: string
/**
* The Concise Transaction Identifier of the transaction (16-byte hex string)
*/
ctid?: string
/** The ledger index of the ledger that includes this transaction. */
ledger_index?: number
/** Unique hashed string Transaction metadata blob, which describes the results of the transaction.
* Can be undefined if a transaction has not been validated yet. This field is omitted if binary
* binary format is not requested. */
meta_blob?: Version extends typeof RIPPLED_API_V2
? TransactionMetadata<T> | string
: never
/** Transaction metadata, which describes the results of the transaction.
* Can be undefined if a transaction has not been validated yet. */
meta?: TransactionMetadata<T> | string
/**
* If true, this data comes from a validated ledger version; if omitted or.
* Set to false, this data is not final.
*/
validated?: boolean
/**
* The time the transaction was closed, in seconds since the Ripple Epoch.
*/
close_time_iso?: string
/**
* This number measures the number of seconds since the "Ripple Epoch" of January 1, 2000 (00:00 UTC)
*/
date?: number
}
/**
* Response expected from a {@link TxRequest}.
*
@@ -48,28 +95,7 @@ export interface TxRequest extends BaseRequest {
*/
export interface TxResponse<T extends BaseTransaction = Transaction>
extends BaseResponse {
result: {
/** The SHA-512 hash of the transaction. */
hash: string
/**
* The Concise Transaction Identifier of the transaction (16-byte hex string)
*/
ctid?: string
/** The ledger index of the ledger that includes this transaction. */
ledger_index?: number
/** Transaction metadata, which describes the results of the transaction.
* Can be undefined if a transaction has not been validated yet. */
meta?: TransactionMetadata<T> | string
/**
* If true, this data comes from a validated ledger version; if omitted or.
* Set to false, this data is not final.
*/
validated?: boolean
/**
* This number measures the number of seconds since the "Ripple Epoch" of January 1, 2000 (00:00 UTC)
*/
date?: number
} & T
result: BaseTxResult<typeof RIPPLED_API_V2, 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
@@ -78,3 +104,29 @@ export interface TxResponse<T extends BaseTransaction = Transaction>
*/
searched_all?: boolean
}
/**
* Response expected from a {@link TxRequest} using API version 1.
*
* @category ResponsesV1
*/
export interface TxV1Response<T extends BaseTransaction = Transaction>
extends BaseResponse {
result: BaseTxResult<typeof RIPPLED_API_V1, T> & 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
* not have all of the specified ledger versions available, so it is not sure.
* If one of them might contain the transaction.
*/
searched_all?: boolean
}
/**
* Type to map between the API version and the response type.
*
* @category Responses
*/
export type TxVersionResponseMap<
Version extends APIVersion = typeof DEFAULT_API_VERSION,
> = Version extends typeof RIPPLED_API_V1 ? TxV1Response : TxResponse

View File

@@ -1,7 +1,7 @@
import BigNumber from 'bignumber.js'
import { xAddressToClassicAddress, isValidXAddress } from 'ripple-address-codec'
import type { Client } from '..'
import { type Client } from '..'
import { ValidationError, XrplError } from '../errors'
import { AccountInfoRequest, AccountObjectsRequest } from '../models/methods'
import { Transaction } from '../models/transactions'

View File

@@ -1,6 +1,6 @@
import BigNumber from 'bignumber.js'
import type { Client } from '..'
import { type Client } from '..'
import { XrplError } from '../errors'
const NUM_DECIMAL_PLACES = 6
@@ -20,8 +20,11 @@ export default async function getFeeXrp(
): Promise<string> {
const feeCushion = cushion ?? client.feeCushion
const serverInfo = (await client.request({ command: 'server_info' })).result
.info
const serverInfo = (
await client.request({
command: 'server_info',
})
).result.info
const baseFee = serverInfo.validated_ledger?.base_fee_xrp

View File

@@ -10,7 +10,7 @@ import type {
} from '..'
import { ValidationError, XrplError } from '../errors'
import { Signer } from '../models/common'
import { TxRequest, TxResponse } from '../models/methods'
import { TxResponse } from '../models/methods'
import { BaseTransaction } from '../models/transactions/common'
/** Approximate time for a ledger to close, in milliseconds */
@@ -129,7 +129,7 @@ export async function waitForFinalTransactionOutcome<
}
const txResponse = await client
.request<TxRequest, TxResponse<T>>({
.request({
command: 'tx',
transaction: txHash,
})
@@ -153,7 +153,9 @@ export async function waitForFinalTransactionOutcome<
})
if (txResponse.result.validated) {
return txResponse
// 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<T>
}
return waitForFinalTransactionOutcome<T>(

View File

@@ -8,8 +8,9 @@ import BigNumber from 'bignumber.js'
import { decode, encode } from 'ripple-binary-codec'
import { ValidationError, XrplError } from '../../errors'
import type { Ledger } from '../../models/ledger'
import { APIVersion } from '../../models'
import { LedgerEntry } from '../../models/ledger'
import { LedgerVersionMap } from '../../models/ledger/Ledger'
import { Transaction, TransactionMetadata } from '../../models/transactions'
import HashPrefix from './HashPrefix'
@@ -99,7 +100,9 @@ export function hashSignedTx(tx: Transaction | string): string {
* @returns The hash of the ledger.
* @category Utilities
*/
export function hashLedgerHeader(ledgerHeader: Ledger): string {
export function hashLedgerHeader(
ledgerHeader: LedgerVersionMap<APIVersion>,
): string {
const prefix = HashPrefix.LEDGER.toString(HEX).toUpperCase()
const ledger =
@@ -158,7 +161,7 @@ export function hashStateTree(entries: LedgerEntry[]): string {
}
function computeTransactionHash(
ledger: Ledger,
ledger: LedgerVersionMap<APIVersion>,
options: HashLedgerHeaderOptions,
): string {
const { transaction_hash } = ledger
@@ -188,7 +191,7 @@ function computeTransactionHash(
}
function computeStateHash(
ledger: Ledger,
ledger: LedgerVersionMap<APIVersion>,
options: HashLedgerHeaderOptions,
): string {
const { account_hash } = ledger
@@ -222,7 +225,7 @@ function computeStateHash(
* @category Utilities
*/
function hashLedger(
ledger: Ledger,
ledger: LedgerVersionMap<APIVersion>,
options: {
computeTreeHashes?: boolean
} = {},

View File

@@ -23,6 +23,7 @@ import {
} from 'ripple-binary-codec'
import { verify as verifyKeypairSignature } from 'ripple-keypairs'
import type { APIVersion } from '../models'
import { LedgerEntry } from '../models/ledger'
import { Response } from '../models/methods'
import { PaymentChannelClaim } from '../models/transactions/paymentChannelClaim'
@@ -157,7 +158,7 @@ function isValidAddress(address: string): boolean {
* @returns Whether the response has more pages of data.
* @category Utilities
*/
function hasNextPage(response: Response): boolean {
function hasNextPage(response: Response<APIVersion>): boolean {
// eslint-disable-next-line @typescript-eslint/dot-notation -- only checking if it exists
return Boolean(response.result['marker'])
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any -- required for formatting transactions */
import { expect } from 'chai'
import cloneDeep from 'lodash/cloneDeep'
@@ -24,7 +23,7 @@ describe('client handling of tfPartialPayments', function () {
testContext.mockRippled!.addResponse('tx', rippled.tx.Payment)
const resp = await testContext.client.request({
command: 'tx',
transaction: rippled.tx.Payment.result.hash,
transaction: rippled.tx.Payment.result.tx_json.hash,
})
expect(resp.warnings).to.equal(undefined)
@@ -35,7 +34,7 @@ describe('client handling of tfPartialPayments', function () {
testContext.mockRippled!.addResponse('tx', mockResponse)
const resp = await testContext.client.request({
command: 'tx',
transaction: mockResponse.result.hash,
transaction: mockResponse.result.tx_json.hash,
})
expect(resp.warnings).to.deep.equal([
@@ -51,7 +50,7 @@ describe('client handling of tfPartialPayments', function () {
testContext.mockRippled!.addResponse('tx', mockResponse)
const resp = await testContext.client.request({
command: 'tx',
transaction: mockResponse.result.hash,
transaction: mockResponse.result.tx_json.hash,
})
expect(resp.warnings).to.deep.equal([
@@ -82,8 +81,10 @@ describe('client handling of tfPartialPayments', function () {
}
const mockResponse = rippled.account_tx.normal
mockResponse.result.transactions.push({
tx: partial.result,
tx_json: partial.result.tx_json,
meta: partial.result.meta,
validated: true,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we are mocking the response
} as any)
testContext.mockRippled!.addResponse('account_tx', mockResponse)
@@ -105,8 +106,10 @@ describe('client handling of tfPartialPayments', function () {
const partial = { ...rippled.tx.Payment, result: partialPaymentXRP }
const mockResponse = rippled.account_tx.normal
mockResponse.result.transactions.push({
tx: partial.result,
tx_json: partial.result.tx_json,
meta: partial.result.meta,
validated: true,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we are mocking the response
} as any)
testContext.mockRippled!.addResponse('account_tx', mockResponse)
@@ -138,7 +141,7 @@ describe('client handling of tfPartialPayments', function () {
it('transaction_entry with XRP tfPartialPayment', async function () {
const mockResponse = cloneDeep(rippled.transaction_entry)
mockResponse.result.tx_json.Amount = '1000'
mockResponse.result.tx_json.DeliverMax = '1000'
testContext.mockRippled!.addResponse('transaction_entry', mockResponse)
const resp = await testContext.client.request({
command: 'transaction_entry',

View File

@@ -91,7 +91,7 @@
"TransactionIndex": 12,
"TransactionResult": "tesSUCCESS"
},
"tx": {
"tx_json": {
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"DestinationTag": 13,
@@ -169,7 +169,7 @@
"TransactionIndex": 59,
"TransactionResult": "tesSUCCESS"
},
"tx": {
"tx_json": {
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Authorize": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "10",

View File

@@ -1,18 +1,20 @@
{
"Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"Amount": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "10"
"tx_json": {
"Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"DeliverMax": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "10"
},
"Destination": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"Fee": "10000",
"Flags": 131072,
"Sequence": 23295,
"SigningPubKey": "02B205F4B92351AC0EEB04254B636F4C49EF922CFA3CAAD03C6477DA1E04E94B53",
"TransactionType": "Payment",
"TxnSignature": "3045022100FAF247A836D601DE74A515B2AADE31186D8B0DA9C23DE489E09753F5CF4BB81F0220477C5B5BC3AC89F2347744F9E00CCA62267E198489D747578162C4C7D156211D",
"hash": "A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A"
},
"Destination": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"Fee": "10000",
"Flags": 131072,
"Sequence": 23295,
"SigningPubKey": "02B205F4B92351AC0EEB04254B636F4C49EF922CFA3CAAD03C6477DA1E04E94B53",
"TransactionType": "Payment",
"TxnSignature": "3045022100FAF247A836D601DE74A515B2AADE31186D8B0DA9C23DE489E09753F5CF4BB81F0220477C5B5BC3AC89F2347744F9E00CCA62267E198489D747578162C4C7D156211D",
"hash": "A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A",
"meta": {
"AffectedNodes": [
{

View File

@@ -1,14 +1,16 @@
{
"Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"Amount": "2000000",
"Destination": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"Fee": "10000",
"Flags": 131072,
"Sequence": 23295,
"SigningPubKey": "02B205F4B92351AC0EEB04254B636F4C49EF922CFA3CAAD03C6477DA1E04E94B53",
"TransactionType": "Payment",
"TxnSignature": "3045022100FAF247A836D601DE74A515B2AADE31186D8B0DA9C23DE489E09753F5CF4BB81F0220477C5B5BC3AC89F2347744F9E00CCA62267E198489D747578162C4C7D156211D",
"hash": "A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A",
"tx_json": {
"Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"DeliverMax": "2000000",
"Destination": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"Fee": "10000",
"Flags": 131072,
"Sequence": 23295,
"SigningPubKey": "02B205F4B92351AC0EEB04254B636F4C49EF922CFA3CAAD03C6477DA1E04E94B53",
"TransactionType": "Payment",
"TxnSignature": "3045022100FAF247A836D601DE74A515B2AADE31186D8B0DA9C23DE489E09753F5CF4BB81F0220477C5B5BC3AC89F2347744F9E00CCA62267E198489D747578162C4C7D156211D",
"hash": "A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A"
},
"meta": {
"AffectedNodes": [
{

View File

@@ -50,7 +50,7 @@
},
"tx_json": {
"Account": "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK",
"Amount": "104169972",
"DeliverMax": "104169972",
"Destination": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh",
"DestinationTag": 109735445,
"Fee": "6000",

View File

@@ -3,41 +3,43 @@
"status": "success",
"type": "response",
"result": {
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"Amount": {
"currency": "USD",
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"value": "0.001"
"tx_json": {
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"DeliverMax": {
"currency": "USD",
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"value": "0.001"
},
"Destination": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"Fee": "10",
"Flags": 0,
"Paths": [
[
{
"currency": "USD",
"issuer": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"type": 48,
"type_hex": "0000000000000030"
},
{
"account": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"currency": "USD",
"issuer": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"type": 49,
"type_hex": "0000000000000031"
}
]
],
"SendMax": "1112209",
"Sequence": 4,
"SigningPubKey": "02BC8C02199949B15C005B997E7C8594574E9B02BA2D0628902E0532989976CF9D",
"TransactionType": "Payment",
"TxnSignature": "304502204EE3E9D1B01D8959B08450FCA9E22025AF503DEF310E34A93863A85CAB3C0BC5022100B61F5B567F77026E8DEED89EED0B7CAF0E6C96C228A2A65216F0DC2D04D52083",
"date": 416447810,
"inLedger": 348860,
"ledger_index": 348860,
"hash": "F4AB442A6D4CBB935D66E1DA7309A5FC71C7143ED4049053EC14E3875B0CF9BF"
},
"Destination": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"Fee": "10",
"Flags": 0,
"Paths": [
[
{
"currency": "USD",
"issuer": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"type": 48,
"type_hex": "0000000000000030"
},
{
"account": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"currency": "USD",
"issuer": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"type": 49,
"type_hex": "0000000000000031"
}
]
],
"SendMax": "1112209",
"Sequence": 4,
"SigningPubKey": "02BC8C02199949B15C005B997E7C8594574E9B02BA2D0628902E0532989976CF9D",
"TransactionType": "Payment",
"TxnSignature": "304502204EE3E9D1B01D8959B08450FCA9E22025AF503DEF310E34A93863A85CAB3C0BC5022100B61F5B567F77026E8DEED89EED0B7CAF0E6C96C228A2A65216F0DC2D04D52083",
"date": 416447810,
"hash": "F4AB442A6D4CBB935D66E1DA7309A5FC71C7143ED4049053EC14E3875B0CF9BF",
"inLedger": 348860,
"ledger_index": 348860,
"meta": {
"AffectedNodes": [
{

View File

@@ -1,7 +1,7 @@
To run integration tests:
1. Run rippled in standalone node, either in a docker container (preferred) or by installing rippled.
* Go to the top-level of the `xrpl.js` repo, just above the `packages` folder.
* With docker, run `docker run -p 6006:6006 --interactive -t --volume $PWD/.ci-config:/config/ xrpllabsofficial/1.12.0-b1 -a --start`
* With docker, run `docker run -p 6006:6006 --interactive -t --volume $PWD/.ci-config:/opt/ripple/etc/ --platform linux/amd64 rippleci/rippled:2.2.0-b3 /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg`
* Or [download and build rippled](https://xrpl.org/install-rippled.html) and run `./rippled -a --start`
* If you'd like to use the latest rippled amendments, you should modify your `rippled.cfg` file to enable amendments in the `[amendments]` section. You can view `.ci-config/rippled.cfg` in the top level folder as an example of this.
2. Run `npm run test:integration` or `npm run test:browser`

View File

@@ -80,4 +80,105 @@ describe('account_info', function () {
},
TIMEOUT,
)
it(
'uses api_version 1',
async () => {
const request: AccountInfoRequest = {
command: 'account_info',
account: testContext.wallet.classicAddress,
strict: true,
ledger_index: 'validated',
api_version: 1,
}
const response = await testContext.client.request(request)
const expected = {
id: 0,
result: {
account_data: {
Account: testContext.wallet.classicAddress,
Balance: '400000000',
Flags: 0,
LedgerEntryType: 'AccountRoot',
OwnerCount: 0,
PreviousTxnID:
'19A8211695785A3A02C1C287D93C2B049E83A9CD609825E721052D63FF4F0EC8',
PreviousTxnLgrSeq: 582,
Sequence: 283,
index:
'BD4815E6EB304136E6044F778FB68D4E464CC8DFC59B8F6CC93D90A3709AE194',
},
ledger_hash:
'F0DEEC46A7185BBB535517EE38CF2025973022D5B0532B36407F492521FDB0C6',
ledger_index: 582,
validated: true,
},
type: 'response',
}
assert.equal(response.type, expected.type)
assert.equal(response.result.validated, expected.result.validated)
assert.equal(typeof response.result.ledger_index, 'number')
assert.equal(typeof response.result.account_data.PreviousTxnID, 'string')
assert.equal(typeof response.result.account_data.index, 'string')
assert.equal(
typeof response.result.account_data.PreviousTxnLgrSeq,
'number',
)
assert.equal(typeof response.result.account_data.Sequence, 'number')
assert.deepEqual(
omit(response.result.account_data, [
'PreviousTxnID',
'PreviousTxnLgrSeq',
'Sequence',
'index',
]),
omit(expected.result.account_data, [
'PreviousTxnID',
'PreviousTxnLgrSeq',
'Sequence',
'index',
]),
)
},
TIMEOUT,
)
it(
'signer_list using api_version 1',
async () => {
const request: AccountInfoRequest = {
command: 'account_info',
account: testContext.wallet.classicAddress,
strict: true,
ledger_index: 'validated',
signer_lists: true,
api_version: 1,
}
const response = await testContext.client.request<AccountInfoRequest, 1>(
request,
)
expect(response.result.account_data.signer_lists).toEqual([])
// @ts-expect-error -- signer_lists is expected to be undefined
expect(response.result.signer_lists).toBeUndefined()
},
TIMEOUT,
)
it(
'signer_list using api_version 2',
async () => {
const request: AccountInfoRequest = {
command: 'account_info',
account: testContext.wallet.classicAddress,
strict: true,
ledger_index: 'validated',
signer_lists: true,
}
const response = await testContext.client.request(request)
// @ts-expect-error -- signer_lists is expected to be undefined
expect(response.result.account_data.signer_lists).toBeUndefined()
expect(response.result.signer_lists).toEqual([])
},
TIMEOUT,
)
})

View File

@@ -32,6 +32,102 @@ describe('account_tx', function () {
ledger_index: 'validated',
}
const response = await testContext.client.request(request)
const expected = {
result: {
account: testContext.wallet.classicAddress,
limit: 400,
transactions: [
{
tx_json: {
Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
DeliverMax: '400000000',
Destination: testContext.wallet.classicAddress,
Fee: '12',
Flags: 0,
LastLedgerSequence: 1753,
Sequence: 843,
SigningPubKey:
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020',
TransactionType: 'Payment',
TxnSignature:
'30440220693D244BC13967E3DA67BDC974096784ED03DD4ACE6F36645E5176988452AFCF02200F8AB172432913899F27EC5523829AEDAD00CC2445690400E294EDF652A85945',
date: 685747005,
hash: '2E68BC15813B4A836FAC4D80E42E6FDA6410E99AB973937DEA5E6C2E9A116BAB',
ledger_index: 1734,
},
},
],
},
type: 'response',
}
assert.equal(response.type, expected.type)
assert.equal(response.result.account, expected.result.account)
assert.equal(
(response.result.transactions[0].meta as TransactionMetadata<Payment>)
.TransactionResult,
'tesSUCCESS',
)
assert.equal(
typeof response.result.transactions[0].tx_json?.LastLedgerSequence,
'number',
)
assert.equal(
typeof response.result.transactions[0].tx_json?.Sequence,
'number',
)
assert.equal(
typeof response.result.transactions[0].tx_json?.SigningPubKey,
'string',
)
assert.equal(
typeof response.result.transactions[0].tx_json?.TxnSignature,
'string',
)
assert.equal(
typeof response.result.transactions[0].tx_json?.Fee,
'string',
)
assert.equal(typeof response.result.transactions[0].hash, 'string')
assert.equal(
typeof response.result.transactions[0].tx_json?.ledger_index,
'number',
)
const responseTx = response.result.transactions[0].tx_json as Payment
const expectedTx = expected.result.transactions[0].tx_json
assert.deepEqual(
[
responseTx.Flags,
responseTx.TransactionType,
responseTx.Account,
// @ts-expect-error -- DeliverMax is a valid field on Payment response
responseTx.DeliverMax,
responseTx.Destination,
],
[
expectedTx.Flags,
expectedTx.TransactionType,
expectedTx.Account,
expectedTx.DeliverMax,
expectedTx.Destination,
],
)
},
TIMEOUT,
)
it(
'uses api_version 1',
async () => {
const request: AccountTxRequest = {
command: 'account_tx',
account: testContext.wallet.classicAddress,
ledger_index: 'validated',
api_version: 1,
}
const response = await testContext.client.request<AccountTxRequest, 1>(
request,
)
const expected = {
result: {
account: testContext.wallet.classicAddress,

View File

@@ -35,6 +35,7 @@ describe('book_offers', function () {
const response = await testContext.client.request(bookOffer)
const expectedResponse: BookOffersResponse = {
api_version: 2,
id: response.id,
type: 'response',
result: {

View File

@@ -35,6 +35,7 @@ describe('channel_verify', function () {
const response = await testContext.client.request(channelVerify)
const expectedResponse: ChannelVerifyResponse = {
api_version: 2,
id: response.id,
type: 'response',
result: {

View File

@@ -36,6 +36,7 @@ describe('deposit_authorized', function () {
const response = await testContext.client.request(depositAuthorized)
const expectedResponse: DepositAuthorizedResponse = {
api_version: 2,
id: response.id,
type: 'response',
result: {

View File

@@ -1,7 +1,7 @@
import { assert } from 'chai'
import { LedgerRequest, LedgerResponse } from '../../../src'
import { Ledger } from '../../../src/models/ledger'
import { LedgerRequest } from '../../../src'
import { Ledger, LedgerV1 } from '../../../src/models/ledger'
import serverUrl from '../serverUrl'
import {
setupClient,
@@ -29,6 +29,7 @@ describe('ledger', function () {
}
const expected = {
api_version: 2,
id: 0,
result: {
ledger: {
@@ -45,7 +46,68 @@ describe('ledger', function () {
type: 'response',
}
const ledgerResponse: LedgerResponse = await testContext.client.request(
const ledgerResponse = await testContext.client.request(ledgerRequest)
assert.equal(ledgerResponse.type, expected.type)
assert.equal(ledgerResponse.result.validated, expected.result.validated)
assert.typeOf(ledgerResponse.result.ledger_hash, 'string')
assert.typeOf(ledgerResponse.result.ledger_index, 'number')
const ledger = ledgerResponse.result.ledger as Ledger & {
accepted: boolean
hash: string
seqNum: string
}
assert.equal(ledger.closed, true)
const stringTypes = [
'account_hash',
'close_time_human',
'ledger_hash',
'parent_hash',
'total_coins',
'transaction_hash',
]
stringTypes.forEach((strType) => assert.typeOf(ledger[strType], 'string'))
const numTypes = [
'close_flags',
'close_time',
'close_time_resolution',
'ledger_index',
'parent_close_time',
]
numTypes.forEach((numType) => assert.typeOf(ledger[numType], 'number'))
},
TIMEOUT,
)
it(
'uses api_version 1',
async () => {
const ledgerRequest: LedgerRequest = {
command: 'ledger',
ledger_index: 'validated',
api_version: 1,
}
const expected = {
id: 0,
result: {
ledger: {
accepted: true,
account_hash: 'string',
close_flags: 0,
close_time: 0,
close_time_human: 'string',
},
ledger_hash: 'string',
ledger_index: 1,
validated: true,
},
type: 'response',
}
const ledgerResponse = await testContext.client.request<LedgerRequest, 1>(
ledgerRequest,
)
@@ -55,7 +117,7 @@ describe('ledger', function () {
assert.typeOf(ledgerResponse.result.ledger_hash, 'string')
assert.typeOf(ledgerResponse.result.ledger_index, 'number')
const ledger = ledgerResponse.result.ledger as Ledger & {
const ledger = ledgerResponse.result.ledger as LedgerV1 & {
accepted: boolean
hash: string
seqNum: string

View File

@@ -40,6 +40,7 @@ describe('ledger_entry', function () {
)
const expectedResponse: LedgerEntryResponse = {
api_version: 2,
id: ledgerEntryResponse.id,
type: 'response',
result: {

View File

@@ -41,6 +41,7 @@ describe('path_find', function () {
const response = await testContext.client.request(pathFind)
const expectedResponse: PathFindResponse = {
api_version: 2,
id: response.id,
type: 'response',
result: {
@@ -98,6 +99,7 @@ describe('path_find', function () {
const response = await testContext.client.request(pathFind)
const expectedResponse: PathFindResponse = {
api_version: 2,
id: response.id,
type: 'response',
result: {

View File

@@ -35,6 +35,7 @@ describe('ripple_path_find', function () {
const response = await testContext.client.request(ripplePathFind)
const expectedResponse: RipplePathFindResponse = {
api_version: 2,
id: response.id,
type: 'response',
result: {

View File

@@ -54,6 +54,7 @@ describe('submit', function () {
)
const expectedResponse: SubmitResponse = {
api_version: 2,
id: submitResponse.id,
type: 'response',
result: {

View File

@@ -8,6 +8,8 @@ import {
Transaction,
SubmitMultisignedResponse,
hashes,
SubmitMultisignedRequest,
SubmitMultisignedV1Response,
} from '../../../src'
import { convertStringToHex } from '../../../src/utils'
import { multisign } from '../../../src/Wallet/signer'
@@ -88,6 +90,81 @@ describe('submit_multisigned', function () {
await verifySubmittedTransaction(testContext.client, multisigned)
const expectedResponse: SubmitMultisignedResponse = {
api_version: 2,
id: submitResponse.id,
type: 'response',
result: {
engine_result: 'tesSUCCESS',
engine_result_code: 0,
engine_result_message:
'The transaction was applied. Only final in a validated ledger.',
tx_blob: multisigned,
tx_json: {
...(decode(multisigned) as unknown as Transaction),
},
hash: hashSignedTx(multisigned),
},
}
assert.deepEqual(submitResponse, expectedResponse)
},
TIMEOUT,
)
it(
'submit_multisigned transaction using api_version 1',
async () => {
const client: Client = testContext.client
const signerWallet1 = await generateFundedWallet(testContext.client)
const signerWallet2 = await generateFundedWallet(testContext.client)
// set up the multisigners for the account
const signerListSet: SignerListSet = {
TransactionType: 'SignerListSet',
Account: testContext.wallet.classicAddress,
SignerEntries: [
{
SignerEntry: {
Account: signerWallet1.classicAddress,
SignerWeight: 1,
},
},
{
SignerEntry: {
Account: signerWallet2.classicAddress,
SignerWeight: 1,
},
},
],
SignerQuorum: 2,
}
await testTransaction(
testContext.client,
signerListSet,
testContext.wallet,
)
// try to multisign
const accountSet: AccountSet = {
TransactionType: 'AccountSet',
Account: testContext.wallet.classicAddress,
Domain: convertStringToHex('example.com'),
}
const accountSetTx = await client.autofill(accountSet, 2)
const signed1 = signerWallet1.sign(accountSetTx, true)
const signed2 = signerWallet2.sign(accountSetTx, true)
const multisigned = multisign([signed1.tx_blob, signed2.tx_blob])
const submitResponse = await client.request<SubmitMultisignedRequest, 1>({
command: 'submit_multisigned',
tx_json: decode(multisigned) as unknown as Transaction,
api_version: 1,
})
await ledgerAccept(client)
assert.strictEqual(submitResponse.result.engine_result, 'tesSUCCESS')
await verifySubmittedTransaction(testContext.client, multisigned)
const expectedResponse: SubmitMultisignedV1Response = {
api_version: 1,
id: submitResponse.id,
type: 'response',
result: {

View File

@@ -1,6 +1,13 @@
import { assert } from 'chai'
import { AccountSet, hashes, SubmitResponse, TxResponse } from '../../../src'
import {
AccountSet,
hashes,
SubmitResponse,
TxRequest,
TxResponse,
TxV1Response,
} from '../../../src'
import { convertStringToHex } from '../../../src/utils'
import serverUrl from '../serverUrl'
import {
@@ -45,17 +52,20 @@ describe('tx', function () {
})
const expectedResponse: TxResponse = {
api_version: 2,
id: txResponse.id,
type: 'response',
result: {
...accountSet,
Fee: txResponse.result.Fee,
Flags: 0,
LastLedgerSequence: txResponse.result.LastLedgerSequence,
Sequence: txResponse.result.Sequence,
SigningPubKey: testContext.wallet.publicKey,
TxnSignature: txResponse.result.TxnSignature,
hash: hashSignedTx(response.result.tx_blob),
tx_json: {
...accountSet,
Fee: txResponse.result.tx_json.Fee,
Flags: 0,
LastLedgerSequence: txResponse.result.tx_json.LastLedgerSequence,
Sequence: txResponse.result.tx_json.Sequence,
SigningPubKey: testContext.wallet.publicKey,
TxnSignature: txResponse.result.tx_json.TxnSignature,
},
validated: false,
},
}
@@ -64,4 +74,50 @@ describe('tx', function () {
},
TIMEOUT,
)
it(
'uses api_version 1',
async () => {
const account = testContext.wallet.classicAddress
const accountSet: AccountSet = {
TransactionType: 'AccountSet',
Account: account,
Domain: convertStringToHex('example.com'),
}
const response: SubmitResponse = await testContext.client.submit(
accountSet,
{
wallet: testContext.wallet,
},
)
const hash = hashSignedTx(response.result.tx_blob)
const txV1Response = await testContext.client.request<TxRequest, 1>({
command: 'tx',
transaction: hash,
api_version: 1,
})
const expectedResponse: TxV1Response = {
api_version: 1,
id: txV1Response.id,
type: 'response',
result: {
...accountSet,
Fee: txV1Response.result.Fee,
Flags: 0,
LastLedgerSequence: txV1Response.result.LastLedgerSequence,
Sequence: txV1Response.result.Sequence,
SigningPubKey: testContext.wallet.publicKey,
TxnSignature: txV1Response.result.TxnSignature,
hash: hashSignedTx(response.result.tx_blob),
validated: false,
},
}
assert.deepEqual(txV1Response, expectedResponse)
},
TIMEOUT,
)
})

View File

@@ -26,6 +26,7 @@ describe('Utility method integration tests', function () {
command: 'ping',
})
const expected: unknown = {
api_version: 2,
result: { role: 'admin', unlimited: true },
type: 'response',
}
@@ -41,6 +42,7 @@ describe('Utility method integration tests', function () {
command: 'random',
})
const expected = {
api_version: 2,
id: 0,
result: {
random: '[random string of 64 bytes]',

View File

@@ -158,8 +158,7 @@ export async function setupBridge(client: Client): Promise<TestBridge> {
account: doorAccount.classicAddress,
signer_lists: true,
})
const signerListInfo =
signerAccountInfoResponse.result.account_data.signer_lists?.[0]
const signerListInfo = signerAccountInfoResponse.result.signer_lists?.[0]
assert.deepEqual(
signerListInfo?.SignerEntries,
signerTx.SignerEntries,

View File

@@ -71,7 +71,7 @@ describe('EscrowCancel', function () {
command: 'tx',
transaction: accountObjects[0].PreviousTxnID,
})
).result.Sequence
).result.tx_json.Sequence
if (!sequence) {
throw new Error('sequence did not exist')

View File

@@ -64,7 +64,7 @@ describe('EscrowFinish', function () {
command: 'tx',
transaction: accountObjects[0].PreviousTxnID,
})
).result.Sequence
).result.tx_json.Sequence
const finishTx: EscrowFinish = {
TransactionType: 'EscrowFinish',

View File

@@ -85,7 +85,7 @@ describe('NFTokenMint', function () {
assert.equal(
nftokenID,
getNFTokenID(binaryTxResponse.result.meta) ?? 'undefined',
getNFTokenID(binaryTxResponse.result.meta_blob) ?? 'undefined',
`getNFTokenID produced a different outcome when decoding the metadata in binary format.`,
)
},

View File

@@ -50,8 +50,7 @@ describe('SignerListSet', function () {
account: testContext.wallet.classicAddress,
signer_lists: true,
})
const signerListInfo =
accountInfoResponse.result.account_data.signer_lists?.[0]
const signerListInfo = accountInfoResponse.result.signer_lists?.[0]
assert.deepEqual(
signerListInfo?.SignerEntries,
tx.SignerEntries,

View File

@@ -191,8 +191,7 @@ describe('XChainCreateBridge', function () {
account: testContext.wallet.classicAddress,
signer_lists: true,
})
const signerListInfo =
signerAccountInfoResponse.result.account_data.signer_lists?.[0]
const signerListInfo = signerAccountInfoResponse.result.signer_lists?.[0]
assert.deepEqual(
signerListInfo?.SignerEntries,
signerTx.SignerEntries,

View File

@@ -198,11 +198,12 @@ export async function verifySubmittedTransaction(
const decodedTx: any = typeof tx === 'string' ? decode(tx) : tx
if (decodedTx.TransactionType === 'Payment') {
decodedTx.DeliverMax = decodedTx.Amount
delete decodedTx.Amount
}
assert(data.result)
assert.deepEqual(
omit(data.result, [
omit(data.result.tx_json, [
'ctid',
'date',
'hash',