From e351c57e88c0ddabb52a395de3b4f3ed04d6a2ea Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 19 Jun 2026 21:32:17 +0900 Subject: [PATCH] Aliter: Implement DeliverMax alias in Payment transactions, through autofill method (#2689) (#63) --- packages/xahau/src/client/index.ts | 30 +++++++- packages/xahau/test/client/autofill.test.ts | 62 +++++++++++++++++ .../integration/transactions/payment.test.ts | 68 +++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/packages/xahau/src/client/index.ts b/packages/xahau/src/client/index.ts index 5c54fff1..9fffc41a 100644 --- a/packages/xahau/src/client/index.ts +++ b/packages/xahau/src/client/index.ts @@ -660,7 +660,7 @@ class Client extends EventEmitter { * @returns The autofilled transaction. * @throws ValidationError If Amount and DeliverMax fields are not identical in a Payment Transaction */ - + // eslint-disable-next-line complexity -- handling Payment transaction API v2 requires more logic public async autofill( transaction: T, signersCount?: number, @@ -681,6 +681,34 @@ class Client extends EventEmitter { promises.push(setLatestValidatedLedgerSequence(this, tx)) } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property + // @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level + if (tx.TransactionType === 'Payment' && tx.DeliverMax != null) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- This is a valid null check for Amount + if (tx.Amount == null) { + // If only DeliverMax is provided, use it to populate the Amount field + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property + // @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- DeliverMax is a known RPC-level property + tx.Amount = tx.DeliverMax + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property + // @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- This is a valid null check for Amount + if (tx.Amount != null && tx.Amount !== tx.DeliverMax) { + return Promise.reject( + new ValidationError( + 'PaymentTransaction: Amount and DeliverMax fields must be identical when both are provided', + ), + ) + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property + // @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level + delete tx.DeliverMax + } + await Promise.all(promises).then(() => tx) if (tx.Fee == null) { diff --git a/packages/xahau/test/client/autofill.test.ts b/packages/xahau/test/client/autofill.test.ts index eef80a11..e768f1ce 100644 --- a/packages/xahau/test/client/autofill.test.ts +++ b/packages/xahau/test/client/autofill.test.ts @@ -1,12 +1,14 @@ import { assert } from 'chai' import { EscrowFinish, Payment, Transaction } from '../../src' +import { ValidationError } from '../../src/errors' import xahaud from '../fixtures/xahaud' import { setupClient, teardownClient, type XrplTestContext, } from '../setupClient' +import { assertRejects } from '../testUtils' const NetworkID = 1025 const Fee = '10' @@ -15,6 +17,8 @@ const LastLedgerSequence = 2908734 describe('client.autofill', function () { let testContext: XrplTestContext + const AMOUNT = '1234' + let paymentTx: Payment async function setupMockRippledVersionAndID( buildVersion: string, @@ -38,6 +42,64 @@ describe('client.autofill', function () { }) afterAll(async () => teardownClient(testContext)) + beforeEach(async () => { + paymentTx = { + TransactionType: 'Payment', + Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo', + Amount: AMOUNT, + Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + DestinationTag: 1, + Fee: '12', + Flags: 2147483648, + LastLedgerSequence: 65953073, + Sequence: 65923914, + SigningPubKey: + '02F9E33F16DF9507705EC954E3F94EB5F10D1FC4A354606DBE6297DBB1096FE654', + TxnSignature: + '3045022100E3FAE0EDEC3D6A8FF6D81BC9CF8288A61B7EEDE8071E90FF9314CB4621058D10022043545CF631706D700CEE65A1DB83EFDD185413808292D9D90F14D87D3DC2D8CB', + InvoiceID: + '6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B', + Paths: [ + [{ currency: 'BTC', issuer: 'r9vbV3EHvXWjSkeQ6CAcYVPGeq7TuiXY2X' }], + ], + SendMax: '100000000', + } + }) + + it('Validate Payment transaction API v2: Payment Transaction: Specify Only Amount field', async function () { + const txResult = await testContext.client.autofill(paymentTx) + + assert.strictEqual(txResult.Amount, AMOUNT) + }) + + it('Validate Payment transaction API v2: Payment Transaction: Specify Only DeliverMax field', async function () { + // @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions + paymentTx.DeliverMax = paymentTx.Amount + // @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions + delete paymentTx.Amount + const txResult = await testContext.client.autofill(paymentTx) + + assert.strictEqual(txResult.Amount, AMOUNT) + }) + + it('Validate Payment transaction API v2: Payment Transaction: identical DeliverMax and Amount fields', async function () { + // @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions + paymentTx.DeliverMax = paymentTx.Amount + + const txResult = await testContext.client.autofill(paymentTx) + + assert.strictEqual(txResult.Amount, AMOUNT) + assert.strictEqual('DeliverMax' in txResult, false) + }) + + it('Validate Payment transaction API v2: Payment Transaction: differing DeliverMax and Amount fields', async function () { + // @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions + paymentTx.DeliverMax = '6789' + paymentTx.Amount = '1234' + + await assertRejects(testContext.client.autofill(paymentTx), ValidationError) + }) + it('should not autofill if fields are present', async function () { const tx: Transaction = { TransactionType: 'DepositPreauth', diff --git a/packages/xahau/test/integration/transactions/payment.test.ts b/packages/xahau/test/integration/transactions/payment.test.ts index fdfe4b16..391ab317 100644 --- a/packages/xahau/test/integration/transactions/payment.test.ts +++ b/packages/xahau/test/integration/transactions/payment.test.ts @@ -1,3 +1,5 @@ +import { assert } from 'chai' + import { Payment, Wallet } from '../../../src' import serverUrl from '../serverUrl' import { @@ -12,9 +14,22 @@ const TIMEOUT = 20000 describe('Payment', function () { let testContext: XrplIntegrationTestContext + let paymentTx: Payment + const AMOUNT = '10000000' // This wallet is used for DeliverMax related tests let senderWallet: Wallet + beforeEach(async () => { + // this payment transaction JSON needs to be refreshed before every test. + // Because, we tinker with Amount and DeliverMax fields in the API v2 tests + paymentTx = { + TransactionType: 'Payment', + Account: senderWallet.classicAddress, + Amount: AMOUNT, + Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + } + }) + beforeAll(async () => { testContext = await setupClient(serverUrl) senderWallet = await generateFundedWallet(testContext.client) @@ -34,4 +49,57 @@ describe('Payment', function () { }, TIMEOUT, ) + + it( + 'Validate Payment transaction API v2: Payment Transaction: Specify Only Amount field', + async () => { + const result = await testTransaction( + testContext.client, + paymentTx, + senderWallet, + ) + + assert.equal(result.result.engine_result_code, 0) + assert.equal((result.result.tx_json as Payment).Amount, AMOUNT) + }, + TIMEOUT, + ) + + it( + 'Validate Payment transaction API v2: Payment Transaction: Specify Only DeliverMax field', + async () => { + // @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions + paymentTx.DeliverMax = paymentTx.Amount + // @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions + delete paymentTx.Amount + + const result = await testTransaction( + testContext.client, + paymentTx, + senderWallet, + ) + + assert.equal(result.result.engine_result_code, 0) + assert.equal((result.result.tx_json as Payment).Amount, AMOUNT) + }, + TIMEOUT, + ) + + it( + 'Validate Payment transaction API v2: Payment Transaction: identical DeliverMax and Amount fields', + async () => { + // @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions + paymentTx.DeliverMax = paymentTx.Amount + + const result = await testTransaction( + testContext.client, + paymentTx, + senderWallet, + ) + + assert.equal(result.result.engine_result_code, 0) + assert.equal((result.result.tx_json as Payment).Amount, AMOUNT) + }, + TIMEOUT, + ) })