mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-03 18:45:48 +00:00
implement Autofill Transaction (#1574)
Implements autofill() and setTransactionFlagsToNumber() to allow a Client to autofill fields in a Transaction.
This commit is contained in:
committed by
Mayukha Vadari
parent
949cc031ee
commit
04dd65af38
@@ -23,6 +23,7 @@ import {
|
|||||||
import { constants, errors, txFlags, ensureClassicAddress } from '../common'
|
import { constants, errors, txFlags, ensureClassicAddress } from '../common'
|
||||||
import { ValidationError, XrplError } from '../common/errors'
|
import { ValidationError, XrplError } from '../common/errors'
|
||||||
import getFee from '../common/fee'
|
import getFee from '../common/fee'
|
||||||
|
import autofill from '../ledger/autofill'
|
||||||
import getBalances from '../ledger/balances'
|
import getBalances from '../ledger/balances'
|
||||||
import { getOrderbook, formatBidsAndAsks } from '../ledger/orderbook'
|
import { getOrderbook, formatBidsAndAsks } from '../ledger/orderbook'
|
||||||
import getPaths from '../ledger/pathfind'
|
import getPaths from '../ledger/pathfind'
|
||||||
@@ -169,6 +170,20 @@ function getCollectKeyFromCommand(command: string): string | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It returns a function that prepends params to the given func.
|
||||||
|
* A sugar function for JavaScript .bind() without the "this" (keyword) binding.
|
||||||
|
*
|
||||||
|
* @param func - A function to prepend params.
|
||||||
|
* @param params - Parameters to prepend to a function.
|
||||||
|
* @returns A function bound with params.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types -- expected param types
|
||||||
|
function prepend(func: Function, ...params: unknown[]): Function {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return -- safe to return
|
||||||
|
return func.bind(null, ...params)
|
||||||
|
}
|
||||||
|
|
||||||
interface MarkerRequest extends BaseRequest {
|
interface MarkerRequest extends BaseRequest {
|
||||||
limit?: number
|
limit?: number
|
||||||
marker?: unknown
|
marker?: unknown
|
||||||
@@ -198,6 +213,9 @@ class Client extends EventEmitter {
|
|||||||
// number. Defaults to '2'.
|
// number. Defaults to '2'.
|
||||||
public readonly maxFeeXRP: string
|
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.
|
* Creates a new Client with a websocket connection to a rippled server.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const BASE_10 = 10
|
|||||||
*/
|
*/
|
||||||
export default async function getFee(
|
export default async function getFee(
|
||||||
this: Client,
|
this: Client,
|
||||||
cushion: number | null,
|
cushion?: number,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const feeCushion = cushion ?? this.feeCushion
|
const feeCushion = cushion ?? this.feeCushion
|
||||||
|
|
||||||
|
|||||||
194
src/ledger/autofill.ts
Normal file
194
src/ledger/autofill.ts
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
import { xAddressToClassicAddress, isValidXAddress } from 'ripple-address-codec'
|
||||||
|
|
||||||
|
import type { Client } from '..'
|
||||||
|
import { ValidationError } from '../common/errors'
|
||||||
|
import { AccountInfoRequest, LedgerRequest } from '../models/methods'
|
||||||
|
import { Transaction } from '../models/transactions'
|
||||||
|
import { setTransactionFlagsToNumber } from '../models/utils'
|
||||||
|
import { xrpToDrops } from '../utils'
|
||||||
|
|
||||||
|
// 20 drops
|
||||||
|
const LEDGER_OFFSET = 20
|
||||||
|
// 5 XRP
|
||||||
|
const ACCOUNT_DELETE_FEE = 5000000
|
||||||
|
interface ClassicAccountAndTag {
|
||||||
|
classicAccount: string
|
||||||
|
tag: number | false | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autofills fields in a transaction.
|
||||||
|
*
|
||||||
|
* @param client - A client.
|
||||||
|
* @param tx - A transaction to autofill fields.
|
||||||
|
* @param signersCount - The expected number of signers for this transaction. Used for multisign.
|
||||||
|
* @returns An autofilled transaction.
|
||||||
|
*/
|
||||||
|
async function autofill(
|
||||||
|
client: Client,
|
||||||
|
tx: Transaction,
|
||||||
|
signersCount?: number,
|
||||||
|
): Promise<Transaction> {
|
||||||
|
setValidAddresses(tx)
|
||||||
|
|
||||||
|
setTransactionFlagsToNumber(tx)
|
||||||
|
|
||||||
|
const promises: Array<Promise<void>> = []
|
||||||
|
if (tx.Sequence == null) {
|
||||||
|
promises.push(setNextValidSequenceNumber(client, tx))
|
||||||
|
}
|
||||||
|
if (tx.Fee == null) {
|
||||||
|
promises.push(calculateFeePerTransactionType(client, tx, signersCount))
|
||||||
|
}
|
||||||
|
if (tx.LastLedgerSequence == null) {
|
||||||
|
promises.push(setLatestValidatedLedgerSequence(client, tx))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValidAddresses(tx: Transaction): void {
|
||||||
|
validateAccountAddress(tx, 'Account', 'SourceTag')
|
||||||
|
// eslint-disable-next-line @typescript-eslint/dot-notation -- Destination can exist on Transaction
|
||||||
|
if (tx['Destination'] != null) {
|
||||||
|
validateAccountAddress(tx, 'Destination', 'DestinationTag')
|
||||||
|
}
|
||||||
|
|
||||||
|
// DepositPreauth:
|
||||||
|
convertToClassicAddress(tx, 'Authorize')
|
||||||
|
convertToClassicAddress(tx, 'Unauthorize')
|
||||||
|
// EscrowCancel, EscrowFinish:
|
||||||
|
convertToClassicAddress(tx, 'Owner')
|
||||||
|
// SetRegularKey:
|
||||||
|
convertToClassicAddress(tx, 'RegularKey')
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateAccountAddress(
|
||||||
|
tx: Transaction,
|
||||||
|
accountField: string,
|
||||||
|
tagField: string,
|
||||||
|
): void {
|
||||||
|
// if X-address is given, convert it to classic address
|
||||||
|
const { classicAccount, tag } = getClassicAccountAndTag(tx[accountField])
|
||||||
|
// eslint-disable-next-line no-param-reassign -- param reassign is safe
|
||||||
|
tx[accountField] = classicAccount
|
||||||
|
|
||||||
|
if (tag != null) {
|
||||||
|
if (tx[tagField] && tx[tagField] !== tag) {
|
||||||
|
throw new ValidationError(
|
||||||
|
`The ${tagField}, if present, must match the tag of the ${accountField} X-address`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-param-reassign -- param reassign is safe
|
||||||
|
tx[tagField] = 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToClassicAddress(tx: Transaction, fieldName: string): void {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- assignment is safe
|
||||||
|
const account = tx[fieldName]
|
||||||
|
if (typeof account === 'string') {
|
||||||
|
const { classicAccount } = getClassicAccountAndTag(account)
|
||||||
|
// eslint-disable-next-line no-param-reassign -- param reassign is safe
|
||||||
|
tx[fieldName] = classicAccount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setNextValidSequenceNumber(
|
||||||
|
client: Client,
|
||||||
|
tx: Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const request: AccountInfoRequest = {
|
||||||
|
command: 'account_info',
|
||||||
|
account: tx.Account,
|
||||||
|
}
|
||||||
|
const data = await client.request(request)
|
||||||
|
// eslint-disable-next-line no-param-reassign, require-atomic-updates -- param reassign is safe with no race condition
|
||||||
|
tx.Sequence = data.result.account_data.Sequence
|
||||||
|
}
|
||||||
|
|
||||||
|
async function calculateFeePerTransactionType(
|
||||||
|
client: Client,
|
||||||
|
tx: Transaction,
|
||||||
|
signersCount = 0,
|
||||||
|
): Promise<void> {
|
||||||
|
// netFee is usually 0.00001 XRP (10 drops)
|
||||||
|
const netFeeXRP: string = await client.getFee()
|
||||||
|
const netFeeDrops: string = xrpToDrops(netFeeXRP)
|
||||||
|
let baseFee: BigNumber = new BigNumber(netFeeDrops)
|
||||||
|
|
||||||
|
// EscrowFinish Transaction with Fulfillment
|
||||||
|
if (tx.TransactionType === 'EscrowFinish' && tx.Fulfillment != null) {
|
||||||
|
const fulfillmentBytesSize: number = Math.ceil(tx.Fulfillment.length / 2)
|
||||||
|
// 10 drops × (33 + (Fulfillment size in bytes / 16))
|
||||||
|
const product = new BigNumber(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- expected use of magic numbers
|
||||||
|
scaleValue(netFeeDrops, 33 + fulfillmentBytesSize / 16),
|
||||||
|
)
|
||||||
|
baseFee = product.dp(0, BigNumber.ROUND_CEIL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountDelete Transaction
|
||||||
|
if (tx.TransactionType === 'AccountDelete') {
|
||||||
|
baseFee = new BigNumber(ACCOUNT_DELETE_FEE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-signed Transaction
|
||||||
|
// 10 drops × (1 + Number of Signatures Provided)
|
||||||
|
if (signersCount > 0) {
|
||||||
|
baseFee = BigNumber.sum(baseFee, scaleValue(netFeeDrops, 1 + signersCount))
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxFeeDrops = xrpToDrops(client.maxFeeXRP)
|
||||||
|
const totalFee =
|
||||||
|
tx.TransactionType === 'AccountDelete'
|
||||||
|
? baseFee
|
||||||
|
: BigNumber.min(baseFee, maxFeeDrops)
|
||||||
|
|
||||||
|
// Round up baseFee and return it as a string
|
||||||
|
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-magic-numbers -- param reassign is safe, base 10 magic num
|
||||||
|
tx.Fee = totalFee.dp(0, BigNumber.ROUND_CEIL).toString(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
function scaleValue(value, multiplier): string {
|
||||||
|
return new BigNumber(value).times(multiplier).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setLatestValidatedLedgerSequence(
|
||||||
|
client: Client,
|
||||||
|
tx: Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const request: LedgerRequest = {
|
||||||
|
command: 'ledger',
|
||||||
|
ledger_index: 'validated',
|
||||||
|
}
|
||||||
|
const data = await client.request(request)
|
||||||
|
const ledgerSequence = data.result.ledger_index
|
||||||
|
// eslint-disable-next-line no-param-reassign -- param reassign is safe
|
||||||
|
tx.LastLedgerSequence = ledgerSequence + LEDGER_OFFSET
|
||||||
|
}
|
||||||
|
|
||||||
|
export default autofill
|
||||||
@@ -95,9 +95,7 @@ export function isAmount(amount: unknown): boolean {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GlobalFlags {
|
export interface GlobalFlags {}
|
||||||
tfFullyCanonicalSig: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BaseTransaction {
|
export interface BaseTransaction {
|
||||||
Account: string
|
Account: string
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ import {
|
|||||||
isAmount,
|
isAmount,
|
||||||
} from './common'
|
} from './common'
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-shadow -- variable declaration is unique
|
||||||
|
export enum OfferCreateFlagsEnum {
|
||||||
|
tfPassive = 0x00010000,
|
||||||
|
tfImmediateOrCancel = 0x00020000,
|
||||||
|
tfFillOrKill = 0x00040000,
|
||||||
|
tfSell = 0x00080000,
|
||||||
|
}
|
||||||
|
|
||||||
export interface OfferCreateFlags extends GlobalFlags {
|
export interface OfferCreateFlags extends GlobalFlags {
|
||||||
tfPassive?: boolean
|
tfPassive?: boolean
|
||||||
tfImmediateOrCancel?: boolean
|
tfImmediateOrCancel?: boolean
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ import { ValidationError } from '../../common/errors'
|
|||||||
|
|
||||||
import { BaseTransaction, GlobalFlags, verifyBaseTransaction } from './common'
|
import { BaseTransaction, GlobalFlags, verifyBaseTransaction } from './common'
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-shadow -- variable declaration is unique
|
||||||
|
export enum PaymentChannelClaimFlagsEnum {
|
||||||
|
tfRenew = 0x00010000,
|
||||||
|
tfClose = 0x00020000,
|
||||||
|
}
|
||||||
|
|
||||||
export interface PaymentChannelClaimFlags extends GlobalFlags {
|
export interface PaymentChannelClaimFlags extends GlobalFlags {
|
||||||
tfRenew?: boolean
|
tfRenew?: boolean
|
||||||
tfClose?: boolean
|
tfClose?: boolean
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/* eslint-disable no-param-reassign -- param reassign is safe */
|
||||||
|
/* eslint-disable no-bitwise -- flags require bitwise operations */
|
||||||
|
import { ValidationError } from '../../common/errors'
|
||||||
|
// eslint-disable-next-line import/no-cycle -- cycle is safe
|
||||||
|
import {
|
||||||
|
OfferCreateFlags,
|
||||||
|
OfferCreateFlagsEnum,
|
||||||
|
PaymentChannelClaimFlags,
|
||||||
|
PaymentChannelClaimFlagsEnum,
|
||||||
|
PaymentTransactionFlags,
|
||||||
|
PaymentTransactionFlagsEnum,
|
||||||
|
Transaction,
|
||||||
|
TrustSetFlags,
|
||||||
|
TrustSetFlagsEnum,
|
||||||
|
} from '../transactions'
|
||||||
|
import type { GlobalFlags } from '../transactions/common'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify that all fields of an object are in fields.
|
* Verify that all fields of an object are in fields.
|
||||||
*
|
*
|
||||||
@@ -20,6 +37,71 @@ export function onlyHasFields(
|
|||||||
* @returns True if checkFlag is enabled within Flags.
|
* @returns True if checkFlag is enabled within Flags.
|
||||||
*/
|
*/
|
||||||
export function isFlagEnabled(Flags: number, checkFlag: number): boolean {
|
export function isFlagEnabled(Flags: number, checkFlag: number): boolean {
|
||||||
// eslint-disable-next-line no-bitwise -- Flags require bitwise operations
|
|
||||||
return (checkFlag & Flags) === checkFlag
|
return (checkFlag & Flags) === checkFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a transaction's flags to its numeric representation.
|
||||||
|
*
|
||||||
|
* @param tx - A transaction to set its flags to its numeric representation.
|
||||||
|
*/
|
||||||
|
export function setTransactionFlagsToNumber(tx: Transaction): void {
|
||||||
|
if (tx.Flags == null) {
|
||||||
|
tx.Flags = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof tx.Flags === 'number') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (tx.TransactionType) {
|
||||||
|
case 'OfferCreate':
|
||||||
|
tx.Flags = convertOfferCreateFlagsToNumber(tx.Flags)
|
||||||
|
return
|
||||||
|
case 'PaymentChannelClaim':
|
||||||
|
tx.Flags = convertPaymentChannelClaimFlagsToNumber(tx.Flags)
|
||||||
|
return
|
||||||
|
case 'Payment':
|
||||||
|
tx.Flags = convertPaymentTransactionFlagsToNumber(tx.Flags)
|
||||||
|
return
|
||||||
|
case 'TrustSet':
|
||||||
|
tx.Flags = convertTrustSetFlagsToNumber(tx.Flags)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
tx.Flags = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertOfferCreateFlagsToNumber(flags: OfferCreateFlags): number {
|
||||||
|
return reduceFlags(flags, OfferCreateFlagsEnum)
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertPaymentChannelClaimFlagsToNumber(
|
||||||
|
flags: PaymentChannelClaimFlags,
|
||||||
|
): number {
|
||||||
|
return reduceFlags(flags, PaymentChannelClaimFlagsEnum)
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertPaymentTransactionFlagsToNumber(
|
||||||
|
flags: PaymentTransactionFlags,
|
||||||
|
): number {
|
||||||
|
return reduceFlags(flags, PaymentTransactionFlagsEnum)
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertTrustSetFlagsToNumber(flags: TrustSetFlags): number {
|
||||||
|
return reduceFlags(flags, TrustSetFlagsEnum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- added ValidationError check for flagEnum
|
||||||
|
function reduceFlags(flags: GlobalFlags, flagEnum: any): number {
|
||||||
|
return Object.keys(flags).reduce((resultFlags, flag) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
|
||||||
|
if (flagEnum[flag] == null) {
|
||||||
|
throw new ValidationError(
|
||||||
|
`flag ${flag} doesn't exist in flagEnum: ${JSON.stringify(flagEnum)}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
|
||||||
|
return flags[flag] ? resultFlags | flagEnum[flag] : resultFlags
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|||||||
241
test/client/autofill.ts
Normal file
241
test/client/autofill.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import {
|
||||||
|
AccountDelete,
|
||||||
|
EscrowFinish,
|
||||||
|
Payment,
|
||||||
|
Transaction,
|
||||||
|
} from '../../src/models/transactions'
|
||||||
|
import rippled from '../fixtures/rippled'
|
||||||
|
import { setupClient, teardownClient } from '../setupClient'
|
||||||
|
|
||||||
|
const Fee = '10'
|
||||||
|
const Sequence = 1432
|
||||||
|
const LastLedgerSequence = 2908734
|
||||||
|
|
||||||
|
describe('client.autofill', function () {
|
||||||
|
beforeEach(setupClient)
|
||||||
|
afterEach(teardownClient)
|
||||||
|
|
||||||
|
it('should not autofill if fields are present', async function () {
|
||||||
|
const tx: Transaction = {
|
||||||
|
TransactionType: 'DepositPreauth',
|
||||||
|
Account: 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||||
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||||
|
Fee,
|
||||||
|
Sequence,
|
||||||
|
LastLedgerSequence,
|
||||||
|
}
|
||||||
|
const txResult = await this.client.autofill(tx)
|
||||||
|
|
||||||
|
assert.strictEqual(txResult.Fee, Fee)
|
||||||
|
assert.strictEqual(txResult.Sequence, Sequence)
|
||||||
|
assert.strictEqual(txResult.LastLedgerSequence, LastLedgerSequence)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('converts Account & Destination X-address to their classic address', async function () {
|
||||||
|
const tx: Payment = {
|
||||||
|
TransactionType: 'Payment',
|
||||||
|
Account: 'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi',
|
||||||
|
Amount: '1234',
|
||||||
|
Destination: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
|
||||||
|
}
|
||||||
|
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
|
||||||
|
this.mockRippled.addResponse('server_info', rippled.server_info.normal)
|
||||||
|
this.mockRippled.addResponse('ledger', rippled.ledger.normal)
|
||||||
|
|
||||||
|
const txResult = await this.client.autofill(tx)
|
||||||
|
|
||||||
|
assert.strictEqual(txResult.Account, 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf')
|
||||||
|
assert.strictEqual(
|
||||||
|
txResult.Destination,
|
||||||
|
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should autofill Sequence when it's missing", async function () {
|
||||||
|
const tx: Transaction = {
|
||||||
|
TransactionType: 'DepositPreauth',
|
||||||
|
Account: 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||||
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||||
|
Fee,
|
||||||
|
LastLedgerSequence,
|
||||||
|
}
|
||||||
|
this.mockRippled.addResponse('account_info', {
|
||||||
|
status: 'success',
|
||||||
|
type: 'response',
|
||||||
|
result: {
|
||||||
|
account_data: {
|
||||||
|
Sequence: 23,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const txResult = await this.client.autofill(tx)
|
||||||
|
|
||||||
|
assert.strictEqual(txResult.Sequence, 23)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when autofill Fee is missing', function () {
|
||||||
|
it('should autofill Fee of a Transaction', async function () {
|
||||||
|
const tx: Transaction = {
|
||||||
|
TransactionType: 'DepositPreauth',
|
||||||
|
Account: 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||||
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||||
|
Sequence,
|
||||||
|
LastLedgerSequence,
|
||||||
|
}
|
||||||
|
this.mockRippled.addResponse('server_info', {
|
||||||
|
status: 'success',
|
||||||
|
type: 'response',
|
||||||
|
result: {
|
||||||
|
info: {
|
||||||
|
validated_ledger: {
|
||||||
|
base_fee_xrp: 0.00001,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const txResult = await this.client.autofill(tx)
|
||||||
|
|
||||||
|
assert.strictEqual(txResult.Fee, '12')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should autofill Fee of an EscrowFinish transaction', async function () {
|
||||||
|
const tx: EscrowFinish = {
|
||||||
|
Account: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn',
|
||||||
|
TransactionType: 'EscrowFinish',
|
||||||
|
Owner: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn',
|
||||||
|
OfferSequence: 7,
|
||||||
|
Condition:
|
||||||
|
'A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100',
|
||||||
|
Fulfillment: 'A0028000',
|
||||||
|
}
|
||||||
|
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
|
||||||
|
this.mockRippled.addResponse('ledger', rippled.ledger.normal)
|
||||||
|
this.mockRippled.addResponse('server_info', {
|
||||||
|
status: 'success',
|
||||||
|
type: 'response',
|
||||||
|
result: {
|
||||||
|
info: {
|
||||||
|
validated_ledger: {
|
||||||
|
base_fee_xrp: 0.00001,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const txResult = await this.client.autofill(tx)
|
||||||
|
|
||||||
|
assert.strictEqual(txResult.Fee, '399')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should autofill Fee of an AccountDelete transaction', async function () {
|
||||||
|
const tx: AccountDelete = {
|
||||||
|
Account: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn',
|
||||||
|
TransactionType: 'AccountDelete',
|
||||||
|
Destination: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
|
||||||
|
}
|
||||||
|
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
|
||||||
|
this.mockRippled.addResponse('ledger', rippled.ledger.normal)
|
||||||
|
this.mockRippled.addResponse('server_info', {
|
||||||
|
status: 'success',
|
||||||
|
type: 'response',
|
||||||
|
result: {
|
||||||
|
info: {
|
||||||
|
validated_ledger: {
|
||||||
|
base_fee_xrp: 0.00001,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const txResult = await this.client.autofill(tx)
|
||||||
|
|
||||||
|
assert.strictEqual(txResult.Fee, '5000000')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should autofill Fee of an EscrowFinish transaction with signersCount', async function () {
|
||||||
|
const tx: EscrowFinish = {
|
||||||
|
Account: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn',
|
||||||
|
TransactionType: 'EscrowFinish',
|
||||||
|
Owner: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn',
|
||||||
|
OfferSequence: 7,
|
||||||
|
Condition:
|
||||||
|
'A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100',
|
||||||
|
Fulfillment: 'A0028000',
|
||||||
|
}
|
||||||
|
this.mockRippled.addResponse('account_info', rippled.account_info.normal)
|
||||||
|
this.mockRippled.addResponse('ledger', rippled.ledger.normal)
|
||||||
|
this.mockRippled.addResponse('server_info', {
|
||||||
|
status: 'success',
|
||||||
|
type: 'response',
|
||||||
|
result: {
|
||||||
|
info: {
|
||||||
|
validated_ledger: {
|
||||||
|
base_fee_xrp: 0.00001,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const txResult = await this.client.autofill(tx, 4)
|
||||||
|
|
||||||
|
assert.strictEqual(txResult.Fee, '459')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should autofill LastLedgerSequence when it's missing", async function () {
|
||||||
|
const tx: Transaction = {
|
||||||
|
TransactionType: 'DepositPreauth',
|
||||||
|
Account: 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||||
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||||
|
Fee,
|
||||||
|
Sequence,
|
||||||
|
}
|
||||||
|
this.mockRippled.addResponse('ledger', {
|
||||||
|
status: 'success',
|
||||||
|
type: 'response',
|
||||||
|
result: {
|
||||||
|
ledger_index: 9038214,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const txResult = await this.client.autofill(tx)
|
||||||
|
assert.strictEqual(txResult.LastLedgerSequence, 9038234)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should autofill fields when all are missing', async function () {
|
||||||
|
const tx: Transaction = {
|
||||||
|
TransactionType: 'DepositPreauth',
|
||||||
|
Account: 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||||
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||||
|
}
|
||||||
|
this.mockRippled.addResponse('account_info', {
|
||||||
|
status: 'success',
|
||||||
|
type: 'response',
|
||||||
|
result: {
|
||||||
|
account_data: {
|
||||||
|
Sequence: 23,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.mockRippled.addResponse('ledger', {
|
||||||
|
status: 'success',
|
||||||
|
type: 'response',
|
||||||
|
result: {
|
||||||
|
ledger_index: 9038214,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.mockRippled.addResponse('server_info', {
|
||||||
|
status: 'success',
|
||||||
|
type: 'response',
|
||||||
|
result: {
|
||||||
|
info: {
|
||||||
|
validated_ledger: {
|
||||||
|
base_fee_xrp: 0.00001,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const txResult = await this.client.autofill(tx)
|
||||||
|
assert.strictEqual(txResult.Fee, '12')
|
||||||
|
assert.strictEqual(txResult.Sequence, 23)
|
||||||
|
assert.strictEqual(txResult.LastLedgerSequence, 9038234)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,6 +1,21 @@
|
|||||||
|
/* eslint-disable no-bitwise -- flags require bitwise operations */
|
||||||
import { assert } from 'chai'
|
import { assert } from 'chai'
|
||||||
|
|
||||||
import { isFlagEnabled } from '../../src/models/utils'
|
import {
|
||||||
|
DepositPreauth,
|
||||||
|
OfferCreate,
|
||||||
|
OfferCreateFlagsEnum,
|
||||||
|
PaymentChannelClaim,
|
||||||
|
PaymentChannelClaimFlagsEnum,
|
||||||
|
Payment,
|
||||||
|
PaymentTransactionFlagsEnum,
|
||||||
|
TrustSet,
|
||||||
|
TrustSetFlagsEnum,
|
||||||
|
} from '../../src/models/transactions'
|
||||||
|
import {
|
||||||
|
isFlagEnabled,
|
||||||
|
setTransactionFlagsToNumber,
|
||||||
|
} from '../../src/models/utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utils Testing.
|
* Utils Testing.
|
||||||
@@ -18,13 +33,120 @@ describe('Models Utils', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('verifies a flag is enabled', function () {
|
it('verifies a flag is enabled', function () {
|
||||||
flags += flag1 + flag2
|
flags |= flag1 | flag2
|
||||||
assert.isTrue(isFlagEnabled(flags, flag1))
|
assert.isTrue(isFlagEnabled(flags, flag1))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('verifies a flag is not enabled', function () {
|
it('verifies a flag is not enabled', function () {
|
||||||
flags += flag2
|
flags |= flag2
|
||||||
assert.isFalse(isFlagEnabled(flags, flag1))
|
assert.isFalse(isFlagEnabled(flags, flag1))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('setTransactionFlagsToNumber', function () {
|
||||||
|
it('sets OfferCreateFlags to its numeric value', function () {
|
||||||
|
const tx: OfferCreate = {
|
||||||
|
Account: 'r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W',
|
||||||
|
Fee: '10',
|
||||||
|
TakerGets: {
|
||||||
|
currency: 'DSH',
|
||||||
|
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
|
||||||
|
value: '43.11584856965009',
|
||||||
|
},
|
||||||
|
TakerPays: '12928290425',
|
||||||
|
TransactionType: 'OfferCreate',
|
||||||
|
TxnSignature:
|
||||||
|
'3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91',
|
||||||
|
Flags: {
|
||||||
|
tfPassive: true,
|
||||||
|
tfImmediateOrCancel: false,
|
||||||
|
tfFillOrKill: true,
|
||||||
|
tfSell: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tfPassive, tfFillOrKill } = OfferCreateFlagsEnum
|
||||||
|
const expected: number = tfPassive | tfFillOrKill
|
||||||
|
|
||||||
|
setTransactionFlagsToNumber(tx)
|
||||||
|
assert.strictEqual(tx.Flags, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets PaymentChannelClaimFlags to its numeric value', function () {
|
||||||
|
const tx: PaymentChannelClaim = {
|
||||||
|
Account: 'r...',
|
||||||
|
TransactionType: 'PaymentChannelClaim',
|
||||||
|
Channel:
|
||||||
|
'C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198',
|
||||||
|
Flags: {
|
||||||
|
tfRenew: true,
|
||||||
|
tfClose: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tfRenew } = PaymentChannelClaimFlagsEnum
|
||||||
|
const expected: number = tfRenew
|
||||||
|
|
||||||
|
setTransactionFlagsToNumber(tx)
|
||||||
|
assert.strictEqual(tx.Flags, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets PaymentTransactionFlags to its numeric value', function () {
|
||||||
|
const tx: Payment = {
|
||||||
|
TransactionType: 'Payment',
|
||||||
|
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
|
||||||
|
Amount: '1234',
|
||||||
|
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
|
||||||
|
Flags: {
|
||||||
|
tfNoDirectRipple: false,
|
||||||
|
tfPartialPayment: true,
|
||||||
|
tfLimitQuality: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tfPartialPayment, tfLimitQuality } = PaymentTransactionFlagsEnum
|
||||||
|
const expected: number = tfPartialPayment | tfLimitQuality
|
||||||
|
|
||||||
|
setTransactionFlagsToNumber(tx)
|
||||||
|
assert.strictEqual(tx.Flags, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets TrustSetFlags to its numeric value', function () {
|
||||||
|
const tx: TrustSet = {
|
||||||
|
TransactionType: 'TrustSet',
|
||||||
|
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
|
||||||
|
LimitAmount: {
|
||||||
|
currency: 'XRP',
|
||||||
|
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
|
||||||
|
value: '4329.23',
|
||||||
|
},
|
||||||
|
QualityIn: 1234,
|
||||||
|
QualityOut: 4321,
|
||||||
|
Flags: {
|
||||||
|
tfSetfAuth: true,
|
||||||
|
tfSetNoRipple: false,
|
||||||
|
tfClearNoRipple: true,
|
||||||
|
tfSetFreeze: false,
|
||||||
|
tfClearFreeze: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tfSetfAuth, tfClearNoRipple, tfClearFreeze } = TrustSetFlagsEnum
|
||||||
|
const expected: number = tfSetfAuth | tfClearNoRipple | tfClearFreeze
|
||||||
|
|
||||||
|
setTransactionFlagsToNumber(tx)
|
||||||
|
assert.strictEqual(tx.Flags, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets other transaction types flags to its numeric value', function () {
|
||||||
|
const tx: DepositPreauth = {
|
||||||
|
TransactionType: 'DepositPreauth',
|
||||||
|
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
|
||||||
|
Flags: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
setTransactionFlagsToNumber(tx)
|
||||||
|
assert.strictEqual(tx.Flags, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user