From 9f1386638d8ab96a44a0a1fd188eadf0f9a31c43 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Wed, 24 Jun 2026 16:57:54 -0700 Subject: [PATCH 1/2] Use makeMaxSpend to build max-spend transactions Migrate the Send Max flow in SendScene2 and the token migration in MigrateWalletCompletionScene to the new EdgeCurrencyWallet.makeMaxSpend API, which builds the max-spend transaction atomically instead of combining getMaxSpendable and makeSpend. Display-only and reduced-amount call sites continue to use getMaxSpendable. --- CHANGELOG.md | 1 + .../scenes/MigrateWalletCompletionScene.tsx | 20 +++++++++---------- src/components/scenes/SendScene2.tsx | 13 +++++++++--- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12fe47fb7f1..09980fe2517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased (develop) - added: Remote enable/disable of gift card providers via the info server's giftCardInfo config, supporting whole-provider disabling for Phaze and Bitrefill and per-brand disabling for Phaze. +- changed: Use the new `makeMaxSpend` wallet API to build max-spend transactions atomically when sending the maximum and when migrating token balances. ## 4.49.0 (staging) diff --git a/src/components/scenes/MigrateWalletCompletionScene.tsx b/src/components/scenes/MigrateWalletCompletionScene.tsx index f108476266e..b7bc9ce4b61 100644 --- a/src/components/scenes/MigrateWalletCompletionScene.tsx +++ b/src/components/scenes/MigrateWalletCompletionScene.tsx @@ -242,22 +242,17 @@ const MigrateWalletCompletionComponent: React.FC = props => { const hasError = false const successfullyTransferredTokenIds: string[] = [] for (const item of tokenItems) { - let tokenSpendInfo: EdgeSpendInfo = { + const tokenSpendInfo: EdgeSpendInfo = { tokenId: item.tokenId, spendTargets: [{ publicAddress: newPublicAddress }], networkFeeOption: 'standard' } try { - const maxAmount = await oldWallet.getMaxSpendable(tokenSpendInfo) - tokenSpendInfo = { - ...tokenSpendInfo, - spendTargets: [ - { ...tokenSpendInfo.spendTargets[0], nativeAmount: maxAmount } - ] - } + // Build and send the full token balance atomically: const tx = await makeSpendSignAndBroadcast( oldWallet, - tokenSpendInfo + tokenSpendInfo, + true ) successfullyTransferredTokenIds.push(item.tokenId) const txFee = tx.parentNetworkFee ?? tx.networkFee @@ -454,9 +449,12 @@ const MigrateWalletCompletionComponent: React.FC = props => { const makeSpendSignAndBroadcast = async ( wallet: EdgeCurrencyWallet, - spendInfo: EdgeSpendInfo + spendInfo: EdgeSpendInfo, + max: boolean = false ): Promise => { - const edgeUnsignedTransaction = await wallet.makeSpend(spendInfo) + const edgeUnsignedTransaction = max + ? await wallet.makeMaxSpend(spendInfo) + : await wallet.makeSpend(spendInfo) const edgeSignedTransaction = await wallet.signTx(edgeUnsignedTransaction) const edgeBroadcastedTransaction = await wallet.broadcastTx( edgeSignedTransaction diff --git a/src/components/scenes/SendScene2.tsx b/src/components/scenes/SendScene2.tsx index 58aa9578ff5..5354cf6d8a2 100644 --- a/src/components/scenes/SendScene2.tsx +++ b/src/components/scenes/SendScene2.tsx @@ -1584,10 +1584,15 @@ const SendComponent: React.FC = props => { setProcessingAmountChanged(false) return } + let maxEdgeTransaction: EdgeTransaction | undefined if (maxSpendSetter === 0) { spendInfo.spendTargets[0].nativeAmount = '0' // Some currencies error without a nativeAmount - const maxSpendable = await coreWallet.getMaxSpendable(spendInfo) - spendInfo.spendTargets[0].nativeAmount = maxSpendable + // Build the max-spend transaction atomically, then reflect its amount + // back onto the spendInfo so the spending-limit / minimum checks and + // the flip input display use the real max amount: + maxEdgeTransaction = await coreWallet.makeMaxSpend(spendInfo) + spendInfo.spendTargets[0].nativeAmount = + maxEdgeTransaction.spendTargets?.[0]?.nativeAmount ?? '0' } if (spendInfo.spendTargets[0].nativeAmount == null) { flipInputModalRef.current?.setFees({ @@ -1650,7 +1655,9 @@ const SendComponent: React.FC = props => { makeSpendCounter.current++ const localMakeSpendCounter = makeSpendCounter.current - const edgeTx = await coreWallet.makeSpend(spendInfo) + // Reuse the max-spend transaction when it was already built above: + const edgeTx = + maxEdgeTransaction ?? (await coreWallet.makeSpend(spendInfo)) if (localMakeSpendCounter < makeSpendCounter.current) { // This makeSpend result is out of date. Throw it away since a newer one is in flight. // This is not REALLY needed since useAsyncEffect seems to serialize calls into the effect From cf584854c658aa3e3ae9ecd21a765eba9eb2f4a4 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Wed, 24 Jun 2026 17:29:01 -0700 Subject: [PATCH 2/2] test: add missing testIDs for maestro selectors Add testID props to the address Enter button (AddressTile2) and the flip input Max button (ExchangedFlipInput2) so the Send Max maestro flow can drive them by id. --- .../scenes/__snapshots__/SendScene2.ui.test.tsx.snap | 1 + src/components/themed/ExchangedFlipInput2.tsx | 6 +++++- src/components/tiles/AddressTile2.tsx | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap index b42537c0ea4..0e57f390200 100644 --- a/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap @@ -16078,6 +16078,7 @@ exports[`SendScene2 Render SendScene 1`] = ` "opacity": 1, } } + testID="sendAddressEnter" > {showMaxButton ? ( - + {lstrings.string_max_cap} diff --git a/src/components/tiles/AddressTile2.tsx b/src/components/tiles/AddressTile2.tsx index 4ce6c84385a..c4bb9a0d5af 100644 --- a/src/components/tiles/AddressTile2.tsx +++ b/src/components/tiles/AddressTile2.tsx @@ -518,6 +518,7 @@ export const AddressTile2 = React.forwardRef(