diff --git a/src/client/index.ts b/src/client/index.ts index 33c8aeb8..1aec3de1 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -6,6 +6,8 @@ import { EventEmitter } from 'events' import { ValidationError, XrplError } from '../errors' import * as errors from '../errors' import { + Request, + Response, // account methods AccountChannelsRequest, AccountChannelsResponse, @@ -96,6 +98,10 @@ import { ConnectionUserOptions, INTENTIONAL_DISCONNECT_CODE, } from './connection' +import { + handlePartialPayment, + handleStreamPartialPayment, +} from './partialPayment' export interface ClientOptions extends ConnectionUserOptions { feeCushion?: number @@ -209,6 +215,7 @@ class Client extends EventEmitter { }) this.connection.on('transaction', (tx) => { + handleStreamPartialPayment(tx) this.emit('transaction', tx) }) @@ -295,6 +302,9 @@ class Client extends EventEmitter { r: TransactionEntryRequest, ): Promise public async request(r: TxRequest): Promise + public async request( + r: R, + ): Promise /** * Makes a request to the client with the given command and * additional request body parameters. @@ -302,17 +312,21 @@ class Client extends EventEmitter { * @param req - Request to send to the server. * @returns The response from the server. */ - public async request( + public async request( req: R, ): Promise { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Necessary for overloading - return this.connection.request({ + const response = (await this.connection.request({ ...req, account: req.account ? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Must be string ensureClassicAddress(req.account as string) : undefined, - }) as unknown as T + })) as T + + handlePartialPayment(req.command, response) + + return response } public async requestNextPage( @@ -357,7 +371,7 @@ class Client extends EventEmitter { } const nextPageRequest = { ...req, marker: resp.result.marker } // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Necessary for overloading - return this.connection.request(nextPageRequest) as unknown as U + return this.request(nextPageRequest) as unknown as U } public on( @@ -392,6 +406,10 @@ class Client extends EventEmitter { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- actually needs to be any here public on(eventName: string, listener: (...args: any[]) => void): this { + // if (args[0]?.type === 'transaction') { + // handlePartialPaymentStream(args[0]) + // } + return super.on(eventName, listener) } diff --git a/src/client/partialPayment.ts b/src/client/partialPayment.ts new file mode 100644 index 00000000..2fe53b90 --- /dev/null +++ b/src/client/partialPayment.ts @@ -0,0 +1,146 @@ +import BigNumber from 'bignumber.js' +import { decode } from 'ripple-binary-codec' + +import type { + AccountTxResponse, + Response, + TransactionEntryResponse, + TransactionStream, + TxResponse, +} from '..' +import type { Amount } from '../models/common' +import { PaymentTransactionFlags, Transaction } from '../models/transactions' +import type TransactionMetadata from '../models/transactions/metadata' +import { isFlagEnabled } from '../models/utils' + +const WARN_PARTIAL_PAYMENT_CODE = 2001 + +function amountsEqual(amt1: Amount, amt2: Amount): boolean { + if (typeof amt1 === 'string' && typeof amt2 === 'string') { + return amt1 === amt2 + } + + if (typeof amt1 === 'string' || typeof amt2 === 'string') { + return false + } + + const aValue = new BigNumber(amt1.value) + const bValue = new BigNumber(amt2.value) + + return ( + amt1.currency === amt2.currency && + amt1.issuer === amt2.issuer && + aValue.isEqualTo(bValue) + ) +} + +function isPartialPayment( + tx?: Transaction, + metadata?: TransactionMetadata | string, +): boolean { + if (tx == null || metadata == null || tx.TransactionType !== 'Payment') { + return false + } + + let meta = metadata + if (typeof meta === 'string') { + if (meta === 'unavailable') { + return false + } + + /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- binary-codec typing */ + meta = decode(meta) as unknown as TransactionMetadata + } + + const tfPartial = + typeof tx.Flags === 'number' + ? isFlagEnabled(tx.Flags, PaymentTransactionFlags.tfPartialPayment) + : tx.Flags?.tfPartialPayment + + if (!tfPartial) { + return false + } + + const delivered = meta.delivered_amount + const amount = tx.Amount + + if (delivered === undefined) { + return false + } + + return !amountsEqual(delivered, amount) +} + +function txHasPartialPayment(response: TxResponse): boolean { + return isPartialPayment(response.result, response.result.meta) +} + +function txEntryHasPartialPayment(response: TransactionEntryResponse): boolean { + return isPartialPayment(response.result.tx_json, response.result.metadata) +} + +function accountTxHasPartialPayment(response: AccountTxResponse): boolean { + const { transactions } = response.result + const foo = transactions.some((tx) => isPartialPayment(tx.tx, tx.meta)) + return foo +} + +function hasPartialPayment(command: string, response: Response): boolean { + /* eslint-disable @typescript-eslint/consistent-type-assertions -- Request type is known at runtime from command */ + switch (command) { + case 'tx': + return txHasPartialPayment(response as TxResponse) + case 'transaction_entry': + return txEntryHasPartialPayment(response as TransactionEntryResponse) + case 'account_tx': + return accountTxHasPartialPayment(response as AccountTxResponse) + default: + return false + } + /* eslint-enable @typescript-eslint/consistent-type-assertions */ +} + +/** + * Checks a response for a partial payment. + * + * @param command - Command from the request, tells us what response to expect. + * @param response - Response to check for a partial payment. + */ +export function handlePartialPayment( + command: string, + response: Response, +): void { + if (hasPartialPayment(command, response)) { + const warnings = response.warnings ?? [] + + const warning = { + id: WARN_PARTIAL_PAYMENT_CODE, + message: 'This response contains a Partial Payment', + } + + warnings.push(warning) + + response.warnings = warnings + } +} + +/** + * Check a transaction from a subscription stream for partial payment. + * + * @param stream - Stream Transaction to check for partial payment,. + */ +export function handleStreamPartialPayment(stream: TransactionStream): void { + if (isPartialPayment(stream.transaction, stream.meta)) { + const warnings = stream.warnings ?? [] + + const warning = { + id: WARN_PARTIAL_PAYMENT_CODE, + message: 'This response contains a Partial Payment', + } + + warnings.push(warning) + + /* eslint-disable-next-line no-param-reassign -- Handles the case where there are no warnings */ + stream.warnings = warnings + } +} diff --git a/src/models/ledger/ledger.ts b/src/models/ledger/ledger.ts index eb9706f3..7ff61f87 100644 --- a/src/models/ledger/ledger.ts +++ b/src/models/ledger/ledger.ts @@ -1,4 +1,5 @@ import { Transaction } from '../transactions' +import TransactionMetadata from '../transactions/metadata' import LedgerEntry from './ledgerEntry' @@ -16,5 +17,5 @@ export default interface Ledger { parent_hash: string total_coins: string transaction_hash: string - transactions?: Transaction[] + transactions?: Array } diff --git a/src/models/methods/subscribe.ts b/src/models/methods/subscribe.ts index 4ad68697..f198874e 100644 --- a/src/models/methods/subscribe.ts +++ b/src/models/methods/subscribe.ts @@ -75,6 +75,7 @@ export interface TransactionStream extends BaseStream { meta?: TransactionMetadata transaction: Transaction validated?: boolean + warnings?: Array<{ id: number; message: string }> } export interface PeerStatusStream extends BaseStream { diff --git a/test/client/partialPayments.ts b/test/client/partialPayments.ts new file mode 100644 index 00000000..1ce3bf73 --- /dev/null +++ b/test/client/partialPayments.ts @@ -0,0 +1,154 @@ +/* eslint-disable @typescript-eslint/no-explicit-any -- required for formatting transactions */ +import { expect } from 'chai' + +import type { TransactionStream } from '../../src' +import rippled from '../fixtures/rippled' +import { setupClient, teardownClient } from '../setupClient' + +const partialPaymentIOU = rippled.partial_payments.iou +const partialPaymentXRP = rippled.partial_payments.xrp + +describe('client handling of tfPartialPayments', function () { + beforeEach(setupClient) + afterEach(teardownClient) + + it('Tx with no tfPartialPayment', async function () { + this.mockRippled.addResponse('tx', rippled.tx.Payment) + const resp = await this.client.request({ command: 'tx' }) + + expect(resp.warnings).to.equal(undefined) + }) + + it('Tx with IOU tfPartialPayment', async function () { + const mockResponse = { ...rippled.tx.Payment, result: partialPaymentIOU } + this.mockRippled.addResponse('tx', mockResponse) + const resp = await this.client.request({ command: 'tx' }) + + expect(resp.warnings).to.deep.equal([ + { + id: 2001, + message: 'This response contains a Partial Payment', + }, + ]) + }) + + it('Tx with XRP tfPartialPayment', async function () { + const mockResponse = { ...rippled.tx.Payment, result: partialPaymentXRP } + this.mockRippled.addResponse('tx', mockResponse) + const resp = await this.client.request({ command: 'tx' }) + + expect(resp.warnings).to.deep.equal([ + { + id: 2001, + message: 'This response contains a Partial Payment', + }, + ]) + }) + + it('account_tx with no tfPartialPayment', async function () { + this.mockRippled.addResponse('account_tx', rippled.account_tx.normal) + const resp = await this.client.request({ command: 'account_tx' }) + + expect(resp.warnings).to.equal(undefined) + }) + + it('account_tx with IOU tfPartialPayment', async function () { + const partial = { + ...rippled.tx.Payment, + result: partialPaymentIOU, + } + const mockResponse = rippled.account_tx.normal + mockResponse.result.transactions.push({ + tx: partial.result, + meta: partial.result.meta, + } as any) + + this.mockRippled.addResponse('account_tx', mockResponse) + const resp = await this.client.request({ + command: 'account_tx', + account: mockResponse.result.account, + }) + + expect(resp.warnings).to.deep.equal([ + { + id: 2001, + message: 'This response contains a Partial Payment', + }, + ]) + }) + + it('account_tx with XRP tfPartialPayment', async function () { + // TODO: Create fixtues with partial payments instead of using ... + const partial = { ...rippled.tx.Payment, result: partialPaymentXRP } + const mockResponse = rippled.account_tx.normal + mockResponse.result.transactions.push({ + tx: partial.result, + meta: partial.result.meta, + } as any) + + this.mockRippled.addResponse('account_tx', mockResponse) + const resp = await this.client.request({ + command: 'account_tx', + account: mockResponse.result.account, + }) + + expect(resp.warnings).to.deep.equal([ + { + id: 2001, + message: 'This response contains a Partial Payment', + }, + ]) + }) + + it('transaction_entry with no tfPartialPayment', async function () { + this.mockRippled.addResponse('transaction_entry', rippled.transaction_entry) + const resp = await this.client.request({ command: 'transaction_entry' }) + + expect(resp.warnings).to.equal(undefined) + }) + + it('transaction_entry with XRP tfPartialPayment', async function () { + const mockResponse = rippled.transaction_entry + mockResponse.result.tx_json.Amount = '1000' + this.mockRippled.addResponse('transaction_entry', rippled.transaction_entry) + const resp = await this.client.request({ command: 'transaction_entry' }) + + expect(resp.warnings).to.deep.equal([ + { + id: 2001, + message: 'This response contains a Partial Payment', + }, + ]) + }) + + it('Transactions stream with no tfPartialPayment', async function (done) { + this.mockRippled.addResponse('transaction_entry', rippled.transaction_entry) + this.client.on('transaction', (tx: TransactionStream) => { + expect(tx.warnings).to.equal(undefined) + done() + }) + + this.client.connection.onMessage( + JSON.stringify(rippled.streams.transaction), + ) + }) + + it('Transactions stream with XRP tfPartialPayment', async function (done) { + this.mockRippled.addResponse('transaction_entry', rippled.transaction_entry) + this.client.on('transaction', (tx: TransactionStream) => { + expect(tx.warnings).to.deep.equal([ + { + id: 2001, + message: 'This response contains a Partial Payment', + }, + ]) + done() + }) + + const partial: any = rippled.streams.transaction + partial.transaction = rippled.tx.Payment.result + partial.meta.delivered_amount = '1000' + partial.transaction.Flags = 0x00020000 + this.client.connection.onMessage(JSON.stringify(partial)) + }) +}) diff --git a/test/fixtures/rippled/accountTx.js b/test/fixtures/rippled/accountTx.js deleted file mode 100644 index 09759213..00000000 --- a/test/fixtures/rippled/accountTx.js +++ /dev/null @@ -1,239 +0,0 @@ -/* eslint-disable max-len */ -'use strict'; -const _ = require('lodash'); -const hashes = require('../hashes'); -const addresses = require('../addresses'); -const AccountSet = require('./tx/accountSet.json'); -const NotFound = require('./tx/notFound.json'); -const binary = require('ripple-binary-codec'); - -module.exports = function(request, options = {}) { - _.defaults(options, { - memos: [{ - Memo: { - MemoFormat: '7274312E352E32', - MemoType: '636C69656E74' - } - }], - hash: hashes.VALID_TRANSACTION_HASH, - validated: true - }); - - let tx = { - Account: addresses.ACCOUNT, - Amount: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '0.001' - }, - Destination: addresses.ISSUER, - Fee: '10', - Flags: 0, - Memos: options.memos, - Paths: [ - [ - { - currency: 'USD', - issuer: addresses.OTHER_ACCOUNT, - type: 48, - type_hex: '0000000000000030' - }, - { - account: addresses.OTHER_ACCOUNT, - currency: 'USD', - issuer: addresses.OTHER_ACCOUNT, - type: 49, - type_hex: '0000000000000031' - } - ] - ], - SendMax: '1112209', - Sequence: 4, - SigningPubKey: '02BC8C02199949B15C005B997E7C8594574E9B02BA2D0628902E0532989976CF9D', - TransactionType: 'Payment', - TxnSignature: '304502204EE3E9D1B01D8959B08450FCA9E22025AF503DEF310E34A93863A85CAB3C0BC5022100B61F5B567F77026E8DEED89EED0B7CAF0E6C96C228A2A65216F0DC2D04D52083' - }; - - let meta = { - AffectedNodes: [ - { - ModifiedNode: { - FinalFields: { - Account: addresses.ACCOUNT, - BookDirectory: '4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5E03E788E09BB000', - BookNode: '0000000000000000', - Flags: 0, - OwnerNode: '0000000000000000', - Sequence: 58, - TakerGets: { - currency: 'USD', - issuer: addresses.OTHER_ACCOUNT, - value: '5.648998' - }, - TakerPays: '6208248802' - }, - LedgerEntryType: 'Offer', - LedgerIndex: '3CFB3C79D4F1BDB1EE5245259372576D926D9A875713422F7169A6CC60AFA68B', - PreviousFields: { - TakerGets: { - currency: 'USD', - issuer: addresses.OTHER_ACCOUNT, - value: '5.65' - }, - TakerPays: '6209350000' - }, - PreviousTxnID: '8F571C346688D89AC1F737AE3B6BB5D976702B171CC7B4DE5CA3D444D5B8D6B4', - PreviousTxnLgrSeq: 348433 - } - }, - { - ModifiedNode: { - FinalFields: { - Balance: { - currency: 'USD', - issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji', - value: '-0.001' - }, - Flags: 131072, - HighLimit: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '1' - }, - HighNode: '0000000000000000', - LowLimit: { - currency: 'USD', - issuer: addresses.OTHER_ACCOUNT, - value: '0' - }, - LowNode: '0000000000000002' - }, - LedgerEntryType: 'RippleState', - LedgerIndex: '4BD1874F8F3A60EDB0C23F5BD43E07953C2B8741B226648310D113DE2B486F01', - PreviousFields: { - Balance: { - currency: 'USD', - issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji', - value: '0' - } - }, - PreviousTxnID: '5B2006DAD0B3130F57ACF7CC5CCAC2EEBCD4B57AAA091A6FD0A24B073D08ABB8', - PreviousTxnLgrSeq: 343703 - } - }, - { - ModifiedNode: { - FinalFields: { - Account: addresses.ACCOUNT, - Balance: '9998898762', - Flags: 0, - OwnerCount: 3, - Sequence: 5 - }, - LedgerEntryType: 'AccountRoot', - LedgerIndex: '4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05', - PreviousFields: { - Balance: '9999999970', - Sequence: 4 - }, - PreviousTxnID: '53354D84BAE8FDFC3F4DA879D984D24B929E7FEB9100D2AD9EFCD2E126BCCDC8', - PreviousTxnLgrSeq: 343570 - } - }, - { - ModifiedNode: { - FinalFields: { - Account: 'r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr', - Balance: '912695302618', - Flags: 0, - OwnerCount: 10, - Sequence: 59 - }, - LedgerEntryType: 'AccountRoot', - LedgerIndex: 'F3E119AAA87AF3607CF87F5523BB8278A83BCB4142833288305D767DD30C392A', - PreviousFields: { - Balance: '912694201420' - }, - PreviousTxnID: '8F571C346688D89AC1F737AE3B6BB5D976702B171CC7B4DE5CA3D444D5B8D6B4', - PreviousTxnLgrSeq: 348433 - } - }, - { - ModifiedNode: { - FinalFields: { - Balance: { - currency: 'USD', - issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji', - value: '-5.5541638883365' - }, - Flags: 131072, - HighLimit: { - currency: 'USD', - issuer: 'r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr', - value: '1000' - }, - HighNode: '0000000000000000', - LowLimit: { - currency: 'USD', - issuer: addresses.OTHER_ACCOUNT, - value: '0' - }, - LowNode: '000000000000000C' - }, - LedgerEntryType: 'RippleState', - LedgerIndex: 'FA1255C2E0407F1945BCF9351257C7C5C28B0F5F09BB81C08D35A03E9F0136BC', - PreviousFields: { - Balance: { - currency: 'USD', - issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji', - value: '-5.5551658883365' - } - }, - PreviousTxnID: '8F571C346688D89AC1F737AE3B6BB5D976702B171CC7B4DE5CA3D444D5B8D6B4', - PreviousTxnLgrSeq: 348433 - } - } - ], - TransactionIndex: 0, - TransactionResult: 'tesSUCCESS' - }; - - let marker = Number(request.marker) || 0; - marker += 1; - if (marker === 5) { - meta.TransactionResult = 'tecINSUFFICIENT_RESERVE'; - } else if (marker === 6) { - tx = _.cloneDeep(AccountSet.result); - meta = tx.meta; - delete tx.meta; - } else if (marker === 7) { - tx.Account = addresses.OTHER_ACCOUNT; - } else if (marker === 8) { - tx.Destination = addresses.THIRD_ACCOUNT; - } else if (marker > 25) { - marker = undefined; - } else if (marker > 15) { - tx.Account = addresses.ISSUER; - tx.Destination = addresses.ACCOUNT; - } - if (request.limit === 13) { - const res = Object.assign({}, NotFound, {id: request.id}); - return JSON.stringify(res); - } - return JSON.stringify({ - id: request.id, - status: 'success', - type: 'response', - result: { - marker: marker == null ? undefined : String(marker), - transactions: [ - { - ledger_index: 348860 - Number(marker || 100), - tx_blob: binary.encode(tx), - meta: binary.encode(meta), - validated: options.validated - } - ] - } - }); -}; diff --git a/test/fixtures/rippled/accountTx.json b/test/fixtures/rippled/accountTx.json new file mode 100644 index 00000000..707334fa --- /dev/null +++ b/test/fixtures/rippled/accountTx.json @@ -0,0 +1,194 @@ +{ + "id": 2, + "result": { + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_index_max": 66496515, + "ledger_index_min": 32570, + "limit": 2, + "marker": { + "ledger": 61965340, + "seq": 0 + }, + "transactions": [ + { + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "AccountTxnID": "4E0AA11CBDD1760DE95B68DF2ABBE75C9698CEB548BEA9789053FCB3EBD444FB", + "Balance": "424021949", + "Domain": "6D64756F31332E636F6D", + "EmailHash": "98B4375E1D753E5B91627516F6D70977", + "Flags": 9568256, + "MessageKey": "0000000000000000000000070000000300", + "OwnerCount": 12, + "RegularKey": "rD9iJmieYHn8jTtPjwwkW2Wm9sVDvPXLoJ", + "Sequence": 385, + "TransferRate": 4294967295 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8", + "PreviousFields": { + "AccountTxnID": "CB1BF910C93D050254C049E9003DA1A265C107E0C8DE4A7CFF55FADFD39D5656", + "Balance": "424021959", + "OwnerCount": 11, + "Sequence": 384 + }, + "PreviousTxnID": "CB1BF910C93D050254C049E9003DA1A265C107E0C8DE4A7CFF55FADFD39D5656", + "PreviousTxnLgrSeq": 61965405 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Flags": 0, + "Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "RootIndex": "3B9C0CE77FCE7BCEE1A68F1E26AC467AF326239D0D816CE705E4A0E2DAD03F6D" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "3B9C0CE77FCE7BCEE1A68F1E26AC467AF326239D0D816CE705E4A0E2DAD03F6D" + } + }, + { + "ModifiedNode": { + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "43EA78783A089B137D5E87610DF3BD4129F989EDD02EFAF6C265924D3A0EF8CE", + "PreviousTxnID": "711C4F606C63076137FAE90ADC36379D7066CF551E96DA6FE2BDAB5ECBFACF2B", + "PreviousTxnLgrSeq": 61965340 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Flags": 0, + "Owner": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "RootIndex": "6C8ECE4529AA025F51059F1B56015A8A5F49987064FBE1E832FF27BFEF3F18CF" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "6C8ECE4529AA025F51059F1B56015A8A5F49987064FBE1E832FF27BFEF3F18CF" + } + }, + { + "CreatedNode": { + "LedgerEntryType": "Check", + "LedgerIndex": "C4A46CCD8F096E994C4B0DEAB6CE98E722FC17D7944C28B95127C2659C47CBEB", + "NewFields": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "DestinationTag": 13, + "SendMax": { + "currency": "USD", + "issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "value": "10" + }, + "Sequence": 384 + } + } + } + ], + "TransactionIndex": 12, + "TransactionResult": "tesSUCCESS" + }, + "tx": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "DestinationTag": 13, + "Fee": "10", + "Flags": 2147483648, + "LastLedgerSequence": 61965654, + "SendMax": { + "currency": "USD", + "issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "value": "10" + }, + "Sequence": 384, + "SigningPubKey": "03CE5C5949DEBBBB5E6D8FA54AC3FA8A3ED4EE1C3E9617571840F9349DE7AEF329", + "TransactionType": "CheckCreate", + "TxnSignature": "304402200EC41D7F6C3C57E697A61EFC0585544399A1ECD7AA233256F6BE785294E2671C022022802AED958D44E6BE5679D41157246A7AA72A4A6CFBF531A39A01CCB6AFEED9", + "date": 668134081, + "hash": "4E0AA11CBDD1760DE95B68DF2ABBE75C9698CEB548BEA9789053FCB3EBD444FB", + "inLedger": 61965653, + "ledger_index": 61965653 + }, + "validated": true + }, + { + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "AccountTxnID": "CB1BF910C93D050254C049E9003DA1A265C107E0C8DE4A7CFF55FADFD39D5656", + "Balance": "424021959", + "Domain": "6D64756F31332E636F6D", + "EmailHash": "98B4375E1D753E5B91627516F6D70977", + "Flags": 9568256, + "MessageKey": "0000000000000000000000070000000300", + "OwnerCount": 11, + "RegularKey": "rD9iJmieYHn8jTtPjwwkW2Wm9sVDvPXLoJ", + "Sequence": 384, + "TransferRate": 4294967295 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8", + "PreviousFields": { + "AccountTxnID": "711C4F606C63076137FAE90ADC36379D7066CF551E96DA6FE2BDAB5ECBFACF2B", + "Balance": "424021969", + "OwnerCount": 10, + "Sequence": 383 + }, + "PreviousTxnID": "711C4F606C63076137FAE90ADC36379D7066CF551E96DA6FE2BDAB5ECBFACF2B", + "PreviousTxnLgrSeq": 61965340 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Flags": 0, + "Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "RootIndex": "3B9C0CE77FCE7BCEE1A68F1E26AC467AF326239D0D816CE705E4A0E2DAD03F6D" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "3B9C0CE77FCE7BCEE1A68F1E26AC467AF326239D0D816CE705E4A0E2DAD03F6D" + } + }, + { + "CreatedNode": { + "LedgerEntryType": "DepositPreauth", + "LedgerIndex": "A43898B685C450DE8E194B24D9D54E62530536A770CCB311BFEE15A27381ABB2", + "NewFields": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Authorize": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX" + } + } + } + ], + "TransactionIndex": 59, + "TransactionResult": "tesSUCCESS" + }, + "tx": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Authorize": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "Fee": "10", + "Flags": 2147483648, + "LastLedgerSequence": 61965405, + "Sequence": 383, + "SigningPubKey": "03CE5C5949DEBBBB5E6D8FA54AC3FA8A3ED4EE1C3E9617571840F9349DE7AEF329", + "TransactionType": "DepositPreauth", + "TxnSignature": "304402206464885794C92713D15141B8C68CD020E5EE0BADB7CA7293CB073F02594BEB6F02205FC46EF82613DB5F2AC583C854B9F3C5FAE223E9C7056CB1625260DD3E0718AC", + "date": 668133130, + "hash": "CB1BF910C93D050254C049E9003DA1A265C107E0C8DE4A7CFF55FADFD39D5656", + "inLedger": 61965405, + "ledger_index": 61965405 + }, + "validated": true + } + ], + "validated": true + }, + "status": "success", + "type": "response" +} diff --git a/test/fixtures/rippled/index.ts b/test/fixtures/rippled/index.ts index 73cefcee..c5b8c4dc 100644 --- a/test/fixtures/rippled/index.ts +++ b/test/fixtures/rippled/index.ts @@ -3,7 +3,7 @@ import notfoundAccountInfo from './accountInfoNotFound.json' import emptyAccountObjects from './accountObjectsEmpty.json' import normalAccountObjects from './accountObjectsNormal.json' import account_offers from './accountOffers' -import normalAccountTx from './accountTx' +import normalAccountTx from './accountTx.json' import fabric from './bookOffers' import usd_xrp from './bookOffersUsdXrp.json' import xrp_usd from './bookOffersXrpUsd.json' @@ -24,6 +24,8 @@ import withoutCloseTime from './ledgerWithoutCloseTime.json' import withPartialPayment from './ledgerWithPartialPayment.json' import withSettingsTx from './ledgerWithSettingsTx.json' import withStateAsHashes from './ledgerWithStateAsHashes.json' +import iouPartialPayment from './partialPaymentIOU.json' +import xrpPartialPayment from './partialPaymentXRP.json' import generate from './pathFind' import sendAll from './pathFindSendAll.json' import sendUSD from './pathFindSendUsd.json' @@ -50,6 +52,7 @@ import successSubmit from './submit.json' import failureSubmit from './submitFailed.json' import successSubscribe from './subscribe.json' import errorSubscribe from './subscribeError.json' +import transaction_entry from './transactionEntry.json' import AccountDelete from './tx/accountDelete.json' import AccountDeleteWithMemo from './tx/accountDeleteWithMemo.json' import AccountSet from './tx/accountSet.json' @@ -132,6 +135,11 @@ const streams = { manifest: manifestStream, } +const partial_payments = { + xrp: xrpPartialPayment, + iou: iouPartialPayment, +} + const account_objects = { normal: normalAccountObjects, empty: emptyAccountObjects, @@ -250,12 +258,14 @@ const rippled = { ledger_data, ledger_entry, ledger_current, + partial_payments, path_find, payment_channel, server_info, streams, submit, subscribe, + transaction_entry, tx, unsubscribe, } diff --git a/test/fixtures/rippled/partialPaymentIOU.json b/test/fixtures/rippled/partialPaymentIOU.json new file mode 100644 index 00000000..711f57f2 --- /dev/null +++ b/test/fixtures/rippled/partialPaymentIOU.json @@ -0,0 +1,116 @@ +{ + "Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX", + "Amount": { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "value": "10" + }, + "Destination": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4", + "Fee": "10000", + "Flags": 131072, + "Sequence": 23295, + "SigningPubKey": "02B205F4B92351AC0EEB04254B636F4C49EF922CFA3CAAD03C6477DA1E04E94B53", + "TransactionType": "Payment", + "TxnSignature": "3045022100FAF247A836D601DE74A515B2AADE31186D8B0DA9C23DE489E09753F5CF4BB81F0220477C5B5BC3AC89F2347744F9E00CCA62267E198489D747578162C4C7D156211D", + "hash": "A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A", + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX", + "Balance": "1930599790", + "Flags": 0, + "OwnerCount": 2, + "Sequence": 23296 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "267C16D24EC42EEF8B03D5BE4E94266B1675FA54AFCE42DE795E02AB61031CBD", + "PreviousFields": { + "Balance": "1930609790", + "Sequence": 23295 + }, + "PreviousTxnID": "0F5396388E91D37BB26C8E24073A57E7C5D51E79AEE4CD855653B8499AE4E3DD", + "PreviousTxnLgrSeq": 22419806 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-9.980959751659681" + }, + "Flags": 2228224, + "HighLimit": { + "currency": "USD", + "issuer": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4", + "value": "1000000" + }, + "HighNode": "0000000000000000", + "LowLimit": { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "value": "0" + }, + "LowNode": "0000000000000423" + }, + "LedgerEntryType": "RippleState", + "LedgerIndex": "C66957AF25229357F9C2D2BA17CE47D88169788EDA7610AD0F29AD5BCB225EE5", + "PreviousFields": { + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-0.0009198315" + } + }, + "PreviousTxnID": "2A01E994D7000000B43DD63825A081B4440A44AB2F6FA0D506158AC9CA6B2869", + "PreviousTxnLgrSeq": 22420532 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-276666.975959" + }, + "Flags": 131072, + "HighLimit": { + "currency": "USD", + "issuer": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX", + "value": "1000000" + }, + "HighNode": "0000000000000000", + "LowLimit": { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "value": "0" + }, + "LowNode": "00000000000002D7" + }, + "LedgerEntryType": "RippleState", + "LedgerIndex": "FFD710AE2074A98D920D00CC352F25744899F069A6C1B9E31DD32D2C6606E615", + "PreviousFields": { + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-276676.975959" + } + }, + "PreviousTxnID": "BB9DFC87E9D4ED09CA2726DDFE83A4A396ED0D6545536322DE17CDACF45C0D5B", + "PreviousTxnLgrSeq": 22419307 + } + } + ], + "delivered_amount": { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "value": "9.980039920159681" + }, + "TransactionIndex": 5, + "TransactionResult": "tesSUCCESS" + } +} diff --git a/test/fixtures/rippled/partialPaymentXRP.json b/test/fixtures/rippled/partialPaymentXRP.json new file mode 100644 index 00000000..cca0515c --- /dev/null +++ b/test/fixtures/rippled/partialPaymentXRP.json @@ -0,0 +1,108 @@ +{ + "Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX", + "Amount": "2000000", + "Destination": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4", + "Fee": "10000", + "Flags": 131072, + "Sequence": 23295, + "SigningPubKey": "02B205F4B92351AC0EEB04254B636F4C49EF922CFA3CAAD03C6477DA1E04E94B53", + "TransactionType": "Payment", + "TxnSignature": "3045022100FAF247A836D601DE74A515B2AADE31186D8B0DA9C23DE489E09753F5CF4BB81F0220477C5B5BC3AC89F2347744F9E00CCA62267E198489D747578162C4C7D156211D", + "hash": "A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A", + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX", + "Balance": "1930599790", + "Flags": 0, + "OwnerCount": 2, + "Sequence": 23296 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "267C16D24EC42EEF8B03D5BE4E94266B1675FA54AFCE42DE795E02AB61031CBD", + "PreviousFields": { + "Balance": "1930609790", + "Sequence": 23295 + }, + "PreviousTxnID": "0F5396388E91D37BB26C8E24073A57E7C5D51E79AEE4CD855653B8499AE4E3DD", + "PreviousTxnLgrSeq": 22419806 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-9.980959751659681" + }, + "Flags": 2228224, + "HighLimit": { + "currency": "USD", + "issuer": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4", + "value": "1000000" + }, + "HighNode": "0000000000000000", + "LowLimit": { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "value": "0" + }, + "LowNode": "0000000000000423" + }, + "LedgerEntryType": "RippleState", + "LedgerIndex": "C66957AF25229357F9C2D2BA17CE47D88169788EDA7610AD0F29AD5BCB225EE5", + "PreviousFields": { + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-0.0009198315" + } + }, + "PreviousTxnID": "2A01E994D7000000B43DD63825A081B4440A44AB2F6FA0D506158AC9CA6B2869", + "PreviousTxnLgrSeq": 22420532 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-276666.975959" + }, + "Flags": 131072, + "HighLimit": { + "currency": "USD", + "issuer": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX", + "value": "1000000" + }, + "HighNode": "0000000000000000", + "LowLimit": { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "value": "0" + }, + "LowNode": "00000000000002D7" + }, + "LedgerEntryType": "RippleState", + "LedgerIndex": "FFD710AE2074A98D920D00CC352F25744899F069A6C1B9E31DD32D2C6606E615", + "PreviousFields": { + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-276676.975959" + } + }, + "PreviousTxnID": "BB9DFC87E9D4ED09CA2726DDFE83A4A396ED0D6545536322DE17CDACF45C0D5B", + "PreviousTxnLgrSeq": 22419307 + } + } + ], + "delivered_amount": "1000000", + "TransactionIndex": 5, + "TransactionResult": "tesSUCCESS" + } +} diff --git a/test/fixtures/rippled/transactionEntry.json b/test/fixtures/rippled/transactionEntry.json new file mode 100644 index 00000000..5f922f6f --- /dev/null +++ b/test/fixtures/rippled/transactionEntry.json @@ -0,0 +1,75 @@ +{ + "id": 4, + "result": { + "ledger_hash": "C3D46598EB9BF92688CE2395496AE3A55E084F57319F1086B7CD5CF002049097", + "ledger_index": 66499739, + "metadata": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK", + "Balance": "1750880278055", + "Flags": 131072, + "MessageKey": "02000000000000000000000000B5F84807633600D3AF4D922486E0ADF9FA5F6359", + "OwnerCount": 0, + "Sequence": 731413 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "D087F0DCA3A847606D90DB606FBB85B7A3334B9AD341E30E98B1653261019708", + "PreviousFields": { + "Balance": "1750984454027", + "Sequence": 731412 + }, + "PreviousTxnID": "9CD134C7FCFFEC73381E32190BA7A963B681CB1A1AE84923AD38A5CD96ABDA47", + "PreviousTxnLgrSeq": 66499731 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Account": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh", + "Balance": "86353857604", + "Flags": 131072, + "OwnerCount": 0, + "Sequence": 314878 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "E50C9EE857E177CE38071B8930F66053C9C86DF9B8ADEDA632CB9DFF50EC0033", + "PreviousFields": { + "Balance": "86249687632" + }, + "PreviousTxnID": "B424651D147B2D6D625AF9F27B4F4030EBBA0DB0A7684D0D84ED23ED0FCC27B2", + "PreviousTxnLgrSeq": 66499738 + } + } + ], + "TransactionIndex": 57, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "104169972" + }, + "tx_json": { + "Account": "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK", + "Amount": "104169972", + "Destination": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh", + "DestinationTag": 109735445, + "Fee": "6000", + "Flags": 2147614720, + "LastLedgerSequence": 66499838, + "Sequence": 731412, + "SigningPubKey": "038944E15BADB379B5A2173B5248F36178DB08ABFF69428266D4068A1A471E3F11", + "TransactionType": "Payment", + "TxnSignature": "3044022060CA007B76E2835EE03AC67B96C9FFF0C946AC19CFD9AAB7E77AB87F8E36239A02204F86B31BCF58C2472334D44364F3FD6528036C415028690F29C85E818DDC676F", + "hash": "3F437BAAC9E713F24DF2954EADB7BE80F608D8B758116298B999BA252DF4816C" + }, + "validated": true, + "warnings": [ + { + "id": 1004, + "message": "This is a reporting server. The default behavior of a reporting server is to only return validated data. If you are looking for not yet validated data, include \"ledger_index : current\" in your request, which will cause this server to forward the request to a p2p node. If the forward is successful the response will include \"forwarded\" : \"true\"" + } + ] + }, + "status": "success", + "type": "response" +}