Removes all prepare methods (#1605)

* deprecate and alias prepareTransaction

* delete prepareTransaction and replace methods in check-cancel

* WIP update check-cash

* remove all prepares

* remove prepares from client

* fix ts issues

* remove tests

* fix tests

* additional cleanup

* fix integration tests

* remove console statement

* re-add helper function

* fix imports

* fix more issues with integration tests

Co-authored-by: Omar Khan <khancodegt@gmail.com>
This commit is contained in:
Mayukha Vadari
2021-09-09 14:31:54 -04:00
parent 1115f17a3e
commit af7b187dc7
44 changed files with 429 additions and 6153 deletions

View File

@@ -107,25 +107,8 @@ import {
UnsubscribeResponse,
} from '../models/methods'
import { BaseRequest, BaseResponse } from '../models/methods/baseMethod'
import prepareCheckCancel from '../transaction/check-cancel'
import prepareCheckCash from '../transaction/check-cash'
import prepareCheckCreate from '../transaction/check-create'
import combine from '../transaction/combine'
import prepareEscrowCancellation from '../transaction/escrow-cancellation'
import prepareEscrowCreation from '../transaction/escrow-creation'
import prepareEscrowExecution from '../transaction/escrow-execution'
import prepareOrder from '../transaction/order'
import prepareOrderCancellation from '../transaction/ordercancellation'
import preparePayment from '../transaction/payment'
import preparePaymentChannelClaim from '../transaction/payment-channel-claim'
import preparePaymentChannelCreate from '../transaction/payment-channel-create'
import preparePaymentChannelFund from '../transaction/payment-channel-fund'
import prepareSettings from '../transaction/settings'
import { sign } from '../transaction/sign'
import prepareTicketCreate from '../transaction/ticket'
import prepareTrustline from '../transaction/trustline'
import { TransactionJSON, Instructions, Prepare } from '../transaction/types'
import * as transactionUtils from '../transaction/utils'
import { deriveAddress, deriveXAddress } from '../utils/derive'
import generateFaucetWallet from '../wallet/generateFaucetWallet'
@@ -213,9 +196,6 @@ class Client extends EventEmitter {
// number. Defaults to '2'.
public readonly maxFeeXRP: string
// TODO: Use partial for other instance methods as well.
public autofill = prepend(autofill, this)
/**
* Creates a new Client with a websocket connection to a rippled server.
*
@@ -426,22 +406,6 @@ class Client extends EventEmitter {
return super.on(eventName, listener)
}
/**
* Prepare a transaction.
*
* You can later submit the transaction with a `submit` request.
*
* @param txJSON - TODO: will be deleted.
* @param instructions - TODO: will be deleted.
* @returns TODO: will be deleted.
*/
public async prepareTransaction(
txJSON: TransactionJSON,
instructions: Instructions = {},
): Promise<Prepare> {
return transactionUtils.prepareTransaction(txJSON, this, instructions)
}
public async requestAll(
req: AccountChannelsRequest,
): Promise<AccountChannelsResponse[]>
@@ -551,6 +515,12 @@ class Client extends EventEmitter {
return this.connection.isConnected()
}
// TODO: Use prepend for other instance methods as well.
public autofill = prepend(autofill, this)
// @deprecated Use autofill instead
public prepareTransaction = prepend(autofill, this)
public getFee = getFee
public getTrustlines = getTrustlines
@@ -558,25 +528,10 @@ class Client extends EventEmitter {
public getPaths = getPaths
public getOrderbook = getOrderbook
public preparePayment = preparePayment
public prepareTrustline = prepareTrustline
public prepareOrder = prepareOrder
public prepareOrderCancellation = prepareOrderCancellation
public prepareEscrowCreation = prepareEscrowCreation
public prepareEscrowExecution = prepareEscrowExecution
public prepareEscrowCancellation = prepareEscrowCancellation
public preparePaymentChannelCreate = preparePaymentChannelCreate
public preparePaymentChannelFund = preparePaymentChannelFund
public preparePaymentChannelClaim = preparePaymentChannelClaim
public prepareCheckCreate = prepareCheckCreate
public prepareCheckCash = prepareCheckCash
public prepareCheckCancel = prepareCheckCancel
public prepareTicketCreate = prepareTicketCreate
public prepareSettings = prepareSettings
public sign = sign
public combine = combine
public generateFaucetWallet = generateFaucetWallet
public generateFaucetWallet = prepend(generateFaucetWallet, this)
public errors = errors

View File

@@ -74,7 +74,7 @@ function validateAccountAddress(
// eslint-disable-next-line no-param-reassign -- param reassign is safe
tx[accountField] = classicAccount
if (tag != null) {
if (tag != null && tag !== false) {
if (tx[tagField] && tx[tagField] !== tag) {
throw new ValidationError(
`The ${tagField}, if present, must match the tag of the ${accountField} X-address`,

View File

@@ -7,7 +7,6 @@ import type { Connection } from '../client'
import * as common from '../common'
import { Issue } from '../common/types/objects'
import { AccountInfoRequest } from '../models/methods'
import { FormattedTransactionType } from '../transaction/types'
import { dropsToXrp } from '../utils'
export interface RecursiveData {
@@ -82,32 +81,6 @@ function renameCounterpartyToIssuerInOrder(order: RequestBookOffersArgs) {
return { ...order, ..._.omitBy(changes, (value) => value == null) }
}
function signum(num) {
return num === 0 ? 0 : num > 0 ? 1 : -1
}
/**
* Order two rippled transactions based on their ledger_index.
* If two transactions took place in the same ledger, sort
* them based on TransactionIndex
* See: https://developers.ripple.com/transaction-metadata.html.
*
* @param first
* @param second
*/
function compareTransactions(
first: FormattedTransactionType,
second: FormattedTransactionType,
): number {
if (!first.outcome || !second.outcome) {
return 0
}
if (first.outcome.ledgerVersion === second.outcome.ledgerVersion) {
return signum(first.outcome.indexInLedger - second.outcome.indexInLedger)
}
return first.outcome.ledgerVersion < second.outcome.ledgerVersion ? -1 : 1
}
async function isPendingLedgerVersion(
client: Client,
maxLedgerVersion?: number,
@@ -142,7 +115,6 @@ async function ensureLedgerVersion(
export {
getXRPBalance,
ensureLedgerVersion,
compareTransactions,
renameCounterpartyToIssuer,
renameCounterpartyToIssuerInOrder,
getRecursive,

View File

@@ -58,6 +58,8 @@ interface PathStep {
export type Path = PathStep[]
export interface SignerEntry {
Account: string
SignerWeight: number
SignerEntry: {
Account: string
SignerWeight: number
}
}

View File

@@ -59,10 +59,8 @@ export function verifyPayment(tx: Record<string, unknown>): void {
throw new ValidationError('PaymentTransaction: invalid Destination')
}
if (
tx.DestinationTag !== undefined &&
typeof tx.DestinationTag !== 'number'
) {
if (tx.DestinationTag != null && typeof tx.DestinationTag !== 'number') {
console.log(tx.DestinationTag)
throw new ValidationError(
'PaymentTransaction: DestinationTag must be a number',
)

View File

@@ -1,37 +0,0 @@
import type { Client } from '..'
import { Instructions, Prepare, TransactionJSON } from './types'
import { prepareTransaction } from './utils'
export interface CheckCancelParameters {
checkID: string
}
function createCheckCancelTransaction(
account: string,
cancel: CheckCancelParameters,
): TransactionJSON {
const txJSON = {
Account: account,
TransactionType: 'CheckCancel',
CheckID: cancel.checkID,
}
return txJSON
}
async function prepareCheckCancel(
this: Client,
address: string,
checkCancel: CheckCancelParameters,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createCheckCancelTransaction(address, checkCancel)
return await prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareCheckCancel

View File

@@ -1,57 +0,0 @@
import type { Client } from '..'
import { ValidationError } from '../common/errors'
import { Amount } from '../common/types/objects'
import { toRippledAmount } from '../utils'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
export interface CheckCashParameters {
checkID: string
amount?: Amount
deliverMin?: Amount
}
function createCheckCashTransaction(
account: string,
checkCash: CheckCashParameters,
): TransactionJSON {
if (checkCash.amount && checkCash.deliverMin) {
throw new ValidationError(
'"amount" and "deliverMin" properties on ' +
'CheckCash are mutually exclusive',
)
}
const txJSON: any = {
Account: account,
TransactionType: 'CheckCash',
CheckID: checkCash.checkID,
}
if (checkCash.amount != null) {
txJSON.Amount = toRippledAmount(checkCash.amount)
}
if (checkCash.deliverMin != null) {
txJSON.DeliverMin = toRippledAmount(checkCash.deliverMin)
}
return txJSON
}
async function prepareCheckCash(
this: Client,
address: string,
checkCash: CheckCashParameters,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createCheckCashTransaction(address, checkCash)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareCheckCash

View File

@@ -1,56 +0,0 @@
import type { Client } from '..'
import { Amount } from '../common/types/objects'
import { ISOTimeToRippleTime, toRippledAmount } from '../utils'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
export interface CheckCreateParameters {
destination: string
sendMax: Amount
destinationTag?: number
expiration?: string
invoiceID?: string
}
function createCheckCreateTransaction(
account: string,
check: CheckCreateParameters,
): TransactionJSON {
const txJSON: any = {
Account: account,
TransactionType: 'CheckCreate',
Destination: check.destination,
SendMax: toRippledAmount(check.sendMax),
}
if (check.destinationTag != null) {
txJSON.DestinationTag = check.destinationTag
}
if (check.expiration != null) {
txJSON.Expiration = ISOTimeToRippleTime(check.expiration)
}
if (check.invoiceID != null) {
txJSON.InvoiceID = check.invoiceID
}
return txJSON
}
async function prepareCheckCreate(
this: Client,
address: string,
checkCreate: CheckCreateParameters,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createCheckCreateTransaction(address, checkCreate)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareCheckCreate

View File

@@ -1,45 +0,0 @@
import type { Client } from '..'
import { Memo } from '../common/types/objects'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
export interface EscrowCancellation {
owner: string
escrowSequence: number
// TODO: This ripple-lib memo format should be deprecated in favor of rippled's format.
// If necessary, expose a public method for converting between the two formats.
memos?: Memo[]
}
function createEscrowCancellationTransaction(
account: string,
payment: EscrowCancellation,
): TransactionJSON {
const txJSON: any = {
TransactionType: 'EscrowCancel',
Account: account,
Owner: payment.owner,
OfferSequence: payment.escrowSequence,
}
if (payment.memos != null) {
txJSON.Memos = payment.memos.map(utils.convertMemo)
}
return txJSON
}
async function prepareEscrowCancellation(
this: Client,
address: string,
escrowCancellation: EscrowCancellation,
instructions: Instructions = {},
): Promise<Prepare> {
const txJSON = createEscrowCancellationTransaction(
address,
escrowCancellation,
)
return utils.prepareTransaction(txJSON, this, instructions)
}
export default prepareEscrowCancellation

View File

@@ -1,77 +0,0 @@
import type { Client } from '..'
import { Memo } from '../common/types/objects'
import { ISOTimeToRippleTime, xrpToDrops } from '../utils'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
const ValidationError = utils.common.errors.ValidationError
export interface EscrowCreation {
amount: string
destination: string
memos?: Memo[]
condition?: string
allowCancelAfter?: string
allowExecuteAfter?: string
sourceTag?: number
destinationTag?: number
}
function createEscrowCreationTransaction(
account: string,
payment: EscrowCreation,
): TransactionJSON {
const txJSON: any = {
TransactionType: 'EscrowCreate',
Account: account,
Destination: payment.destination,
Amount: xrpToDrops(payment.amount),
}
if (payment.condition != null) {
txJSON.Condition = payment.condition
}
if (payment.allowCancelAfter != null) {
txJSON.CancelAfter = ISOTimeToRippleTime(payment.allowCancelAfter)
}
if (payment.allowExecuteAfter != null) {
txJSON.FinishAfter = ISOTimeToRippleTime(payment.allowExecuteAfter)
}
if (payment.sourceTag != null) {
txJSON.SourceTag = payment.sourceTag
}
if (payment.destinationTag != null) {
txJSON.DestinationTag = payment.destinationTag
}
if (payment.memos != null) {
txJSON.Memos = payment.memos.map(utils.convertMemo)
}
if (
Boolean(payment.allowCancelAfter) &&
Boolean(payment.allowExecuteAfter) &&
txJSON.CancelAfter <= txJSON.FinishAfter
) {
throw new ValidationError(
'prepareEscrowCreation: ' +
'"allowCancelAfter" must be after "allowExecuteAfter"',
)
}
return txJSON
}
async function prepareEscrowCreation(
this: Client,
address: string,
escrowCreation: EscrowCreation,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createEscrowCreationTransaction(address, escrowCreation)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareEscrowCreation

View File

@@ -1,61 +0,0 @@
import type { Client } from '..'
import { Memo } from '../common/types/objects'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
const ValidationError = utils.common.errors.ValidationError
export interface EscrowExecution {
owner: string
escrowSequence: number
memos?: Memo[]
condition?: string
fulfillment?: string
}
function createEscrowExecutionTransaction(
account: string,
payment: EscrowExecution,
): TransactionJSON {
const txJSON: any = {
TransactionType: 'EscrowFinish',
Account: account,
Owner: payment.owner,
OfferSequence: payment.escrowSequence,
}
if (Boolean(payment.condition) !== Boolean(payment.fulfillment)) {
throw new ValidationError(
'"condition" and "fulfillment" fields on' +
' EscrowFinish must only be specified together.',
)
}
if (payment.condition != null) {
txJSON.Condition = payment.condition
}
if (payment.fulfillment != null) {
txJSON.Fulfillment = payment.fulfillment
}
if (payment.memos != null) {
txJSON.Memos = payment.memos.map(utils.convertMemo)
}
return txJSON
}
async function prepareEscrowExecution(
this: Client,
address: string,
escrowExecution: EscrowExecution,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createEscrowExecutionTransaction(address, escrowExecution)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareEscrowExecution

View File

@@ -1,67 +0,0 @@
import type { Client } from '..'
import { FormattedOrderSpecification } from '../common/types/objects/index'
import { ISOTimeToRippleTime, toRippledAmount } from '../utils'
import { Instructions, Prepare, OfferCreateTransaction } from './types'
import * as utils from './utils'
const offerFlags = utils.common.txFlags.OfferCreate
function createOrderTransaction(
account: string,
order: FormattedOrderSpecification,
): OfferCreateTransaction {
const takerPays = toRippledAmount(
order.direction === 'buy' ? order.quantity : order.totalPrice,
)
const takerGets = toRippledAmount(
order.direction === 'buy' ? order.totalPrice : order.quantity,
)
const txJSON: Partial<OfferCreateTransaction> = {
TransactionType: 'OfferCreate',
Account: account,
TakerGets: takerGets,
TakerPays: takerPays,
}
txJSON.Flags = 0
if (order.direction === 'sell') {
txJSON.Flags |= offerFlags.Sell
}
if (order.passive) {
txJSON.Flags |= offerFlags.Passive
}
if (order.immediateOrCancel) {
txJSON.Flags |= offerFlags.ImmediateOrCancel
}
if (order.fillOrKill) {
txJSON.Flags |= offerFlags.FillOrKill
}
if (order.expirationTime != null) {
txJSON.Expiration = ISOTimeToRippleTime(order.expirationTime)
}
if (order.orderToReplace != null) {
txJSON.OfferSequence = order.orderToReplace
}
if (order.memos != null) {
txJSON.Memos = order.memos.map(utils.convertMemo)
}
return txJSON as OfferCreateTransaction
}
async function prepareOrder(
this: Client,
address: string,
order: FormattedOrderSpecification,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createOrderTransaction(address, order)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareOrder

View File

@@ -1,38 +0,0 @@
import type { Client } from '..'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
function createOrderCancellationTransaction(
account: string,
orderCancellation: any,
): TransactionJSON {
const txJSON: any = {
TransactionType: 'OfferCancel',
Account: account,
OfferSequence: orderCancellation.orderSequence,
}
if (orderCancellation.memos != null) {
txJSON.Memos = orderCancellation.memos.map(utils.convertMemo)
}
return txJSON
}
async function prepareOrderCancellation(
this: Client,
address: string,
orderCancellation: object,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createOrderCancellationTransaction(
address,
orderCancellation,
)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareOrderCancellation

View File

@@ -1,87 +0,0 @@
import type { Client } from '..'
import { xrpToDrops } from '../utils'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
const ValidationError = utils.common.errors.ValidationError
const claimFlags = utils.common.txFlags.PaymentChannelClaim
export interface PaymentChannelClaim {
channel: string
balance?: string
amount?: string
signature?: string
publicKey?: string
renew?: boolean
close?: boolean
}
function createPaymentChannelClaimTransaction(
account: string,
claim: PaymentChannelClaim,
): TransactionJSON {
const txJSON: TransactionJSON = {
Account: account,
TransactionType: 'PaymentChannelClaim',
Channel: claim.channel,
Flags: 0,
}
if (claim.balance != null) {
txJSON.Balance = xrpToDrops(claim.balance)
}
if (claim.amount != null) {
txJSON.Amount = xrpToDrops(claim.amount)
}
if (Boolean(claim.signature) !== Boolean(claim.publicKey)) {
throw new ValidationError(
'"signature" and "publicKey" fields on' +
' PaymentChannelClaim must only be specified together.',
)
}
if (claim.signature != null) {
txJSON.Signature = claim.signature
}
if (claim.publicKey != null) {
txJSON.PublicKey = claim.publicKey
}
if (claim.renew && claim.close) {
throw new ValidationError(
'"renew" and "close" flags on PaymentChannelClaim' +
' are mutually exclusive',
)
}
txJSON.Flags = 0
if (claim.renew) {
txJSON.Flags |= claimFlags.Renew
}
if (claim.close) {
txJSON.Flags |= claimFlags.Close
}
return txJSON
}
async function preparePaymentChannelClaim(
this: Client,
address: string,
paymentChannelClaim: PaymentChannelClaim,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createPaymentChannelClaimTransaction(
address,
paymentChannelClaim,
)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default preparePaymentChannelClaim

View File

@@ -1,60 +0,0 @@
import type { Client } from '..'
import { ISOTimeToRippleTime, xrpToDrops } from '../utils'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
export interface PaymentChannelCreate {
amount: string
destination: string
settleDelay: number
publicKey: string
cancelAfter?: string
sourceTag?: number
destinationTag?: number
}
function createPaymentChannelCreateTransaction(
account: string,
paymentChannel: PaymentChannelCreate,
): TransactionJSON {
const txJSON: any = {
Account: account,
TransactionType: 'PaymentChannelCreate',
Amount: xrpToDrops(paymentChannel.amount),
Destination: paymentChannel.destination,
SettleDelay: paymentChannel.settleDelay,
PublicKey: paymentChannel.publicKey.toUpperCase(),
}
if (paymentChannel.cancelAfter != null) {
txJSON.CancelAfter = ISOTimeToRippleTime(paymentChannel.cancelAfter)
}
if (paymentChannel.sourceTag != null) {
txJSON.SourceTag = paymentChannel.sourceTag
}
if (paymentChannel.destinationTag != null) {
txJSON.DestinationTag = paymentChannel.destinationTag
}
return txJSON
}
async function preparePaymentChannelCreate(
this: Client,
address: string,
paymentChannelCreate: PaymentChannelCreate,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createPaymentChannelCreateTransaction(
address,
paymentChannelCreate,
)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default preparePaymentChannelCreate

View File

@@ -1,48 +0,0 @@
import type { Client } from '..'
import { ISOTimeToRippleTime, xrpToDrops } from '../utils'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
export interface PaymentChannelFund {
channel: string
amount: string
expiration?: string
}
function createPaymentChannelFundTransaction(
account: string,
fund: PaymentChannelFund,
): TransactionJSON {
const txJSON: TransactionJSON = {
Account: account,
TransactionType: 'PaymentChannelFund',
Channel: fund.channel,
Amount: xrpToDrops(fund.amount),
}
if (fund.expiration != null) {
txJSON.Expiration = ISOTimeToRippleTime(fund.expiration)
}
return txJSON
}
async function preparePaymentChannelFund(
this: Client,
address: string,
paymentChannelFund: PaymentChannelFund,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createPaymentChannelFundTransaction(
address,
paymentChannelFund,
)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default preparePaymentChannelFund

View File

@@ -1,261 +0,0 @@
import _ from 'lodash'
import type { Client } from '../client'
import {
Amount,
Adjustment,
MaxAdjustment,
MinAdjustment,
Memo,
} from '../common/types/objects'
import { toRippledAmount, xrpToDrops } from '../utils'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
import { getClassicAccountAndTag, ClassicAccountAndTag } from './utils'
const paymentFlags = utils.common.txFlags.Payment
const ValidationError = utils.common.errors.ValidationError
export interface Payment {
source: Adjustment | MaxAdjustment
destination: Adjustment | MinAdjustment
paths?: string
memos?: Memo[]
// A 256-bit hash that can be used to identify a particular payment
invoiceID?: string
// A boolean that, if set to true, indicates that this payment should go
// through even if the whole amount cannot be delivered because of a lack of
// liquidity or funds in the source_account account
allowPartialPayment?: boolean
// A boolean that can be set to true if paths are specified and the sender
// would like the Ripple Network to disregard any direct paths from
// the source_account to the destination_account. This may be used to take
// advantage of an arbitrage opportunity or by gateways wishing to issue
// balances from a hot wallet to a user who has mistakenly set a trustline
// directly to the hot wallet
noDirectRipple?: boolean
limitQuality?: boolean
}
function isMaxAdjustment(
source: Adjustment | MaxAdjustment,
): source is MaxAdjustment {
return (source as MaxAdjustment).maxAmount != null
}
function isMinAdjustment(
destination: Adjustment | MinAdjustment,
): destination is MinAdjustment {
return (destination as MinAdjustment).minAmount != null
}
function isXRPToXRPPayment(payment: Payment): boolean {
const { source, destination } = payment
const sourceCurrency = isMaxAdjustment(source)
? source.maxAmount.currency
: source.amount.currency
const destinationCurrency = isMinAdjustment(destination)
? destination.minAmount.currency
: destination.amount.currency
return (
(sourceCurrency === 'XRP' || sourceCurrency === 'drops') &&
(destinationCurrency === 'XRP' || destinationCurrency === 'drops')
)
}
function isIOUWithoutCounterparty(amount: Amount): boolean {
return (
amount &&
amount.currency !== 'XRP' &&
amount.currency !== 'drops' &&
amount.counterparty == null
)
}
function applyAnyCounterpartyEncoding(payment: Payment): void {
// Convert blank counterparty to sender or receiver's address
// (Ripple convention for 'any counterparty')
// https://developers.ripple.com/payment.html#special-issuer-values-for-sendmax-and-amount
;[payment.source, payment.destination].forEach((adjustment) => {
;['amount', 'minAmount', 'maxAmount'].forEach((key) => {
if (isIOUWithoutCounterparty(adjustment[key])) {
adjustment[key].counterparty = adjustment.address
}
})
})
}
function createMaximalAmount(amount: Amount): Amount {
const maxXRPValue = '100000000000'
// Equivalent to '9999999999999999e80' but we cannot use that because sign()
// now checks that the encoded representation exactly matches the transaction
// as it was originally provided.
const maxIOUValue =
'999999999999999900000000000000000000000000000000000000000000000000000000000000000000000000000000'
let maxValue
if (amount.currency === 'XRP') {
maxValue = maxXRPValue
} else if (amount.currency === 'drops') {
maxValue = xrpToDrops(maxXRPValue)
} else {
maxValue = maxIOUValue
}
return { ...amount, value: maxValue }
}
/**
* Given an address and tag:
* 1. Get the classic account and tag;
* 2. If a tag is provided:
* 2a. If the address was an X-address, validate that the X-address has the expected tag;
* 2b. If the address was a classic address, return `expectedTag` as the tag.
* 3. If we do not want to use a tag in this case,
* set the tag in the return value to `undefined`.
*
* @param address - The address to parse.
* @param expectedTag - If provided, and the `Account` is an X-address,
* this method throws an error if `expectedTag`
* does not match the tag of the X-address.
* @returns
* The classic account and tag.
*/
function validateAndNormalizeAddress(
address: string,
expectedTag: number | undefined,
): ClassicAccountAndTag {
const classicAddress = getClassicAccountAndTag(address, expectedTag)
classicAddress.tag =
classicAddress.tag === false ? undefined : classicAddress.tag
return classicAddress
}
function createPaymentTransaction(
address: string,
paymentArgument: Payment,
): TransactionJSON {
const payment = _.cloneDeep(paymentArgument)
applyAnyCounterpartyEncoding(payment)
const sourceAddressAndTag = validateAndNormalizeAddress(
payment.source.address,
payment.source.tag,
)
const addressToVerifyAgainst = validateAndNormalizeAddress(address, undefined)
if (
addressToVerifyAgainst.classicAccount !== sourceAddressAndTag.classicAccount
) {
throw new ValidationError('address must match payment.source.address')
}
if (
addressToVerifyAgainst.tag != null &&
sourceAddressAndTag.tag != null &&
addressToVerifyAgainst.tag !== sourceAddressAndTag.tag
) {
throw new ValidationError(
'address includes a tag that does not match payment.source.tag',
)
}
const destinationAddressAndTag = validateAndNormalizeAddress(
payment.destination.address,
payment.destination.tag,
)
if (
(isMaxAdjustment(payment.source) && isMinAdjustment(payment.destination)) ||
(!isMaxAdjustment(payment.source) && !isMinAdjustment(payment.destination))
) {
throw new ValidationError(
'payment must specify either (source.maxAmount ' +
'and destination.amount) or (source.amount and destination.minAmount)',
)
}
const destinationAmount = isMinAdjustment(payment.destination)
? payment.destination.minAmount
: payment.destination.amount
const sourceAmount = isMaxAdjustment(payment.source)
? payment.source.maxAmount
: payment.source.amount
// when using destination.minAmount, rippled still requires that we set
// a destination amount in addition to DeliverMin. the destination amount
// is interpreted as the maximum amount to send. we want to be sure to
// send the whole source amount, so we set the destination amount to the
// maximum possible amount. otherwise it's possible that the destination
// cap could be hit before the source cap.
const amount =
isMinAdjustment(payment.destination) && !isXRPToXRPPayment(payment)
? createMaximalAmount(destinationAmount)
: destinationAmount
const txJSON: any = {
TransactionType: 'Payment',
Account: sourceAddressAndTag.classicAccount,
Destination: destinationAddressAndTag.classicAccount,
Amount: toRippledAmount(amount),
Flags: 0,
}
if (payment.invoiceID != null) {
txJSON.InvoiceID = payment.invoiceID
}
if (sourceAddressAndTag.tag != null) {
txJSON.SourceTag = sourceAddressAndTag.tag
}
if (destinationAddressAndTag.tag != null) {
txJSON.DestinationTag = destinationAddressAndTag.tag
}
if (payment.memos != null) {
txJSON.Memos = payment.memos.map(utils.convertMemo)
}
if (payment.noDirectRipple) {
txJSON.Flags |= paymentFlags.NoRippleDirect
}
if (payment.limitQuality) {
txJSON.Flags |= paymentFlags.LimitQuality
}
if (!isXRPToXRPPayment(payment)) {
// Don't set SendMax for XRP->XRP payment
// temREDUNDANT_SEND_MAX removed in:
// https://github.com/ripple/rippled/commit/
// c522ffa6db2648f1d8a987843e7feabf1a0b7de8/
if (payment.allowPartialPayment || isMinAdjustment(payment.destination)) {
txJSON.Flags |= paymentFlags.PartialPayment
}
txJSON.SendMax = toRippledAmount(sourceAmount)
if (isMinAdjustment(payment.destination)) {
txJSON.DeliverMin = toRippledAmount(destinationAmount)
}
if (payment.paths != null) {
txJSON.Paths = JSON.parse(payment.paths)
}
} else if (payment.allowPartialPayment) {
throw new ValidationError('XRP to XRP payments cannot be partial payments')
}
return txJSON
}
async function preparePayment(
this: Client,
address: string,
payment: Payment,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createPaymentTransaction(address, payment)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default preparePayment

View File

@@ -1,163 +0,0 @@
import * as assert from 'assert'
import BigNumber from 'bignumber.js'
import type { Client } from '..'
import { FormattedSettings, WeightedSigner } from '../common/types/objects'
import {
Instructions,
Prepare,
SettingsTransaction,
TransactionJSON,
} from './types'
import * as utils from './utils'
const AccountSetFlags = utils.common.constants.AccountSetFlags
const AccountFields = utils.common.constants.AccountFields
function setTransactionFlags(
txJSON: TransactionJSON,
values: FormattedSettings,
) {
const keys = Object.keys(values).filter((key) => AccountSetFlags[key] != null)
assert.ok(keys.length <= 1, 'ERROR: can only set one setting per transaction')
const flagName = keys[0]
const value = values[flagName]
const index = AccountSetFlags[flagName]
if (index != null) {
if (value) {
txJSON.SetFlag = index
} else {
txJSON.ClearFlag = index
}
}
}
// Sets `null` fields to their `default`.
function setTransactionFields(
txJSON: TransactionJSON,
input: FormattedSettings,
) {
const fieldSchema = AccountFields
for (const fieldName in fieldSchema) {
const field = fieldSchema[fieldName]
let value = input[field.name]
if (value === undefined) {
continue
}
// The value required to clear an account root field varies
if (value === null && field.hasOwnProperty('defaults')) {
value = field.defaults
}
if (field.encoding === 'hex' && !field.length) {
// This is currently only used for Domain field
value = Buffer.from(value, 'ascii').toString('hex').toUpperCase()
}
txJSON[fieldName] = value
}
}
/**
* Note: A fee of 1% requires 101% of the destination to be sent for the
* destination to receive 100%.
* The transfer rate is specified as the input amount as fraction of 1.
* To specify the default rate of 0%, a 100% input amount, specify 1.
* To specify a rate of 1%, a 101% input amount, specify 1.01.
*
* @param {Number|String} transferRate
*
* @returns {Number|String} Numbers will be converted while strings
* are returned.
*/
function convertTransferRate(transferRate: number): number {
return new BigNumber(transferRate).shiftedBy(9).toNumber()
}
function formatSignerEntry(signer: WeightedSigner): object {
return {
SignerEntry: {
Account: signer.address,
SignerWeight: signer.weight,
},
}
}
function createSettingsTransactionWithoutMemos(
account: string,
settings: FormattedSettings,
): SettingsTransaction {
if (settings.regularKey !== undefined) {
const removeRegularKey = {
TransactionType: 'SetRegularKey',
Account: account,
}
if (settings.regularKey === null) {
return removeRegularKey
}
return { ...removeRegularKey, RegularKey: settings.regularKey }
}
if (settings.signers != null) {
const setSignerList: SettingsTransaction = {
TransactionType: 'SignerListSet',
Account: account,
SignerEntries: [],
SignerQuorum: settings.signers.threshold,
}
if (settings.signers.weights != null) {
setSignerList.SignerEntries =
settings.signers.weights.map(formatSignerEntry)
}
return setSignerList
}
const txJSON: SettingsTransaction = {
TransactionType: 'AccountSet',
Account: account,
}
const settingsWithoutMemos = { ...settings }
delete settingsWithoutMemos.memos
setTransactionFlags(txJSON, settingsWithoutMemos)
setTransactionFields(txJSON, settings) // Sets `null` fields to their `default`.
if (txJSON.TransferRate != null) {
txJSON.TransferRate = convertTransferRate(txJSON.TransferRate)
}
return txJSON
}
function createSettingsTransaction(
account: string,
settings: FormattedSettings,
): SettingsTransaction {
const txJSON = createSettingsTransactionWithoutMemos(account, settings)
if (settings.memos != null) {
txJSON.Memos = settings.memos.map(utils.convertMemo)
}
return txJSON
}
async function prepareSettings(
this: Client,
address: string,
settings: FormattedSettings,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createSettingsTransaction(address, settings)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareSettings

View File

@@ -5,10 +5,11 @@ import keypairs from 'ripple-keypairs'
import type { Client, Wallet } from '..'
import { ValidationError } from '../common/errors'
import { Transaction } from '../models/transactions'
import { xrpToDrops } from '../utils'
import { computeSignedTransactionHash } from '../utils/hashes'
import { SignOptions, KeyPair, TransactionJSON } from './types'
import { SignOptions, KeyPair } from './types'
function computeSignature(tx: object, privateKey: string, signAs?: string) {
const signingData = signAs
@@ -140,7 +141,7 @@ function objectDiff(a: object, b: object): object {
*
* @returns This method does not return a value, but throws an error if the check fails.
*/
function checkTxSerialization(serialized: string, tx: TransactionJSON): void {
function checkTxSerialization(serialized: string, tx: Transaction): void {
// Decode the serialized transaction:
const decoded = binaryCodec.decode(serialized)

View File

@@ -1,46 +0,0 @@
import _ from 'lodash'
import type { Client } from '..'
import { Prepare, TransactionJSON, Instructions } from './types'
import * as utils from './utils'
const ValidationError = utils.common.errors.ValidationError
export interface Ticket {
account: string
sequence: number
}
function createTicketTransaction(
account: string,
ticketCount: number,
): TransactionJSON {
if (!ticketCount || ticketCount === 0) {
throw new ValidationError('Ticket count must be greater than 0.')
}
const txJSON: any = {
TransactionType: 'TicketCreate',
Account: account,
TicketCount: ticketCount,
}
return txJSON
}
async function prepareTicketCreate(
this: Client,
address: string,
ticketCount: number,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createTicketTransaction(address, ticketCount)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareTicketCreate

View File

@@ -1,73 +0,0 @@
import BigNumber from 'bignumber.js'
import type { Client } from '..'
import { FormattedTrustlineSpecification } from '../common/types/objects/trustlines'
import { Instructions, Prepare, TransactionJSON } from './types'
import * as utils from './utils'
const trustlineFlags = utils.common.txFlags.TrustSet
function convertQuality(quality) {
return new BigNumber(quality)
.shiftedBy(9)
.integerValue(BigNumber.ROUND_DOWN)
.toNumber()
}
function createTrustlineTransaction(
account: string,
trustline: FormattedTrustlineSpecification,
): TransactionJSON {
const limit = {
currency: trustline.currency,
issuer: trustline.counterparty,
value: trustline.limit,
}
const txJSON: any = {
TransactionType: 'TrustSet',
Account: account,
LimitAmount: limit,
Flags: 0,
}
if (trustline.qualityIn != null) {
txJSON.QualityIn = convertQuality(trustline.qualityIn)
}
if (trustline.qualityOut != null) {
txJSON.QualityOut = convertQuality(trustline.qualityOut)
}
if (trustline.authorized) {
txJSON.Flags |= trustlineFlags.SetAuth
}
if (trustline.ripplingDisabled != null) {
txJSON.Flags |= trustline.ripplingDisabled
? trustlineFlags.NoRipple
: trustlineFlags.ClearNoRipple
}
if (trustline.frozen != null) {
txJSON.Flags |= trustline.frozen
? trustlineFlags.SetFreeze
: trustlineFlags.ClearFreeze
}
if (trustline.memos != null) {
txJSON.Memos = trustline.memos.map(utils.convertMemo)
}
return txJSON
}
async function prepareTrustline(
this: Client,
address: string,
trustline: FormattedTrustlineSpecification,
instructions: Instructions = {},
): Promise<Prepare> {
try {
const txJSON = createTrustlineTransaction(address, trustline)
return await utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareTrustline

View File

@@ -1,44 +1,3 @@
import {
FormattedOrderSpecification,
FormattedTrustlineSpecification,
Adjustment,
RippledAmount,
Memo,
FormattedSettings,
} from '../common/types/objects'
import { ApiMemo } from './utils'
export interface TransactionJSON {
Account: string
TransactionType: string
Memos?: Array<{ Memo: ApiMemo }>
Flags?: number
Fulfillment?: string
[Field: string]: string | number | any[] | RippledAmount | undefined
}
export interface Instructions {
sequence?: number
ticketSequence?: number
fee?: string
// @deprecated
maxFee?: string
maxLedgerVersion?: number
maxLedgerVersionOffset?: number
signersCount?: number
}
export interface Prepare {
txJSON: string
instructions: {
fee: string
sequence?: number
ticketSequence?: number
maxLedgerVersion?: number
}
}
export interface Submit {
success: boolean
engineResult: string
@@ -48,24 +7,6 @@ export interface Submit {
txJson?: object
}
export interface OfferCreateTransaction extends TransactionJSON {
TransactionType: 'OfferCreate'
Account: string
Fee: string
Flags: number
LastLedgerSequence: number
Sequence: number
TakerGets: RippledAmount
TakerPays: RippledAmount
Expiration?: number
OfferSequence?: number
Memos?: Array<{ Memo: ApiMemo }>
}
export interface SettingsTransaction extends TransactionJSON {
TransferRate?: number
}
export interface KeyPair {
publicKey: string
privateKey: string
@@ -95,70 +36,3 @@ export interface Outcome {
}
timestamp?: string
}
export interface FormattedOrderCancellation {
orderSequence: number
}
export interface FormattedPayment {
source: Adjustment
destination: Adjustment
paths?: string
memos?: Memo[]
invoiceID?: string
allowPartialPayment?: boolean
noDirectRipple?: boolean
limitQuality?: boolean
}
export interface FormattedPaymentTransaction {
type: string
specification: FormattedPayment
outcome: Outcome
id: string
address: string
sequence: number
}
export interface FormattedOrderTransaction {
type: string
specification: FormattedOrderSpecification
outcome: Outcome
id: string
address: string
sequence: number
}
export interface FormattedOrderCancellationTransaction {
type: string
specification: FormattedOrderCancellation
outcome: Outcome
id: string
address: string
sequence: number
}
export interface FormattedTrustlineTransaction {
type: string
specification: FormattedTrustlineSpecification
outcome: Outcome
id: string
address: string
sequence: number
}
export interface FormattedSettingsTransaction {
type: string
specification: FormattedSettings
outcome: Outcome
id: string
address: string
sequence: number
}
export type FormattedTransactionType =
| FormattedPaymentTransaction
| FormattedOrderTransaction
| FormattedOrderCancellationTransaction
| FormattedTrustlineTransaction
| FormattedSettingsTransaction

View File

@@ -1,418 +0,0 @@
import BigNumber from 'bignumber.js'
import { xAddressToClassicAddress, isValidXAddress } from 'ripple-address-codec'
import type { Client } from '..'
import * as common from '../common'
import { ValidationError } from '../common/errors'
import { Memo } from '../common/types/objects'
import {
toRippledAmount,
dropsToXrp,
removeUndefined,
xrpToDrops,
} from '../utils'
import { Instructions, Prepare, TransactionJSON } from './types'
const txFlags = common.txFlags
const TRANSACTION_TYPES_WITH_DESTINATION_TAG_FIELD = [
'Payment',
'CheckCreate',
'EscrowCreate',
'PaymentChannelCreate',
]
export interface ApiMemo {
MemoData?: string
MemoType?: string
MemoFormat?: string
}
// TODO: move relevant methods from here to `src/utils` (such as `convertStringToHex`?)
function formatPrepareResponse(txJSON: any): Prepare {
const instructions: any = {
fee: dropsToXrp(txJSON.Fee),
maxLedgerVersion:
txJSON.LastLedgerSequence == null ? null : txJSON.LastLedgerSequence,
}
if (txJSON.TicketSequence != null) {
instructions.ticketSequence = txJSON.TicketSequence
} else {
instructions.sequence = txJSON.Sequence
}
return {
txJSON: JSON.stringify(txJSON),
instructions,
}
}
/**
* Set the `tfFullyCanonicalSig` flag on a transaction.
*
* See https://xrpl.org/transaction-malleability.html.
*
* @param txJSON - The transaction object to modify.
* This method will modify object's `Flags` property, or add it if it does not exist.
*
* @returns This method mutates the original txJSON and does not return a value.
*/
function setCanonicalFlag(txJSON: TransactionJSON): void {
if (txJSON.Flags == null) {
txJSON.Flags = 0
}
txJSON.Flags |= txFlags.Universal.FullyCanonicalSig
// JavaScript converts operands to 32-bit signed ints before doing bitwise
// operations. We need to convert it back to an unsigned int.
txJSON.Flags >>>= 0
}
function scaleValue(value, multiplier, extra = 0) {
return new BigNumber(value).times(multiplier).plus(extra).toString()
}
/**
* @typedef {Object} ClassicAccountAndTag
* @property {string} classicAccount - The classic account address.
* @property {number | false | undefined } tag - The destination tag;
* `false` if no tag should be used;
* `undefined` if the input could not specify whether a tag should be used.
*/
export interface ClassicAccountAndTag {
classicAccount: string
tag: number | false | undefined
}
/**
* Given an address (account), get the classic account and tag.
* If an `expectedTag` is provided:
* 1. If the `Account` is an X-address, validate that the tags match.
* 2. If the `Account` is a classic address, return `expectedTag` as the tag.
*
* @param Account - The address to parse.
* @param expectedTag - If provided, and the `Account` is an X-address,
* this method throws an error if `expectedTag`
* does not match the tag of the X-address.
* @returns
* The classic account and tag.
*/
function getClassicAccountAndTag(
Account: string,
expectedTag?: number,
): ClassicAccountAndTag {
if (isValidXAddress(Account)) {
const classic = xAddressToClassicAddress(Account)
if (expectedTag != null && classic.tag !== expectedTag) {
throw new ValidationError(
'address includes a tag that does not match the tag specified in the transaction',
)
}
return {
classicAccount: classic.classicAddress,
tag: classic.tag,
}
}
return {
classicAccount: Account,
tag: expectedTag,
}
}
async function prepareTransaction(
txJSON: TransactionJSON,
client: Client,
instructions: Instructions,
): Promise<Prepare> {
// We allow 0 values in the Sequence schema to support the Tickets feature
// When a ticketSequence is used, sequence has to be 0
// We validate that a sequence with value 0 is not passed even if the json schema allows it
if (instructions.sequence != null && instructions.sequence === 0) {
return Promise.reject(new ValidationError('`sequence` cannot be 0'))
}
const disallowedFieldsInTxJSON = [
'maxLedgerVersion',
'maxLedgerVersionOffset',
'fee',
'sequence',
'ticketSequence',
]
const badFields = disallowedFieldsInTxJSON.filter((field) => txJSON[field])
if (badFields.length) {
return Promise.reject(
new ValidationError(
`txJSON additionalProperty "${badFields[0]}" exists in instance when not allowed`,
),
)
}
const newTxJSON = { ...txJSON }
// To remove the signer list, `SignerEntries` field should be omitted.
if (txJSON.SignerQuorum === 0) {
delete newTxJSON.SignerEntries
}
// Sender:
const { classicAccount, tag: sourceTag } = getClassicAccountAndTag(
txJSON.Account,
)
newTxJSON.Account = classicAccount
if (sourceTag != null) {
if (txJSON.SourceTag && txJSON.SourceTag !== sourceTag) {
return Promise.reject(
new ValidationError(
'The `SourceTag`, if present, must match the tag of the `Account` X-address',
),
)
}
if (sourceTag) {
newTxJSON.SourceTag = sourceTag
}
}
// Destination:
if (typeof txJSON.Destination === 'string') {
const { classicAccount: destinationAccount, tag: destinationTag } =
getClassicAccountAndTag(txJSON.Destination)
newTxJSON.Destination = destinationAccount
if (destinationTag != null) {
if (
TRANSACTION_TYPES_WITH_DESTINATION_TAG_FIELD.includes(
txJSON.TransactionType,
)
) {
if (txJSON.DestinationTag && txJSON.DestinationTag !== destinationTag) {
return Promise.reject(
new ValidationError(
'The Payment `DestinationTag`, if present, must match the tag of the `Destination` X-address',
),
)
}
if (destinationTag) {
newTxJSON.DestinationTag = destinationTag
}
}
}
}
function convertToClassicAccountIfPresent(fieldName: string): void {
const account = txJSON[fieldName]
if (typeof account === 'string') {
const { classicAccount: ca } = getClassicAccountAndTag(account)
newTxJSON[fieldName] = ca
}
}
function convertIssuedCurrencyToAccountIfPresent(fieldName: string): void {
const amount = txJSON[fieldName]
if (
typeof amount === 'number' ||
amount instanceof Array ||
amount == null
) {
return
}
newTxJSON[fieldName] = toRippledAmount(amount)
}
// DepositPreauth:
convertToClassicAccountIfPresent('Authorize')
convertToClassicAccountIfPresent('Unauthorize')
// EscrowCancel, EscrowFinish:
convertToClassicAccountIfPresent('Owner')
// SetRegularKey:
convertToClassicAccountIfPresent('RegularKey')
// Payment
convertIssuedCurrencyToAccountIfPresent('Amount')
convertIssuedCurrencyToAccountIfPresent('SendMax')
convertIssuedCurrencyToAccountIfPresent('DeliverMin')
// OfferCreate
convertIssuedCurrencyToAccountIfPresent('TakerPays')
convertIssuedCurrencyToAccountIfPresent('TakerGets')
// TrustSet
convertIssuedCurrencyToAccountIfPresent('LimitAmount')
setCanonicalFlag(newTxJSON)
async function prepareMaxLedgerVersion(): Promise<void> {
// Up to one of the following is allowed:
// txJSON.LastLedgerSequence
// instructions.maxLedgerVersion
// instructions.maxLedgerVersionOffset
if (newTxJSON.LastLedgerSequence && instructions.maxLedgerVersion) {
return Promise.reject(
new ValidationError(
'`LastLedgerSequence` in txJSON and `maxLedgerVersion`' +
' in `instructions` cannot both be set',
),
)
}
if (newTxJSON.LastLedgerSequence && instructions.maxLedgerVersionOffset) {
return Promise.reject(
new ValidationError(
'`LastLedgerSequence` in txJSON and `maxLedgerVersionOffset`' +
' in `instructions` cannot both be set',
),
)
}
if (newTxJSON.LastLedgerSequence) {
return Promise.resolve()
}
if (instructions.maxLedgerVersion !== undefined) {
if (instructions.maxLedgerVersion !== null) {
newTxJSON.LastLedgerSequence = instructions.maxLedgerVersion
}
return Promise.resolve()
}
const offset =
instructions.maxLedgerVersionOffset != null
? instructions.maxLedgerVersionOffset
: 3
return client
.request({ command: 'ledger_current' })
.then((response) => response.result.ledger_current_index)
.then((ledgerVersion) => {
newTxJSON.LastLedgerSequence = ledgerVersion + offset
})
}
async function prepareFee(): Promise<void> {
// instructions.fee is scaled (for multi-signed transactions) while txJSON.Fee is not.
// Due to this difference, we do NOT allow both to be set, as the behavior would be complex and
// potentially ambiguous.
// Furthermore, txJSON.Fee is in drops while instructions.fee is in XRP, which would just add to
// the confusion. It is simpler to require that only one is used.
if (newTxJSON.Fee && instructions.fee) {
return Promise.reject(
new ValidationError(
'`Fee` in txJSON and `fee` in `instructions` cannot both be set',
),
)
}
if (newTxJSON.Fee) {
// txJSON.Fee is set. Use this value and do not scale it.
return Promise.resolve()
}
const multiplier =
instructions.signersCount == null ? 1 : instructions.signersCount + 1
if (instructions.fee != null) {
const fee = new BigNumber(instructions.fee)
if (fee.isGreaterThan(client.maxFeeXRP)) {
return Promise.reject(
new ValidationError(
`Fee of ${fee.toString(10)} XRP exceeds ` +
`max of ${client.maxFeeXRP} XRP. To use this fee, increase ` +
'`maxFeeXRP` in the Client constructor.',
),
)
}
newTxJSON.Fee = scaleValue(xrpToDrops(instructions.fee), multiplier)
return Promise.resolve()
}
const cushion = client.feeCushion
return client.getFee(cushion).then(async (fee) => {
return client
.request({ command: 'fee' })
.then((response) => Number(response.result.drops.minimum_fee))
.then((feeRef) => {
// feeRef is the reference transaction cost in "fee units"
const extraFee =
newTxJSON.TransactionType !== 'EscrowFinish' ||
newTxJSON.Fulfillment == null
? 0
: cushion *
feeRef *
(32 +
Math.floor(
Buffer.from(newTxJSON.Fulfillment, 'hex').length / 16,
))
const feeDrops = xrpToDrops(fee)
const maxFeeXRP = instructions.maxFee
? BigNumber.min(client.maxFeeXRP, instructions.maxFee)
: client.maxFeeXRP
const maxFeeDrops = xrpToDrops(maxFeeXRP)
const normalFee = scaleValue(feeDrops, multiplier, extraFee)
newTxJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10)
})
})
}
async function prepareSequence(): Promise<void> {
if (instructions.sequence != null) {
if (
newTxJSON.Sequence == null ||
instructions.sequence === newTxJSON.Sequence
) {
newTxJSON.Sequence = instructions.sequence
return Promise.resolve()
}
// Both txJSON.Sequence and instructions.sequence are defined, and they are NOT equal
return Promise.reject(
new ValidationError(
'`Sequence` in txJSON must match `sequence` in `instructions`',
),
)
}
if (newTxJSON.Sequence != null) {
return Promise.resolve()
}
// Ticket Sequence
if (instructions.ticketSequence != null) {
newTxJSON.Sequence = 0
newTxJSON.TicketSequence = instructions.ticketSequence
return Promise.resolve()
}
try {
const response = await client.request({
command: 'account_info',
account: classicAccount,
ledger_index: 'current', // Fix #999
})
newTxJSON.Sequence = response.result.account_data.Sequence
return await Promise.resolve()
} catch (e) {
return await Promise.reject(e)
}
}
return Promise.all([
prepareMaxLedgerVersion(),
prepareFee(),
prepareSequence(),
]).then(() => formatPrepareResponse(newTxJSON))
}
function convertStringToHex(string: string): string {
return Buffer.from(string, 'utf8').toString('hex').toUpperCase()
}
function convertMemo(memo: Memo): { Memo: ApiMemo } {
return {
Memo: removeUndefined({
MemoData: memo.data ? convertStringToHex(memo.data) : undefined,
MemoType: memo.type ? convertStringToHex(memo.type) : undefined,
MemoFormat: memo.format ? convertStringToHex(memo.format) : undefined,
}),
}
}
export {
convertStringToHex,
convertMemo,
prepareTransaction,
common,
setCanonicalFlag,
getClassicAccountAndTag,
}

View File

@@ -213,6 +213,10 @@ function ISOTimeToRippleTime(iso8601: string): number {
return unixToRippleTimestamp(Date.parse(iso8601))
}
function convertStringToHex(string: string): string {
return Buffer.from(string, 'utf8').toString('hex').toUpperCase()
}
export {
computeLedgerHeaderHash,
dropsToXrp,
@@ -240,4 +244,5 @@ export {
deriveXAddress,
signPaymentChannelClaim,
verifyPaymentChannelClaim,
convertStringToHex,
}

View File

@@ -150,7 +150,7 @@ class Wallet {
* @param test - A boolean to indicate if X-address should be in Testnet (true) or Mainnet (false) format.
* @returns An X-address.
*/
getXAddress(tag: number, test = false): string {
getXAddress(tag: number | false = false, test = false): string {
return classicAddressToXAddress(this.classicAddress, tag, test)
}

View File

@@ -1,60 +0,0 @@
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareCheckCancel', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('prepareCheckCancel', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareCheckCancel(
test.address,
requests.prepareCheckCancel.normal,
)
assertResultMatch(
result,
responses.prepareCheckCancel.normal,
'prepare',
)
})
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const result = await this.client.prepareCheckCancel(
test.address,
requests.prepareCheckCancel.normal,
localInstructions,
)
assertResultMatch(
result,
responses.prepareCheckCancel.ticket,
'prepare',
)
})
})
})
})

View File

@@ -1,71 +0,0 @@
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareCheckCash', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('prepareCheckCash amount', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareCheckCash(
test.address,
requests.prepareCheckCash.amount,
)
assertResultMatch(result, responses.prepareCheckCash.amount, 'prepare')
})
it('prepareCheckCash deliverMin', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareCheckCash(
test.address,
requests.prepareCheckCash.deliverMin,
)
assertResultMatch(
result,
responses.prepareCheckCash.deliverMin,
'prepare',
)
})
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const result = await this.client.prepareCheckCash(
test.address,
requests.prepareCheckCash.amount,
localInstructions,
)
assertResultMatch(result, responses.prepareCheckCash.ticket, 'prepare')
})
})
})
})

View File

@@ -1,80 +0,0 @@
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareCheckCreate', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('prepareCheckCreate', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
}
const result = await this.client.prepareCheckCreate(
test.address,
requests.prepareCheckCreate.normal,
localInstructions,
)
assertResultMatch(
result,
responses.prepareCheckCreate.normal,
'prepare',
)
})
it('prepareCheckCreate full', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareCheckCreate(
test.address,
requests.prepareCheckCreate.full,
)
assertResultMatch(result, responses.prepareCheckCreate.full, 'prepare')
})
it('prepareCheckCreate with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const result = await this.client.prepareCheckCreate(
test.address,
requests.prepareCheckCreate.normal,
localInstructions,
)
assertResultMatch(
result,
responses.prepareCheckCreate.ticket,
'prepare',
)
})
})
})
})

View File

@@ -1,80 +0,0 @@
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareEscrowCancellation', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('prepareEscrowCancellation', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareEscrowCancellation(
test.address,
requests.prepareEscrowCancellation.normal,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
result,
responses.prepareEscrowCancellation.normal,
'prepare',
)
})
it('prepareEscrowCancellation with memos', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareEscrowCancellation(
test.address,
requests.prepareEscrowCancellation.memos,
)
assertResultMatch(
result,
responses.prepareEscrowCancellation.memos,
'prepare',
)
})
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const result = await this.client.prepareEscrowCancellation(
test.address,
requests.prepareEscrowCancellation.normal,
localInstructions,
)
assertResultMatch(
result,
responses.prepareEscrowCancellation.ticket,
'prepare',
)
})
})
})
})

View File

@@ -1,74 +0,0 @@
import addresses from '../fixtures/addresses.json'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareEscrowCreation', function () {
beforeEach(setupClient)
afterEach(teardownClient)
it('prepareEscrowCreation', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
}
const result = await this.client.prepareEscrowCreation(
addresses.ACCOUNT,
requests.prepareEscrowCreation.normal,
localInstructions,
)
assertResultMatch(result, responses.prepareEscrowCreation.normal, 'prepare')
})
it('prepareEscrowCreation full', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
const result = await this.client.prepareEscrowCreation(
addresses.ACCOUNT,
requests.prepareEscrowCreation.full,
)
assertResultMatch(result, responses.prepareEscrowCreation.full, 'prepare')
})
// it("prepareEscrowCreation - invalid", async function () {
// this.mockRippled.addResponse("server_info", rippled.server_info.normal);
// this.mockRippled.addResponse("fee", rippled.fee);
// this.mockRippled.addResponse("ledger_current", rippled.ledger_current);
// this.mockRippled.addResponse("account_info", rippled.account_info.normal);
// const escrow = { ...requests.prepareEscrowCreation.full };
// delete escrow.amount; // Make invalid
// await assertRejects(
// this.client.prepareEscrowCreation(addresses.ACCOUNT, escrow),
// this.client.errors.ValidationError,
// 'instance.escrowCreation requires property "amount"'
// );
// });
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000396',
ticketSequence: 23,
}
const result = await this.client.prepareEscrowCreation(
addresses.ACCOUNT,
requests.prepareEscrowCreation.normal,
localInstructions,
)
assertResultMatch(result, responses.prepareEscrowCreation.ticket, 'prepare')
})
})

View File

@@ -1,118 +0,0 @@
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { addressTests, assertRejects, assertResultMatch } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareEscrowExecution', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('prepareEscrowExecution', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareEscrowExecution(
test.address,
requests.prepareEscrowExecution.normal,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
result,
responses.prepareEscrowExecution.normal,
'prepare',
)
})
it('prepareEscrowExecution - simple', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareEscrowExecution(
test.address,
requests.prepareEscrowExecution.simple,
)
assertResultMatch(
result,
responses.prepareEscrowExecution.simple,
'prepare',
)
})
it('prepareEscrowExecution - no condition', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
await assertRejects(
this.client.prepareEscrowExecution(
test.address,
requests.prepareEscrowExecution.noCondition,
instructionsWithMaxLedgerVersionOffset,
),
this.client.errors.ValidationError,
'"condition" and "fulfillment" fields on EscrowFinish must only be specified together.',
)
})
it('prepareEscrowExecution - no fulfillment', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
await assertRejects(
this.client.prepareEscrowExecution(
test.address,
requests.prepareEscrowExecution.noFulfillment,
instructionsWithMaxLedgerVersionOffset,
),
this.client.errors.ValidationError,
'"condition" and "fulfillment" fields on EscrowFinish must only be specified together.',
)
})
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000396',
ticketSequence: 23,
}
const result = await this.client.prepareEscrowExecution(
test.address,
requests.prepareEscrowExecution.normal,
localInstructions,
)
assertResultMatch(
result,
responses.prepareEscrowExecution.ticket,
'prepare',
)
})
})
})
})

View File

@@ -1,107 +0,0 @@
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareOrder', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('buy order', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const request = requests.prepareOrder.buy
const result = await this.client.prepareOrder(test.address, request)
assertResultMatch(result, responses.prepareOrder.buy, 'prepare')
})
it('buy order with expiration', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const request = requests.prepareOrder.expiration
const response = responses.prepareOrder.expiration
const result = await this.client.prepareOrder(
test.address,
request,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(result, response, 'prepare')
})
it('sell order', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const request = requests.prepareOrder.sell
const result = await this.client.prepareOrder(
test.address,
request,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(result, responses.prepareOrder.sell, 'prepare')
})
// it("invalid", async function () {
// this.mockRippled.addResponse("server_info", rippled.server_info.normal);
// this.mockRippled.addResponse("fee", rippled.fee);
// this.mockRippled.addResponse("ledger_current", rippled.ledger_current);
// this.mockRippled.addResponse(
// "account_info",
// rippled.account_info.normal
// );
// const request = { ...requests.prepareOrder.sell };
// delete request.direction; // Make invalid
// await assertRejects(
// this.client.prepareOrder(
// test.address,
// request,
// instructionsWithMaxLedgerVersionOffset
// ),
// this.client.errors.ValidationError,
// 'instance.order requires property "direction"'
// );
// });
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const request = requests.prepareOrder.sell
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const result = await this.client.prepareOrder(
test.address,
request,
localInstructions,
)
assertResultMatch(result, responses.prepareOrder.ticket, 'prepare')
})
})
})
})

View File

@@ -1,123 +0,0 @@
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareOrderCancellation', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('prepareOrderCancellation', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const request = requests.prepareOrderCancellation.simple
const result = await this.client.prepareOrderCancellation(
test.address,
request,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
result,
responses.prepareOrderCancellation.normal,
'prepare',
)
})
it('no instructions', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const request = requests.prepareOrderCancellation.simple
const result = await this.client.prepareOrderCancellation(
test.address,
request,
)
assertResultMatch(
result,
responses.prepareOrderCancellation.noInstructions,
'prepare',
)
})
it('with memos', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const request = requests.prepareOrderCancellation.withMemos
const result = await this.client.prepareOrderCancellation(
test.address,
request,
)
assertResultMatch(
result,
responses.prepareOrderCancellation.withMemos,
'prepare',
)
})
// it("invalid", async function () {
// this.mockRippled.addResponse("server_info", rippled.server_info.normal);
// this.mockRippled.addResponse("fee", rippled.fee);
// this.mockRippled.addResponse("ledger_current", rippled.ledger_current);
// this.mockRippled.addResponse(
// "account_info",
// rippled.account_info.normal
// );
// const request = {
// ...requests.prepareOrderCancellation.withMemos,
// };
// delete request.orderSequence; // Make invalid
// await assertRejects(
// this.client.prepareOrderCancellation(test.address, request),
// this.client.errors.ValidationError,
// 'instance.orderCancellation requires property "orderSequence"'
// );
// });
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const request = requests.prepareOrderCancellation.simple
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const result = await this.client.prepareOrderCancellation(
test.address,
request,
localInstructions,
)
assertResultMatch(
result,
responses.prepareOrderCancellation.ticket,
'prepare',
)
})
})
})
})

View File

@@ -1,663 +0,0 @@
import { ValidationError } from 'xrpl-local/common/errors'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests, assertRejects } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
const { preparePayment: REQUEST_FIXTURES } = requests
const { preparePayment: RESPONSE_FIXTURES } = responses
const RECIPIENT_ADDRESS = 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
describe('client.preparePayment', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('normal', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
}
const response = await this.client.preparePayment(
test.address,
REQUEST_FIXTURES.normal,
localInstructions,
)
assertResultMatch(response, RESPONSE_FIXTURES.normal, 'prepare')
})
it('min amount xrp', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
}
const response = await this.client.preparePayment(
test.address,
REQUEST_FIXTURES.minAmountXRP,
localInstructions,
)
assertResultMatch(response, RESPONSE_FIXTURES.minAmountXRP, 'prepare')
})
it('min amount xrp2xrp', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const response = await this.client.preparePayment(
test.address,
REQUEST_FIXTURES.minAmount,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
response,
RESPONSE_FIXTURES.minAmountXRPXRP,
'prepare',
)
})
it('XRP to XRP', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const payment = {
source: {
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
maxAmount: { value: '1', currency: 'XRP' },
},
destination: {
address: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
amount: { value: '1', currency: 'XRP' },
},
}
const expected = {
txJSON:
'{"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":"1000000","Flags":2147483648,"LastLedgerSequence":8820051,"Sequence":23,"Fee":"12"}',
instructions: {
fee: '0.000012',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
const response = await this.client.preparePayment(
test.address,
payment,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(response, expected, 'prepare')
})
it('XRP drops to XRP drops', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const payment = {
source: {
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
maxAmount: { value: '1000000', currency: 'drops' },
},
destination: {
address: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
amount: { value: '1000000', currency: 'drops' },
},
}
const expected = {
txJSON:
'{"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":"1000000","Flags":2147483648,"LastLedgerSequence":8820051,"Sequence":23,"Fee":"12"}',
instructions: {
fee: '0.000012',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
const response = await this.client.preparePayment(
test.address,
payment,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(response, expected, 'prepare')
})
it('XRP drops to XRP', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const payment = {
source: {
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
maxAmount: { value: '1000000', currency: 'drops' },
},
destination: {
address: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
amount: { value: '1', currency: 'XRP' },
},
}
const expected = {
txJSON:
'{"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":"1000000","Flags":2147483648,"LastLedgerSequence":8820051,"Sequence":23,"Fee":"12"}',
instructions: {
fee: '0.000012',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
const response = await this.client.preparePayment(
test.address,
payment,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(response, expected, 'prepare')
})
it('XRP to XRP drops', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const payment = {
source: {
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
maxAmount: { value: '1', currency: 'XRP' },
},
destination: {
address: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
amount: { value: '1000000', currency: 'drops' },
},
}
const expected = {
txJSON:
'{"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":"1000000","Flags":2147483648,"LastLedgerSequence":8820051,"Sequence":23,"Fee":"12"}',
instructions: {
fee: '0.000012',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
const response = await this.client.preparePayment(
test.address,
payment,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(response, expected, 'prepare')
})
// Errors
it('rejects promise and does not throw when payment object is invalid', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const payment = {
source: {
address: test.address,
// instead of `maxAmount`
amount: { value: '1000', currency: 'drops' },
},
destination: {
address: RECIPIENT_ADDRESS,
amount: { value: '1000', currency: 'drops' },
},
}
return assertRejects(
this.client.preparePayment(test.address, payment),
ValidationError,
'payment must specify either (source.maxAmount and destination.amount) or (source.amount and destination.minAmount)',
)
})
// it("rejects promise and does not throw when field is missing", async function () {
// this.mockRippled.addResponse("server_info", rippled.server_info.normal);
// this.mockRippled.addResponse("fee", rippled.fee);
// this.mockRippled.addResponse("ledger_current", rippled.ledger_current);
// this.mockRippled.addResponse(
// "account_info",
// rippled.account_info.normal
// );
// // Marking as "any" to get around the fact that TS won't allow this.
// const payment: any = {
// source: { address: test.address },
// destination: {
// address: RECIPIENT_ADDRESS,
// amount: { value: "1000", currency: "drops" },
// },
// };
// return assertRejects(
// this.client.preparePayment(test.address, payment),
// ValidationError,
// "instance.payment.source is not exactly one from <sourceExactAdjustment>,<maxAdjustment>"
// );
// });
it('rejects promise and does not throw when fee exceeds maxFeeXRP', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const payment = {
source: {
address: test.address,
maxAmount: { value: '1000', currency: 'drops' },
},
destination: {
address: RECIPIENT_ADDRESS,
amount: { value: '1000', currency: 'drops' },
},
}
return assertRejects(
this.client.preparePayment(test.address, payment, { fee: '3' }),
ValidationError,
'Fee of 3 XRP exceeds max of 2 XRP. To use this fee, increase `maxFeeXRP` in the Client constructor.',
)
})
it('XRP to XRP no partial', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
return assertRejects(
this.client.preparePayment(
test.address,
REQUEST_FIXTURES.wrongPartial,
),
ValidationError,
'XRP to XRP payments cannot be partial payments',
)
})
it('address must match payment.source.address', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
return assertRejects(
this.client.preparePayment(
test.address,
REQUEST_FIXTURES.wrongAddress,
),
ValidationError,
'address must match payment.source.address',
)
})
it('wrong amount', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
return assertRejects(
this.client.preparePayment(
test.address,
REQUEST_FIXTURES.wrongAmount,
),
ValidationError,
'payment must specify either (source.maxAmount and destination.amount) or (source.amount and destination.minAmount)',
)
})
it('throws when fee exceeds 2 XRP', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
fee: '2.1',
}
return assertRejects(
this.client.preparePayment(
test.address,
REQUEST_FIXTURES.normal,
localInstructions,
),
ValidationError,
'Fee of 2.1 XRP exceeds max of 2 XRP. To use this fee, increase `maxFeeXRP` in the Client constructor.',
)
})
// 'preparePayment with all options specified': async (client, test.address) => {
// const ledgerResponse = await this.client.request({command: 'ledger', ledger_index: 'validated'})
// const version = ledgerResponse.result.ledger_index
// const localInstructions = {
// maxLedgerVersion: version + 100,
// fee: '0.000012'
// }
// const response = await this.client.preparePayment(
// test.address,
// REQUEST_FIXTURES.allOptions,
// localInstructions
// )
// assertResultMatch(response, RESPONSE_FIXTURES.allOptions, 'prepare')
// },
it('preparePayment without counterparty set', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
sequence: 23,
}
const response = await this.client.preparePayment(
test.address,
REQUEST_FIXTURES.noCounterparty,
localInstructions,
)
assertResultMatch(response, RESPONSE_FIXTURES.noCounterparty, 'prepare')
})
it('preparePayment with source.amount/destination.minAmount can be signed', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
// See also: 'sign succeeds with source.amount/destination.minAmount'
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
sequence: 23,
}
const response = await this.client.preparePayment(
test.address,
REQUEST_FIXTURES.noCounterparty,
localInstructions,
)
assertResultMatch(response, RESPONSE_FIXTURES.noCounterparty, 'prepare')
})
it('destination.minAmount', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const response = await this.client.preparePayment(
test.address,
responses.getPaths.sendAll[0],
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(response, RESPONSE_FIXTURES.minAmount, 'prepare')
})
it('caps fee at 2 XRP by default', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
this.client.feeCushion = 1000000
const expectedResponse = {
txJSON:
'{"Flags":2147483648,"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"SendMax":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"LastLedgerSequence":8820051,"Fee":"2000000","Sequence":23}',
instructions: {
fee: '2',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
const response = await this.client.preparePayment(
test.address,
REQUEST_FIXTURES.normal,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(response, expectedResponse, 'prepare')
})
it('allows fee exceeding 2 XRP when maxFeeXRP is higher', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
this.client.maxFeeXRP = '2.2'
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
fee: '2.1',
}
const expectedResponse = {
txJSON:
'{"Flags":2147483648,"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"SendMax":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"LastLedgerSequence":8820051,"Fee":"2100000","Sequence":23}',
instructions: {
fee: '2.1',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
const response = await this.client.preparePayment(
test.address,
REQUEST_FIXTURES.normal,
localInstructions,
)
assertResultMatch(response, expectedResponse, 'prepare')
})
it('fee - default maxFee of 2 XRP', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
this.client.feeCushion = 1000000
const expectedResponse = {
txJSON:
'{"Flags":2147483648,"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"SendMax":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"LastLedgerSequence":8820051,"Fee":"2000000","Sequence":23}',
instructions: {
fee: '2',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
const response = await this.client.preparePayment(
test.address,
requests.preparePayment.normal,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(response, expectedResponse, 'prepare')
})
it('fee - capped to maxFeeXRP when maxFee exceeds maxFeeXRP', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
this.client.feeCushion = 1000000
this.client.maxFeeXRP = '3'
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '4',
}
const expectedResponse = {
txJSON:
'{"Flags":2147483648,"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"SendMax":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"LastLedgerSequence":8820051,"Fee":"3000000","Sequence":23}',
instructions: {
fee: '3',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
const response = await this.client.preparePayment(
test.address,
requests.preparePayment.normal,
localInstructions,
)
assertResultMatch(response, expectedResponse, 'prepare')
})
it('fee - capped to maxFee', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
this.client.feeCushion = 1000000
this.client.maxFeeXRP = '5'
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '4',
}
const expectedResponse = {
txJSON:
'{"Flags":2147483648,"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"SendMax":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"LastLedgerSequence":8820051,"Fee":"4000000","Sequence":23}',
instructions: {
fee: '4',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
const response = await this.client.preparePayment(
test.address,
requests.preparePayment.normal,
localInstructions,
)
assertResultMatch(response, expectedResponse, 'prepare')
})
// 'fee - calculated fee does not use more than 6 decimal places': async (
// client,
// test.address
// ) => {
// this.client.connection.request({
// command: 'config',
// data: {loadFactor: 5407.96875}
// })
// const expectedResponse = {
// txJSON:
// '{"Flags":2147483648,"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"SendMax":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"LastLedgerSequence":8820051,"Fee":"64896","Sequence":23}',
// instructions: {
// fee: '0.064896',
// sequence: 23,
// maxLedgerVersion: 8820051
// }
// }
// const response = await this.client.preparePayment(
// test.address,
// requests.preparePayment.normal,
// instructionsWithMaxLedgerVersionOffset
// )
// assertResultMatch(response, expectedResponse, 'prepare')
// },
// Tickets
// 'preparePayment with ticketSequence': async (client, test.address) => {
// const ledgerResponse = await this.client.request({
// command: 'ledger',
// ledger_index: 'validated'
// })
// const version = ledgerResponse.result.ledger_index
// const localInstructions = {
// maxLedgerVersion: version + 100,
// fee: '0.000012',
// ticketSequence: 23
// }
// const response = await this.client.preparePayment(
// test.address,
// REQUEST_FIXTURES.allOptions,
// localInstructions
// )
// assertResultMatch(response, RESPONSE_FIXTURES.ticketSequence, 'prepare')
// },
// 'throws when both sequence and ticketSequence are set': async (
// client,
// test.address
// ) => {
// const ledgerResponse = await this.client.request({
// command: 'ledger',
// ledger_index: 'validated'
// })
// const version = ledgerResponse.result.ledger_index
// const localInstructions = {
// maxLedgerVersion: version + 100,
// fee: '0.000012',
// ticketSequence: 23,
// sequence: 12
// }
// return assertRejects(
// this.client.preparePayment(
// test.address,
// REQUEST_FIXTURES.allOptions,
// localInstructions
// ),
// ValidationError,
// 'instance.instructions is of prohibited type [object Object]'
// )
// }
})
})
})

View File

@@ -1,155 +0,0 @@
import { assert } from 'chai'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
const { preparePaymentChannelClaim: REQUEST_FIXTURES } = requests
const { preparePaymentChannelClaim: RESPONSE_FIXTURES } = responses
describe('client.preparePaymentChannelClaim', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('default', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
}
const response = await this.client.preparePaymentChannelClaim(
test.address,
REQUEST_FIXTURES.normal,
localInstructions,
)
assertResultMatch(response, RESPONSE_FIXTURES.normal, 'prepare')
})
it('with renew', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
}
const response = await this.client.preparePaymentChannelClaim(
test.address,
REQUEST_FIXTURES.renew,
localInstructions,
)
assertResultMatch(response, RESPONSE_FIXTURES.renew, 'prepare')
})
it('with close', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
}
const response = await this.client.preparePaymentChannelClaim(
test.address,
REQUEST_FIXTURES.close,
localInstructions,
)
assertResultMatch(response, RESPONSE_FIXTURES.close, 'prepare')
})
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const response = await this.client.preparePaymentChannelClaim(
test.address,
REQUEST_FIXTURES.normal,
localInstructions,
)
assertResultMatch(response, RESPONSE_FIXTURES.ticket, 'prepare')
})
it('rejects Promise on preparePaymentChannelClaim with renew and close', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
try {
const prepared = await this.client.preparePaymentChannelClaim(
test.address,
REQUEST_FIXTURES.full,
)
throw new Error(
`Expected method to reject. Prepared transaction: ${JSON.stringify(
prepared,
)}`,
)
} catch (err) {
assert.strictEqual(err.name, 'ValidationError')
assert.strictEqual(
err.message,
'"renew" and "close" flags on PaymentChannelClaim are mutually exclusive',
)
}
})
it('rejects Promise on preparePaymentChannelClaim with no signature', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
try {
const prepared = await this.client.preparePaymentChannelClaim(
test.address,
REQUEST_FIXTURES.noSignature,
)
throw new Error(
`Expected method to reject. Prepared transaction: ${JSON.stringify(
prepared,
)}`,
)
} catch (err) {
assert.strictEqual(err.name, 'ValidationError')
assert.strictEqual(
err.message,
'"signature" and "publicKey" fields on PaymentChannelClaim must only be specified together.',
)
}
})
})
})
})

View File

@@ -1,72 +0,0 @@
import addresses from '../fixtures/addresses.json'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.preparePaymentChannelCreate', function () {
beforeEach(setupClient)
afterEach(teardownClient)
it('preparePaymentChannelCreate', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
}
const result = await this.client.preparePaymentChannelCreate(
addresses.ACCOUNT,
requests.preparePaymentChannelCreate.normal,
localInstructions,
)
assertResultMatch(
result,
responses.preparePaymentChannelCreate.normal,
'prepare',
)
})
it('preparePaymentChannelCreate full', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
const result = await this.client.preparePaymentChannelCreate(
addresses.ACCOUNT,
requests.preparePaymentChannelCreate.full,
)
assertResultMatch(
result,
responses.preparePaymentChannelCreate.full,
'prepare',
)
})
it('preparePaymentChannelCreate with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const result = await this.client.preparePaymentChannelCreate(
addresses.ACCOUNT,
requests.preparePaymentChannelCreate.normal,
localInstructions,
)
assertResultMatch(
result,
responses.preparePaymentChannelCreate.ticket,
'prepare',
)
})
})

View File

@@ -1,84 +0,0 @@
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.preparePaymentChannelFund', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('preparePaymentChannelFund', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
}
const result = await this.client.preparePaymentChannelFund(
test.address,
requests.preparePaymentChannelFund.normal,
localInstructions,
)
assertResultMatch(
result,
responses.preparePaymentChannelFund.normal,
'prepare',
)
})
it('preparePaymentChannelFund full', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.preparePaymentChannelFund(
test.address,
requests.preparePaymentChannelFund.full,
)
assertResultMatch(
result,
responses.preparePaymentChannelFund.full,
'prepare',
)
})
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const result = await this.client.preparePaymentChannelFund(
test.address,
requests.preparePaymentChannelFund.normal,
localInstructions,
)
assertResultMatch(
result,
responses.preparePaymentChannelFund.ticket,
'prepare',
)
})
})
})
})

View File

@@ -1,431 +0,0 @@
import { assert } from 'chai'
import { FormattedSettings } from '../../src/common/types/objects'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareSettings', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('simple test', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const response = await this.client.prepareSettings(
test.address,
requests.prepareSettings.domain,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(response, responses.prepareSettings.flags, 'prepare')
})
it('no maxLedgerVersion', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const response = await this.client.prepareSettings(
test.address,
requests.prepareSettings.domain,
{
maxLedgerVersion: null as unknown as undefined,
},
)
assertResultMatch(
response,
responses.prepareSettings.noMaxLedgerVersion,
'prepare',
)
})
it('no instructions', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const response = await this.client.prepareSettings(
test.address,
requests.prepareSettings.domain,
)
assertResultMatch(
response,
responses.prepareSettings.noInstructions,
'prepare',
)
})
it('regularKey', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const regularKey = { regularKey: 'rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD' }
const response = await this.client.prepareSettings(
test.address,
regularKey,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
response,
responses.prepareSettings.regularKey,
'prepare',
)
})
it('remove regularKey', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const regularKey = { regularKey: null }
const response = await this.client.prepareSettings(
test.address,
regularKey as unknown as FormattedSettings,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
response,
responses.prepareSettings.removeRegularKey,
'prepare',
)
})
it('flag set', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const settings = { requireDestinationTag: true }
const response = await this.client.prepareSettings(
test.address,
settings,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
response,
responses.prepareSettings.flagSet,
'prepare',
)
})
it('flag clear', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const settings = { requireDestinationTag: false }
const response = await this.client.prepareSettings(
test.address,
settings,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
response,
responses.prepareSettings.flagClear,
'prepare',
)
})
it('set depositAuth flag', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const settings = { depositAuth: true }
const response = await this.client.prepareSettings(
test.address,
settings,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
response,
responses.prepareSettings.flagSetDepositAuth,
'prepare',
)
})
it('clear depositAuth flag', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const settings = { depositAuth: false }
const response = await this.client.prepareSettings(
test.address,
settings,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
response,
responses.prepareSettings.flagClearDepositAuth,
'prepare',
)
})
it('integer field clear', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const settings = { transferRate: null }
const response = await this.client.prepareSettings(
test.address,
settings,
instructionsWithMaxLedgerVersionOffset,
)
assert(response)
assert.strictEqual(JSON.parse(response.txJSON).TransferRate, 0)
})
it('set transferRate', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const settings = { transferRate: 1 }
const response = await this.client.prepareSettings(
test.address,
settings,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
response,
responses.prepareSettings.setTransferRate,
'prepare',
)
})
it('set signers', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const settings = requests.prepareSettings.signers.normal
const response = await this.client.prepareSettings(
test.address,
settings,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
response,
responses.prepareSettings.signers,
'prepare',
)
})
// it("signers no threshold", async function () {
// this.mockRippled.addResponse("server_info", rippled.server_info.normal);
// this.mockRippled.addResponse("fee", rippled.fee);
// this.mockRippled.addResponse("ledger_current", rippled.ledger_current);
// this.mockRippled.addResponse(
// "account_info",
// rippled.account_info.normal
// );
// const settings = requests.prepareSettings.signers.noThreshold;
// try {
// const response = await this.client.prepareSettings(
// test.address,
// settings,
// instructionsWithMaxLedgerVersionOffset
// );
// throw new Error(
// `Expected method to reject. Prepared transaction: ${JSON.stringify(
// response
// )}`
// );
// } catch (err) {
// assert.strictEqual(
// err.message,
// 'instance.settings.signers requires property "threshold"'
// );
// assert.strictEqual(err.name, "ValidationError");
// }
// });
it('signers no weights', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const settings = requests.prepareSettings.signers.noWeights
const localInstructions = {
signersCount: 1,
...instructionsWithMaxLedgerVersionOffset,
}
const response = await this.client.prepareSettings(
test.address,
settings,
localInstructions,
)
assertResultMatch(
response,
responses.prepareSettings.noWeights,
'prepare',
)
})
it('fee for multisign', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
signersCount: 4,
...instructionsWithMaxLedgerVersionOffset,
}
const response = await this.client.prepareSettings(
test.address,
requests.prepareSettings.domain,
localInstructions,
)
assertResultMatch(
response,
responses.prepareSettings.flagsMultisign,
'prepare',
)
})
it('no signer list', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const settings = requests.prepareSettings.noSignerEntries
const localInstructions = {
signersCount: 1,
...instructionsWithMaxLedgerVersionOffset,
}
const response = await this.client.prepareSettings(
test.address,
settings,
localInstructions,
)
assertResultMatch(
response,
responses.prepareSettings.noSignerList,
'prepare',
)
})
// it("invalid", async function () {
// this.mockRippled.addResponse("server_info", rippled.server_info.normal);
// this.mockRippled.addResponse("fee", rippled.fee);
// this.mockRippled.addResponse("ledger_current", rippled.ledger_current);
// this.mockRippled.addResponse(
// "account_info",
// rippled.account_info.normal
// );
// // domain must be a string
// const settings = { ...requests.prepareSettings.domain, domain: 123 };
// const localInstructions = {
// signersCount: 4,
// ...instructionsWithMaxLedgerVersionOffset,
// };
// try {
// const response = await this.client.prepareSettings(
// test.address,
// settings,
// localInstructions
// );
// throw new Error(
// `Expected method to reject. Prepared transaction: ${JSON.stringify(
// response
// )}`
// );
// } catch (err) {
// assert.strictEqual(
// err.message,
// "instance.settings.domain is not of a type(s) string"
// );
// assert.strictEqual(err.name, "ValidationError");
// }
// });
it('offline', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const settings = requests.prepareSettings.domain
const instructions = {
sequence: 23,
maxLedgerVersion: 8820051,
fee: '0.000012',
}
const result = await this.client.prepareSettings(
test.address,
settings,
instructions,
)
assertResultMatch(result, responses.prepareSettings.flags, 'prepare')
assert.deepEqual(
this.client.sign(result.txJSON, secret),
responses.prepareSettings.signed,
)
})
it('prepare settings with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const instructions = {
ticketSequence: 23,
maxLedgerVersion: 8820051,
fee: '0.000012',
}
const response = await this.client.prepareSettings(
test.address,
requests.prepareSettings.domain,
instructions,
)
assertResultMatch(response, responses.prepareSettings.ticket, 'prepare')
})
})
})
})

View File

@@ -1,75 +0,0 @@
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
// import responses from '../fixtures/responses'
// import requests from '../fixtures/requests'
// import {ValidationError} from 'xrpl-local/common/errors'
// import binary from 'ripple-binary-codec'
// import {assert} from 'chai'
// import {Client} from 'xrpl-local'
// import * as schemaValidator from 'xrpl-local/common/schema-validator'
// const instructionsWithMaxLedgerVersionOffset = {maxLedgerVersionOffset: 100}
// const {preparePayment: REQUEST_FIXTURES} = requests
// const {preparePayment: RESPONSE_FIXTURES} = responses
// const ADDRESS = 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
describe('client.prepareTicket', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('creates a ticket successfully with a sequence number', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const expected = {
txJSON:
'{"TransactionType":"TicketCreate", "TicketCount": 2, "Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Flags":2147483648,"LastLedgerSequence":8819954,"Sequence":23,"Fee":"12"}',
instructions: {
maxLedgerVersion: 8819954,
sequence: 23,
fee: '0.000012',
},
}
const response = await this.client.prepareTicketCreate(test.address, 2)
assertResultMatch(response, expected, 'prepare')
})
it('creates a ticket successfully with another ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const expected = {
txJSON:
'{"TransactionType":"TicketCreate", "TicketCount": 1, "Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Flags":2147483648,"LastLedgerSequence":8819954,"Sequence": 0,"TicketSequence":23,"Fee":"12"}',
instructions: {
maxLedgerVersion: 8819954,
ticketSequence: 23,
fee: '0.000012',
},
}
const instructions = {
maxLedgerVersion: 8819954,
ticketSequence: 23,
fee: '0.000012',
}
const response = await this.client.prepareTicketCreate(
test.address,
1,
instructions,
)
assertResultMatch(response, expected, 'prepare')
})
})
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -1,126 +0,0 @@
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { assertResultMatch, addressTests } from '../testUtils'
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 }
describe('client.prepareTrustline', function () {
beforeEach(setupClient)
afterEach(teardownClient)
addressTests.forEach(function (test) {
describe(test.type, function () {
it('simple', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareTrustline(
test.address,
requests.prepareTrustline.simple,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(result, responses.prepareTrustline.simple, 'prepare')
})
it('frozen', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareTrustline(
test.address,
requests.prepareTrustline.frozen,
)
assertResultMatch(result, responses.prepareTrustline.frozen, 'prepare')
})
it('complex', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareTrustline(
test.address,
requests.prepareTrustline.complex,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(result, responses.prepareTrustline.complex, 'prepare')
})
// it("invalid", async function () {
// this.mockRippled.addResponse("server_info", rippled.server_info.normal);
// this.mockRippled.addResponse("fee", rippled.fee);
// this.mockRippled.addResponse("ledger_current", rippled.ledger_current);
// this.mockRippled.addResponse(
// "account_info",
// rippled.account_info.normal
// );
// const trustline = { ...requests.prepareTrustline.complex };
// delete trustline.limit; // Make invalid
// await assertRejects(
// this.client.prepareTrustline(
// test.address,
// trustline,
// instructionsWithMaxLedgerVersionOffset
// ),
// this.client.errors.ValidationError,
// 'instance.trustline requires property "limit"'
// );
// });
it('xtest.address-issuer', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const result = await this.client.prepareTrustline(
test.address,
requests.prepareTrustline.issuedXAddress,
instructionsWithMaxLedgerVersionOffset,
)
assertResultMatch(
result,
responses.prepareTrustline.issuedXAddress,
'prepare',
)
})
it('with ticket', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '0.000012',
ticketSequence: 23,
}
const result = await this.client.prepareTrustline(
test.address,
requests.prepareTrustline.simple,
localInstructions,
)
assertResultMatch(result, responses.prepareTrustline.ticket, 'prepare')
})
})
})
})

View File

@@ -3,9 +3,7 @@ import binary from 'ripple-binary-codec'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import rippled from '../fixtures/rippled'
import { setupClient, teardownClient } from '../setupClient'
import { addressTests } from '../testUtils'
const { sign: REQUEST_FIXTURES } = requests
const { sign: RESPONSE_FIXTURES } = responses
@@ -228,188 +226,143 @@ describe('client.sign', function () {
assert.deepEqual(result, RESPONSE_FIXTURES.ticket)
})
addressTests.forEach(function (test) {
describe(test.type, function () {
it('throws when Fee exceeds maxFeeXRP (in drops)', async function () {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const request = {
txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"${test.address}","Domain":"6578616D706C652E636F6D","LastLedgerSequence":8820051,"Fee":"2010000","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`,
instructions: {
fee: '2.01',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
it('throws when Fee exceeds maxFeeXRP (in drops)', async function () {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const request = {
txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"6578616D706C652E636F6D","LastLedgerSequence":8820051,"Fee":"2010000","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`,
instructions: {
fee: '2.01',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
assert.throws(() => {
this.client.sign(request.txJSON, secret)
}, /Fee" should not exceed "2000000"\. To use a higher fee, set `maxFeeXRP` in the Client constructor\./)
})
assert.throws(() => {
this.client.sign(request.txJSON, secret)
}, /Fee" should not exceed "2000000"\. To use a higher fee, set `maxFeeXRP` in the Client constructor\./)
})
it('throws when Fee exceeds maxFeeXRP (in drops) - custom maxFeeXRP', async function () {
this.client.maxFeeXRP = '1.9'
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const request = {
txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"${test.address}","Domain":"6578616D706C652E636F6D","LastLedgerSequence":8820051,"Fee":"2010000","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`,
instructions: {
fee: '2.01',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
it('throws when Fee exceeds maxFeeXRP (in drops) - custom maxFeeXRP', async function () {
this.client.maxFeeXRP = '1.9'
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const request = {
txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"6578616D706C652E636F6D","LastLedgerSequence":8820051,"Fee":"2010000","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`,
instructions: {
fee: '2.01',
sequence: 23,
maxLedgerVersion: 8820051,
},
}
assert.throws(() => {
this.client.sign(request.txJSON, secret)
}, /Fee" should not exceed "1900000"\. To use a higher fee, set `maxFeeXRP` in the Client constructor\./)
})
assert.throws(() => {
this.client.sign(request.txJSON, secret)
}, /Fee" should not exceed "1900000"\. To use a higher fee, set `maxFeeXRP` in the Client constructor\./)
})
it('sign with paths', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const payment = {
source: {
address: test.address,
amount: {
currency: 'drops',
value: '100',
},
},
destination: {
address: 'rKT4JX4cCof6LcDYRz8o3rGRu7qxzZ2Zwj',
minAmount: {
currency: 'USD',
value: '0.00004579644712312366',
counterparty: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc',
},
},
// eslint-disable-next-line no-useless-escape
paths:
'[[{"currency":"USD","issuer":"rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc"}]]',
}
const ret = await this.client.preparePayment(test.address, payment, {
sequence: 1,
maxLedgerVersion: 15696358,
})
const result = this.client.sign(ret.txJSON, secret)
assert.deepEqual(result, {
signedTransaction:
'12000022800200002400000001201B00EF81E661EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402200A693FB5CA6B21250EBDFD8CFF526EE0DF7C9E4E31EB0660692E75E6A93BF5F802203CC39463DDA21386898CA31E18AD1A6828647D65741DD637BAD71BC83E29DB9481145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900',
id: '78874FE5F5299FEE3EA85D3CF6C1FB1F1D46BB08F716662A3E3D1F0ADE4EF796',
})
})
it('succeeds - prepared payment', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const payment = await this.client.preparePayment(test.address, {
source: {
address: test.address,
maxAmount: {
value: '1',
currency: 'drops',
},
},
destination: {
address: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
amount: {
value: '1',
currency: 'drops',
},
},
})
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const result = this.client.sign(payment.txJSON, secret)
const expectedResult = {
signedTransaction:
'12000022800000002400000017201B008694F261400000000000000168400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100A9C91D4CFAE45686146EE0B56D4C53A2E7C2D672FB834D43E0BE2D2E9106519A022075DDA2F92DE552B0C45D83D4E6D35889B3FBF51BFBBD9B25EBF70DE3C96D0D6681145E7B112523F68D2F5E879DB4EAC51C6698A693048314FDB08D07AAA0EB711793A3027304D688E10C3648',
id: '88D6B913C66279EA31ADC25C5806C48B2D4E5680261666790A736E1961217700',
}
assert.deepEqual(result, expectedResult)
})
it('throws when encoded tx does not match decoded tx - prepared payment', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const payment = await this.client.preparePayment(test.address, {
source: {
address: test.address,
maxAmount: {
value: '1.1234567',
currency: 'drops',
},
},
destination: {
address: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
amount: {
value: '1.1234567',
currency: 'drops',
},
},
})
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
assert.throws(() => {
this.client.sign(payment.txJSON, secret)
}, /^1.1234567 is an illegal amount/)
})
it('throws when encoded tx does not match decoded tx - prepared order', async function () {
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
this.mockRippled.addResponse('fee', rippled.fee)
this.mockRippled.addResponse('ledger_current', rippled.ledger_current)
this.mockRippled.addResponse(
'account_info',
rippled.account_info.normal,
)
const order = {
direction: 'sell',
quantity: {
currency: 'USD',
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '3.140000',
},
totalPrice: {
currency: 'XRP',
value: '31415',
},
}
const prepared = await this.client.prepareOrder(test.address, order, {
sequence: 123,
})
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
try {
this.client.sign(prepared.txJSON, secret)
return await Promise.reject(
new Error('this.client.sign should have thrown'),
)
} catch (error) {
assert.equal(error.name, 'ValidationError')
assert.equal(
error.message,
'Serialized transaction does not match original txJSON. See `error.data`',
)
assert.deepEqual(error.data.diff, {
TakerGets: {
value: '3.14',
},
})
}
})
it('sign with paths', async function () {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const payment = {
TransactionType: 'Payment',
Account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
Destination: 'rKT4JX4cCof6LcDYRz8o3rGRu7qxzZ2Zwj',
Amount: {
currency: 'USD',
issuer: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc',
value:
'999999999999999900000000000000000000000000000000000000000000000000000000000000000000000000000000',
},
Flags: 2147614720,
SendMax: '100',
DeliverMin: {
currency: 'USD',
issuer: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc',
value: '0.00004579644712312366',
},
Paths: [
[{ currency: 'USD', issuer: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc' }],
],
LastLedgerSequence: 15696358,
Sequence: 1,
Fee: '12',
}
const result = this.client.sign(JSON.stringify(payment), secret)
assert.deepEqual(result, {
signedTransaction:
'12000022800200002400000001201B00EF81E661EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402200A693FB5CA6B21250EBDFD8CFF526EE0DF7C9E4E31EB0660692E75E6A93BF5F802203CC39463DDA21386898CA31E18AD1A6828647D65741DD637BAD71BC83E29DB9481145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900',
id: '78874FE5F5299FEE3EA85D3CF6C1FB1F1D46BB08F716662A3E3D1F0ADE4EF796',
})
})
it('succeeds - prepared payment', async function () {
const payment = {
TransactionType: 'Payment',
Account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
Destination: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
Amount: '1',
Flags: 2147483648,
Sequence: 23,
LastLedgerSequence: 8819954,
Fee: '12',
}
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const result = this.client.sign(JSON.stringify(payment), secret)
const expectedResult = {
signedTransaction:
'12000022800000002400000017201B008694F261400000000000000168400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100A9C91D4CFAE45686146EE0B56D4C53A2E7C2D672FB834D43E0BE2D2E9106519A022075DDA2F92DE552B0C45D83D4E6D35889B3FBF51BFBBD9B25EBF70DE3C96D0D6681145E7B112523F68D2F5E879DB4EAC51C6698A693048314FDB08D07AAA0EB711793A3027304D688E10C3648',
id: '88D6B913C66279EA31ADC25C5806C48B2D4E5680261666790A736E1961217700',
}
assert.deepEqual(result, expectedResult)
})
it('throws when encoded tx does not match decoded tx - prepared payment', async function () {
const payment = {
TransactionType: 'Payment',
Account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
Destination: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
Amount: '1.1234567',
Flags: 2147483648,
Sequence: 23,
LastLedgerSequence: 8819954,
Fee: '12',
}
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
assert.throws(() => {
this.client.sign(JSON.stringify(payment), secret)
}, /^1.1234567 is an illegal amount/)
})
it('throws when encoded tx does not match decoded tx - prepared order', async function () {
const offerCreate = {
TransactionType: 'OfferCreate',
Account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
TakerGets: {
currency: 'USD',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '3.140000',
},
TakerPays: '31415000000',
Flags: 2148007936,
Sequence: 123,
LastLedgerSequence: 8819954,
Fee: '12',
}
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
try {
this.client.sign(JSON.stringify(offerCreate), secret)
return await Promise.reject(
new Error('this.client.sign should have thrown'),
)
} catch (error) {
assert.equal(error.name, 'ValidationError')
assert.equal(
error.message,
'Serialized transaction does not match original txJSON. See `error.data`',
)
assert.deepEqual(error.data.diff, {
TakerGets: {
value: '3.14',
},
})
}
})
})

View File

@@ -4,10 +4,20 @@ import _ from 'lodash'
import { isValidXAddress } from 'ripple-address-codec'
import { Client } from 'xrpl-local'
import { isValidSecret } from 'xrpl-local/utils'
import {
AccountSet,
OfferCreate,
SignerListSet,
TrustSet,
} from 'xrpl-local/models/transactions'
import {
isValidSecret,
generateXAddress,
xrpToDrops,
convertStringToHex,
} from 'xrpl-local/utils'
import { generateXAddress } from '../../src/utils/generateAddress'
import requests from '../fixtures/requests'
// import requests from '../fixtures/requests'
import { payTo, ledgerAccept } from './utils'
import wallet from './wallet'
@@ -116,16 +126,20 @@ const masterSecret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb'
function makeTrustLine(testcase, address, secret) {
const client = testcase.client
const specification = {
currency: 'USD',
counterparty: masterAccount,
limit: '1341.1',
ripplingDisabled: true,
const trustSet: TrustSet = {
TransactionType: 'TrustSet',
Account: address,
LimitAmount: {
value: '1341.1',
issuer: masterAccount,
currency: 'USD',
},
Flags: 0x00020000,
}
const trust = client
.prepareTrustline(address, specification, {})
.then((data) => {
const signed = client.sign(data.txJSON, secret)
.autofill(trustSet)
.then(async (tx) => {
const signed = client.sign(JSON.stringify(tx), secret)
if (address === wallet.getAddress()) {
testcase.transactions.push(signed.id)
}
@@ -134,32 +148,65 @@ function makeTrustLine(testcase, address, secret) {
tx_blob: signed.signedTransaction,
})
})
.then(() => ledgerAccept(client))
.then((response) => {
if (
response.result.engine_result !== 'tesSUCCESS' &&
response.result.engine_result !== 'tecPATH_PARTIAL'
) {
console.log(response)
assert.fail(`Response not successful, ${response.result.engine_result}`)
}
ledgerAccept(client)
})
return trust
}
function makeOrder(client, address, specification, secret) {
function makeOrder(client, offerCreate, secret) {
return client
.prepareOrder(address, specification)
.then((data) => client.sign(data.txJSON, secret))
.autofill(offerCreate)
.then((tx) => client.sign(JSON.stringify(tx), secret))
.then((signed) =>
client.request({ command: 'submit', tx_blob: signed.signedTransaction }),
)
.then(() => ledgerAccept(client))
.then((response) => {
if (
response.result.engine_result !== 'tesSUCCESS' &&
response.result.engine_result !== 'tecPATH_PARTIAL'
) {
console.log(response)
assert.fail(`Response not successful, ${response.result.engine_result}`)
}
ledgerAccept(client)
})
}
function setupAccounts(testcase) {
const client = testcase.client
let fundAmount = '20'
const promise = payTo(client, 'rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM')
.then(() => payTo(client, wallet.getAddress()))
.then(() => payTo(client, testcase.newWallet.xAddress))
.then(() => payTo(client, 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc'))
.then(() => payTo(client, 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q'))
const promise = client
.request({ command: 'server_info' })
.then(
(response) =>
(fundAmount = xrpToDrops(
Number(response.result.info.validated_ledger.reserve_base_xrp) * 2,
)),
)
.then(() => payTo(client, 'rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM', fundAmount))
.then(() => payTo(client, wallet.getAddress(), fundAmount))
.then(() => payTo(client, testcase.newWallet.classicAddress, fundAmount))
.then(() => payTo(client, 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc', fundAmount))
.then(() => payTo(client, 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', fundAmount))
.then(() => {
const accountSet: AccountSet = {
TransactionType: 'AccountSet',
Account: masterAccount,
// default ripple
SetFlag: 8,
}
return client
.prepareSettings(masterAccount, { defaultRipple: true })
.then((data) => client.sign(data.txJSON, masterSecret))
.autofill(accountSet)
.then((tx) => client.sign(JSON.stringify(tx), masterSecret))
.then((signed) =>
client.request({
command: 'submit',
@@ -181,44 +228,30 @@ function setupAccounts(testcase) {
.then(() => payTo(client, wallet.getAddress(), '123', 'USD', masterAccount))
.then(() => payTo(client, 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q'))
.then(() => {
const orderSpecification = {
direction: 'buy',
quantity: {
const offerCreate: OfferCreate = {
TransactionType: 'OfferCreate',
Account: testcase.newWallet.xAddress,
TakerPays: {
currency: 'USD',
value: '432',
counterparty: masterAccount,
},
totalPrice: {
currency: 'XRP',
value: '432',
issuer: masterAccount,
},
TakerGets: xrpToDrops('432'),
}
return makeOrder(
testcase.client,
testcase.newWallet.xAddress,
orderSpecification,
testcase.newWallet.secret,
)
return makeOrder(testcase.client, offerCreate, testcase.newWallet.secret)
})
.then(() => {
const orderSpecification = {
direction: 'buy',
quantity: {
currency: 'XRP',
value: '1741',
},
totalPrice: {
const offerCreate: OfferCreate = {
TransactionType: 'OfferCreate',
Account: masterAccount,
TakerPays: xrpToDrops('1741'),
TakerGets: {
currency: 'USD',
value: '171',
counterparty: masterAccount,
issuer: masterAccount,
},
}
return makeOrder(
testcase.client,
masterAccount,
orderSpecification,
masterSecret,
)
return makeOrder(testcase.client, offerCreate, masterSecret)
})
return promise
}
@@ -234,7 +267,10 @@ function suiteSetup(this: any) {
setup
.bind(this)(serverUrl)
.then(() => ledgerAccept(this.client))
.then(() => (this.newWallet = generateXAddress()))
.then(
() =>
(this.newWallet = generateXAddress({ includeClassicAddress: true })),
)
// two times to give time to server to send `ledgerClosed` event
// so getLedgerVersion will return right value
.then(() => ledgerAccept(this.client))
@@ -256,128 +292,12 @@ function suiteSetup(this: any) {
describe('integration tests', function () {
const address = wallet.getAddress()
const instructions = { maxLedgerVersionOffset: 10 }
this.timeout(TIMEOUT)
before(suiteSetup)
beforeEach(_.partial(setup, serverUrl))
afterEach(teardown)
it('trustline', function () {
return this.client
.request({
command: 'ledger',
ledger_index: 'validated',
})
.then((response) => response.result.ledger_index)
.then((ledgerVersion) => {
return this.client
.prepareTrustline(
address,
requests.prepareTrustline.simple,
instructions,
)
.then((prepared) =>
testTransaction(this, 'TrustSet', ledgerVersion, prepared),
)
})
})
it('payment', function () {
const amount = { currency: 'XRP', value: '0.000001' }
const paymentSpecification = {
source: {
address,
maxAmount: amount,
},
destination: {
address: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
amount,
},
}
return this.client
.request({
command: 'ledger',
ledger_index: 'validated',
})
.then((response) => response.result.ledger_index)
.then((ledgerVersion) => {
return this.client
.preparePayment(address, paymentSpecification, instructions)
.then((prepared) =>
testTransaction(this, 'Payment', ledgerVersion, prepared),
)
})
})
it('order', function () {
const orderSpecification = {
direction: 'buy',
quantity: {
currency: 'USD',
value: '237',
counterparty: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
},
totalPrice: {
currency: 'XRP',
value: '0.0002',
},
}
const expectedOrder = {
flags: 0,
quality: '1.185',
taker_gets: '200',
taker_pays: {
currency: 'USD',
value: '237',
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
},
}
return this.client
.request({
command: 'ledger',
ledger_index: 'validated',
})
.then((response) => response.result.ledger_index)
.then((ledgerVersion) => {
return this.client
.prepareOrder(address, orderSpecification, instructions)
.then((prepared) =>
testTransaction(this, 'OfferCreate', ledgerVersion, prepared),
)
.then((result) => {
const txData = JSON.parse(result.txJSON)
return this.client
.request({
command: 'account_offers',
account: address,
})
.then((response) => response.result.offers)
.then((orders) => {
assert(orders && orders.length > 0)
const createdOrder = orders.filter((order) => {
return order.seq === txData.Sequence
})[0]
assert(createdOrder)
delete createdOrder.seq
assert.deepEqual(createdOrder, expectedOrder)
return txData
})
})
.then((txData) =>
this.client
.prepareOrderCancellation(
address,
{ orderSequence: txData.Sequence },
instructions,
)
.then((prepared) =>
testTransaction(this, 'OfferCancel', ledgerVersion, prepared),
),
)
})
})
it('isConnected', function () {
assert(this.client.isConnected())
})
@@ -390,29 +310,29 @@ describe('integration tests', function () {
})
})
it('getTrustlines', function () {
const fixture = requests.prepareTrustline.simple
const { currency, counterparty } = fixture
const options = { currency, counterparty }
return this.client.getTrustlines(address, options).then((data) => {
assert(data && data.length > 0 && data[0] && data[0].specification)
const specification = data[0].specification
assert.strictEqual(Number(specification.limit), Number(fixture.limit))
assert.strictEqual(specification.currency, fixture.currency)
assert.strictEqual(specification.counterparty, fixture.counterparty)
})
})
// it('getTrustlines', function () {
// const fixture = requests.prepareTrustline.simple
// const { currency, counterparty } = fixture
// const options = { currency, counterparty }
// return this.client.getTrustlines(address, options).then((data) => {
// assert(data && data.length > 0 && data[0] && data[0].specification)
// const specification = data[0].specification
// assert.strictEqual(Number(specification.limit), Number(fixture.limit))
// assert.strictEqual(specification.currency, fixture.currency)
// assert.strictEqual(specification.counterparty, fixture.counterparty)
// })
// })
it('getBalances', function () {
const fixture = requests.prepareTrustline.simple
const { currency, counterparty } = fixture
const options = { currency, counterparty }
return this.client.getBalances(address, options).then((data) => {
assert(data && data.length > 0 && data[0])
assert.strictEqual(data[0].currency, fixture.currency)
assert.strictEqual(data[0].counterparty, fixture.counterparty)
})
})
// it('getBalances', function () {
// const fixture = requests.prepareTrustline.simple
// const { currency, counterparty } = fixture
// const options = { currency, counterparty }
// return this.client.getBalances(address, options).then((data) => {
// assert(data && data.length > 0 && data[0])
// assert.strictEqual(data[0].currency, fixture.currency)
// assert.strictEqual(data[0].counterparty, fixture.counterparty)
// })
// })
it('getOrderbook', function () {
const orderbook = {
@@ -505,98 +425,122 @@ describe('integration tests', function () {
assert(isValidXAddress(newWallet.xAddress))
assert(isValidSecret(newWallet.secret))
})
})
describe('integration tests - standalone rippled', function () {
const instructions = { maxLedgerVersionOffset: 10 }
this.timeout(TIMEOUT)
beforeEach(_.partial(setup, serverUrl))
afterEach(teardown)
const address = 'r5nx8ZkwEbFztnc8Qyi22DE9JYjRzNmvs'
const secret = 'ss6F8381Br6wwpy9p582H8sBt19J3'
const multisignAccount = 'r5nx8ZkwEbFztnc8Qyi22DE9JYjRzNmvs'
const multisignSecret = 'ss6F8381Br6wwpy9p582H8sBt19J3'
const signer1address = 'rQDhz2ZNXmhxzCYwxU6qAbdxsHA4HV45Y2'
const signer1secret = 'shK6YXzwYfnFVn3YZSaMh5zuAddKx'
const signer2address = 'r3RtUvGw9nMoJ5FuHxuoVJvcENhKtuF9ud'
const signer2secret = 'shUHQnL4EH27V4EiBrj6EfhWvZngF'
it('submit multisigned transaction', function () {
const signers = {
threshold: 2,
weights: [
{ address: signer1address, weight: 1 },
{ address: signer2address, weight: 1 },
const signerListSet: SignerListSet = {
TransactionType: 'SignerListSet',
Account: multisignAccount,
SignerEntries: [
{
SignerEntry: {
Account: signer1address,
SignerWeight: 1,
},
},
{
SignerEntry: {
Account: signer2address,
SignerWeight: 1,
},
},
],
SignerQuorum: 2,
}
let minLedgerVersion = null
return payTo(this.client, address)
.then(() => {
return this.client
.request({
command: 'ledger',
ledger_index: 'validated',
})
.then((response) => response.result.ledger_index)
.then((ledgerVersion) => {
minLedgerVersion = ledgerVersion
let fundAmount = '20'
return this.client
.request({ command: 'server_info' })
.then(
(response) =>
(fundAmount = xrpToDrops(
Number(response.result.info.validated_ledger.reserve_base_xrp) * 2,
)),
)
.then(() =>
payTo(this.client, multisignAccount, fundAmount)
.then(() => {
return this.client
.prepareSettings(address, { signers }, instructions)
.then((prepared) => {
.request({
command: 'ledger',
ledger_index: 'validated',
})
.then((response) => response.result.ledger_index)
.then((ledgerVersion) => {
minLedgerVersion = ledgerVersion
})
.then(() => this.client.autofill(signerListSet, 2))
.then((tx) => {
return testTransaction(
this,
'SignerListSet',
ledgerVersion,
prepared,
address,
secret,
minLedgerVersion,
{ txJSON: JSON.stringify(tx) },
multisignAccount,
multisignSecret,
)
})
})
})
.then(() => {
const multisignInstructions = { ...instructions, signersCount: 2 }
return this.client
.prepareSettings(
address,
{ domain: 'example.com' },
multisignInstructions,
)
.then((prepared) => {
const signed1 = this.client.sign(prepared.txJSON, signer1secret, {
signAs: signer1address,
})
const signed2 = this.client.sign(prepared.txJSON, signer2secret, {
signAs: signer2address,
})
const combined = this.client.combine([
signed1.signedTransaction,
signed2.signedTransaction,
])
return this.client
.request({
command: 'submit',
tx_blob: combined.signedTransaction,
})
.then((response) =>
acceptLedger(this.client).then(() => response),
.then(() => {
const accountSet: AccountSet = {
TransactionType: 'AccountSet',
Account: multisignAccount,
Domain: convertStringToHex('example.com'),
}
return this.client.autofill(accountSet, 2).then((tx) => {
const signed1 = this.client.sign(
JSON.stringify(tx),
signer1secret,
{
signAs: signer1address,
},
)
.then((response) => {
assert.strictEqual(response.result.engine_result, 'tesSUCCESS')
const options = { minLedgerVersion }
return verifyTransaction(
this,
combined.id,
'AccountSet',
options,
{},
address,
const signed2 = this.client.sign(
JSON.stringify(tx),
signer2secret,
{
signAs: signer2address,
},
)
const combined = this.client.combine([
signed1.signedTransaction,
signed2.signedTransaction,
])
return this.client
.request({
command: 'submit',
tx_blob: combined.signedTransaction,
})
.then((response) =>
acceptLedger(this.client).then(() => response),
)
})
.catch((error) => {
console.log(error.message)
throw error
})
})
})
.then((response) => {
assert.strictEqual(
response.result.engine_result,
'tesSUCCESS',
)
const options = { minLedgerVersion }
return verifyTransaction(
this,
combined.id,
'AccountSet',
options,
{},
multisignAccount,
)
})
.catch((error) => {
console.log(error.message)
throw error
})
})
}),
)
})
})

View File

@@ -1,57 +1,80 @@
'use strict';
const assert = require('chai').assert
const masterAccount = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
const masterSecret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb';
const models = require('xrpl-local/models/transactions')
const utils = require('xrpl-local/utils')
const masterAccount = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
const masterSecret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb'
function ledgerAccept(client) {
const request = {command: 'ledger_accept'};
return client.connection.request(request);
const request = { command: 'ledger_accept' }
return client.connection.request(request)
}
function pay(client, from, to, amount, secret, currency = 'XRP', counterparty) {
const paymentSpecification = {
source: {
address: from,
maxAmount: {
value: amount,
currency: currency
}
},
destination: {
address: to,
amount: {
value: amount,
currency: currency
}
}
};
function pay(client, from, to, amount, secret, currency = 'XRP', issuer) {
const paymentAmount =
currency === 'XRP' ? amount : { value: amount, currency, issuer }
if (counterparty != null) {
paymentSpecification.source.maxAmount.counterparty = counterparty;
paymentSpecification.destination.amount.counterparty = counterparty;
const payment = {
TransactionType: 'Payment',
Account: from,
Destination: to,
Amount: paymentAmount,
}
let id = null;
return client.preparePayment(from, paymentSpecification, {})
.then(data => client.sign(data.txJSON, secret))
.then(signed => {
id = signed.id;
return client.request({command: 'submit', tx_blob: signed.signedTransaction});
})
// TODO: add better error handling here
.then(() => ledgerAccept(client))
.then(() => id);
let id = null
return (
client
.autofill(payment, 1)
.then((tx) => {
models.verifyPayment(payment)
return client.sign(JSON.stringify(tx), secret)
})
.then((signed) => {
id = signed.id
return client.request({
command: 'submit',
tx_blob: signed.signedTransaction,
})
})
// TODO: add better error handling here
// TODO: fix path issues
.then((response) => {
if (
response.result.engine_result !== 'tesSUCCESS' &&
response.result.engine_result !== 'tecPATH_PARTIAL'
) {
console.log(response)
assert.fail(
`Response not successful, ${response.result.engine_result}`,
)
}
ledgerAccept(client)
})
.then(() => id)
)
}
function payTo(client, to, amount = '4003218', currency = 'XRP', counterparty) {
return pay(client, masterAccount, to, amount, masterSecret, currency,
counterparty);
function payTo(
client,
to,
amount = '40000000',
currency = 'XRP',
counterparty,
) {
return pay(
client,
masterAccount,
to,
amount,
masterSecret,
currency,
counterparty,
)
}
module.exports = {
pay,
payTo,
ledgerAccept
};
ledgerAccept,
}