Skip to content
102 changes: 96 additions & 6 deletions src/controllers/transfer/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 = ''

/**
Expand Down Expand Up @@ -220,15 +229,21 @@ 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'
}
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
}
}
}

Expand Down Expand Up @@ -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
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the else logic? op.calls already holds the transaction, doesn't it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're updating the calls depending on the chosen fee token. If the first case is "send max USDT and pay in USDT", we will perform the "if" calculation. But if the user then changes to another fee option instead, we enter the "else" and allow the user to send the full max USDT he has. Otherwise, he would have sent the subtracted value


this.emitUpdate()
}

async update({
humanizerInfo,
selectedToken,
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
}
}

Expand All @@ -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(
Expand All @@ -667,6 +754,7 @@ export class TransferController extends EventEmitter implements ITransferControl
})
)

// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.reestimate()
}

Expand Down Expand Up @@ -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
Expand All @@ -702,6 +791,7 @@ export class TransferController extends EventEmitter implements ITransferControl
}
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
loop()
}

Expand Down
3 changes: 3 additions & 0 deletions src/libs/accountOp/accountOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/libs/estimate/ambireEstimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/libs/transfer/userRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,6 @@ export {
buildClaimWalletRequest,
buildMintVestingRequest,
buildTransferUserRequest,
prepareIntentUserRequest,
isPlainTextMessage
isPlainTextMessage,
prepareIntentUserRequest
}
Loading