Compare commits

..

19 Commits

Author SHA1 Message Date
Nathan Nichols
028655d682 build: makes connection private 2021-09-26 11:06:20 -07:00
Mayukha Vadari
c260512bf3 fix: make generateFaucetWallet functional (#1669) 2021-09-24 18:58:09 -04:00
Mayukha Vadari
97049e1120 feat: add client.getLedgerIndex (#1668)
* write getLedgerIndex

* add to client

* rename file

* add tests

* remove unused import

* fix browser tests

* respond to comments, more cleanup
2021-09-24 18:40:03 -04:00
Nathan Nichols
b939a1d5ba feat: Add warning for partial payment (#1641) 2021-09-24 15:36:31 -07:00
Jackson Mills
1ec42392a2 Add trustSet integration test and browser test (#1667) 2021-09-24 13:47:27 -07:00
Mayukha Vadari
f5dd4ac794 fix: resolve browser issue with Error.captureStackTrace (#1663)
* fix webpacking error issue

* fix eslint comment
2021-09-24 15:18:48 -04:00
Nathan Nichols
b82df40562 fix: adds mode to webpack to stop warning (#1666) 2021-09-24 12:10:12 -07:00
Nathan Nichols
903ed2e3e6 Autofills AccountDelete Fee w/ reserve_inc_xrp (#1639)
* fix: fetch Owner Reserves from ledger
2021-09-24 12:09:54 -07:00
Mayukha Vadari
814add2aac chore: remove ripple-lib-transactionparser (#1661) 2021-09-24 15:09:21 -04:00
Mayukha Vadari
2533cc8654 feat: extra protection for AccountDelete transactions (#1626)
* add deletion blockers check to autofill

* add tests

* add fail_hard: true

* pass in account_objects response to error

* only fail_hard for AccountDelete

* reject promise instead of throwing error

* fix rebase issue
2021-09-24 15:05:38 -04:00
Mayukha Vadari
ab3f7f7ca5 fix: disallow two pending requests with the same id (#1628)
* only increment nextId if id used

* throw error if promise already pending with id

* add test

* modify existing tests

* fix bug + tests
2021-09-24 15:01:43 -04:00
Jackson Mills
94749a4a57 test: Add PayChannel Transaction Integration Tests (#1662)
* Add test for PaymentChannelCreate

* Add PaymentChannelFund test

* Add PaymentChannelClaim test
2021-09-24 11:03:17 -07:00
Jackson Mills
70d9396247 Add Payment Channel Tests (#1646)
* Add ChannelVerify integration and browser test
2021-09-24 10:52:59 -07:00
Jackson Mills
8991133b28 Add Path and Orderbook integration tests (#1644)
Add bookOrder, depositAuthorized, and ripplePathFind, and pathFind integration and browser tests
2021-09-24 10:46:54 -07:00
Mayukha Vadari
3929d60903 build: prepare repo for beta release (#1665)
* fix github links

* fix webpacking

* more cleanup

* add 2.x to SECURITY.md

* update beta version
2021-09-24 13:44:42 -04:00
Jackson Mills
9601e5b604 Add Ledger Method Integration Tests (#1640)
Add integration tests for ledger, ledgerEntry, ledgerData, ledgerClosed and ledgerCurrent, plus add them to the browser tests.
2021-09-24 09:38:52 -07:00
Mayukha Vadari
c5a9ce2113 test: integration tests for CheckCreate, CheckCancel, CheckCash (#1664)
* test checkCreate

* test CheckCancel

* test CheckCash

* add browser tests
2021-09-24 12:29:41 -04:00
Mayukha Vadari
07f07b1201 test: add integration tests for Account requests (#1643) 2021-09-24 12:20:04 -04:00
Jackson Mills
009b86d2f2 Add integration tests for Transaction methods (#1636)
Add tx, submit, and submit_multisign integration tests, along with browser tests.
2021-09-24 08:40:56 -07:00
81 changed files with 2695 additions and 410 deletions

View File

@@ -6,6 +6,7 @@ This table shows which versions of xrpl.js are currently supported with security
| Version | Supported |
| ------- | ---------------------- |
| 2.x | :white_check_mark: Yes |
| 1.x | :white_check_mark: Yes |
| 0.x | :x: No |

View File

@@ -1,18 +0,0 @@
machine:
node:
version: 6.11.3
hosts:
testripple.circleci.com: 127.0.0.1
dependencies:
pre:
- wget https://s3-us-west-2.amazonaws.com/ripple-debs/rippled_0.30.1-b11-1.deb
- sudo dpkg -i rippled_0.30.1-b11-1.deb
test:
pre:
- rippled -a --start --conf "$HOME/$CIRCLE_PROJECT_REPONAME/test/integration/rippled.cfg":
background: true
override:
- scripts/ci.sh "$CIRCLE_NODE_INDEX" "$CIRCLE_NODE_TOTAL":
parallel: true
post:
- killall /usr/bin/rippled

23
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "xrpl",
"version": "2.0.0-beta.0",
"version": "2.0.0-beta.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "xrpl",
"version": "2.0.0-beta.0",
"version": "2.0.0-beta.1",
"license": "ISC",
"dependencies": {
"@types/lodash": "^4.14.136",
@@ -19,7 +19,6 @@
"ripple-address-codec": "^4.1.1",
"ripple-binary-codec": "^1.1.3",
"ripple-keypairs": "^1.0.3",
"ripple-lib-transactionparser": "0.8.2",
"ws": "^8.2.2"
},
"devDependencies": {
@@ -6974,15 +6973,6 @@
"node": ">= 10"
}
},
"node_modules/ripple-lib-transactionparser": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.8.2.tgz",
"integrity": "sha512-1teosQLjYHLyOQrKUQfYyMjDR3MAq/Ga+MJuLUfpBMypl4LZB4bEoMcmG99/+WVTEiZOezJmH9iCSvm/MyxD+g==",
"dependencies": {
"bignumber.js": "^9.0.0",
"lodash": "^4.17.15"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -13859,15 +13849,6 @@
"ripple-address-codec": "^4.0.0"
}
},
"ripple-lib-transactionparser": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.8.2.tgz",
"integrity": "sha512-1teosQLjYHLyOQrKUQfYyMjDR3MAq/Ga+MJuLUfpBMypl4LZB4bEoMcmG99/+WVTEiZOezJmH9iCSvm/MyxD+g==",
"requires": {
"bignumber.js": "^9.0.0",
"lodash": "^4.17.15"
}
},
"run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",

View File

@@ -1,16 +1,16 @@
{
"name": "xrpl",
"version": "2.0.0-beta.0",
"version": "2.0.0-beta.1",
"license": "ISC",
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
"files": [
"dist/npm/*",
"build/ripple-latest-min.js",
"build/ripple-latest.js"
"build/xrpl-latest-min.js",
"build/xrpl-latest.js"
],
"main": "dist/npm/",
"unpkg": "build/ripple-latest-min.js",
"jsdelivr": "build/ripple-latest-min.js",
"unpkg": "build/xrpl-latest-min.js",
"jsdelivr": "build/xrpl-latest-min.js",
"types": "dist/npm/index.d.ts",
"directories": {
"test": "test"
@@ -26,7 +26,6 @@
"ripple-address-codec": "^4.1.1",
"ripple-binary-codec": "^1.1.3",
"ripple-keypairs": "^1.0.3",
"ripple-lib-transactionparser": "0.8.2",
"ws": "^8.2.2"
},
"resolutions": {
@@ -99,7 +98,7 @@
"prettier": "@xrplf/prettier-config",
"repository": {
"type": "git",
"url": "git://github.com/ripple/ripple-lib.git"
"url": "git://github.com/XRPLF/xrpl.js.git"
},
"readmeFilename": "README.md",
"engines": {

View File

@@ -304,7 +304,7 @@ export class Connection extends EventEmitter {
public async reconnect(): Promise<void> {
// NOTE: We currently have a "reconnecting" event, but that only triggers
// through an unexpected connection retry logic.
// See: https://github.com/ripple/ripple-lib/pull/1101#issuecomment-565360423
// See: https://github.com/XRPLF/xrpl.js/pull/1101#issuecomment-565360423
this.emit('reconnect')
await this.disconnect()
await this.connect()

View File

@@ -6,6 +6,8 @@ import { EventEmitter } from 'events'
import { ValidationError, XrplError } from '../errors'
import * as errors from '../errors'
import {
Request,
Response,
// account methods
AccountChannelsRequest,
AccountChannelsResponse,
@@ -86,6 +88,7 @@ import { BaseRequest, BaseResponse } from '../models/methods/baseMethod'
import autofill from '../sugar/autofill'
import getBalances from '../sugar/balances'
import getFee from '../sugar/fee'
import getLedgerIndex from '../sugar/ledgerIndex'
import getOrderbook from '../sugar/orderbook'
import { submitTransaction, submitSignedTransaction } from '../sugar/submit'
import { ensureClassicAddress } from '../sugar/utils'
@@ -96,6 +99,10 @@ import {
ConnectionUserOptions,
INTENTIONAL_DISCONNECT_CODE,
} from './connection'
import {
handlePartialPayment,
handleStreamPartialPayment,
} from './partialPayment'
export interface ClientOptions extends ConnectionUserOptions {
feeCushion?: number
@@ -157,7 +164,7 @@ const MAX_LIMIT = 400
class Client extends EventEmitter {
// New in > 0.21.0
// non-validated ledger versions are allowed, and passed to rippled as-is.
public readonly connection: Connection
private readonly connection: Connection
// Factor to multiply estimated fee by to provide a cushion in case the
// required fee rises during submission of a transaction. Defaults to 1.2.
@@ -209,6 +216,7 @@ class Client extends EventEmitter {
})
this.connection.on('transaction', (tx) => {
handleStreamPartialPayment(tx)
this.emit('transaction', tx)
})
@@ -233,6 +241,15 @@ class Client extends EventEmitter {
})
}
/**
* Get the URL of the server the Client is connected to.
*
* @returns The URL of the rippled server this Client is connected to.
*/
public get url(): string {
return this.connection.getUrl()
}
/**
* Returns true if there are more pages of data.
*
@@ -295,6 +312,9 @@ class Client extends EventEmitter {
r: TransactionEntryRequest,
): Promise<TransactionEntryResponse>
public async request(r: TxRequest): Promise<TxResponse>
public async request<R extends BaseRequest, T extends BaseResponse>(
r: R,
): Promise<T>
/**
* Makes a request to the client with the given command and
* additional request body parameters.
@@ -302,17 +322,21 @@ class Client extends EventEmitter {
* @param req - Request to send to the server.
* @returns The response from the server.
*/
public async request<R extends BaseRequest, T extends BaseResponse>(
public async request<R extends Request, T extends Response>(
req: R,
): Promise<T> {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Necessary for overloading
return this.connection.request({
const response = (await this.connection.request({
...req,
account: req.account
? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Must be string
ensureClassicAddress(req.account as string)
: undefined,
}) as unknown as T
})) as T
handlePartialPayment(req.command, response)
return response
}
public async requestNextPage(
@@ -357,7 +381,7 @@ class Client extends EventEmitter {
}
const nextPageRequest = { ...req, marker: resp.result.marker }
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Necessary for overloading
return this.connection.request(nextPageRequest) as unknown as U
return this.request(nextPageRequest) as unknown as U
}
public on(
@@ -392,6 +416,10 @@ class Client extends EventEmitter {
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- actually needs to be any here
public on(eventName: string, listener: (...args: any[]) => void): this {
// if (args[0]?.type === 'transaction') {
// handlePartialPaymentStream(args[0])
// }
return super.on(eventName, listener)
}
@@ -504,23 +532,22 @@ class Client extends EventEmitter {
return this.connection.isConnected()
}
// syntactic sugar
public autofill = autofill
public getFee = getFee
// @deprecated Use autofill instead
public prepareTransaction = autofill
public submitTransaction = submitTransaction
public getFee = getFee
public getLedgerIndex = getLedgerIndex
public submitTransaction = submitTransaction
public submitSignedTransaction = submitSignedTransaction
public getBalances = getBalances
public getOrderbook = getOrderbook
public generateFaucetWallet = generateFaucetWallet
public errors = errors
}
export { Client }

View File

@@ -0,0 +1,146 @@
import BigNumber from 'bignumber.js'
import { decode } from 'ripple-binary-codec'
import type {
AccountTxResponse,
Response,
TransactionEntryResponse,
TransactionStream,
TxResponse,
} from '..'
import type { Amount } from '../models/common'
import { PaymentTransactionFlags, Transaction } from '../models/transactions'
import type TransactionMetadata from '../models/transactions/metadata'
import { isFlagEnabled } from '../models/utils'
const WARN_PARTIAL_PAYMENT_CODE = 2001
function amountsEqual(amt1: Amount, amt2: Amount): boolean {
if (typeof amt1 === 'string' && typeof amt2 === 'string') {
return amt1 === amt2
}
if (typeof amt1 === 'string' || typeof amt2 === 'string') {
return false
}
const aValue = new BigNumber(amt1.value)
const bValue = new BigNumber(amt2.value)
return (
amt1.currency === amt2.currency &&
amt1.issuer === amt2.issuer &&
aValue.isEqualTo(bValue)
)
}
function isPartialPayment(
tx?: Transaction,
metadata?: TransactionMetadata | string,
): boolean {
if (tx == null || metadata == null || tx.TransactionType !== 'Payment') {
return false
}
let meta = metadata
if (typeof meta === 'string') {
if (meta === 'unavailable') {
return false
}
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- binary-codec typing */
meta = decode(meta) as unknown as TransactionMetadata
}
const tfPartial =
typeof tx.Flags === 'number'
? isFlagEnabled(tx.Flags, PaymentTransactionFlags.tfPartialPayment)
: tx.Flags?.tfPartialPayment
if (!tfPartial) {
return false
}
const delivered = meta.delivered_amount
const amount = tx.Amount
if (delivered === undefined) {
return false
}
return !amountsEqual(delivered, amount)
}
function txHasPartialPayment(response: TxResponse): boolean {
return isPartialPayment(response.result, response.result.meta)
}
function txEntryHasPartialPayment(response: TransactionEntryResponse): boolean {
return isPartialPayment(response.result.tx_json, response.result.metadata)
}
function accountTxHasPartialPayment(response: AccountTxResponse): boolean {
const { transactions } = response.result
const foo = transactions.some((tx) => isPartialPayment(tx.tx, tx.meta))
return foo
}
function hasPartialPayment(command: string, response: Response): boolean {
/* eslint-disable @typescript-eslint/consistent-type-assertions -- Request type is known at runtime from command */
switch (command) {
case 'tx':
return txHasPartialPayment(response as TxResponse)
case 'transaction_entry':
return txEntryHasPartialPayment(response as TransactionEntryResponse)
case 'account_tx':
return accountTxHasPartialPayment(response as AccountTxResponse)
default:
return false
}
/* eslint-enable @typescript-eslint/consistent-type-assertions */
}
/**
* Checks a response for a partial payment.
*
* @param command - Command from the request, tells us what response to expect.
* @param response - Response to check for a partial payment.
*/
export function handlePartialPayment(
command: string,
response: Response,
): void {
if (hasPartialPayment(command, response)) {
const warnings = response.warnings ?? []
const warning = {
id: WARN_PARTIAL_PAYMENT_CODE,
message: 'This response contains a Partial Payment',
}
warnings.push(warning)
response.warnings = warnings
}
}
/**
* Check a transaction from a subscription stream for partial payment.
*
* @param stream - Stream Transaction to check for partial payment,.
*/
export function handleStreamPartialPayment(stream: TransactionStream): void {
if (isPartialPayment(stream.transaction, stream.meta)) {
const warnings = stream.warnings ?? []
const warning = {
id: WARN_PARTIAL_PAYMENT_CODE,
message: 'This response contains a Partial Payment',
}
warnings.push(warning)
/* eslint-disable-next-line no-param-reassign -- Handles the case where there are no warnings */
stream.warnings = warnings
}
}

View File

@@ -1,4 +1,9 @@
import { ResponseFormatError, RippledError, TimeoutError } from '../errors'
import {
ResponseFormatError,
RippledError,
TimeoutError,
XrplError,
} from '../errors'
import { Response } from '../models/methods'
import { BaseRequest, ErrorResponse } from '../models/methods/baseMethod'
@@ -77,6 +82,7 @@ export default class RequestManager {
public rejectAll(error: Error): void {
this.promisesAwaitingResponse.forEach((_promise, id, _map) => {
this.reject(id, error)
this.deletePromise(id)
})
}
@@ -88,13 +94,19 @@ export default class RequestManager {
* @param request - Request to create.
* @param timeout - Timeout length to catch hung responses.
* @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<T extends BaseRequest>(
request: T,
timeout: number,
): [string | number, string, Promise<Response>] {
const newId = request.id ? request.id : this.nextId
this.nextId += 1
let newId: string | number
if (request.id == null) {
newId = this.nextId
this.nextId += 1
} else {
newId = request.id
}
const newRequest = JSON.stringify({ ...request, id: newId })
const timer = setTimeout(
() => this.reject(newId, new TimeoutError()),
@@ -106,6 +118,9 @@ export default class RequestManager {
if (timer.unref) {
timer.unref()
}
if (this.promisesAwaitingResponse.has(newId)) {
throw new XrplError(`Response with id '${newId}' is already pending`)
}
const newPromise = new Promise<Response>(
(resolve: (value: Response | PromiseLike<Response>) => void, reject) => {
this.promisesAwaitingResponse.set(newId, { resolve, reject, timer })
@@ -125,8 +140,7 @@ export default class RequestManager {
public handleResponse(response: Partial<Response | ErrorResponse>): void {
if (
response.id == null ||
!Number.isInteger(response.id) ||
response.id < 0
!(typeof response.id === 'string' || typeof response.id === 'number')
) {
throw new ResponseFormatError('valid id not found in response', response)
}
@@ -165,7 +179,6 @@ export default class RequestManager {
* @param id - ID of the request.
*/
private deletePromise(id: string | number): void {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- Needs to delete promise after request has been fulfilled.
delete this.promisesAwaitingResponse[id]
this.promisesAwaitingResponse.delete(id)
}
}

View File

@@ -20,7 +20,10 @@ class XrplError extends Error {
this.name = this.constructor.name
this.message = message
this.data = data
Error.captureStackTrace(this, this.constructor)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- `captureStackTrace` can be null in browsers
if (Error.captureStackTrace != null) {
Error.captureStackTrace(this, this.constructor)
}
}
/**

View File

@@ -1,4 +1,5 @@
import { Transaction } from '../transactions'
import TransactionMetadata from '../transactions/metadata'
import LedgerEntry from './ledgerEntry'
@@ -16,5 +17,5 @@ export default interface Ledger {
parent_hash: string
total_coins: string
transaction_hash: string
transactions?: Transaction[]
transactions?: Array<Transaction & { metaData?: TransactionMetadata }>
}

View File

@@ -23,7 +23,7 @@ export interface AccountChannelsRequest extends BaseRequest {
destination_account?: string
ledger_hash?: string
ledger_index?: LedgerIndex
limit: number
limit?: number
marker?: unknown
}

View File

@@ -31,5 +31,6 @@ export interface BookOffersResponse extends BaseResponse {
ledger_index?: number
ledger_hash?: string
offers: BookOffer[]
validated?: boolean
}
}

View File

@@ -6,7 +6,7 @@ export interface GatewayBalancesRequest extends BaseRequest {
command: 'gateway_balances'
account: string
strict?: boolean
hotwallet: string | string[]
hotwallet?: string | string[]
ledger_hash?: string
ledger_index?: LedgerIndex
}

View File

@@ -26,5 +26,6 @@ export interface LedgerDataResponse extends BaseResponse {
ledger_hash: string
state: State[]
marker?: unknown
validated?: boolean
}
}

View File

@@ -65,8 +65,9 @@ export interface LedgerEntryRequest extends BaseRequest {
export interface LedgerEntryResponse extends BaseResponse {
result: {
index: string
ledger_index: number
ledger_current_index: number
node?: LedgerEntry
node_binary?: string
validated?: boolean
}
}

View File

@@ -28,5 +28,11 @@ export interface RipplePathFindResponse extends BaseResponse {
alternatives: PathOption[]
destination_account: string
destination_currencies: string[]
destination_amount: Amount
full_reply?: boolean
id?: number | string
ledger_current_index?: number
source_account: string
validated: boolean
}
}

View File

@@ -14,7 +14,7 @@ export interface SubmitResponse extends BaseResponse {
engine_result_code: number
engine_result_message: string
tx_blob: string
tx_json: Transaction
tx_json: Transaction & { hash?: string }
accepted: boolean
account_sequence_available: number
account_sequence_next: number

View File

@@ -14,6 +14,6 @@ export interface SubmitMultisignedResponse extends BaseResponse {
engine_result_code: number
engine_result_message: string
tx_blob: string
tx_json: Transaction
tx_json: Transaction & { hash?: string }
}
}

View File

@@ -75,6 +75,7 @@ export interface TransactionStream extends BaseStream {
meta?: TransactionMetadata
transaction: Transaction
validated?: boolean
warnings?: Array<{ id: number; message: string }>
}
export interface PeerStatusStream extends BaseStream {

View File

@@ -14,8 +14,8 @@ export interface TxRequest extends BaseRequest {
export interface TxResponse extends BaseResponse {
result: {
hash: string
ledger_index: number
meta: TransactionMetadata | string
ledger_index?: number
meta?: TransactionMetadata | string
validated?: boolean
} & Transaction
searched_all?: boolean

View File

@@ -2,16 +2,14 @@ import BigNumber from 'bignumber.js'
import { xAddressToClassicAddress, isValidXAddress } from 'ripple-address-codec'
import type { Client } from '..'
import { ValidationError } from '../errors'
import { AccountInfoRequest, LedgerRequest } from '../models/methods'
import { ValidationError, XrplError } from '../errors'
import { AccountInfoRequest, AccountObjectsRequest } from '../models/methods'
import { Transaction } from '../models/transactions'
import setTransactionFlagsToNumber from '../models/utils/flags'
import { xrpToDrops } from '../utils'
// 20 drops
// Expire unconfirmed transactions after 20 ledger versions, approximately 1 minute, by default
const LEDGER_OFFSET = 20
// 5 XRP
const ACCOUNT_DELETE_FEE = 5000000
interface ClassicAccountAndTag {
classicAccount: string
tag: number | false | undefined
@@ -46,6 +44,9 @@ async function autofill<T extends Transaction>(
if (tx.LastLedgerSequence == null) {
promises.push(setLatestValidatedLedgerSequence(this, tx))
}
if (tx.TransactionType === 'AccountDelete') {
promises.push(checkAccountDeleteBlockers(this, tx))
}
return Promise.all(promises).then(() => tx)
}
@@ -132,6 +133,17 @@ async function setNextValidSequenceNumber(
tx.Sequence = data.result.account_data.Sequence
}
async function fetchAccountDeleteFee(client: Client): Promise<BigNumber> {
const response = await client.request({ command: 'server_state' })
const fee = response.result.state.validated_ledger?.reserve_inc
if (fee == null) {
return Promise.reject(new Error('Could not fetch Owner Reserve.'))
}
return new BigNumber(fee)
}
async function calculateFeePerTransactionType(
client: Client,
tx: Transaction,
@@ -155,7 +167,7 @@ async function calculateFeePerTransactionType(
// AccountDelete Transaction
if (tx.TransactionType === 'AccountDelete') {
baseFee = new BigNumber(ACCOUNT_DELETE_FEE)
baseFee = await fetchAccountDeleteFee(client)
}
// Multi-signed Transaction
@@ -183,14 +195,33 @@ async function setLatestValidatedLedgerSequence(
client: Client,
tx: Transaction,
): Promise<void> {
const request: LedgerRequest = {
command: 'ledger',
ledger_index: 'validated',
}
const data = await client.request(request)
const ledgerSequence = data.result.ledger_index
const ledgerSequence = await client.getLedgerIndex()
// eslint-disable-next-line no-param-reassign -- param reassign is safe
tx.LastLedgerSequence = ledgerSequence + LEDGER_OFFSET
}
async function checkAccountDeleteBlockers(
client: Client,
tx: Transaction,
): Promise<void> {
const request: AccountObjectsRequest = {
command: 'account_objects',
account: tx.Account,
ledger_index: 'validated',
deletion_blockers_only: true,
}
const response = await client.request(request)
return new Promise((resolve, reject) => {
if (response.result.account_objects.length > 0) {
reject(
new XrplError(
`Account ${tx.Account} cannot be deleted; there are Escrows, PayChannels, RippleStates, or Checks associated with the account.`,
response.result.account_objects,
),
)
}
resolve()
})
}
export default autofill

View File

@@ -8,7 +8,6 @@ const BASE_10 = 10
/**
* Calculates the current transaction fee for the ledger.
* Note: This is a public API that can be called directly.
* This is not used by the `prepare*` methods. See `src/transaction/utils.ts`.
*
* @param this - The Client used to connect to the ledger.
* @param cushion - The fee cushion to use.

15
src/sugar/ledgerIndex.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { Client } from '..'
/**
* Returns the index of the most recently validated ledger.
*
* @param this - The Client used to connect to the ledger.
* @returns The ledger index.
*/
export default async function getLedgerIndex(this: Client): Promise<number> {
const ledgerResponse = await this.request({
command: 'ledger',
ledger_index: 'validated',
})
return ledgerResponse.result.ledger_index
}

View File

@@ -51,6 +51,7 @@ async function submitSignedTransaction(
const request: SubmitRequest = {
command: 'submit',
tx_blob: signedTxEncoded,
fail_hard: isAccountDelete(signedTransaction),
}
return this.request(request)
}
@@ -63,4 +64,9 @@ function isSigned(transaction: Transaction | string): boolean {
)
}
function isAccountDelete(transaction: Transaction | string): boolean {
const tx = typeof transaction === 'string' ? decode(transaction) : transaction
return tx.TransactionType === 'AccountDelete'
}
export { submitTransaction, submitSignedTransaction }

View File

@@ -32,7 +32,12 @@ import {
computePaymentChannelHash,
} from './hashes'
import signPaymentChannelClaim from './signPaymentChannelClaim'
import { rippleTimeToISOTime, ISOTimeToRippleTime } from './timeConversion'
import {
rippleTimeToISOTime,
ISOTimeToRippleTime,
rippleTimeToUnixTime,
unixTimeToRippleTime,
} from './timeConversion'
import verifyPaymentChannelClaim from './verifyPaymentChannelClaim'
import { xrpToDrops, dropsToXrp } from './xrpConversion'
@@ -86,6 +91,8 @@ export {
removeUndefined,
rippleTimeToISOTime,
ISOTimeToRippleTime,
rippleTimeToUnixTime,
unixTimeToRippleTime,
isValidSecret,
computeSignedTransactionHash,
computeBinaryTransactionSigningHash,

View File

@@ -6,7 +6,7 @@ const RIPPLE_EPOCH_DIFF = 0x386d4380
* @param rpepoch - (seconds since 1/1/2000 GMT).
* @returns Milliseconds since unix epoch.
*/
function rippleToUnixTimestamp(rpepoch: number): number {
function rippleTimeToUnixTime(rpepoch: number): number {
return (rpepoch + RIPPLE_EPOCH_DIFF) * 1000
}
@@ -16,7 +16,7 @@ function rippleToUnixTimestamp(rpepoch: number): number {
* @param timestamp - (ms since unix epoch).
* @returns Seconds since Ripple Epoch (1/1/2000 GMT).
*/
function unixToRippleTimestamp(timestamp: number): number {
function unixTimeToRippleTime(timestamp: number): number {
return Math.round(timestamp / 1000) - RIPPLE_EPOCH_DIFF
}
@@ -27,7 +27,7 @@ function unixToRippleTimestamp(timestamp: number): number {
* @returns Iso8601 international standard date format.
*/
function rippleTimeToISOTime(rippleTime: number): string {
return new Date(rippleToUnixTimestamp(rippleTime)).toISOString()
return new Date(rippleTimeToUnixTime(rippleTime)).toISOString()
}
/**
@@ -37,7 +37,12 @@ function rippleTimeToISOTime(rippleTime: number): string {
* @returns Seconds since ripple epoch (1/1/2000 GMT).
*/
function ISOTimeToRippleTime(iso8601: string): number {
return unixToRippleTimestamp(Date.parse(iso8601))
return unixTimeToRippleTime(Date.parse(iso8601))
}
export { rippleTimeToISOTime, ISOTimeToRippleTime }
export {
rippleTimeToUnixTime,
unixTimeToRippleTime,
rippleTimeToISOTime,
ISOTimeToRippleTime,
}

View File

@@ -28,16 +28,16 @@ const MAX_ATTEMPTS = 20
/**
* Generates a random wallet with some amount of XRP (usually 1000 XRP).
*
* @param client - Client.
* @param this - Client.
* @param wallet - An existing XRPL Wallet to fund, if undefined, a new Wallet will be created.
* @returns A Wallet on the Testnet or Devnet that contains some amount of XRP.
* @throws When either Client isn't connected or unable to fund wallet address.
*/
async function generateFaucetWallet(
client: Client,
this: Client,
wallet?: Wallet,
): Promise<Wallet | undefined> {
if (!client.isConnected()) {
if (!this.isConnected()) {
throw new RippledError('Client not connected, cannot call faucet')
}
@@ -55,7 +55,7 @@ async function generateFaucetWallet(
)
// Retrieve the existing account balance
const addressToFundBalance = await getAddressXrpBalance(
client,
this,
fundWallet.classicAddress,
)
@@ -66,9 +66,9 @@ async function generateFaucetWallet(
: 0
// Options to pass to https.request
const options = getOptions(client, postBody)
const options = getOptions(this, postBody)
return returnPromise(options, client, startingBalance, fundWallet, postBody)
return returnPromise(options, this, startingBalance, fundWallet, postBody)
}
// eslint-disable-next-line max-params -- Helper function created for organizational purposes
@@ -222,7 +222,7 @@ async function getAddressXrpBalance(
} catch (err) {
if (err instanceof Error) {
throw new XRPLFaucetError(
`Unable to retrieve ${address} balance. Error: ${err.message}`,
`Unable to retrieve balance of ${address}. Error: ${err.message}`,
)
}
throw err
@@ -281,7 +281,7 @@ async function hasAddressBalanceIncreased(
* @returns A {@link FaucetNetwork}.
*/
function getFaucetUrl(client: Client): FaucetNetwork | undefined {
const connectionUrl = client.connection.getUrl()
const connectionUrl = client.url
// 'altnet' for Ripple Testnet server and 'testnet' for XRPL Labs Testnet server
if (connectionUrl.includes('altnet') || connectionUrl.includes('testnet')) {

View File

@@ -25,8 +25,7 @@ describe('Client', function () {
it('Client valid options', function () {
const client = new Client('wss://s:1')
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: fix when src/client linting is merged
const privateConnectionUrl = (client.connection as any).url
const privateConnectionUrl = client.url
assert.deepEqual(privateConnectionUrl, 'wss://s:1')
})

View File

@@ -1,9 +1,16 @@
import { assert } from 'chai'
import { AccountDelete, EscrowFinish, Payment, Transaction } from 'xrpl-local'
import {
XrplError,
AccountDelete,
EscrowFinish,
Payment,
Transaction,
} from 'xrpl-local'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertRejects } from '../testUtils'
const Fee = '10'
const Sequence = 1432
@@ -71,6 +78,27 @@ describe('client.autofill', function () {
assert.strictEqual(txResult.Sequence, 23)
})
it('should throw error if account deletion blockers exist', async function () {
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
this.mockRippled.addResponse('ledger', rippled.ledger.normal)
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse(
'account_objects',
rippled.account_objects.normal,
)
const tx: AccountDelete = {
Account: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn',
TransactionType: 'AccountDelete',
Destination: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
Fee,
Sequence,
LastLedgerSequence,
}
await assertRejects(this.client.autofill(tx), XrplError)
})
describe('when autofill Fee is missing', function () {
it('should autofill Fee of a Transaction', async function () {
const tx: Transaction = {
@@ -80,17 +108,7 @@ describe('client.autofill', function () {
Sequence,
LastLedgerSequence,
}
this.mockRippled.addResponse('server_info', {
status: 'success',
type: 'response',
result: {
info: {
validated_ledger: {
base_fee_xrp: 0.00001,
},
},
},
})
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
const txResult = await this.client.autofill(tx)
assert.strictEqual(txResult.Fee, '12')
@@ -108,19 +126,9 @@ describe('client.autofill', function () {
}
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
this.mockRippled.addResponse('ledger', rippled.ledger.normal)
this.mockRippled.addResponse('server_info', {
status: 'success',
type: 'response',
result: {
info: {
validated_ledger: {
base_fee_xrp: 0.00001,
},
},
},
})
const txResult = await this.client.autofill(tx)
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
const txResult = await this.client.autofill(tx)
assert.strictEqual(txResult.Fee, '399')
})
@@ -132,20 +140,25 @@ describe('client.autofill', function () {
}
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
this.mockRippled.addResponse('ledger', rippled.ledger.normal)
this.mockRippled.addResponse('server_info', {
this.mockRippled.addResponse('server_state', {
status: 'success',
type: 'response',
result: {
info: {
state: {
validated_ledger: {
base_fee_xrp: 0.00001,
reserve_inc: 2000000,
},
},
},
})
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse(
'account_objects',
rippled.account_objects.empty,
)
const txResult = await this.client.autofill(tx)
assert.strictEqual(txResult.Fee, '5000000')
assert.strictEqual(txResult.Fee, '2000000')
})
it('should autofill Fee of an EscrowFinish transaction with signersCount', async function () {
@@ -160,17 +173,7 @@ describe('client.autofill', function () {
}
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
this.mockRippled.addResponse('ledger', rippled.ledger.normal)
this.mockRippled.addResponse('server_info', {
status: 'success',
type: 'response',
result: {
info: {
validated_ledger: {
base_fee_xrp: 0.00001,
},
},
},
})
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
const txResult = await this.client.autofill(tx, 4)
assert.strictEqual(txResult.Fee, '459')

View File

@@ -15,7 +15,7 @@ describe('client constructor', function () {
it('Client valid options', function () {
const client = new Client('wss://s:1')
const privateConnectionUrl = client.connection.getUrl()
const privateConnectionUrl = client.url
assert.deepEqual(privateConnectionUrl, 'wss://s:1')
})

View File

@@ -1,5 +1,6 @@
import { assert } from 'chai'
import { XrplError, NotFoundError } from '../../src'
import { setupClient, teardownClient } from '../setupClient'
describe('client errors', function () {
@@ -7,12 +8,12 @@ describe('client errors', function () {
afterEach(teardownClient)
it('XrplError with data', async function () {
const error = new this.client.errors.XrplError('_message_', '_data_')
const error = new XrplError('_message_', '_data_')
assert.strictEqual(error.toString(), "[XrplError(_message_, '_data_')]")
})
it('NotFoundError default message', async function () {
const error = new this.client.errors.NotFoundError()
const error = new NotFoundError()
assert.strictEqual(error.toString(), '[NotFoundError(Not found)]')
})
})

View File

@@ -0,0 +1,15 @@
import { assert } from 'chai'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
describe('client.getLedgerIndex', function () {
beforeEach(setupClient)
afterEach(teardownClient)
it('getLedgerIndex', async function () {
this.mockRippled.addResponse('ledger', rippled.ledger.normal)
const ledgerIndex = await this.client.getLedgerIndex()
assert.strictEqual(ledgerIndex, 9038214)
})
})

View File

@@ -1,7 +1,7 @@
import BigNumber from 'bignumber.js'
import { assert } from 'chai'
import { BookOffersRequest } from '../../src'
import { BookOffersRequest, ValidationError } from '../../src'
import { OfferLedgerFlags } from '../../src/models/ledger/offer'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
@@ -101,7 +101,7 @@ describe('client.getOrderbook', function () {
invalid: 'options',
},
),
this.client.errors.ValidationError,
ValidationError,
)
})

View File

@@ -0,0 +1,154 @@
/* eslint-disable @typescript-eslint/no-explicit-any -- required for formatting transactions */
import { expect } from 'chai'
import type { TransactionStream } from '../../src'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
const partialPaymentIOU = rippled.partial_payments.iou
const partialPaymentXRP = rippled.partial_payments.xrp
describe('client handling of tfPartialPayments', function () {
beforeEach(setupClient)
afterEach(teardownClient)
it('Tx with no tfPartialPayment', async function () {
this.mockRippled.addResponse('tx', rippled.tx.Payment)
const resp = await this.client.request({ command: 'tx' })
expect(resp.warnings).to.equal(undefined)
})
it('Tx with IOU tfPartialPayment', async function () {
const mockResponse = { ...rippled.tx.Payment, result: partialPaymentIOU }
this.mockRippled.addResponse('tx', mockResponse)
const resp = await this.client.request({ command: 'tx' })
expect(resp.warnings).to.deep.equal([
{
id: 2001,
message: 'This response contains a Partial Payment',
},
])
})
it('Tx with XRP tfPartialPayment', async function () {
const mockResponse = { ...rippled.tx.Payment, result: partialPaymentXRP }
this.mockRippled.addResponse('tx', mockResponse)
const resp = await this.client.request({ command: 'tx' })
expect(resp.warnings).to.deep.equal([
{
id: 2001,
message: 'This response contains a Partial Payment',
},
])
})
it('account_tx with no tfPartialPayment', async function () {
this.mockRippled.addResponse('account_tx', rippled.account_tx.normal)
const resp = await this.client.request({ command: 'account_tx' })
expect(resp.warnings).to.equal(undefined)
})
it('account_tx with IOU tfPartialPayment', async function () {
const partial = {
...rippled.tx.Payment,
result: partialPaymentIOU,
}
const mockResponse = rippled.account_tx.normal
mockResponse.result.transactions.push({
tx: partial.result,
meta: partial.result.meta,
} as any)
this.mockRippled.addResponse('account_tx', mockResponse)
const resp = await this.client.request({
command: 'account_tx',
account: mockResponse.result.account,
})
expect(resp.warnings).to.deep.equal([
{
id: 2001,
message: 'This response contains a Partial Payment',
},
])
})
it('account_tx with XRP tfPartialPayment', async function () {
// TODO: Create fixtues with partial payments instead of using ...
const partial = { ...rippled.tx.Payment, result: partialPaymentXRP }
const mockResponse = rippled.account_tx.normal
mockResponse.result.transactions.push({
tx: partial.result,
meta: partial.result.meta,
} as any)
this.mockRippled.addResponse('account_tx', mockResponse)
const resp = await this.client.request({
command: 'account_tx',
account: mockResponse.result.account,
})
expect(resp.warnings).to.deep.equal([
{
id: 2001,
message: 'This response contains a Partial Payment',
},
])
})
it('transaction_entry with no tfPartialPayment', async function () {
this.mockRippled.addResponse('transaction_entry', rippled.transaction_entry)
const resp = await this.client.request({ command: 'transaction_entry' })
expect(resp.warnings).to.equal(undefined)
})
it('transaction_entry with XRP tfPartialPayment', async function () {
const mockResponse = rippled.transaction_entry
mockResponse.result.tx_json.Amount = '1000'
this.mockRippled.addResponse('transaction_entry', rippled.transaction_entry)
const resp = await this.client.request({ command: 'transaction_entry' })
expect(resp.warnings).to.deep.equal([
{
id: 2001,
message: 'This response contains a Partial Payment',
},
])
})
it('Transactions stream with no tfPartialPayment', async function (done) {
this.mockRippled.addResponse('transaction_entry', rippled.transaction_entry)
this.client.on('transaction', (tx: TransactionStream) => {
expect(tx.warnings).to.equal(undefined)
done()
})
this.client.connection.onMessage(
JSON.stringify(rippled.streams.transaction),
)
})
it('Transactions stream with XRP tfPartialPayment', async function (done) {
this.mockRippled.addResponse('transaction_entry', rippled.transaction_entry)
this.client.on('transaction', (tx: TransactionStream) => {
expect(tx.warnings).to.deep.equal([
{
id: 2001,
message: 'This response contains a Partial Payment',
},
])
done()
})
const partial: any = rippled.streams.transaction
partial.transaction = rippled.tx.Payment.result
partial.meta.delivered_amount = '1000'
partial.transaction.Flags = 0x00020000
this.client.connection.onMessage(JSON.stringify(partial))
})
})

View File

@@ -62,7 +62,7 @@ describe('client.submitSignedTransaction', function () {
this.mockRippled.addResponse('submit', rippled.submit.success)
assertRejects(
await assertRejects(
this.client.submitSignedTransaction(signedTx),
ValidationError,
'Transaction must be signed',

View File

@@ -17,7 +17,7 @@ import { Connection } from 'xrpl-local/client/connection'
import rippled from './fixtures/rippled'
import { setupClient, teardownClient } from './setupClient'
import { ignoreWebSocketDisconnect } from './testUtils'
import { assertRejects, ignoreWebSocketDisconnect } from './testUtils'
// how long before each test case times out
const TIMEOUT = 20000
@@ -514,13 +514,13 @@ describe('Connection', function () {
this.client.on('error', (errorCode, errorMessage, message) => {
assert.strictEqual(errorCode, 'badMessage')
assert.strictEqual(errorMessage, 'valid id not found in response')
assert.strictEqual(message, '{"type":"response","id":"must be integer"}')
assert.strictEqual(message, '{"type":"response","id":{}}')
done()
})
this.client.connection.onMessage(
JSON.stringify({
type: 'response',
id: 'must be integer',
id: {},
}),
)
})
@@ -619,4 +619,20 @@ describe('Connection', function () {
.then(() => new Error('Should not have succeeded'))
.catch(done())
})
it('should throw error if pending response with same ID', async function () {
const promise1 = this.client.connection.request({
id: 'test',
command: 'ping',
})
const promise2 = this.client.connection.request({
id: 'test',
command: 'ping',
})
await assertRejects(
Promise.all([promise1, promise2]),
XrplError,
"Response with id 'test' is already pending",
)
})
})

View File

@@ -0,0 +1,13 @@
{
"id": 1,
"status": "success",
"type": "response",
"result": {
"account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"account_objects": [],
"ledger_hash":
"053DF17D2289D1C4971C22F235BC1FCA7D4B3AE966F842E5819D0749E0B8ECD3",
"ledger_index": 14378733,
"validated": true
}
}

View File

@@ -1,239 +0,0 @@
/* eslint-disable max-len */
'use strict';
const _ = require('lodash');
const hashes = require('../hashes');
const addresses = require('../addresses');
const AccountSet = require('./tx/accountSet.json');
const NotFound = require('./tx/notFound.json');
const binary = require('ripple-binary-codec');
module.exports = function(request, options = {}) {
_.defaults(options, {
memos: [{
Memo: {
MemoFormat: '7274312E352E32',
MemoType: '636C69656E74'
}
}],
hash: hashes.VALID_TRANSACTION_HASH,
validated: true
});
let tx = {
Account: addresses.ACCOUNT,
Amount: {
currency: 'USD',
issuer: addresses.ISSUER,
value: '0.001'
},
Destination: addresses.ISSUER,
Fee: '10',
Flags: 0,
Memos: options.memos,
Paths: [
[
{
currency: 'USD',
issuer: addresses.OTHER_ACCOUNT,
type: 48,
type_hex: '0000000000000030'
},
{
account: addresses.OTHER_ACCOUNT,
currency: 'USD',
issuer: addresses.OTHER_ACCOUNT,
type: 49,
type_hex: '0000000000000031'
}
]
],
SendMax: '1112209',
Sequence: 4,
SigningPubKey: '02BC8C02199949B15C005B997E7C8594574E9B02BA2D0628902E0532989976CF9D',
TransactionType: 'Payment',
TxnSignature: '304502204EE3E9D1B01D8959B08450FCA9E22025AF503DEF310E34A93863A85CAB3C0BC5022100B61F5B567F77026E8DEED89EED0B7CAF0E6C96C228A2A65216F0DC2D04D52083'
};
let meta = {
AffectedNodes: [
{
ModifiedNode: {
FinalFields: {
Account: addresses.ACCOUNT,
BookDirectory: '4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5E03E788E09BB000',
BookNode: '0000000000000000',
Flags: 0,
OwnerNode: '0000000000000000',
Sequence: 58,
TakerGets: {
currency: 'USD',
issuer: addresses.OTHER_ACCOUNT,
value: '5.648998'
},
TakerPays: '6208248802'
},
LedgerEntryType: 'Offer',
LedgerIndex: '3CFB3C79D4F1BDB1EE5245259372576D926D9A875713422F7169A6CC60AFA68B',
PreviousFields: {
TakerGets: {
currency: 'USD',
issuer: addresses.OTHER_ACCOUNT,
value: '5.65'
},
TakerPays: '6209350000'
},
PreviousTxnID: '8F571C346688D89AC1F737AE3B6BB5D976702B171CC7B4DE5CA3D444D5B8D6B4',
PreviousTxnLgrSeq: 348433
}
},
{
ModifiedNode: {
FinalFields: {
Balance: {
currency: 'USD',
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
value: '-0.001'
},
Flags: 131072,
HighLimit: {
currency: 'USD',
issuer: addresses.ISSUER,
value: '1'
},
HighNode: '0000000000000000',
LowLimit: {
currency: 'USD',
issuer: addresses.OTHER_ACCOUNT,
value: '0'
},
LowNode: '0000000000000002'
},
LedgerEntryType: 'RippleState',
LedgerIndex: '4BD1874F8F3A60EDB0C23F5BD43E07953C2B8741B226648310D113DE2B486F01',
PreviousFields: {
Balance: {
currency: 'USD',
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
value: '0'
}
},
PreviousTxnID: '5B2006DAD0B3130F57ACF7CC5CCAC2EEBCD4B57AAA091A6FD0A24B073D08ABB8',
PreviousTxnLgrSeq: 343703
}
},
{
ModifiedNode: {
FinalFields: {
Account: addresses.ACCOUNT,
Balance: '9998898762',
Flags: 0,
OwnerCount: 3,
Sequence: 5
},
LedgerEntryType: 'AccountRoot',
LedgerIndex: '4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05',
PreviousFields: {
Balance: '9999999970',
Sequence: 4
},
PreviousTxnID: '53354D84BAE8FDFC3F4DA879D984D24B929E7FEB9100D2AD9EFCD2E126BCCDC8',
PreviousTxnLgrSeq: 343570
}
},
{
ModifiedNode: {
FinalFields: {
Account: 'r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr',
Balance: '912695302618',
Flags: 0,
OwnerCount: 10,
Sequence: 59
},
LedgerEntryType: 'AccountRoot',
LedgerIndex: 'F3E119AAA87AF3607CF87F5523BB8278A83BCB4142833288305D767DD30C392A',
PreviousFields: {
Balance: '912694201420'
},
PreviousTxnID: '8F571C346688D89AC1F737AE3B6BB5D976702B171CC7B4DE5CA3D444D5B8D6B4',
PreviousTxnLgrSeq: 348433
}
},
{
ModifiedNode: {
FinalFields: {
Balance: {
currency: 'USD',
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
value: '-5.5541638883365'
},
Flags: 131072,
HighLimit: {
currency: 'USD',
issuer: 'r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr',
value: '1000'
},
HighNode: '0000000000000000',
LowLimit: {
currency: 'USD',
issuer: addresses.OTHER_ACCOUNT,
value: '0'
},
LowNode: '000000000000000C'
},
LedgerEntryType: 'RippleState',
LedgerIndex: 'FA1255C2E0407F1945BCF9351257C7C5C28B0F5F09BB81C08D35A03E9F0136BC',
PreviousFields: {
Balance: {
currency: 'USD',
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
value: '-5.5551658883365'
}
},
PreviousTxnID: '8F571C346688D89AC1F737AE3B6BB5D976702B171CC7B4DE5CA3D444D5B8D6B4',
PreviousTxnLgrSeq: 348433
}
}
],
TransactionIndex: 0,
TransactionResult: 'tesSUCCESS'
};
let marker = Number(request.marker) || 0;
marker += 1;
if (marker === 5) {
meta.TransactionResult = 'tecINSUFFICIENT_RESERVE';
} else if (marker === 6) {
tx = _.cloneDeep(AccountSet.result);
meta = tx.meta;
delete tx.meta;
} else if (marker === 7) {
tx.Account = addresses.OTHER_ACCOUNT;
} else if (marker === 8) {
tx.Destination = addresses.THIRD_ACCOUNT;
} else if (marker > 25) {
marker = undefined;
} else if (marker > 15) {
tx.Account = addresses.ISSUER;
tx.Destination = addresses.ACCOUNT;
}
if (request.limit === 13) {
const res = Object.assign({}, NotFound, {id: request.id});
return JSON.stringify(res);
}
return JSON.stringify({
id: request.id,
status: 'success',
type: 'response',
result: {
marker: marker == null ? undefined : String(marker),
transactions: [
{
ledger_index: 348860 - Number(marker || 100),
tx_blob: binary.encode(tx),
meta: binary.encode(meta),
validated: options.validated
}
]
}
});
};

194
test/fixtures/rippled/accountTx.json vendored Normal file
View File

@@ -0,0 +1,194 @@
{
"id": 2,
"result": {
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 66496515,
"ledger_index_min": 32570,
"limit": 2,
"marker": {
"ledger": 61965340,
"seq": 0
},
"transactions": [
{
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"AccountTxnID": "4E0AA11CBDD1760DE95B68DF2ABBE75C9698CEB548BEA9789053FCB3EBD444FB",
"Balance": "424021949",
"Domain": "6D64756F31332E636F6D",
"EmailHash": "98B4375E1D753E5B91627516F6D70977",
"Flags": 9568256,
"MessageKey": "0000000000000000000000070000000300",
"OwnerCount": 12,
"RegularKey": "rD9iJmieYHn8jTtPjwwkW2Wm9sVDvPXLoJ",
"Sequence": 385,
"TransferRate": 4294967295
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
"PreviousFields": {
"AccountTxnID": "CB1BF910C93D050254C049E9003DA1A265C107E0C8DE4A7CFF55FADFD39D5656",
"Balance": "424021959",
"OwnerCount": 11,
"Sequence": 384
},
"PreviousTxnID": "CB1BF910C93D050254C049E9003DA1A265C107E0C8DE4A7CFF55FADFD39D5656",
"PreviousTxnLgrSeq": 61965405
}
},
{
"ModifiedNode": {
"FinalFields": {
"Flags": 0,
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"RootIndex": "3B9C0CE77FCE7BCEE1A68F1E26AC467AF326239D0D816CE705E4A0E2DAD03F6D"
},
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "3B9C0CE77FCE7BCEE1A68F1E26AC467AF326239D0D816CE705E4A0E2DAD03F6D"
}
},
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "43EA78783A089B137D5E87610DF3BD4129F989EDD02EFAF6C265924D3A0EF8CE",
"PreviousTxnID": "711C4F606C63076137FAE90ADC36379D7066CF551E96DA6FE2BDAB5ECBFACF2B",
"PreviousTxnLgrSeq": 61965340
}
},
{
"ModifiedNode": {
"FinalFields": {
"Flags": 0,
"Owner": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"RootIndex": "6C8ECE4529AA025F51059F1B56015A8A5F49987064FBE1E832FF27BFEF3F18CF"
},
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "6C8ECE4529AA025F51059F1B56015A8A5F49987064FBE1E832FF27BFEF3F18CF"
}
},
{
"CreatedNode": {
"LedgerEntryType": "Check",
"LedgerIndex": "C4A46CCD8F096E994C4B0DEAB6CE98E722FC17D7944C28B95127C2659C47CBEB",
"NewFields": {
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"DestinationTag": 13,
"SendMax": {
"currency": "USD",
"issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"value": "10"
},
"Sequence": 384
}
}
}
],
"TransactionIndex": 12,
"TransactionResult": "tesSUCCESS"
},
"tx": {
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"DestinationTag": 13,
"Fee": "10",
"Flags": 2147483648,
"LastLedgerSequence": 61965654,
"SendMax": {
"currency": "USD",
"issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"value": "10"
},
"Sequence": 384,
"SigningPubKey": "03CE5C5949DEBBBB5E6D8FA54AC3FA8A3ED4EE1C3E9617571840F9349DE7AEF329",
"TransactionType": "CheckCreate",
"TxnSignature": "304402200EC41D7F6C3C57E697A61EFC0585544399A1ECD7AA233256F6BE785294E2671C022022802AED958D44E6BE5679D41157246A7AA72A4A6CFBF531A39A01CCB6AFEED9",
"date": 668134081,
"hash": "4E0AA11CBDD1760DE95B68DF2ABBE75C9698CEB548BEA9789053FCB3EBD444FB",
"inLedger": 61965653,
"ledger_index": 61965653
},
"validated": true
},
{
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"AccountTxnID": "CB1BF910C93D050254C049E9003DA1A265C107E0C8DE4A7CFF55FADFD39D5656",
"Balance": "424021959",
"Domain": "6D64756F31332E636F6D",
"EmailHash": "98B4375E1D753E5B91627516F6D70977",
"Flags": 9568256,
"MessageKey": "0000000000000000000000070000000300",
"OwnerCount": 11,
"RegularKey": "rD9iJmieYHn8jTtPjwwkW2Wm9sVDvPXLoJ",
"Sequence": 384,
"TransferRate": 4294967295
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
"PreviousFields": {
"AccountTxnID": "711C4F606C63076137FAE90ADC36379D7066CF551E96DA6FE2BDAB5ECBFACF2B",
"Balance": "424021969",
"OwnerCount": 10,
"Sequence": 383
},
"PreviousTxnID": "711C4F606C63076137FAE90ADC36379D7066CF551E96DA6FE2BDAB5ECBFACF2B",
"PreviousTxnLgrSeq": 61965340
}
},
{
"ModifiedNode": {
"FinalFields": {
"Flags": 0,
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"RootIndex": "3B9C0CE77FCE7BCEE1A68F1E26AC467AF326239D0D816CE705E4A0E2DAD03F6D"
},
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "3B9C0CE77FCE7BCEE1A68F1E26AC467AF326239D0D816CE705E4A0E2DAD03F6D"
}
},
{
"CreatedNode": {
"LedgerEntryType": "DepositPreauth",
"LedgerIndex": "A43898B685C450DE8E194B24D9D54E62530536A770CCB311BFEE15A27381ABB2",
"NewFields": {
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Authorize": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX"
}
}
}
],
"TransactionIndex": 59,
"TransactionResult": "tesSUCCESS"
},
"tx": {
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Authorize": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "10",
"Flags": 2147483648,
"LastLedgerSequence": 61965405,
"Sequence": 383,
"SigningPubKey": "03CE5C5949DEBBBB5E6D8FA54AC3FA8A3ED4EE1C3E9617571840F9349DE7AEF329",
"TransactionType": "DepositPreauth",
"TxnSignature": "304402206464885794C92713D15141B8C68CD020E5EE0BADB7CA7293CB073F02594BEB6F02205FC46EF82613DB5F2AC583C854B9F3C5FAE223E9C7056CB1625260DD3E0718AC",
"date": 668133130,
"hash": "CB1BF910C93D050254C049E9003DA1A265C107E0C8DE4A7CFF55FADFD39D5656",
"inLedger": 61965405,
"ledger_index": 61965405
},
"validated": true
}
],
"validated": true
},
"status": "success",
"type": "response"
}

View File

@@ -1,8 +1,9 @@
import normalAccountInfo from './accountInfo.json'
import notfoundAccountInfo from './accountInfoNotFound.json'
import emptyAccountObjects from './accountObjectsEmpty.json'
import normalAccountObjects from './accountObjectsNormal.json'
import account_offers from './accountOffers'
import normalAccountTx from './accountTx'
import normalAccountTx from './accountTx.json'
import fabric from './bookOffers'
import usd_xrp from './bookOffersUsdXrp.json'
import xrp_usd from './bookOffersXrpUsd.json'
@@ -23,6 +24,8 @@ import withoutCloseTime from './ledgerWithoutCloseTime.json'
import withPartialPayment from './ledgerWithPartialPayment.json'
import withSettingsTx from './ledgerWithSettingsTx.json'
import withStateAsHashes from './ledgerWithStateAsHashes.json'
import iouPartialPayment from './partialPaymentIOU.json'
import xrpPartialPayment from './partialPaymentXRP.json'
import generate from './pathFind'
import sendAll from './pathFindSendAll.json'
import sendUSD from './pathFindSendUsd.json'
@@ -49,6 +52,7 @@ import successSubmit from './submit.json'
import failureSubmit from './submitFailed.json'
import successSubscribe from './subscribe.json'
import errorSubscribe from './subscribeError.json'
import transaction_entry from './transactionEntry.json'
import AccountDelete from './tx/accountDelete.json'
import AccountDeleteWithMemo from './tx/accountDeleteWithMemo.json'
import AccountSet from './tx/accountSet.json'
@@ -131,9 +135,14 @@ const streams = {
manifest: manifestStream,
}
const partial_payments = {
xrp: xrpPartialPayment,
iou: iouPartialPayment,
}
const account_objects = {
normal: normalAccountObjects,
// notfound: notfoundAccountObjects
empty: emptyAccountObjects,
}
const account_info = {
@@ -249,12 +258,14 @@ const rippled = {
ledger_data,
ledger_entry,
ledger_current,
partial_payments,
path_find,
payment_channel,
server_info,
streams,
submit,
subscribe,
transaction_entry,
tx,
unsubscribe,
}

View File

@@ -20,6 +20,9 @@
"1FC4D12C30CE206A6E23F46FAC62BD393BE9A79A1C452C6F3A04A13BC7A5E5A3",
"E25C38FDB8DD4A2429649588638EE05D055EE6D839CABAF8ABFB4BD17CFE1F3E"
]
}
},
"ledger_hash": "1723099E269C77C4BDE86C83FA6415D71CF20AA5CB4A94E5C388ED97123FB55B",
"ledger_index": 9038214,
"validated": true
}
}

View File

@@ -0,0 +1,116 @@
{
"Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"Amount": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "10"
},
"Destination": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"Fee": "10000",
"Flags": 131072,
"Sequence": 23295,
"SigningPubKey": "02B205F4B92351AC0EEB04254B636F4C49EF922CFA3CAAD03C6477DA1E04E94B53",
"TransactionType": "Payment",
"TxnSignature": "3045022100FAF247A836D601DE74A515B2AADE31186D8B0DA9C23DE489E09753F5CF4BB81F0220477C5B5BC3AC89F2347744F9E00CCA62267E198489D747578162C4C7D156211D",
"hash": "A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A",
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"Balance": "1930599790",
"Flags": 0,
"OwnerCount": 2,
"Sequence": 23296
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "267C16D24EC42EEF8B03D5BE4E94266B1675FA54AFCE42DE795E02AB61031CBD",
"PreviousFields": {
"Balance": "1930609790",
"Sequence": 23295
},
"PreviousTxnID": "0F5396388E91D37BB26C8E24073A57E7C5D51E79AEE4CD855653B8499AE4E3DD",
"PreviousTxnLgrSeq": 22419806
}
},
{
"ModifiedNode": {
"FinalFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-9.980959751659681"
},
"Flags": 2228224,
"HighLimit": {
"currency": "USD",
"issuer": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"value": "1000000"
},
"HighNode": "0000000000000000",
"LowLimit": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0"
},
"LowNode": "0000000000000423"
},
"LedgerEntryType": "RippleState",
"LedgerIndex": "C66957AF25229357F9C2D2BA17CE47D88169788EDA7610AD0F29AD5BCB225EE5",
"PreviousFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-0.0009198315"
}
},
"PreviousTxnID": "2A01E994D7000000B43DD63825A081B4440A44AB2F6FA0D506158AC9CA6B2869",
"PreviousTxnLgrSeq": 22420532
}
},
{
"ModifiedNode": {
"FinalFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-276666.975959"
},
"Flags": 131072,
"HighLimit": {
"currency": "USD",
"issuer": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"value": "1000000"
},
"HighNode": "0000000000000000",
"LowLimit": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0"
},
"LowNode": "00000000000002D7"
},
"LedgerEntryType": "RippleState",
"LedgerIndex": "FFD710AE2074A98D920D00CC352F25744899F069A6C1B9E31DD32D2C6606E615",
"PreviousFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-276676.975959"
}
},
"PreviousTxnID": "BB9DFC87E9D4ED09CA2726DDFE83A4A396ED0D6545536322DE17CDACF45C0D5B",
"PreviousTxnLgrSeq": 22419307
}
}
],
"delivered_amount": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "9.980039920159681"
},
"TransactionIndex": 5,
"TransactionResult": "tesSUCCESS"
}
}

View File

@@ -0,0 +1,108 @@
{
"Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"Amount": "2000000",
"Destination": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"Fee": "10000",
"Flags": 131072,
"Sequence": 23295,
"SigningPubKey": "02B205F4B92351AC0EEB04254B636F4C49EF922CFA3CAAD03C6477DA1E04E94B53",
"TransactionType": "Payment",
"TxnSignature": "3045022100FAF247A836D601DE74A515B2AADE31186D8B0DA9C23DE489E09753F5CF4BB81F0220477C5B5BC3AC89F2347744F9E00CCA62267E198489D747578162C4C7D156211D",
"hash": "A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A",
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"Balance": "1930599790",
"Flags": 0,
"OwnerCount": 2,
"Sequence": 23296
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "267C16D24EC42EEF8B03D5BE4E94266B1675FA54AFCE42DE795E02AB61031CBD",
"PreviousFields": {
"Balance": "1930609790",
"Sequence": 23295
},
"PreviousTxnID": "0F5396388E91D37BB26C8E24073A57E7C5D51E79AEE4CD855653B8499AE4E3DD",
"PreviousTxnLgrSeq": 22419806
}
},
{
"ModifiedNode": {
"FinalFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-9.980959751659681"
},
"Flags": 2228224,
"HighLimit": {
"currency": "USD",
"issuer": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"value": "1000000"
},
"HighNode": "0000000000000000",
"LowLimit": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0"
},
"LowNode": "0000000000000423"
},
"LedgerEntryType": "RippleState",
"LedgerIndex": "C66957AF25229357F9C2D2BA17CE47D88169788EDA7610AD0F29AD5BCB225EE5",
"PreviousFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-0.0009198315"
}
},
"PreviousTxnID": "2A01E994D7000000B43DD63825A081B4440A44AB2F6FA0D506158AC9CA6B2869",
"PreviousTxnLgrSeq": 22420532
}
},
{
"ModifiedNode": {
"FinalFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-276666.975959"
},
"Flags": 131072,
"HighLimit": {
"currency": "USD",
"issuer": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"value": "1000000"
},
"HighNode": "0000000000000000",
"LowLimit": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0"
},
"LowNode": "00000000000002D7"
},
"LedgerEntryType": "RippleState",
"LedgerIndex": "FFD710AE2074A98D920D00CC352F25744899F069A6C1B9E31DD32D2C6606E615",
"PreviousFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-276676.975959"
}
},
"PreviousTxnID": "BB9DFC87E9D4ED09CA2726DDFE83A4A396ED0D6545536322DE17CDACF45C0D5B",
"PreviousTxnLgrSeq": 22419307
}
}
],
"delivered_amount": "1000000",
"TransactionIndex": 5,
"TransactionResult": "tesSUCCESS"
}
}

View File

@@ -0,0 +1,75 @@
{
"id": 4,
"result": {
"ledger_hash": "C3D46598EB9BF92688CE2395496AE3A55E084F57319F1086B7CD5CF002049097",
"ledger_index": 66499739,
"metadata": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK",
"Balance": "1750880278055",
"Flags": 131072,
"MessageKey": "02000000000000000000000000B5F84807633600D3AF4D922486E0ADF9FA5F6359",
"OwnerCount": 0,
"Sequence": 731413
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "D087F0DCA3A847606D90DB606FBB85B7A3334B9AD341E30E98B1653261019708",
"PreviousFields": {
"Balance": "1750984454027",
"Sequence": 731412
},
"PreviousTxnID": "9CD134C7FCFFEC73381E32190BA7A963B681CB1A1AE84923AD38A5CD96ABDA47",
"PreviousTxnLgrSeq": 66499731
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh",
"Balance": "86353857604",
"Flags": 131072,
"OwnerCount": 0,
"Sequence": 314878
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "E50C9EE857E177CE38071B8930F66053C9C86DF9B8ADEDA632CB9DFF50EC0033",
"PreviousFields": {
"Balance": "86249687632"
},
"PreviousTxnID": "B424651D147B2D6D625AF9F27B4F4030EBBA0DB0A7684D0D84ED23ED0FCC27B2",
"PreviousTxnLgrSeq": 66499738
}
}
],
"TransactionIndex": 57,
"TransactionResult": "tesSUCCESS",
"delivered_amount": "104169972"
},
"tx_json": {
"Account": "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK",
"Amount": "104169972",
"Destination": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh",
"DestinationTag": 109735445,
"Fee": "6000",
"Flags": 2147614720,
"LastLedgerSequence": 66499838,
"Sequence": 731412,
"SigningPubKey": "038944E15BADB379B5A2173B5248F36178DB08ABFF69428266D4068A1A471E3F11",
"TransactionType": "Payment",
"TxnSignature": "3044022060CA007B76E2835EE03AC67B96C9FFF0C946AC19CFD9AAB7E77AB87F8E36239A02204F86B31BCF58C2472334D44364F3FD6528036C415028690F29C85E818DDC676F",
"hash": "3F437BAAC9E713F24DF2954EADB7BE80F608D8B758116298B999BA252DF4816C"
},
"validated": true,
"warnings": [
{
"id": 1004,
"message": "This is a reporting server. The default behavior of a reporting server is to only return validated data. If you are looking for not yet validated data, include \"ledger_index : current\" in your request, which will cause this server to forward the request to a p2p node. If the forward is successful the response will include \"forwarded\" : \"true\""
}
]
},
"status": "success",
"type": "response"
}

View File

@@ -2,7 +2,7 @@ To run integration tests:
1. Run rippled-standalone node, either in a docker container (preferred) or by installing rippled.
* With docker, run `docker run -p 6006:6006 -it natenichols/rippled-standalone:latest`
* Or [download and build rippled](https://xrpl.org/install-rippled.html) and run `./rippled -a`
2. Run `yarn test:integration` or `yarn test:browser`
2. Run `npm test:integration` or `npm test:browser`
When editing integration tests:
* All imports should be from `xrpl-local` instead of `../../src` (browser tests need this)

View File

@@ -1,8 +1,39 @@
/* eslint-disable import/export -- Tells webpack which files exist. */
export * from './integration'
export * from './transactions/signerListSet'
export * from './transactions/payment'
export * from './transactions/offerCreate'
export * from './transactions/offerCancel'
export * from './transactions/signerListSet'
export * from './transactions/checkCancel'
export * from './transactions/checkCash'
export * from './transactions/checkCreate'
export * from './transactions/depositPreauth'
export * from './transactions/paymentChannelCreate'
export * from './transactions/paymentChannelClaim'
export * from './transactions/paymentChannelFund'
export * from './transactions/trustSet'
export * from './requests/accountChannels'
export * from './requests/accountCurrencies'
export * from './requests/accountInfo'
export * from './requests/accountLines'
export * from './requests/accountObjects'
export * from './requests/accountOffers'
export * from './requests/accountTx'
export * from './requests/bookOffers'
export * from './requests/channelVerify'
export * from './requests/depositAuthorized'
export * from './requests/gatewayBalances'
export * from './requests/ledger'
export * from './requests/ledgerClosed'
export * from './requests/ledgerCurrent'
export * from './requests/ledgerData'
export * from './requests/ledgerEntry'
export * from './requests/multisign'
export * from './requests/noRippleCheck'
export * from './requests/pathFind'
export * from './requests/ripplePathFind'
export * from './requests/submit'
export * from './requests/tx'
export * from './requests/utility'
export * from './integration'

View File

@@ -0,0 +1,48 @@
import { assert } from 'chai'
import _ from 'lodash'
import { AccountChannelsRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('account_channels', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const request: AccountChannelsRequest = {
command: 'account_channels',
account: this.wallet.getClassicAddress(),
ledger_index: 'validated',
}
const response = await this.client.request(request)
const expected = {
id: 0,
result: {
account: this.wallet.getClassicAddress(),
channels: [],
ledger_hash:
'C8BFA74A740AA22AD9BD724781589319052398B0C6C817B88D55628E07B7B4A1',
ledger_index: 150,
validated: true,
},
status: 'success',
type: 'response',
}
assert.equal(response.status, expected.status)
assert.equal(response.type, expected.type)
assert.equal(typeof response.result.ledger_hash, 'string')
assert.equal(typeof response.result.ledger_index, 'number')
assert.deepEqual(
_.omit(response.result, ['ledger_hash', 'ledger_index']),
_.omit(expected.result, ['ledger_hash', 'ledger_index']),
)
})
})

View File

@@ -0,0 +1,49 @@
import { assert } from 'chai'
import _ from 'lodash'
import { AccountCurrenciesRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('account_currencies', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const request: AccountCurrenciesRequest = {
command: 'account_currencies',
account: this.wallet.getClassicAddress(),
strict: true,
ledger_index: 'validated',
}
const response = await this.client.request(request)
const expected = {
id: 0,
result: {
receive_currencies: [],
send_currencies: [],
ledger_hash:
'C8BFA74A740AA22AD9BD724781589319052398B0C6C817B88D55628E07B7B4A1',
ledger_index: 150,
validated: true,
},
status: 'success',
type: 'response',
}
assert.equal(response.status, expected.status)
assert.equal(response.type, expected.type)
assert.equal(typeof response.result.ledger_hash, 'string')
assert.equal(typeof response.result.ledger_index, 'number')
assert.deepEqual(
_.omit(response.result, ['ledger_hash', 'ledger_index']),
_.omit(expected.result, ['ledger_hash', 'ledger_index']),
)
})
})

View File

@@ -0,0 +1,78 @@
import { assert } from 'chai'
import _ from 'lodash'
import { AccountInfoRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('account_info', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const request: AccountInfoRequest = {
command: 'account_info',
account: this.wallet.getClassicAddress(),
strict: true,
ledger_index: 'validated',
}
const response = await this.client.request(request)
const expected = {
id: 0,
result: {
account_data: {
Account: this.wallet.getClassicAddress(),
Balance: '400000000',
Flags: 0,
LedgerEntryType: 'AccountRoot',
OwnerCount: 0,
PreviousTxnID:
'19A8211695785A3A02C1C287D93C2B049E83A9CD609825E721052D63FF4F0EC8',
PreviousTxnLgrSeq: 582,
Sequence: 283,
index:
'BD4815E6EB304136E6044F778FB68D4E464CC8DFC59B8F6CC93D90A3709AE194',
},
ledger_hash:
'F0DEEC46A7185BBB535517EE38CF2025973022D5B0532B36407F492521FDB0C6',
ledger_index: 582,
validated: true,
},
status: 'success',
type: 'response',
}
assert.equal(response.status, expected.status)
assert.equal(response.type, expected.type)
assert.equal(response.result.validated, expected.result.validated)
assert.equal(typeof response.result.ledger_hash, 'string')
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',
]),
)
})
})

View File

@@ -0,0 +1,49 @@
import { assert } from 'chai'
import _ from 'lodash'
import { AccountLinesRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('account_lines', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const request: AccountLinesRequest = {
command: 'account_lines',
account: this.wallet.getClassicAddress(),
strict: true,
ledger_index: 'validated',
}
const response = await this.client.request(request)
const expected = {
id: 0,
result: {
account: this.wallet.getClassicAddress(),
ledger_hash:
'0C09AAFA88AC1A616058220CF33269788D3985DAA6F2386196D4A7404252BB61',
ledger_index: 1074,
lines: [],
validated: true,
},
status: 'success',
type: 'response',
}
assert.equal(response.status, expected.status)
assert.equal(response.type, expected.type)
assert.equal(typeof response.result.ledger_hash, 'string')
assert.equal(typeof response.result.ledger_index, 'number')
assert.deepEqual(
_.omit(response.result, ['ledger_hash', 'ledger_index']),
_.omit(expected.result, ['ledger_hash', 'ledger_index']),
)
})
})

View File

@@ -0,0 +1,48 @@
import { assert } from 'chai'
import _ from 'lodash'
import { AccountObjectsRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('account_objects', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const request: AccountObjectsRequest = {
command: 'account_objects',
account: this.wallet.getClassicAddress(),
ledger_index: 'validated',
}
const response = await this.client.request(request)
const expected = {
id: 0,
result: {
account: this.wallet.getClassicAddress(),
account_objects: [],
ledger_hash:
'28D68B351ED58B9819502EF5FC05BA4412A048597E5159E1C226703BDF7C7897',
ledger_index: 1294,
validated: true,
},
status: 'success',
type: 'response',
}
assert.equal(response.status, expected.status)
assert.equal(response.type, expected.type)
assert.equal(typeof response.result.ledger_hash, 'string')
assert.equal(typeof response.result.ledger_index, 'number')
assert.deepEqual(
_.omit(response.result, ['ledger_hash', 'ledger_index']),
_.omit(expected.result, ['ledger_hash', 'ledger_index']),
)
})
})

View File

@@ -0,0 +1,45 @@
import { assert } from 'chai'
import _ from 'lodash'
import { AccountOffersRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('account_offers', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const request: AccountOffersRequest = {
command: 'account_offers',
account: this.wallet.getClassicAddress(),
strict: true,
}
const response = await this.client.request(request)
const expected = {
id: 0,
result: {
account: this.wallet.getClassicAddress(),
ledger_current_index: 1443,
offers: [],
validated: false,
},
status: 'success',
type: 'response',
}
assert.equal(response.status, expected.status)
assert.equal(response.type, expected.type)
assert.equal(typeof response.result.ledger_current_index, 'number')
assert.deepEqual(
_.omit(response.result, 'ledger_current_index'),
_.omit(expected.result, 'ledger_current_index'),
)
})
})

View File

@@ -0,0 +1,103 @@
import { assert } from 'chai'
import _ from 'lodash'
import { AccountTxRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('account_tx', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const request: AccountTxRequest = {
command: 'account_tx',
account: this.wallet.getClassicAddress(),
ledger_index: 'validated',
}
const response = await this.client.request(request)
const expected = {
result: {
account: this.wallet.getClassicAddress(),
limit: 400,
transactions: [
{
tx: {
Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
Amount: '400000000',
Destination: this.wallet.getClassicAddress(),
Fee: '12',
Flags: 0,
LastLedgerSequence: 1753,
Sequence: 843,
SigningPubKey:
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020',
TransactionType: 'Payment',
TxnSignature:
'30440220693D244BC13967E3DA67BDC974096784ED03DD4ACE6F36645E5176988452AFCF02200F8AB172432913899F27EC5523829AEDAD00CC2445690400E294EDF652A85945',
date: 685747005,
hash: '2E68BC15813B4A836FAC4D80E42E6FDA6410E99AB973937DEA5E6C2E9A116BAB',
inLedger: 1734,
ledger_index: 1734,
},
},
],
},
status: 'success',
type: 'response',
}
assert.equal(response.status, expected.status)
assert.equal(response.type, expected.type)
assert.equal(response.result.account, expected.result.account)
assert.equal(
response.result.transactions[0].meta.TransactionResult,
'tesSUCCESS',
)
assert.equal(
typeof response.result.transactions[0].tx.LastLedgerSequence,
'number',
)
assert.equal(typeof response.result.transactions[0].tx.Sequence, 'number')
assert.equal(
typeof response.result.transactions[0].tx.SigningPubKey,
'string',
)
assert.equal(
typeof response.result.transactions[0].tx.TxnSignature,
'string',
)
assert.equal(typeof response.result.transactions[0].tx.Fee, 'string')
assert.equal(typeof response.result.transactions[0].tx.hash, 'string')
assert.equal(typeof response.result.transactions[0].tx.inLedger, 'number')
assert.equal(
typeof response.result.transactions[0].tx.ledger_index,
'number',
)
const responseTx = response.result.transactions[0].tx
const expectedTx = expected.result.transactions[0].tx
assert.deepEqual(
[
responseTx.Flags,
responseTx.TransactionType,
responseTx.Account,
responseTx.Amount,
responseTx.Destination,
],
[
expectedTx.Flags,
expectedTx.TransactionType,
expectedTx.Account,
expectedTx.Amount,
expectedTx.Destination,
],
)
})
})

View File

@@ -0,0 +1,45 @@
import { assert } from 'chai'
import _ from 'lodash'
import { BookOffersRequest, BookOffersResponse } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('book_offers', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const bookOffer: BookOffersRequest = {
command: 'book_offers',
taker_gets: {
currency: 'XRP',
},
taker_pays: {
currency: 'USD',
issuer: this.wallet.getClassicAddress(),
},
}
const response = await this.client.request(bookOffer)
const expectedResponse: BookOffersResponse = {
id: response.id,
status: 'success',
type: 'response',
result: {
ledger_current_index: response.result.ledger_current_index,
offers: response.result.offers,
validated: false,
},
}
assert.deepEqual(response, expectedResponse)
})
})

View File

@@ -0,0 +1,43 @@
import { assert } from 'chai'
import _ from 'lodash'
import { ChannelVerifyRequest, ChannelVerifyResponse } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('channel_verify', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const channelVerify: ChannelVerifyRequest = {
command: 'channel_verify',
channel_id:
'5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3',
signature:
'304402204EF0AFB78AC23ED1C472E74F4299C0C21F1B21D07EFC0A3838A420F76D783A400220154FB11B6F54320666E4C36CA7F686C16A3A0456800BBC43746F34AF50290064',
public_key: 'aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3',
amount: '1000000',
}
const response = await this.client.request(channelVerify)
const expectedResponse: ChannelVerifyResponse = {
id: response.id,
status: 'success',
type: 'response',
result: {
signature_verified: response.result.signature_verified,
},
}
assert.deepEqual(response, expectedResponse)
})
})

View File

@@ -0,0 +1,45 @@
import { assert } from 'chai'
import _ from 'lodash'
import { DepositAuthorizedRequest, DepositAuthorizedResponse } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('deposit_authorized', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const depositAuthorized: DepositAuthorizedRequest = {
command: 'deposit_authorized',
source_account: this.wallet.getClassicAddress(),
destination_account: wallet2.getClassicAddress(),
}
const response = await this.client.request(depositAuthorized)
const expectedResponse: DepositAuthorizedResponse = {
id: response.id,
status: 'success',
type: 'response',
result: {
deposit_authorized: true,
destination_account: depositAuthorized.destination_account,
ledger_current_index: response.result.ledger_current_index,
source_account: depositAuthorized.source_account,
validated: false,
},
}
assert.deepEqual(response, expectedResponse)
})
})

View File

@@ -0,0 +1,48 @@
import { assert } from 'chai'
import _ from 'lodash'
import { GatewayBalancesRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('gateway_balances', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const request: GatewayBalancesRequest = {
command: 'gateway_balances',
account: this.wallet.getClassicAddress(),
ledger_index: 'validated',
strict: true,
}
const response = await this.client.request(request)
const expected = {
id: 0,
result: {
account: this.wallet.getClassicAddress(),
ledger_hash:
'28D68B351ED58B9819502EF5FC05BA4412A048597E5159E1C226703BDF7C7897',
ledger_index: 1294,
validated: true,
},
status: 'success',
type: 'response',
}
assert.equal(response.status, expected.status)
assert.equal(response.type, expected.type)
assert.equal(typeof response.result.ledger_hash, 'string')
assert.equal(typeof response.result.ledger_index, 'number')
assert.deepEqual(
_.omit(response.result, ['ledger_hash', 'ledger_index']),
_.omit(expected.result, ['ledger_hash', 'ledger_index']),
)
})
})

View File

@@ -0,0 +1,31 @@
import { assert } from 'chai'
import _ from 'lodash'
import { LedgerRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { verifySuccessfulResponse } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('ledger', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const ledgerRequest: LedgerRequest = {
command: 'ledger',
ledger_index: 'validated',
}
const ledgerResponse = await this.client.request(ledgerRequest)
verifySuccessfulResponse(ledgerResponse)
assert(ledgerResponse.result.validated)
})
})

View File

@@ -0,0 +1,40 @@
import { assert } from 'chai'
import _ from 'lodash'
import { LedgerClosedRequest, LedgerClosedResponse } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { verifySuccessfulResponse } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('ledger_closed', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const ledgerClosedRequest: LedgerClosedRequest = {
command: 'ledger_closed',
}
const ledgerClosedResponse: LedgerClosedResponse =
await this.client.request(ledgerClosedRequest)
verifySuccessfulResponse(ledgerClosedResponse)
const expectedResponse: LedgerClosedResponse = {
id: ledgerClosedResponse.id,
status: 'success',
type: 'response',
result: {
ledger_hash: ledgerClosedResponse.result.ledger_hash,
ledger_index: ledgerClosedResponse.result.ledger_index,
},
}
assert.deepEqual(ledgerClosedResponse, expectedResponse)
})
})

View File

@@ -0,0 +1,41 @@
import { assert } from 'chai'
import _ from 'lodash'
import { LedgerCurrentResponse, LedgerCurrentRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { verifySuccessfulResponse } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('ledger_current', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const ledgerCurrentRequest: LedgerCurrentRequest = {
command: 'ledger_current',
}
const ledgerCurrentResponse = await this.client.request(
ledgerCurrentRequest,
)
verifySuccessfulResponse(ledgerCurrentResponse)
const expectedResponse: LedgerCurrentResponse = {
id: ledgerCurrentResponse.id,
status: 'success',
type: 'response',
result: {
ledger_current_index: ledgerCurrentResponse.result.ledger_current_index,
},
}
assert.deepEqual(ledgerCurrentResponse, expectedResponse)
})
})

View File

@@ -0,0 +1,35 @@
import { assert } from 'chai'
import _ from 'lodash'
import { LedgerDataRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { verifySuccessfulResponse } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('ledger_data', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const ledgerDataRequest: LedgerDataRequest = {
command: 'ledger_data',
ledger_index: 'validated',
}
const ledgerDataResponse = await this.client.request(ledgerDataRequest)
verifySuccessfulResponse(ledgerDataResponse)
assert.equal(ledgerDataResponse.result.validated, true)
assert(ledgerDataResponse.result.state.length > 0)
assert.equal(ledgerDataResponse.status, 'success')
assert.equal(ledgerDataResponse.type, 'response')
})
})

View File

@@ -0,0 +1,51 @@
import { assert } from 'chai'
import _ from 'lodash'
import { LedgerEntryRequest, LedgerEntryResponse } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { verifySuccessfulResponse } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('ledger_entry', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const validatedLedgerResponse = await this.client.request({
command: 'ledger_data',
ledger_index: 'validated',
})
verifySuccessfulResponse(validatedLedgerResponse)
const ledgerEntryIndex = validatedLedgerResponse.result.state[0].index
const ledgerEntryRequest: LedgerEntryRequest = {
command: 'ledger_entry',
index: ledgerEntryIndex,
}
const ledgerEntryResponse = await this.client.request(ledgerEntryRequest)
const expectedResponse: LedgerEntryResponse = {
id: ledgerEntryResponse.id,
status: 'success',
type: 'response',
result: {
index: ledgerEntryIndex,
ledger_current_index: ledgerEntryResponse.result.ledger_current_index,
node: ledgerEntryResponse.result.node,
validated: false,
},
}
verifySuccessfulResponse(ledgerEntryResponse)
assert.deepEqual(ledgerEntryResponse, expectedResponse)
})
})

View File

@@ -0,0 +1,101 @@
import { assert } from 'chai'
import _ from 'lodash'
import { decode } from 'ripple-binary-codec/dist'
import {
AccountSet,
Client,
SignerListSet,
SubmitMultisignedRequest,
Transaction,
SubmitMultisignedResponse,
computeSignedTransactionHash,
} from 'xrpl-local'
import { convertStringToHex } from 'xrpl-local/utils'
import { multisign, sign } from 'xrpl-local/wallet/signer'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import {
generateFundedWallet,
ledgerAccept,
testTransaction,
verifySubmittedTransaction,
} from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('submit_multisigned', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('submit_multisigned transaction', async function () {
const client: Client = this.client
const signerWallet1 = await generateFundedWallet(this.client)
const signerWallet2 = await generateFundedWallet(this.client)
// set up the multisigners for the account
const signerListSet: SignerListSet = {
TransactionType: 'SignerListSet',
Account: this.wallet.getClassicAddress(),
SignerEntries: [
{
SignerEntry: {
Account: signerWallet1.getClassicAddress(),
SignerWeight: 1,
},
},
{
SignerEntry: {
Account: signerWallet2.getClassicAddress(),
SignerWeight: 1,
},
},
],
SignerQuorum: 2,
}
await testTransaction(this.client, signerListSet, this.wallet)
// try to multisign
const accountSet: AccountSet = {
TransactionType: 'AccountSet',
Account: this.wallet.getClassicAddress(),
Domain: convertStringToHex('example.com'),
}
const accountSetTx = await client.autofill(accountSet, 2)
const signed1 = sign(signerWallet1, accountSetTx, true)
const signed2 = sign(signerWallet2, accountSetTx, true)
const multisigned = multisign([signed1, signed2])
const multisignedRequest: SubmitMultisignedRequest = {
command: 'submit_multisigned',
tx_json: decode(multisigned) as unknown as Transaction,
}
const submitResponse = await client.request(multisignedRequest)
await ledgerAccept(client)
assert.strictEqual(submitResponse.result.engine_result, 'tesSUCCESS')
await verifySubmittedTransaction(this.client, multisigned)
const expectedResponse: SubmitMultisignedResponse = {
id: submitResponse.id,
status: 'success',
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: computeSignedTransactionHash(multisigned),
},
},
}
assert.deepEqual(submitResponse, expectedResponse)
})
})

View File

@@ -0,0 +1,61 @@
import { assert } from 'chai'
import _ from 'lodash'
import { NoRippleCheckRequest } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('noripple_check', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const request: NoRippleCheckRequest = {
command: 'noripple_check',
account: this.wallet.getClassicAddress(),
role: 'gateway',
ledger_index: 'current',
transactions: true,
}
const response = await this.client.request(request)
const expected = {
id: 0,
result: {
ledger_current_index: 2535,
problems: ['You should immediately set your default ripple flag'],
transactions: [
{
Account: this.wallet.getClassicAddress(),
Fee: 10,
Sequence: 1268,
SetFlag: 8,
TransactionType: 'AccountSet',
},
],
validated: false,
},
status: 'success',
type: 'response',
}
assert.equal(response.status, expected.status)
assert.equal(response.type, expected.type)
assert.equal(typeof response.result.transactions[0].Fee, 'number')
assert.equal(typeof response.result.transactions[0].Sequence, 'number')
assert.equal(typeof response.result.problems, 'object')
assert.equal(typeof response.result.problems[0], 'string')
const responseTx = response.result.transactions[0]
const expectedTx = expected.result.transactions[0]
assert.deepEqual(
[responseTx.Account, responseTx.SetFlag, responseTx.TransactionType],
[expectedTx.Account, expectedTx.SetFlag, expectedTx.TransactionType],
)
})
})

View File

@@ -0,0 +1,48 @@
import { assert } from 'chai'
import _ from 'lodash'
import { PathFindRequest, PathFindResponse } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('path_find', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const pathFind: PathFindRequest = {
command: 'path_find',
subcommand: 'create',
source_account: this.wallet.getClassicAddress(),
destination_account: wallet2.getClassicAddress(),
destination_amount: '100',
}
const response = await this.client.request(pathFind)
const expectedResponse: PathFindResponse = {
id: response.id,
status: 'success',
type: 'response',
result: {
alternatives: response.result.alternatives,
destination_account: pathFind.destination_account,
destination_amount: pathFind.destination_amount,
source_account: pathFind.source_account,
full_reply: false,
id: response.id,
},
}
assert.deepEqual(response, expectedResponse)
})
})

View File

@@ -0,0 +1,51 @@
import { assert } from 'chai'
import _ from 'lodash'
import { RipplePathFindRequest, RipplePathFindResponse } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('ripple_path_find', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const ripplePathFind: RipplePathFindRequest = {
command: 'ripple_path_find',
subcommand: 'create',
source_account: this.wallet.getClassicAddress(),
destination_account: wallet2.getClassicAddress(),
destination_amount: '100',
}
const response = await this.client.request(ripplePathFind)
const expectedResponse: RipplePathFindResponse = {
id: response.id,
status: 'success',
type: 'response',
result: {
alternatives: response.result.alternatives,
destination_account: wallet2.getClassicAddress(),
destination_currencies: response.result.destination_currencies,
destination_amount: ripplePathFind.destination_amount,
full_reply: true,
id: response.id,
ledger_current_index: response.result.ledger_current_index,
source_account: ripplePathFind.source_account,
validated: false,
},
}
assert.deepEqual(response, expectedResponse)
})
})

View File

@@ -0,0 +1,76 @@
import { assert } from 'chai'
import _ from 'lodash'
import { decode } from 'ripple-binary-codec/dist'
import {
AccountSet,
SubmitRequest,
SubmitResponse,
computeSignedTransactionHash,
Transaction,
} from 'xrpl-local'
import { convertStringToHex } from 'xrpl-local/utils'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { ledgerAccept, verifySubmittedTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('submit', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('submit', async function () {
const accountSet: AccountSet = {
TransactionType: 'AccountSet',
Account: this.wallet.getClassicAddress(),
Domain: convertStringToHex('example.com'),
}
const autofilledTx = await this.client.autofill(accountSet)
const signedTx = this.wallet.signTransaction(autofilledTx)
const submitRequest: SubmitRequest = {
command: 'submit',
tx_blob: signedTx,
}
const submitResponse = await this.client.request(submitRequest)
assert.equal(submitResponse.status, 'success')
await ledgerAccept(this.client)
await verifySubmittedTransaction(this.client, signedTx)
const expectedResponse: SubmitResponse = {
id: submitResponse.id,
type: 'response',
status: 'success',
result: {
engine_result: 'tesSUCCESS',
engine_result_code: 0,
engine_result_message:
'The transaction was applied. Only final in a validated ledger.',
tx_blob: signedTx,
tx_json: {
...(decode(signedTx) as unknown as Transaction),
hash: computeSignedTransactionHash(signedTx),
},
accepted: true,
account_sequence_available:
submitResponse.result.account_sequence_available,
account_sequence_next: submitResponse.result.account_sequence_next,
applied: true,
broadcast: submitResponse.result.broadcast,
kept: true,
queued: false,
open_ledger_cost: submitResponse.result.open_ledger_cost,
validated_ledger_index: submitResponse.result.validated_ledger_index,
},
}
assert.deepEqual(submitResponse, expectedResponse)
})
})

View File

@@ -0,0 +1,63 @@
import { assert } from 'chai'
import _ from 'lodash'
import {
AccountSet,
computeSignedTransactionHash,
SubmitResponse,
TxResponse,
} from 'xrpl-local'
import { convertStringToHex } from 'xrpl-local/utils'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
// how long before each test case times out
const TIMEOUT = 20000
describe('tx', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const account = this.wallet.getClassicAddress()
const accountSet: AccountSet = {
TransactionType: 'AccountSet',
Account: account,
Domain: convertStringToHex('example.com'),
}
const response: SubmitResponse = await this.client.submitTransaction(
this.wallet,
accountSet,
)
const hash = computeSignedTransactionHash(response.result.tx_blob)
const txResponse = await this.client.request({
command: 'tx',
transaction: hash,
})
const expectedResponse: TxResponse = {
id: txResponse.id,
type: 'response',
status: 'success',
result: {
...accountSet,
Fee: txResponse.result.Fee,
Flags: 0,
LastLedgerSequence: txResponse.result.LastLedgerSequence,
Sequence: txResponse.result.Sequence,
SigningPubKey: this.wallet.publicKey,
TxnSignature: txResponse.result.TxnSignature,
hash: computeSignedTransactionHash(response.result.tx_blob),
validated: false,
},
}
assert.deepEqual(txResponse, expectedResponse)
})
})

View File

@@ -13,15 +13,6 @@ export async function suiteClientSetup(this: Mocha.Context): Promise<void> {
await setupClient.bind(this)(serverUrl)
await ledgerAccept(this.client)
this.newWallet = generateXAddress({ includeClassicAddress: true })
// two times to give time to server to send `ledgerClosed` event
// so getLedgerVersion will return right value
await ledgerAccept(this.client)
const response = await this.client.request({
command: 'ledger',
ledger_index: 'validated',
})
const ledgerVersion = response.result.ledger_index
this.startLedgerVersion = ledgerVersion
await teardownClient.bind(this)()
}

View File

@@ -0,0 +1,65 @@
import { assert } from 'chai'
import _ from 'lodash'
import { CheckCreate, CheckCancel } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('CheckCancel', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const setupTx: CheckCreate = {
TransactionType: 'CheckCreate',
Account: this.wallet.getClassicAddress(),
Destination: wallet2.getClassicAddress(),
SendMax: '50',
}
await testTransaction(this.client, setupTx, this.wallet)
// get check ID
const response1 = await this.client.request({
command: 'account_objects',
account: this.wallet.getClassicAddress(),
type: 'check',
})
assert.lengthOf(
response1.result.account_objects,
1,
'Should be exactly one check on the ledger',
)
const checkId = response1.result.account_objects[0].index
// actual test - cancel the check
const tx: CheckCancel = {
TransactionType: 'CheckCancel',
Account: this.wallet.getClassicAddress(),
CheckID: checkId,
}
await testTransaction(this.client, tx, this.wallet)
// confirm that the check no longer exists
const accountOffersResponse = await this.client.request({
command: 'account_objects',
account: this.wallet.getClassicAddress(),
type: 'check',
})
assert.lengthOf(
accountOffersResponse.result.account_objects,
0,
'Should be no checks on the ledger',
)
})
})

View File

@@ -0,0 +1,68 @@
import { assert } from 'chai'
import _ from 'lodash'
import { CheckCreate, CheckCash } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('CheckCash', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const amount = '500'
const setupTx: CheckCreate = {
TransactionType: 'CheckCreate',
Account: this.wallet.getClassicAddress(),
Destination: wallet2.getClassicAddress(),
SendMax: amount,
}
await testTransaction(this.client, setupTx, this.wallet)
// get check ID
const response1 = await this.client.request({
command: 'account_objects',
account: this.wallet.getClassicAddress(),
type: 'check',
})
assert.lengthOf(
response1.result.account_objects,
1,
'Should be exactly one check on the ledger',
)
const checkId = response1.result.account_objects[0].index
// actual test - cash the check
const tx: CheckCash = {
TransactionType: 'CheckCash',
Account: wallet2.getClassicAddress(),
CheckID: checkId,
Amount: amount,
}
await testTransaction(this.client, tx, wallet2)
// confirm that the check no longer exists
const accountOffersResponse = await this.client.request({
command: 'account_objects',
account: this.wallet.getClassicAddress(),
type: 'check',
})
assert.lengthOf(
accountOffersResponse.result.account_objects,
0,
'Should be no checks on the ledger',
)
})
})

View File

@@ -0,0 +1,43 @@
import { assert } from 'chai'
import _ from 'lodash'
import { CheckCreate } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('CheckCreate', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const tx: CheckCreate = {
TransactionType: 'CheckCreate',
Account: this.wallet.getClassicAddress(),
Destination: wallet2.getClassicAddress(),
SendMax: '50',
}
await testTransaction(this.client, tx, this.wallet)
// confirm that the check actually went through
const accountOffersResponse = await this.client.request({
command: 'account_objects',
account: this.wallet.getClassicAddress(),
type: 'check',
})
assert.lengthOf(
accountOffersResponse.result.account_objects,
1,
'Should be exactly one check on the ledger',
)
})
})

View File

@@ -0,0 +1,54 @@
import _ from 'lodash'
import {
PaymentChannelCreate,
computePaymentChannelHash,
PaymentChannelClaim,
} from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('PaymentChannelFund', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const paymentChannelCreate: PaymentChannelCreate = {
TransactionType: 'PaymentChannelCreate',
Account: this.wallet.getClassicAddress(),
Amount: '100',
Destination: wallet2.getClassicAddress(),
SettleDelay: 86400,
PublicKey: this.wallet.publicKey,
}
const paymentChannelResponse = await this.client.submitTransaction(
this.wallet,
paymentChannelCreate,
)
await testTransaction(this.client, paymentChannelCreate, this.wallet)
const paymentChannelClaim: PaymentChannelClaim = {
Account: this.wallet.getClassicAddress(),
TransactionType: 'PaymentChannelClaim',
Channel: computePaymentChannelHash(
this.wallet.getClassicAddress(),
wallet2.getClassicAddress(),
paymentChannelResponse.result.tx_json.Sequence ?? 0,
),
Amount: '100',
}
await testTransaction(this.client, paymentChannelClaim, this.wallet)
})
})

View File

@@ -0,0 +1,32 @@
import _ from 'lodash'
import { PaymentChannelCreate } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('PaymentChannelCreate', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const paymentChannelCreate: PaymentChannelCreate = {
TransactionType: 'PaymentChannelCreate',
Account: this.wallet.getClassicAddress(),
Amount: '100',
Destination: wallet2.getClassicAddress(),
SettleDelay: 86400,
PublicKey: this.wallet.publicKey,
}
await testTransaction(this.client, paymentChannelCreate, this.wallet)
})
})

View File

@@ -0,0 +1,54 @@
import _ from 'lodash'
import {
PaymentChannelCreate,
computePaymentChannelHash,
PaymentChannelFund,
} from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('PaymentChannelFund', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const paymentChannelCreate: PaymentChannelCreate = {
TransactionType: 'PaymentChannelCreate',
Account: this.wallet.getClassicAddress(),
Amount: '100',
Destination: wallet2.getClassicAddress(),
SettleDelay: 86400,
PublicKey: this.wallet.publicKey,
}
const paymentChannelResponse = await this.client.submitTransaction(
this.wallet,
paymentChannelCreate,
)
await testTransaction(this.client, paymentChannelCreate, this.wallet)
const paymentChannelFund: PaymentChannelFund = {
Account: this.wallet.getClassicAddress(),
TransactionType: 'PaymentChannelFund',
Channel: computePaymentChannelHash(
this.wallet.getClassicAddress(),
wallet2.getClassicAddress(),
paymentChannelResponse.result.tx_json.Sequence ?? 0,
),
Amount: '100',
}
await testTransaction(this.client, paymentChannelFund, this.wallet)
})
})

View File

@@ -0,0 +1,33 @@
import _ from 'lodash'
import { TrustSet } from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('TrustSet', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
it('base', async function () {
const wallet2 = await generateFundedWallet(this.client)
const tx: TrustSet = {
TransactionType: 'TrustSet',
Account: this.wallet.getClassicAddress(),
LimitAmount: {
currency: 'USD',
issuer: wallet2.getClassicAddress(),
value: '100',
},
}
await testTransaction(this.client, tx, this.wallet)
})
})

View File

@@ -2,7 +2,7 @@ import { assert } from 'chai'
import _ from 'lodash'
import { decode } from 'ripple-binary-codec'
import { Client, Wallet } from 'xrpl-local'
import { Client, Wallet, Response } from 'xrpl-local'
import { Payment, Transaction } from 'xrpl-local/models/transactions'
import { computeSignedTransactionHash } from 'xrpl-local/utils/hashes'
@@ -11,7 +11,7 @@ const masterSecret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb'
export async function ledgerAccept(client: Client): Promise<void> {
const request = { command: 'ledger_accept' }
await client.connection.request(request)
await client.request(request)
}
export async function fundAccount(
@@ -73,6 +73,11 @@ export async function verifySubmittedTransaction(
}
}
export function verifySuccessfulResponse(response: Response): void {
assert.equal(response.status, 'success')
assert.equal(response.type, 'response')
}
export async function testTransaction(
client: Client,
transaction: Transaction,
@@ -84,7 +89,11 @@ export async function testTransaction(
// check that the transaction was successful
assert.equal(response.status, 'success')
assert.equal(response.type, 'response')
assert.equal(response.result.engine_result, 'tesSUCCESS')
assert.equal(
response.result.engine_result,
'tesSUCCESS',
response.result.engine_result_message,
)
// check that the transaction is on the ledger
const signedTx = _.omit(response.result.tx_json, 'hash')

View File

@@ -180,6 +180,16 @@ export default function createMockRippled(port: number): MockedWebSocketServer {
)
} else if (request.data.closeServer) {
conn.close()
} else if (request.data.delayedResponseIn) {
setTimeout(() => {
conn.send(
createResponse(request, {
status: 'success',
type: 'response',
result: {},
}),
)
}, request.data.delayedResponseIn)
}
}

View File

@@ -12,7 +12,7 @@ async function setupClient(this: any, port = defaultPort): Promise<void> {
return tclient
.connect()
.then(async () => {
return tclient.connection.request({
return tclient.request({
command: 'test_command',
data: { openOnOtherPort: true },
})

View File

@@ -58,7 +58,7 @@ export function assertResultMatch(
* @param message - Expected error message/substring of the error message.
*/
export async function assertRejects(
promise: PromiseLike<Record<string, unknown>>,
promise: PromiseLike<any>,
instanceOf: any,
message?: string | RegExp,
): Promise<void> {
@@ -72,7 +72,7 @@ export async function assertRejects(
assert(error instanceof instanceOf, error.message)
if (typeof message === 'string') {
assert.strictEqual(error.message, message)
assert.strictEqual(error.message, message, 'Messages do not match')
} else if (message instanceof RegExp) {
assert(message.test(error.message))
}

View File

@@ -58,6 +58,7 @@ function webpackForTest(testFileName) {
}
const test = {
mode: 'production',
cache: true,
externals: [
{