Compare commits

..

21 Commits

Author SHA1 Message Date
Omar Khan
275c95752b release: v4.0.0 (#2739)
* update HISTORY

* update package.json
2024-07-16 14:20:55 -04:00
Chenna Keshava B S
3bc3c2029b feat: Upgrade to Node 18 and remove cross-fetch (#2678) (#2737)
* feat: Upgrade to Node 18 and remove cross-fetch (#2678)

BREAKING CHANGE: fetch now relies on the native javascript environment in browsers and node.js

Co-authored-by: justinr1234 <justinr1234@gmail.com>
2024-07-16 11:04:47 -07:00
tequ
c9ef96e0a2 Add transaction hash to ledger command response (#2717)
* add transaction hash to ledger command response

* Update packages/xrpl/src/models/ledger/Ledger.ts

* Update HISTORY.md

* delete unnesessary space
2024-07-12 09:57:24 -04:00
Mayukha Vadari
00f1a6bcdd feat: add feature RPC (#2719)
* add feature RPC

* export, add tests

* update history

* fix test

* update models

* update feature models to correctly handle both cases

* fix models/tests

* fix test, improve type

* undo type change

* fix test
2024-07-10 13:18:16 -04:00
dependabot[bot]
3858a09e1f build(deps-dev): bump ts-jest from 29.1.5 to 29.2.0 (#2729) 2024-07-09 15:25:33 +00:00
dependabot[bot]
3aaf526107 build(deps): bump ws from 8.17.1 to 8.18.0 (#2730) 2024-07-09 15:15:50 +00:00
Mayukha Vadari
1460cf5026 feat: add support for the fixPreviousTxnID amendment (#2720)
* add support for fixPreviousTxnID

* update history
2024-07-08 17:24:38 -04:00
dependabot[bot]
7e733c4446 build(deps-dev): bump @types/lodash from 4.17.4 to 4.17.6 (#2718)
Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.17.4 to 4.17.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

---
updated-dependencies:
- dependency-name: "@types/lodash"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 08:45:32 -05:00
dependabot[bot]
735ac2eb07 build(deps): bump @noble/curves from 1.4.0 to 1.4.2 (#2723) 2024-07-03 17:27:31 +00:00
dependabot[bot]
c79a5db8f2 build(deps-dev): bump typedoc from 0.26.2 to 0.26.3 (#2722)
Bumps [typedoc](https://github.com/TypeStrong/TypeDoc) from 0.26.2 to 0.26.3.
- [Release notes](https://github.com/TypeStrong/TypeDoc/releases)
- [Changelog](https://github.com/TypeStrong/typedoc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/TypeDoc/compare/v0.26.2...v0.26.3)

---
updated-dependencies:
- dependency-name: typedoc
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 11:02:18 -05:00
Omar Khan
8e2aba3b78 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
2024-06-28 08:26:21 -04:00
Chenna Keshava B S
39fed49654 Aliter: Implement DeliverMax alias in Payment transactions, through autofill method (#2689)
Co-authored-by: Omar Khan <khancodegt@gmail.com>
2024-06-27 09:46:49 -07:00
dependabot[bot]
a0678857a1 build(deps-dev): bump webpack from 5.91.0 to 5.92.1 (#2713) 2024-06-25 14:18:08 +00:00
dependabot[bot]
38e2091fd2 build(deps-dev): bump ts-jest from 29.1.4 to 29.1.5 (#2709) 2024-06-25 14:11:44 +00:00
dependabot[bot]
a528d6632a build(deps-dev): bump typedoc from 0.25.0 to 0.26.2 (#2712) 2024-06-25 13:59:07 +00:00
dependabot[bot]
ab081e7db9 build(deps): bump @scure/base from 1.1.6 to 1.1.7 (#2708)
Bumps [@scure/base](https://github.com/paulmillr/scure-base) from 1.1.6 to 1.1.7.
- [Release notes](https://github.com/paulmillr/scure-base/releases)
- [Commits](https://github.com/paulmillr/scure-base/compare/1.1.6...1.1.7)

---
updated-dependencies:
- dependency-name: "@scure/base"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Chenna Keshava B S <21219765+ckeshava@users.noreply.github.com>
2024-06-24 19:03:54 -04:00
dependabot[bot]
493c56c0fa build(deps): bump ws from 8.17.0 to 8.17.1 (#2707)
Bumps [ws](https://github.com/websockets/ws) from 8.17.0 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.17.0...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Chenna Keshava B S <21219765+ckeshava@users.noreply.github.com>
Co-authored-by: justinr1234 <justinr1234@gmail.com>
2024-06-24 15:07:04 -07:00
dependabot[bot]
c73b2c5a86 build(deps-dev): bump braces from 3.0.2 to 3.0.3 (#2706)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Chenna Keshava B S <21219765+ckeshava@users.noreply.github.com>
2024-06-24 17:59:39 -04:00
Chenna Keshava B S
02e2b0e48e remove references to the hooks testnet faucet in xrpl.js codebase (#2711) 2024-06-20 17:18:11 -07:00
Zhiyuan Wang
d3b03a536d Add nfts_by_issuer data type (#2694)
* add nfts_by_issuer data type

* update HISTORY.md

* update HISTORY.md

* added to index and change field name

* change to added in history

* reformat change in history

* reformat history on bfts_by_issuer

---------

Authored-by: Kassaking <kassaking7@gmail.com>
2024-06-11 14:43:47 -04:00
dependabot[bot]
036f1f9850 build(deps-dev): bump @types/lodash from 4.17.1 to 4.17.4 (#2697) 2024-06-05 16:30:17 +00:00
74 changed files with 3040 additions and 15049 deletions

View File

@@ -19,7 +19,7 @@ jobs:
strategy:
matrix:
node-version: [16.x]
node-version: [18.x]
steps:
- uses: actions/checkout@v3
@@ -60,7 +60,7 @@ jobs:
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3
@@ -101,7 +101,7 @@ jobs:
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3
@@ -153,7 +153,7 @@ jobs:
strategy:
matrix:
node-version: [16.x]
node-version: [18.x]
steps:
- uses: actions/checkout@v3
@@ -205,7 +205,7 @@ jobs:
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3

View File

@@ -12,7 +12,7 @@
### Requirements
We use Node v16 for development - that is the version that our linters require.
We use Node v18 for development - that is the version that our linters require.
You must also use `npm` v7. You can check your `npm` version with:
```bash
@@ -104,6 +104,8 @@ The 4 packages currently here are:
2. ripple-binary-codec - A library for serializing and deserializing transactions for the ledger.
3. ripple-keypairs - A library for generating and using cryptographic keypairs.
4. ripple-address-codec - A library for encoding and decoding XRP Ledger addresses and seeds.
5. isomorphic - A collection of isomorphic implementations of crypto and utility functions.
6. secret-numbers - Generate XRPL Accounts with a number-based secret: 8 chunks of 6 digits.
Each package has it's own README which dives deeper into what it's main purpose is, and the core functionality it offers.
They also run tests independently as they were originally in separate repositories.

View File

@@ -19,13 +19,13 @@ See the full reference documentation for all classes, methods, and utilities.
4. Subscribing to changes in the ledger ([Ex. ledger, transactions, & more...](https://xrpl.org/subscribe.html))
5. Parsing ledger data into more convenient formats ([`xrpToDrops`](https://js.xrpl.org/functions/xrpToDrops.html) and [`rippleTimeToISOTime`](https://js.xrpl.org/functions/rippleTimeToISOTime.html))
All of which works in Node.js (tested for v16+) & web browsers (tested for Chrome).
All of which works in Node.js (tested for v18+) & web browsers (tested for Chrome).
# Quickstart
### Requirements
+ **[Node.js v16](https://nodejs.org/)** is recommended. We also support v18 and v20. Other versions may work but are not frequently tested.
+ **[Node.js v18](https://nodejs.org/)** is recommended. We also support v20. Other versions may work but are not frequently tested.
### Installing xrpl.js
@@ -56,7 +56,7 @@ async function main() {
});
console.log(response);
client.disconnect();
await client.disconnect();
}
main();
```

16262
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@
"@types/chai": "^4.2.21",
"@types/jest": "^29.2.2",
"@types/lodash": "^4.14.136",
"@types/node": "^16.18.38",
"@types/node": "^18.19.29",
"@types/ws": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
@@ -64,7 +64,7 @@
"./packages/*"
],
"engines": {
"node": ">=16.0.0",
"node": ">=18.0.0",
"npm": ">=7.10.0 < 10.0.0"
}
}

View File

@@ -33,7 +33,7 @@
"ws": "^8.13.0"
},
"devDependencies": {
"@types/node": "^16.18.38",
"@types/node": "^18.18.38",
"@types/ws": "^8.5.6"
},
"repository": {
@@ -43,6 +43,6 @@
"license": "ISC",
"prettier": "@xrplf/prettier-config",
"engines": {
"node": ">=16.0.0"
"node": ">=18.0.0"
}
}

View File

@@ -34,6 +34,6 @@
},
"prettier": "@xrplf/prettier-config",
"engines": {
"node": ">= 16"
"node": ">= 18"
}
}

View File

@@ -41,6 +41,6 @@
"readmeFilename": "README.md",
"prettier": "@xrplf/prettier-config",
"engines": {
"node": ">= 16"
"node": ">= 18"
}
}

View File

@@ -36,6 +36,6 @@
"license": "ISC",
"prettier": "@xrplf/prettier-config",
"engines": {
"node": ">= 16"
"node": ">= 18"
}
}

View File

@@ -2,12 +2,26 @@
Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xrpl-announce) for release announcements. We recommend that xrpl.js (ripple-lib) users stay up-to-date with the latest stable release.
## Unreleased
## 4.0.0 (2024-07-15)
### BREAKING CHANGES
* Use rippled api_version v2 as default while maintaining support for v1.
### Added
* Add `nfts_by_issuer` clio-only API definition
* Support for the `fixPreviousTxnID` amendment.
* Support for the user version of the `feature` RPC.
* Add `hash` field to `ledger` command response
### Removed
* Remove references to the Hooks testnet faucet in the xrpl.js code repository.
## 3.1.0 (2024-06-03)
### BREAKING CHANGES
* Small fix in the API to use a new flag name `tfNoDirectRipple` instead of the existing flag name `tfNoRippleDirect`
* Node.js has been upgraded to a minimum version of 18
* `fetch` now relies on the native javascript environment in browsers and Node.js
### Added
* Support for the Price Oracles amendment (XLS-47).

View File

@@ -1,6 +1,6 @@
{
"name": "xrpl",
"version": "3.1.0",
"version": "4.0.0",
"license": "ISC",
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
"files": [
@@ -27,14 +27,13 @@
"@xrplf/isomorphic": "^1.0.1",
"@xrplf/secret-numbers": "^1.0.0",
"bignumber.js": "^9.0.0",
"cross-fetch": "^4.0.0",
"eventemitter3": "^5.0.1",
"ripple-address-codec": "^5.0.0",
"ripple-binary-codec": "^2.1.0",
"ripple-keypairs": "^2.0.0"
},
"devDependencies": {
"@types/node": "^16.18.38",
"@types/node": "^18.18.38",
"eventemitter3": "^5.0.1",
"https-proxy-agent": "^7.0.1",
"karma": "^6.4.1",
@@ -44,7 +43,7 @@
"lodash": "^4.17.4",
"react": "^18.2.0",
"run-s": "^0.0.0",
"typedoc": "0.25.0",
"typedoc": "0.26.3",
"ws": "^8.14.2"
},
"resolutions": {
@@ -87,6 +86,6 @@
"xrpl"
],
"engines": {
"node": ">=16.0.0"
"node": ">=18.0.0"
}
}

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

@@ -14,13 +14,11 @@ export interface FaucetWallet {
export enum FaucetNetwork {
Testnet = 'faucet.altnet.rippletest.net',
Devnet = 'faucet.devnet.rippletest.net',
HooksV3Testnet = 'hooks-testnet-v3.xrpl-labs.com',
}
export const FaucetNetworkPaths: Record<string, string> = {
[FaucetNetwork.Testnet]: '/accounts',
[FaucetNetwork.Devnet]: '/accounts',
[FaucetNetwork.HooksV3Testnet]: '/accounts',
}
/**
@@ -33,10 +31,6 @@ export const FaucetNetworkPaths: Record<string, string> = {
export function getFaucetHost(client: Client): FaucetNetwork | undefined {
const connectionUrl = client.url
if (connectionUrl.includes('hooks-testnet-v3')) {
return FaucetNetwork.HooksV3Testnet
}
// 'altnet' for Ripple Testnet server and 'testnet' for XRPL Labs Testnet server
if (connectionUrl.includes('altnet') || connectionUrl.includes('testnet')) {
return FaucetNetwork.Testnet

View File

@@ -1,4 +1,3 @@
import fetch from 'cross-fetch'
import { isValidClassicAddress } from 'ripple-address-codec'
import type { Client } from '../client'

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.
*
@@ -226,7 +237,7 @@ class Client extends EventEmitter<EventTypes> {
* const client = new Client('wss://s.altnet.rippletest.net:51233')
* ```
*/
// eslint-disable-next-line max-lines-per-function -- okay because we have to set up all the connection handlers
/* eslint-disable max-lines-per-function -- the constructor requires more lines to implement the logic */
public constructor(server: string, options: ClientOptions = {}) {
super()
if (typeof server !== 'string' || !/wss?(?:\+unix)?:\/\//u.exec(server)) {
@@ -290,6 +301,7 @@ class Client extends EventEmitter<EventTypes> {
this.emit('path_find', path)
})
}
/* eslint-enable max-lines-per-function */
/**
* Get the url that the client is connected to.
@@ -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.
*
@@ -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,
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)
@@ -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
@@ -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`)
}
@@ -638,7 +654,10 @@ class Client extends EventEmitter<EventTypes> {
* @param signersCount - The expected number of signers for this transaction.
* Only used for multisigned transactions.
* @returns The autofilled transaction.
* @throws ValidationError If Amount and DeliverMax fields are not identical in a Payment Transaction
*/
// eslint-disable-next-line complexity -- handling Payment transaction API v2 requires more logic
public async autofill<T extends SubmittableTransaction>(
transaction: T,
signersCount?: number,
@@ -646,7 +665,6 @@ class Client extends EventEmitter<EventTypes> {
const tx = { ...transaction }
setValidAddresses(tx)
setTransactionFlagsToNumber(tx)
const promises: Array<Promise<void>> = []
@@ -666,6 +684,34 @@ class Client extends EventEmitter<EventTypes> {
promises.push(checkAccountDeleteBlockers(this, tx))
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
if (tx.TransactionType === 'Payment' && tx.DeliverMax != null) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- This is a valid null check for Amount
if (tx.Amount == null) {
// If only DeliverMax is provided, use it to populate the Amount field
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- DeliverMax is a known RPC-level property
tx.Amount = tx.DeliverMax
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- This is a valid null check for Amount
if (tx.Amount != null && tx.Amount !== tx.DeliverMax) {
return Promise.reject(
new ValidationError(
'PaymentTransaction: Amount and DeliverMax fields must be identical when both are provided',
),
)
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
delete tx.DeliverMax
}
return Promise.all(promises).then(() => tx)
}
@@ -909,7 +955,7 @@ class Client extends EventEmitter<EventTypes> {
* @param options.limit - Limit number of balances to return.
* @returns An array of XRP/non-XRP balances for the given account.
*/
// eslint-disable-next-line max-lines-per-function -- Longer definition is required for end users to see the definition.
/* eslint-disable max-lines-per-function -- getBalances requires more lines to implement logic */
public async getBalances(
address: string,
options: {
@@ -957,6 +1003,7 @@ class Client extends EventEmitter<EventTypes> {
)
return balances.slice(0, options.limit)
}
/* eslint-enable max-lines-per-function */
/**
* Fetch orderbook (buy/sell orders) between two currency pairs. This checks both sides of the orderbook

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,6 +1,6 @@
import { AuthAccount, Currency, IssuedCurrencyAmount } from '../common'
import { BaseLedgerEntry, MissingPreviousTxnID } from './BaseLedgerEntry'
import { BaseLedgerEntry, HasOptionalPreviousTxnID } from './BaseLedgerEntry'
export interface VoteSlot {
VoteEntry: {
@@ -15,7 +15,7 @@ export interface VoteSlot {
*
* @category Ledger Entries
*/
export default interface AMM extends BaseLedgerEntry, MissingPreviousTxnID {
export default interface AMM extends BaseLedgerEntry, HasOptionalPreviousTxnID {
LedgerEntryType: 'AMM'
/**
* The address of the special account that holds this AMM's assets.

View File

@@ -1,4 +1,4 @@
import { BaseLedgerEntry, MissingPreviousTxnID } from './BaseLedgerEntry'
import { BaseLedgerEntry, HasOptionalPreviousTxnID } from './BaseLedgerEntry'
/**
* The unique id for the Amendments object https://xrpl.org/amendments-object.html#amendments-id-format
@@ -26,7 +26,7 @@ export interface Majority {
*/
export default interface Amendments
extends BaseLedgerEntry,
MissingPreviousTxnID {
HasOptionalPreviousTxnID {
LedgerEntryType: 'Amendments'
/**
* Array of 256-bit amendment IDs for all currently-enabled amendments. If

View File

@@ -15,13 +15,17 @@ export interface HasPreviousTxnID {
PreviousTxnLgrSeq: number
}
export interface MissingPreviousTxnID {
export interface HasOptionalPreviousTxnID {
/**
* This field is missing on this object but is present on most other returned objects.
* The identifying hash of the transaction that most recently modified this
* object. This field was added in the `fixPreviousTxnID` amendment, so it
* may not be present in every object.
*/
PreviousTxnID: never
PreviousTxnID?: string
/**
* This field is missing on this object but is present on most other returned objects.
* The index of the ledger that contains the transaction that most recently
* modified this object. This field was added in the `fixPreviousTxnID`
* amendment, so it may not be present in every object.
*/
PreviousTxnLgrSeq: never
PreviousTxnLgrSeq?: number
}

View File

@@ -1,4 +1,4 @@
import { BaseLedgerEntry, MissingPreviousTxnID } from './BaseLedgerEntry'
import { BaseLedgerEntry, HasOptionalPreviousTxnID } from './BaseLedgerEntry'
/**
* The DirectoryNode object type provides a list of links to other objects in
@@ -8,7 +8,7 @@ import { BaseLedgerEntry, MissingPreviousTxnID } from './BaseLedgerEntry'
*/
export default interface DirectoryNode
extends BaseLedgerEntry,
MissingPreviousTxnID {
HasOptionalPreviousTxnID {
LedgerEntryType: 'DirectoryNode'
/**
* A bit-map of boolean flags enabled for this directory. Currently, the

View File

@@ -1,4 +1,4 @@
import { BaseLedgerEntry, MissingPreviousTxnID } from './BaseLedgerEntry'
import { BaseLedgerEntry, HasOptionalPreviousTxnID } from './BaseLedgerEntry'
/**
* The unique id for the FeeSettings object https://xrpl.org/feesettings.html#feesettings-id-format
@@ -26,7 +26,9 @@ export interface FeeSettingsPostAmendmentFields {
ReserveIncrementDrops: string
}
export interface FeeSettingsBase extends BaseLedgerEntry, MissingPreviousTxnID {
export interface FeeSettingsBase
extends BaseLedgerEntry,
HasOptionalPreviousTxnID {
LedgerEntryType: 'FeeSettings'
/**
* A bit-map of boolean flags for this object. No flags are defined for this type.

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
/**
@@ -61,5 +56,47 @@ export default interface Ledger {
* either JSON or binary depending on whether the request specified binary
* as true.
*/
transactions?: Array<Transaction & { metaData?: TransactionMetadata }>
transactions?: Array<
Transaction & {
hash: string
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

@@ -1,4 +1,4 @@
import { BaseLedgerEntry, MissingPreviousTxnID } from './BaseLedgerEntry'
import { BaseLedgerEntry } from './BaseLedgerEntry'
/**
* The LedgerHashes objects exist to make it possible to look up a previous
@@ -7,9 +7,7 @@ import { BaseLedgerEntry, MissingPreviousTxnID } from './BaseLedgerEntry'
*
* @category Ledger Entries
*/
export default interface LedgerHashes
extends BaseLedgerEntry,
MissingPreviousTxnID {
export default interface LedgerHashes extends BaseLedgerEntry {
LedgerEntryType: 'LedgerHashes'
/** The Ledger Index of the last entry in this object's Hashes array. */
LastLedgerSequence?: number

View File

@@ -1,4 +1,4 @@
import { BaseLedgerEntry, MissingPreviousTxnID } from './BaseLedgerEntry'
import { BaseLedgerEntry, HasOptionalPreviousTxnID } from './BaseLedgerEntry'
/**
* The unique id for the nUNL object https://xrpl.org/negativeunl.html#negativeunl-id-format
@@ -14,7 +14,7 @@ export const NEGATIVE_UNL_ID =
*/
export default interface NegativeUNL
extends BaseLedgerEntry,
MissingPreviousTxnID {
HasOptionalPreviousTxnID {
LedgerEntryType: 'NegativeUNL'
/**
* A list of trusted validators that are currently disabled.

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

@@ -0,0 +1,68 @@
import { BaseRequest, BaseResponse } from './baseMethod'
export interface FeatureAllRequest extends BaseRequest {
command: 'feature'
feature?: never
}
export interface FeatureOneRequest extends BaseRequest {
command: 'feature'
feature: string
}
/**
* The `feature` command returns information about amendments this server knows about, including whether they are enabled.
* Returns an {@link FeatureResponse}.
*
* @category Requests
*/
export type FeatureRequest = FeatureAllRequest | FeatureOneRequest
export interface FeatureAllResponse extends BaseResponse {
result: {
features: Record<
string,
{
/*
* Whether this amendment is currently enabled in the latest ledger.
*/
enabled: boolean
/*
* The human-readable name for this amendment, if known.
*/
name: string
supported: boolean
}
>
}
}
export interface FeatureOneResponse extends BaseResponse {
result: Record<
string,
{
/*
* Whether this amendment is currently enabled in the latest ledger.
*/
enabled: boolean
/*
* The human-readable name for this amendment, if known.
*/
name: string
supported: boolean
}
>
}
/**
* Response expected from an {@link FeatureRequest}.
*
* @category Responses
*/
export type FeatureResponse = FeatureAllResponse | FeatureOneResponse

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'
@@ -60,6 +66,14 @@ import {
DepositAuthorizedRequest,
DepositAuthorizedResponse,
} from './depositAuthorized'
import {
FeatureAllRequest,
FeatureAllResponse,
FeatureOneRequest,
FeatureOneResponse,
FeatureRequest,
FeatureResponse,
} from './feature'
import { FeeRequest, FeeResponse } from './fee'
import {
GatewayBalance,
@@ -76,11 +90,13 @@ import {
LedgerQueueData,
LedgerRequest,
LedgerResponse,
LedgerV1Response,
LedgerRequestExpandedTransactionsOnly,
LedgerResponseExpanded,
LedgerRequestExpandedAccountsAndTransactions,
LedgerRequestExpandedAccountsOnly,
LedgerRequestExpandedTransactionsBinary,
LedgerVersionResponseMap,
} from './ledger'
import { LedgerClosedRequest, LedgerClosedResponse } from './ledgerClosed'
import { LedgerCurrentRequest, LedgerCurrentResponse } from './ledgerCurrent'
@@ -100,6 +116,7 @@ import {
NFTHistoryTransaction,
} from './nftHistory'
import { NFTInfoRequest, NFTInfoResponse } from './nftInfo'
import { NFTsByIssuerRequest, NFTsByIssuerResponse } from './nftsByIssuer'
import { NFTSellOffersRequest, NFTSellOffersResponse } from './nftSellOffers'
import { NoRippleCheckRequest, NoRippleCheckResponse } from './norippleCheck'
import {
@@ -135,6 +152,8 @@ import { SubmitRequest, SubmitResponse } from './submit'
import {
SubmitMultisignedRequest,
SubmitMultisignedResponse,
SubmitMultisignedV1Response,
SubmitMultisignedVersionResponseMap,
} from './submitMultisigned'
import {
BooksSnapshot,
@@ -155,7 +174,7 @@ import {
TransactionEntryRequest,
TransactionEntryResponse,
} from './transactionEntry'
import { TxRequest, TxResponse } from './tx'
import { TxRequest, TxResponse, TxV1Response, TxVersionResponseMap } from './tx'
import {
UnsubscribeBook,
UnsubscribeRequest,
@@ -203,6 +222,7 @@ type Request =
| ServerDefinitionsRequest
| ServerInfoRequest
| ServerStateRequest
| FeatureRequest
// utility methods
| PingRequest
| RandomRequest
@@ -212,6 +232,7 @@ type Request =
// clio only methods
| NFTInfoRequest
| NFTHistoryRequest
| NFTsByIssuerRequest
// AMM methods
| AMMInfoRequest
// Price Oracle methods
@@ -220,27 +241,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
@@ -259,6 +280,7 @@ type Response =
| ServerDefinitionsResponse
| ServerInfoResponse
| ServerStateResponse
| FeatureResponse
// utility methods
| PingResponse
| RandomResponse
@@ -268,17 +290,21 @@ type Response =
// clio only methods
| NFTInfoResponse
| NFTHistoryResponse
| NFTsByIssuerResponse
// AMM methods
| AMMInfoResponse
// 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
@@ -288,7 +314,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
@@ -354,15 +380,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
@@ -374,11 +400,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
@@ -403,6 +429,10 @@ export type RequestResponseMap<T> = T extends AccountChannelsRequest
? ServerStateResponse
: T extends ServerDefinitionsRequest
? ServerDefinitionsResponse
: T extends FeatureAllRequest
? FeatureAllResponse
: T extends FeatureOneRequest
? FeatureOneResponse
: T extends PingRequest
? PingResponse
: T extends RandomRequest
@@ -413,22 +443,29 @@ export type RequestResponseMap<T> = T extends AccountChannelsRequest
? NFTSellOffersResponse
: T extends NFTInfoRequest
? NFTInfoResponse
: T extends NFTsByIssuerRequest
? 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
@@ -437,14 +474,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
@@ -462,6 +497,7 @@ export {
AccountInfoAccountFlags,
AccountInfoRequest,
AccountInfoResponse,
AccountInfoV1Response,
AccountQueueData,
AccountQueueTransaction,
AccountLinesRequest,
@@ -479,6 +515,7 @@ export {
AccountOffersResponse,
AccountTxRequest,
AccountTxResponse,
AccountTxV1Response,
AccountTxTransaction,
GatewayBalance,
GatewayBalancesRequest,
@@ -490,6 +527,7 @@ export {
// ledger methods
LedgerRequest,
LedgerResponse,
LedgerV1Response,
LedgerQueueData,
LedgerBinary,
LedgerModifiedOfferCreateTransaction,
@@ -509,10 +547,12 @@ export {
SubmitResponse,
SubmitMultisignedRequest,
SubmitMultisignedResponse,
SubmitMultisignedV1Response,
TransactionEntryRequest,
TransactionEntryResponse,
TxRequest,
TxResponse,
TxV1Response,
// path and order book methods with types
BookOffersRequest,
BookOffer,
@@ -565,6 +605,8 @@ export {
ServerState,
StateAccountingFinal,
StateAccounting,
FeatureRequest,
FeatureResponse,
// utility methods
PingRequest,
PingResponse,
@@ -582,6 +624,8 @@ export {
NFTHistoryRequest,
NFTHistoryResponse,
NFTHistoryTransaction,
NFTsByIssuerRequest,
NFTsByIssuerResponse,
// AMM methods
AMMInfoRequest,
AMMInfoResponse,

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

@@ -0,0 +1,68 @@
import { NFToken } from '../common'
import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod'
/**
* The nfts_by_issuer method returns a list of NFTokens issued by the account.
* The order of the NFTs is not associated with the date the NFTs were minted.
* Expects a response in the form of a {@link
* NFTsByIssuerResponse}.
*
* @category Requests
*/
export interface NFTsByIssuerRequest
extends BaseRequest,
LookupByLedgerRequest {
command: 'nfts_by_issuer'
/**
* A unique identifier for the account, most commonly the account's address
*/
issuer: string
/**
* Value from a previous paginated response. Resume retrieving data where
* that response left off. This value is stable even if there is a change in
* the server's range of available ledgers.
*/
marker?: unknown
/**
* Filter NFTs issued by this issuer that have this taxon.
*/
nft_taxon?: number
/**
* Default varies. Limit the number of transactions to retrieve. The server
* is not required to honor this value.
*/
limit?: number
}
/**
* Expected response from an {@link NFTsByIssuerRequest}.
*
* @category Responses
*/
export interface NFTsByIssuerResponse extends BaseResponse {
result: {
/**
* The unique identifier for the account, most commonly the account's address
*/
issuer: string
/**
* A list of NFTs issued by the account.
* The order of the NFTs is not associated with the date the NFTs were minted.
*/
nfts: NFToken[]
/**
* Server-defined value indicating the response is paginated. Pass this
* to the next call to resume where this call left off.
*/
marker?: unknown
/**
* The limit value used in the request.
*/
limit?: number
/**
* Use to filter NFTs issued by this issuer that have this taxon.
*/
nft_taxon?: number
}
}

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'
@@ -17,7 +17,6 @@ const LEDGER_OFFSET = 20
// Mainnet and testnet are exceptions. More context: https://github.com/XRPLF/rippled/pull/4370
const RESTRICTED_NETWORKS = 1024
const REQUIRED_NETWORKID_VERSION = '1.11.0'
const HOOKS_TESTNET_ID = 21338
/**
* Determines whether the source rippled version is not later than the target rippled version.
@@ -87,8 +86,7 @@ function isNotLaterRippledVersion(source: string, target: string): boolean {
/**
* Determine if the transaction required a networkID to be valid.
* Transaction needs networkID if later than restricted ID and either the network is hooks testnet
* or build version is >= 1.11.0
* Transaction needs networkID if later than restricted ID and build version is >= 1.11.0
*
* @param client -- The connected client.
* @returns True if required networkID, false otherwise.
@@ -99,12 +97,8 @@ export function txNeedsNetworkID(client: Client): boolean {
client.networkID > RESTRICTED_NETWORKS
) {
if (
(client.buildVersion &&
isNotLaterRippledVersion(
REQUIRED_NETWORKID_VERSION,
client.buildVersion,
)) ||
client.networkID === HOOKS_TESTNET_ID
client.buildVersion &&
isNotLaterRippledVersion(REQUIRED_NETWORKID_VERSION, client.buildVersion)
) {
return true
}

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

@@ -7,6 +7,7 @@ import {
Payment,
Transaction,
} from '../../src'
import { ValidationError } from '../../src/errors'
import rippled from '../fixtures/rippled'
import {
setupClient,
@@ -19,10 +20,11 @@ const NetworkID = 1025
const Fee = '10'
const Sequence = 1432
const LastLedgerSequence = 2908734
const HOOKS_TESTNET_ID = 21338
describe('client.autofill', function () {
let testContext: XrplTestContext
const AMOUNT = '1234'
let paymentTx: Payment
async function setupMockRippledVersionAndID(
buildVersion: string,
@@ -41,10 +43,68 @@ describe('client.autofill', function () {
await testContext.client.connect()
}
beforeEach(async () => {
beforeAll(async () => {
testContext = await setupClient()
})
afterEach(async () => teardownClient(testContext))
afterAll(async () => teardownClient(testContext))
beforeEach(async () => {
paymentTx = {
TransactionType: 'Payment',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Amount: AMOUNT,
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
DestinationTag: 1,
Fee: '12',
Flags: 2147483648,
LastLedgerSequence: 65953073,
Sequence: 65923914,
SigningPubKey:
'02F9E33F16DF9507705EC954E3F94EB5F10D1FC4A354606DBE6297DBB1096FE654',
TxnSignature:
'3045022100E3FAE0EDEC3D6A8FF6D81BC9CF8288A61B7EEDE8071E90FF9314CB4621058D10022043545CF631706D700CEE65A1DB83EFDD185413808292D9D90F14D87D3DC2D8CB',
InvoiceID:
'6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B',
Paths: [
[{ currency: 'BTC', issuer: 'r9vbV3EHvXWjSkeQ6CAcYVPGeq7TuiXY2X' }],
],
SendMax: '100000000',
}
})
it('Validate Payment transaction API v2: Payment Transaction: Specify Only Amount field', async function () {
const txResult = await testContext.client.autofill(paymentTx)
assert.strictEqual(txResult.Amount, AMOUNT)
})
it('Validate Payment transaction API v2: Payment Transaction: Specify Only DeliverMax field', async function () {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
delete paymentTx.Amount
const txResult = await testContext.client.autofill(paymentTx)
assert.strictEqual(txResult.Amount, AMOUNT)
})
it('Validate Payment transaction API v2: Payment Transaction: identical DeliverMax and Amount fields', async function () {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
const txResult = await testContext.client.autofill(paymentTx)
assert.strictEqual(txResult.Amount, AMOUNT)
assert.strictEqual('DeliverMax' in txResult, false)
})
it('Validate Payment transaction API v2: Payment Transaction: differing DeliverMax and Amount fields', async function () {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = '6789'
paymentTx.Amount = '1234'
await assertRejects(testContext.client.autofill(paymentTx), ValidationError)
})
it('should not autofill if fields are present', async function () {
const tx: Transaction = {
@@ -141,26 +201,6 @@ describe('client.autofill', function () {
assert.strictEqual(txResult.NetworkID, undefined)
})
// Hooks Testnet requires networkID in transaction regardless of version.
// More context: https://github.com/XRPLF/rippled/pull/4370
it('overrides network ID for hooks testnet', async function () {
await setupMockRippledVersionAndID('1.10.1', HOOKS_TESTNET_ID)
const tx: Payment = {
TransactionType: 'Payment',
Account: 'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi',
Amount: '1234',
Destination: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
Fee,
Sequence,
LastLedgerSequence,
}
testContext.mockRippled!.addResponse('ledger', rippled.ledger.normal)
const txResult = await testContext.client.autofill(tx)
assert.strictEqual(txResult.NetworkID, HOOKS_TESTNET_ID)
})
it('converts Account & Destination X-address to their classic address', async function () {
const tx: Payment = {
TransactionType: 'Payment',

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

@@ -83,40 +83,6 @@ describe('fundWallet', function () {
// )
// })
it(
'can generate wallet on hooks v3 testnet',
async function () {
const api = new Client('wss://hooks-testnet-v3.xrpl-labs.com')
await api.connect()
const { wallet, balance } = await api.fundWallet(null, {
usageContext: 'integration-test',
})
assert.notStrictEqual(wallet, undefined)
assert(isValidClassicAddress(wallet.classicAddress))
assert(isValidXAddress(wallet.getXAddress()))
const info = await api.request({
command: 'account_info',
account: wallet.classicAddress,
})
assert.equal(dropsToXrp(info.result.account_data.Balance), balance)
assert.equal(balance, 1000)
/*
* No test for fund given wallet because the hooks v3 testnet faucet
* requires 10 seconds between requests. Would significantly slow down
* the test suite.
*/
await api.disconnect()
},
TIMEOUT,
)
it(
'submit funds wallet with custom amount',
async function () {

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

@@ -0,0 +1,68 @@
import { assert } from 'chai'
import { FeatureRequest } from '../../../src'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
const AMENDMENT =
'8CC0774A3BF66D1D22E76BBDA8E8A232E6B6313834301B3B23E8601196AE6455'
describe('feature', function () {
let testContext: XrplIntegrationTestContext
beforeEach(async () => {
testContext = await setupClient(serverUrl)
})
afterEach(async () => teardownClient(testContext))
it(
'all',
async () => {
const featureRequest: FeatureRequest = {
command: 'feature',
}
const featureResponse = await testContext.client.request(featureRequest)
assert.equal(featureResponse.type, 'response')
assert.typeOf(featureResponse.result.features, 'object')
assert.isTrue(AMENDMENT in featureResponse.result.features)
const amendmentData = featureResponse.result.features[AMENDMENT]
assert.equal(amendmentData.name, 'AMM')
// TODO: rippled says "false" for standalone nodes for some reason
assert.typeOf(amendmentData.enabled, 'boolean')
assert.equal(amendmentData.supported, true)
},
TIMEOUT,
)
it(
'one',
async () => {
const featureRequest: FeatureRequest = {
command: 'feature',
feature: AMENDMENT,
}
const featureResponse = await testContext.client.request(featureRequest)
assert.equal(featureResponse.type, 'response')
assert.typeOf(featureResponse.result, 'object')
assert.isTrue(AMENDMENT in featureResponse.result)
assert.lengthOf(Object.keys(featureResponse.result), 1)
const amendmentData = featureResponse.result[AMENDMENT]
assert.equal(amendmentData.name, 'AMM')
// TODO: rippled says "false" for standalone nodes for some reason
assert.typeOf(amendmentData.enabled, 'boolean')
assert.equal(amendmentData.supported, true)
},
TIMEOUT,
)
})

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

@@ -1,4 +1,6 @@
import { Payment } from '../../../src'
import { assert } from 'chai'
import { Payment, Wallet } from '../../../src'
import serverUrl from '../serverUrl'
import {
setupClient,
@@ -12,24 +14,92 @@ const TIMEOUT = 20000
describe('Payment', function () {
let testContext: XrplIntegrationTestContext
let paymentTx: Payment
const AMOUNT = '10000000'
// This wallet is used for DeliverMax related tests
let senderWallet: Wallet
beforeEach(async () => {
testContext = await setupClient(serverUrl)
// this payment transaction JSON needs to be refreshed before every test.
// Because, we tinker with Amount and DeliverMax fields in the API v2 tests
paymentTx = {
TransactionType: 'Payment',
Account: senderWallet.classicAddress,
Amount: AMOUNT,
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
}
})
afterEach(async () => teardownClient(testContext))
beforeAll(async () => {
testContext = await setupClient(serverUrl)
senderWallet = await generateFundedWallet(testContext.client)
})
afterAll(async () => teardownClient(testContext))
it(
'base',
async () => {
const wallet2 = await generateFundedWallet(testContext.client)
const tx: Payment = {
TransactionType: 'Payment',
Account: testContext.wallet.classicAddress,
Destination: wallet2.classicAddress,
Destination: senderWallet.classicAddress,
Amount: '1000',
}
await testTransaction(testContext.client, tx, testContext.wallet)
},
TIMEOUT,
)
it(
'Validate Payment transaction API v2: Payment Transaction: Specify Only Amount field',
async () => {
const result = await testTransaction(
testContext.client,
paymentTx,
senderWallet,
)
assert.equal(result.result.engine_result_code, 0)
assert.equal((result.result.tx_json as Payment).Amount, AMOUNT)
},
TIMEOUT,
)
it(
'Validate Payment transaction API v2: Payment Transaction: Specify Only DeliverMax field',
async () => {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
delete paymentTx.Amount
const result = await testTransaction(
testContext.client,
paymentTx,
senderWallet,
)
assert.equal(result.result.engine_result_code, 0)
assert.equal((result.result.tx_json as Payment).Amount, AMOUNT)
},
TIMEOUT,
)
it(
'Validate Payment transaction API v2: Payment Transaction: identical DeliverMax and Amount fields',
async () => {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
const result = await testTransaction(
testContext.client,
paymentTx,
senderWallet,
)
assert.equal(result.result.engine_result_code, 0)
assert.equal((result.result.tx_json as Payment).Amount, AMOUNT)
},
TIMEOUT,
)
})

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

View File

@@ -44,14 +44,6 @@ describe('Get Faucet host ', function () {
assert.strictEqual(getFaucetHost(testContext.client), expectedFaucet)
})
it('returns the Hooks V3 Testnet host', function () {
const expectedFaucet = FaucetNetwork.HooksV3Testnet
// @ts-expect-error Intentionally modifying private data for test
testContext.client.connection.url = FaucetNetwork.HooksV3Testnet
assert.strictEqual(getFaucetHost(testContext.client), expectedFaucet)
})
it('returns the correct faucetPath for Devnet host', function () {
const expectedFaucetPath = FaucetNetworkPaths[FaucetNetwork.Devnet]
// @ts-expect-error Intentionally modifying private data for test
@@ -63,17 +55,6 @@ describe('Get Faucet host ', function () {
)
})
it('returns the correct faucetPath for Hooks V3 Testnet host', function () {
const expectedFaucetPath = FaucetNetworkPaths[FaucetNetwork.HooksV3Testnet]
// @ts-expect-error Intentionally modifying private data for test
testContext.client.connection.url = FaucetNetwork.HooksV3Testnet
assert.strictEqual(
getDefaultFaucetPath(getFaucetHost(testContext.client)),
expectedFaucetPath,
)
})
it('returns the correct faucetPath for undefined host', function () {
const expectedFaucetPath = '/accounts'