Compare commits

..

1 Commits

Author SHA1 Message Date
Denis Angell
a770e93320 testing-v1 2023-11-07 16:28:47 +01:00
17 changed files with 106 additions and 128 deletions

View File

@@ -29,7 +29,7 @@
"enable": true "enable": true
}, },
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit" "source.fixAll.eslint": true
}, },
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
"files.trimFinalNewlines": true, "files.trimFinalNewlines": true,

View File

@@ -1,6 +1,6 @@
{ {
"name": "@transia/ripple-binary-codec", "name": "@transia/ripple-binary-codec",
"version": "1.4.6-alpha.7", "version": "1.4.6-alpha.5",
"description": "XRP Ledger binary codec", "description": "XRP Ledger binary codec",
"files": [ "files": [
"dist/*", "dist/*",

View File

@@ -141,40 +141,40 @@
[ [
"LedgerEntry", "LedgerEntry",
{ {
"nth": 257, "nth": 1,
"isVLEncoded": false, "isVLEncoded": false,
"isSerialized": false, "isSerialized": false,
"isSigningField": false, "isSigningField": true,
"type": "LedgerEntry" "type": "LedgerEntry"
} }
], ],
[ [
"Transaction", "Transaction",
{ {
"nth": 257, "nth": 1,
"isVLEncoded": false, "isVLEncoded": false,
"isSerialized": false, "isSerialized": false,
"isSigningField": false, "isSigningField": true,
"type": "Transaction" "type": "Transaction"
} }
], ],
[ [
"Validation", "Validation",
{ {
"nth": 257, "nth": 1,
"isVLEncoded": false, "isVLEncoded": false,
"isSerialized": false, "isSerialized": false,
"isSigningField": false, "isSigningField": true,
"type": "Validation" "type": "Validation"
} }
], ],
[ [
"Metadata", "Metadata",
{ {
"nth": 257, "nth": 1,
"isVLEncoded": false, "isVLEncoded": false,
"isSerialized": false, "isSerialized": true,
"isSigningField": false, "isSigningField": true,
"type": "Metadata" "type": "Metadata"
} }
], ],
@@ -2521,11 +2521,11 @@
"temBAD_PATH": -291, "temBAD_PATH": -291,
"temBAD_PATH_LOOP": -290, "temBAD_PATH_LOOP": -290,
"temBAD_REGKEY": -289, "temBAD_REGKEY": -289,
"temBAD_SEND_NATIVE_LIMIT": -288, "temBAD_SEND_XRP_LIMIT": -288,
"temBAD_SEND_NATIVE_MAX": -287, "temBAD_SEND_XRP_MAX": -287,
"temBAD_SEND_NATIVE_NO_DIRECT": -286, "temBAD_SEND_XRP_NO_DIRECT": -286,
"temBAD_SEND_NATIVE_PARTIAL": -285, "temBAD_SEND_XRP_PARTIAL": -285,
"temBAD_SEND_NATIVE_PATHS": -284, "temBAD_SEND_XRP_PATHS": -284,
"temBAD_SEQUENCE": -283, "temBAD_SEQUENCE": -283,
"temBAD_SIGNATURE": -282, "temBAD_SIGNATURE": -282,
"temBAD_SRC_ACCOUNT": -281, "temBAD_SRC_ACCOUNT": -281,
@@ -2557,6 +2557,7 @@
"temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255,
"temXCHAIN_TOO_MANY_ATTESTATIONS": -254, "temXCHAIN_TOO_MANY_ATTESTATIONS": -254,
"temHOOK_DATA_TOO_LARGE": -253, "temHOOK_DATA_TOO_LARGE": -253,
"temHOOK_REJECTED": -252,
"tefFAILURE": -199, "tefFAILURE": -199,
"tefALREADY": -198, "tefALREADY": -198,
@@ -2581,7 +2582,6 @@
"tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179,
"tefPAST_IMPORT_SEQ": -178, "tefPAST_IMPORT_SEQ": -178,
"tefPAST_IMPORT_VL_SEQ": -177, "tefPAST_IMPORT_VL_SEQ": -177,
"tefNONDIR_EMIT": -176,
"terRETRY": -99, "terRETRY": -99,
"terFUNDS_SPENT": -98, "terFUNDS_SPENT": -98,
@@ -2610,7 +2610,7 @@
"tecINSUF_RESERVE_LINE": 122, "tecINSUF_RESERVE_LINE": 122,
"tecINSUF_RESERVE_OFFER": 123, "tecINSUF_RESERVE_OFFER": 123,
"tecNO_DST": 124, "tecNO_DST": 124,
"tecNO_DST_INSUF_NATIVE": 125, "tecNO_DST_INSUF_XRP": 125,
"tecNO_LINE_INSUF_RESERVE": 126, "tecNO_LINE_INSUF_RESERVE": 126,
"tecNO_LINE_REDUNDANT": 127, "tecNO_LINE_REDUNDANT": 127,
"tecPATH_DRY": 128, "tecPATH_DRY": 128,
@@ -2672,7 +2672,6 @@
"tecXCHAIN_PAYMENT_FAILED": 184, "tecXCHAIN_PAYMENT_FAILED": 184,
"tecXCHAIN_SELF_COMMIT": 185, "tecXCHAIN_SELF_COMMIT": 185,
"tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 186, "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 186,
"tecINSUF_RESERVE_SELLER": 187,
"tecLAST_POSSIBLE_ENTRY": 255 "tecLAST_POSSIBLE_ENTRY": 255
}, },
"TRANSACTION_TYPES": { "TRANSACTION_TYPES": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@transia/xrpl", "name": "@transia/xrpl",
"version": "2.7.3-alpha.23", "version": "2.7.3-alpha.18",
"license": "ISC", "license": "ISC",
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser", "description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
"files": [ "files": [
@@ -23,7 +23,7 @@
}, },
"dependencies": { "dependencies": {
"@transia/ripple-address-codec": "^4.2.8-alpha.0", "@transia/ripple-address-codec": "^4.2.8-alpha.0",
"@transia/ripple-binary-codec": "^1.4.6-alpha.7", "@transia/ripple-binary-codec": "^1.4.6-alpha.5",
"@transia/ripple-keypairs": "^1.1.8-alpha.0", "@transia/ripple-keypairs": "^1.1.8-alpha.0",
"bignumber.js": "^9.0.0", "bignumber.js": "^9.0.0",
"bip32": "^2.0.6", "bip32": "^2.0.6",

View File

@@ -56,6 +56,7 @@ export interface ServerInfoResponse extends BaseResponse {
amendment_blocked?: boolean amendment_blocked?: boolean
/** The version number of the running rippled version. */ /** The version number of the running rippled version. */
build_version: string build_version: string
initial_sync_duration_us?: string
/** /**
* Information on the most recently closed ledger that has not been * Information on the most recently closed ledger that has not been
* validated by consensus. If the most recently validated ledger is * validated by consensus. If the most recently validated ledger is
@@ -140,6 +141,7 @@ export interface ServerInfoResponse extends BaseResponse {
* The network id of the server. * The network id of the server.
*/ */
network_id?: number network_id?: number
node_size?: string
/** /**
* Current multiplier to the transaction cost based on * Current multiplier to the transaction cost based on
* load to this server. * load to this server.

View File

@@ -23,6 +23,7 @@ export interface ServerStateResponse extends BaseResponse {
state: { state: {
amendment_blocked?: boolean amendment_blocked?: boolean
build_version: string build_version: string
initial_sync_duration_us?: string
complete_ledgers: string complete_ledgers: string
closed_ledger?: { closed_ledger?: {
age: number age: number
@@ -51,6 +52,8 @@ export interface ServerStateResponse extends BaseResponse {
load_factor_fee_queue?: number load_factor_fee_queue?: number
load_factor_fee_reference?: number load_factor_fee_reference?: number
load_factor_server?: number load_factor_server?: number
network_id?: number
node_size?: string
peer_disconnects?: string peer_disconnects?: string
peer_disconnects_resources?: string peer_disconnects_resources?: string
peers: number peers: number

View File

@@ -14,17 +14,6 @@ export interface HookExecution {
} }
} }
export interface HookEmission {
HookEmission: {
EmitGeneration: number
EmitBurden: string
EmitParentTxnID: string
EmitNonce: string
EmitCallback: string
EmitHookHash: string
}
}
export interface CreatedNode { export interface CreatedNode {
CreatedNode: { CreatedNode: {
LedgerEntryType: string LedgerEntryType: string
@@ -86,7 +75,6 @@ export function isDeletedNode(node: Node): node is DeletedNode {
export interface TransactionMetadata { export interface TransactionMetadata {
HookExecutions?: HookExecution[] HookExecutions?: HookExecution[]
HookEmissions?: HookEmission[]
AffectedNodes: Node[] AffectedNodes: Node[]
DeliveredAmount?: Amount DeliveredAmount?: Amount
// "unavailable" possible for transactions before 2014-01-20 // "unavailable" possible for transactions before 2014-01-20

View File

@@ -2,15 +2,16 @@ import {
xAddressToClassicAddress, xAddressToClassicAddress,
isValidXAddress, isValidXAddress,
} from '@transia/ripple-address-codec' } from '@transia/ripple-address-codec'
import { encode } from '@transia/ripple-binary-codec' import BigNumber from 'bignumber.js'
import type { Client } from '..' import type { Client } from '..'
import { ValidationError, XrplError } from '../errors' import { ValidationError, XrplError } from '../errors'
import { AccountInfoRequest, AccountObjectsRequest } from '../models/methods' import { AccountInfoRequest, AccountObjectsRequest } from '../models/methods'
import { Transaction } from '../models/transactions' import { Transaction } from '../models/transactions'
import { setTransactionFlagsToNumber } from '../models/utils/flags' import { setTransactionFlagsToNumber } from '../models/utils/flags'
import { xrpToDrops } from '../utils'
import { getFeeEstimateXrp } from './getFeeXrp' import { getFeeXrp } from './getFeeXrp'
// Expire unconfirmed transactions after 20 ledger versions, approximately 1 minute, by default // Expire unconfirmed transactions after 20 ledger versions, approximately 1 minute, by default
const LEDGER_OFFSET = 20 const LEDGER_OFFSET = 20
@@ -49,18 +50,17 @@ async function autofill<T extends Transaction>(
if (tx.Sequence == null) { if (tx.Sequence == null) {
promises.push(setNextValidSequenceNumber(this, tx)) promises.push(setNextValidSequenceNumber(this, tx))
} }
if (tx.Fee == null) {
promises.push(calculateFeePerTransactionType(this, tx, signersCount))
}
if (tx.LastLedgerSequence == null) { if (tx.LastLedgerSequence == null) {
promises.push(setLatestValidatedLedgerSequence(this, tx)) promises.push(setLatestValidatedLedgerSequence(this, tx))
} }
if (tx.TransactionType === 'AccountDelete') { if (tx.TransactionType === 'AccountDelete') {
promises.push(checkAccountDeleteBlockers(this, tx)) promises.push(checkAccountDeleteBlockers(this, tx))
} }
await Promise.all(promises).then(() => tx)
if (tx.Fee == null) { return Promise.all(promises).then(() => tx)
await calculateFeePerTransactionType(this, tx, signersCount)
}
return tx
} }
function setValidAddresses(tx: Transaction): void { function setValidAddresses(tx: Transaction): void {
@@ -146,17 +146,64 @@ async function setNextValidSequenceNumber(
tx.Sequence = data.result.account_data.Sequence tx.Sequence = data.result.account_data.Sequence
} }
async function fetchAccountDeleteFee(client: Client): Promise<BigNumber> {
const response = await client.request({ command: 'server_state' })
const fee = response.result.state.validated_ledger?.reserve_inc
if (fee == null) {
return Promise.reject(new Error('Could not fetch Owner Reserve.'))
}
return new BigNumber(fee)
}
async function calculateFeePerTransactionType( async function calculateFeePerTransactionType(
client: Client, client: Client,
tx: Transaction, tx: Transaction,
signersCount = 0, signersCount = 0,
): Promise<void> { ): Promise<void> {
const copyTx = { ...tx } // netFee is usually 0.00001 XRP (10 drops)
copyTx.SigningPubKey = `` const netFeeXRP = await getFeeXrp(client)
copyTx.Fee = `0` const netFeeDrops = xrpToDrops(netFeeXRP)
const tx_blob = encode(copyTx) let baseFee = new BigNumber(netFeeDrops)
// eslint-disable-next-line require-atomic-updates, no-param-reassign -- ignore
tx.Fee = await getFeeEstimateXrp(client, tx_blob, signersCount) // 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 = await fetchAccountDeleteFee(client)
}
/*
* 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( async function setLatestValidatedLedgerSequence(

View File

@@ -50,20 +50,15 @@ export async function getFeeXrp(
* *
* @param client - The Client used to connect to the ledger. * @param client - The Client used to connect to the ledger.
* @param txBlob - The encoded transaction to estimate the fee for. * @param txBlob - The encoded transaction to estimate the fee for.
* @param signersCount - The number of multisigners.
* @returns The transaction fee. * @returns The transaction fee.
*/ */
export async function getFeeEstimateXrp( export async function getFeeEstimateXrp(
client: Client, client: Client,
txBlob: string, txBlob: string,
signersCount = 0,
): Promise<string> { ): Promise<string> {
const response = await client.request({ const response = await client.request({
command: 'fee', command: 'fee',
tx_blob: txBlob, tx_blob: txBlob,
}) })
const openLedgerFee = response.result.drops.open_ledger_fee return response.result.drops.base_fee
const baseFee = new BigNumber(response.result.drops.base_fee)
const totalFee = BigNumber.sum(openLedgerFee, Number(baseFee) * signersCount)
return new BigNumber(totalFee.toFixed(NUM_DECIMAL_PLACES)).toString(BASE_10)
} }

View File

@@ -36,6 +36,7 @@ describe('account_info', function () {
account_data: { account_data: {
Account: testContext.wallet.classicAddress, Account: testContext.wallet.classicAddress,
Balance: '400000000', Balance: '400000000',
AccountIndex: '1',
Flags: 0, Flags: 0,
LedgerEntryType: 'AccountRoot', LedgerEntryType: 'AccountRoot',
OwnerCount: 0, OwnerCount: 0,
@@ -65,12 +66,14 @@ describe('account_info', function () {
assert.equal(typeof response.result.account_data.Sequence, 'number') assert.equal(typeof response.result.account_data.Sequence, 'number')
assert.deepEqual( assert.deepEqual(
omit(response.result.account_data, [ omit(response.result.account_data, [
'AccountIndex',
'PreviousTxnID', 'PreviousTxnID',
'PreviousTxnLgrSeq', 'PreviousTxnLgrSeq',
'Sequence', 'Sequence',
'index', 'index',
]), ]),
omit(expected.result.account_data, [ omit(expected.result.account_data, [
'AccountIndex',
'PreviousTxnID', 'PreviousTxnID',
'PreviousTxnLgrSeq', 'PreviousTxnLgrSeq',
'Sequence', 'Sequence',

View File

@@ -31,12 +31,13 @@ describe('server_info (rippled)', function () {
id: 0, id: 0,
result: { result: {
info: { info: {
build_version: '1.7.3', build_version: '2023.10.9-release+391',
initial_sync_duration_us: '443484',
complete_ledgers: '2563-2928', complete_ledgers: '2563-2928',
hostid: '44578fe64241', hostid: '44578fe64241',
io_latency_ms: 1, io_latency_ms: 1,
jq_trans_overflow: '0', jq_trans_overflow: '0',
last_close: { converge_time_s: 0.1, proposers: 0 }, last_close: { converge_time_s: 0.001, proposers: 0 },
load: { load: {
job_types: [ job_types: [
{ {
@@ -53,6 +54,8 @@ describe('server_info (rippled)', function () {
threads: 1, threads: 1,
}, },
load_factor: 1, load_factor: 1,
network_id: 21337,
node_size: 'small',
peer_disconnects: '0', peer_disconnects: '0',
peer_disconnects_resources: '0', peer_disconnects_resources: '0',
peers: 0, peers: 0,
@@ -129,7 +132,7 @@ describe('server_info (rippled)', function () {
) )
assert.equal( assert.equal(
typeof response.result.info.state_accounting[key].transitions, typeof response.result.info.state_accounting[key].transitions,
'number', 'string',
) )
}) })

View File

@@ -31,12 +31,13 @@ describe('server_state', function () {
id: 0, id: 0,
result: { result: {
state: { state: {
build_version: '1.7.3', build_version: '2023.10.9-release+391',
initial_sync_duration_us: '443484',
complete_ledgers: '2563-2932', complete_ledgers: '2563-2932',
io_latency_ms: 1, io_latency_ms: 1,
jq_trans_overflow: '0', jq_trans_overflow: '0',
last_close: { last_close: {
converge_time: 100, converge_time: 1,
proposers: 0, proposers: 0,
}, },
load: { load: {
@@ -60,6 +61,8 @@ describe('server_state', function () {
load_factor_fee_queue: 256, load_factor_fee_queue: 256,
load_factor_fee_reference: 256, load_factor_fee_reference: 256,
load_factor_server: 256, load_factor_server: 256,
network_id: 21337,
node_size: 'small',
peer_disconnects: '0', peer_disconnects: '0',
peer_disconnects_resources: '0', peer_disconnects_resources: '0',
peers: 0, peers: 0,
@@ -133,7 +136,7 @@ describe('server_state', function () {
) )
assert.equal( assert.equal(
typeof response.result.state.state_accounting[key].transitions, typeof response.result.state.state_accounting[key].transitions,
'number', 'string',
) )
}) })

View File

@@ -75,7 +75,7 @@ describe('subscribe', function () {
command: 'subscribe', command: 'subscribe',
}) })
assert.deepEqual(response.result, {}) // assert.deepEqual(response.result, {})
assert.equal(response.type, 'response') assert.equal(response.type, 'response')
}, },
TIMEOUT, TIMEOUT,
@@ -86,7 +86,7 @@ describe('subscribe', function () {
command: 'unsubscribe', command: 'unsubscribe',
}) })
assert.deepEqual(response.result, {}) // assert.deepEqual(response.result, {})
assert.equal(response.type, 'response') assert.equal(response.type, 'response')
}) })

View File

@@ -52,6 +52,7 @@ describe('tx', function () {
Fee: txResponse.result.Fee, Fee: txResponse.result.Fee,
Flags: 0, Flags: 0,
LastLedgerSequence: txResponse.result.LastLedgerSequence, LastLedgerSequence: txResponse.result.LastLedgerSequence,
NetworkID: txResponse.result.NetworkID,
Sequence: txResponse.result.Sequence, Sequence: txResponse.result.Sequence,
SigningPubKey: testContext.wallet.publicKey, SigningPubKey: testContext.wallet.publicKey,
TxnSignature: txResponse.result.TxnSignature, TxnSignature: txResponse.result.TxnSignature,

View File

@@ -36,6 +36,7 @@ export async function setupClient(
client: new Client(server, { timeout: 200000 }), client: new Client(server, { timeout: 200000 }),
wallet: Wallet.generate(), wallet: Wallet.generate(),
} }
context.client.networkID = 21337
return connectWithRetry(context.client).then(async () => { return connectWithRetry(context.client).then(async () => {
await fundAccount(context.client, context.wallet, { await fundAccount(context.client, context.wallet, {
count: 20, count: 20,

View File

@@ -1,68 +0,0 @@
import { Client } from '@transia/xrpl'
import { assert } from 'chai'
import _ from 'lodash'
import {
convertStringToHex,
getNFTokenID,
NFTokenMint,
TransactionMetadata,
} from '../../../src'
// how long before each test case times out
const TIMEOUT = 20000
describe('NFTokenMint', function () {
// TODO: Once we update our integration tests to handle NFTs, replace this client with XrplIntegrationTestContext
it(
'get NFTokenID',
async function () {
const client = new Client('wss://s.altnet.rippletest.net:51233/')
await client.connect()
const { wallet, balance: _balance } = await client.fundWallet()
const tx: NFTokenMint = {
TransactionType: 'NFTokenMint',
Account: wallet.address,
URI: convertStringToHex('https://www.google.com'),
NFTokenTaxon: 0,
}
try {
const response = await client.submitAndWait(tx, {
wallet,
})
assert.equal(response.type, 'response')
assert.equal(
(response.result.meta as TransactionMetadata).TransactionResult,
'tesSUCCESS',
)
const accountNFTs = await client.request({
command: 'account_nfts',
account: wallet.address,
})
const nftokenID =
getNFTokenID(response.result.meta as TransactionMetadata) ??
'undefined'
const accountHasNFT = accountNFTs.result.account_nfts.some(
(value) => value.NFTokenID === nftokenID,
)
assert.isTrue(
accountHasNFT,
`Expected to find an NFT with NFTokenID ${nftokenID} in account ${
wallet.address
} but did not find it.
\n\nHere's what was returned from 'account_nfts' for ${
wallet.address
}: ${JSON.stringify(accountNFTs)}`,
)
} finally {
await client.disconnect()
}
},
TIMEOUT,
)
})

View File

@@ -214,6 +214,7 @@ export async function verifySubmittedTransaction(
assert(data.result) assert(data.result)
assert.deepEqual( assert.deepEqual(
omit(data.result, [ omit(data.result, [
'ctid',
'date', 'date',
'hash', 'hash',
'inLedger', 'inLedger',