diff --git a/src/controllers/transfer/transfer.ts b/src/controllers/transfer/transfer.ts index 8835ae9a36..2e41698197 100644 --- a/src/controllers/transfer/transfer.ts +++ b/src/controllers/transfer/transfer.ts @@ -33,7 +33,7 @@ import { import wait from '../../utils/wait' import { EstimationStatus } from '../estimation/types' import EventEmitter from '../eventEmitter/eventEmitter' -import { SignAccountOpController } from '../signAccountOp/signAccountOp' +import { SignAccountOpController, SigningStatus } from '../signAccountOp/signAccountOp' const CONVERSION_PRECISION = 16 const CONVERSION_PRECISION_POW = BigInt(10 ** CONVERSION_PRECISION) @@ -80,6 +80,15 @@ export class TransferController extends EventEmitter implements ITransferControl */ amount = '' + /** + * The final amount to send. Another property is needed so we could + * differentiate between the amount the user wants to send and the + * amount he could actually send. This is in case the user selected MAX + * but doesn't have anything left to pay the fee with + * Sanitized + */ + finalAmount: bigint = 0n + amountInFiat = '' /** @@ -220,8 +229,8 @@ export class TransferController extends EventEmitter implements ITransferControl this.#selectedToken = token if ( - prevSelectedToken?.address !== token?.address || - prevSelectedToken?.chainId !== token?.chainId + prevSelectedToken?.address !== token.address || + prevSelectedToken?.chainId !== token.chainId ) { if (!token.priceIn.length) { this.amountFieldMode = 'token' @@ -229,6 +238,12 @@ export class TransferController extends EventEmitter implements ITransferControl this.#setAmountAndNotifyUI('') this.#setAmountInFiatAndNotifyUI('') this.#setSWWarningVisibleIfNeeded() + + // upon an update of the selected token, update the accountOp meta + if (this.signAccountOpController?.accountOp.meta?.adjustableAccountOpMode) { + this.signAccountOpController.accountOp.meta.adjustableAccountOpMode.tokenAddress = + token.address + } } } @@ -349,6 +364,74 @@ export class TransferController extends EventEmitter implements ITransferControl return this.addressState.ensAddress || this.addressState.fieldValue } + /** + * Adjust the amount and fee + */ + async adjustTransferAmountAndFee() { + const op = this.signAccountOpController?.accountOp + if ( + !op?.gasFeePayment || + !this.#selectedToken || + this.signAccountOpController?.status?.type !== SigningStatus.ReadyToSign + ) { + this.emitUpdate() + return + } + + this.finalAmount = parseUnits( + getSanitizedAmount(this.amount, this.#selectedToken.decimals), + this.#selectedToken.decimals + ) + if ( + this.signAccountOpController.feeTokenResult!.address === this.#selectedToken.address && + this.signAccountOpController.selectedOption!.paidBy === + this.signAccountOpController.accountOp.accountAddr + ) { + // check if we're sending max amount + // if we are, reduce it minus estimated fee and update the calls + const currentPortfolio = this.#portfolio.getLatestPortfolioState(op.accountAddr) + const currentPortfolioNetwork = currentPortfolio[op.chainId.toString()] + const portfolioToken = currentPortfolioNetwork?.result?.tokens.find( + (token) => token.address === this.#selectedToken!.address + ) + // @justInCase + if (portfolioToken) { + // is max? + if (portfolioToken.amount === this.finalAmount) { + this.finalAmount -= op.gasFeePayment.amount + const userRequest = buildTransferUserRequest({ + selectedAccount: op.accountAddr, + amount: formatUnits(this.finalAmount, this.#selectedToken.decimals), + selectedToken: this.#selectedToken, + recipientAddress: this.isTopUp + ? FEE_COLLECTOR + : getAddressFromAddressState(this.addressState) + }) + + // @justInCase + if (userRequest && userRequest.action.kind === 'calls') { + this.signAccountOpController.accountOp.calls = userRequest.action.calls + } + } + } + } else { + const userRequest = buildTransferUserRequest({ + selectedAccount: op.accountAddr, + amount: formatUnits(this.finalAmount, this.#selectedToken.decimals), + selectedToken: this.#selectedToken, + recipientAddress: this.isTopUp + ? FEE_COLLECTOR + : getAddressFromAddressState(this.addressState) + }) + // @justInCase + if (userRequest && userRequest.action.kind === 'calls') { + this.signAccountOpController.accountOp.calls = userRequest.action.calls + } + } + + this.emitUpdate() + } + async update({ humanizerInfo, selectedToken, @@ -620,7 +703,7 @@ export class TransferController extends EventEmitter implements ITransferControl network ) - const accountOp = { + const accountOp: AccountOp = { accountAddr: this.#selectedAccountData.account.addr, chainId: network.chainId, signingKeyAddr: null, @@ -631,7 +714,10 @@ export class TransferController extends EventEmitter implements ITransferControl signature: null, calls, meta: { - paymasterService: getAmbirePaymasterService(baseAcc, this.#relayerUrl) + paymasterService: getAmbirePaymasterService(baseAcc, this.#relayerUrl), + adjustableAccountOpMode: { + tokenAddress: this.#selectedToken!.address + } } } @@ -656,7 +742,8 @@ export class TransferController extends EventEmitter implements ITransferControl // propagate updates from signAccountOp here this.#signAccountOpSubscriptions.push( this.signAccountOpController.onUpdate(() => { - this.emitUpdate() + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.adjustTransferAmountAndFee() }) ) this.#signAccountOpSubscriptions.push( @@ -667,6 +754,7 @@ export class TransferController extends EventEmitter implements ITransferControl }) ) + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.reestimate() } @@ -694,6 +782,7 @@ export class TransferController extends EventEmitter implements ITransferControl } if (this.signAccountOpController?.estimation.errors.length) { + // eslint-disable-next-line no-console console.log( 'Errors on Transfer re-estimate', this.signAccountOpController.estimation.errors @@ -702,6 +791,7 @@ export class TransferController extends EventEmitter implements ITransferControl } } + // eslint-disable-next-line @typescript-eslint/no-floating-promises loop() } diff --git a/src/libs/accountOp/accountOp.ts b/src/libs/accountOp/accountOp.ts index 02df01cde4..811aba9003 100644 --- a/src/libs/accountOp/accountOp.ts +++ b/src/libs/accountOp/accountOp.ts @@ -75,6 +75,9 @@ export interface AccountOp { setDelegation?: boolean /** Used to determine if the account op is up-to-date with the latest quote */ fromQuoteId?: string + adjustableAccountOpMode?: { + tokenAddress: string + } } flags?: { hideActivityBanner?: boolean diff --git a/src/libs/estimate/ambireEstimation.ts b/src/libs/estimate/ambireEstimation.ts index d88a2bc93c..0d48ea1537 100644 --- a/src/libs/estimate/ambireEstimation.ts +++ b/src/libs/estimate/ambireEstimation.ts @@ -125,6 +125,17 @@ export async function ambireEstimateGas( ? token.availableAmount || token.amount : feeTokenOutcomes[key].amount + // if we're using the estimation in adjustable mode, + // the token getting actioned in the calls could be use to pay the fee + if ( + !token.flags.onGasTank && + op.meta && + op.meta.adjustableAccountOpMode && + op.meta.adjustableAccountOpMode.tokenAddress === token.address + ) { + availableAmount = token.amount + } + // if the token is native and the account type cannot pay for the // transaction with the receiving amount from the estimation, // override the amount to the original, in-account amount. diff --git a/src/libs/transfer/userRequest.ts b/src/libs/transfer/userRequest.ts index b40516d165..31eee24ba8 100644 --- a/src/libs/transfer/userRequest.ts +++ b/src/libs/transfer/userRequest.ts @@ -316,6 +316,6 @@ export { buildClaimWalletRequest, buildMintVestingRequest, buildTransferUserRequest, - prepareIntentUserRequest, - isPlainTextMessage + isPlainTextMessage, + prepareIntentUserRequest }