Skip to content

Commit

Permalink
refactor: send flow (#7608)
Browse files Browse the repository at this point in the history
* feat: Rework SendFormPopup flow by splitting in Nft and Token forms [WiP]

* feat: Rework Send flow by splitting components and simplifying logic

* chore: Revert accidentally commited commented line

* feat: Wire isTransfering to SendNftForm

* feat: Update layer2Parameters when setting transaction details in forms

* fix: Order variable declarations in components by block scope keyword

* fix: Move one-time initialization calls to onMount

* fix: Move utils to a better home and improve imports

* fix: Remove use of calculatedStorageDeposit

* fix: Open in SendNft tab when comming from CollectiblesDetailsView (and select relevant NFT)

* fix: Fix lint/prettier

* feat: Send output and close popup after stronghold unlock

* feat: Move initialExpirationDate init to onMount

* fix: Fix type warnings in sendUtils

* fix: Improve return type of rebuildActivity

* fix: Set reopenPopup to true for stronghold profiles

* fix: Honour disableAssetSelection when rendering tabs

* feat: Show expirationDate properly in SendConfirmation after unlock stronghold popup

* chore: Replace the old "Send" components with the new ones

* feat: Do not display gifted storarge deposit in SendConfirmationPopup activity

* feat: Prevent crash on SendConfirmation when preparedOutput prop is undefined

* fix: Initialize expirationDate picker when comming from deeplinks

---------

Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
  • Loading branch information
msarcev and begonaalvarezd authored Nov 13, 2023
1 parent 11e1e33 commit 3d9a52e
Show file tree
Hide file tree
Showing 8 changed files with 557 additions and 306 deletions.
203 changes: 83 additions & 120 deletions packages/desktop/components/popups/send/SendConfirmationPopup.svelte
Original file line number Diff line number Diff line change
@@ -1,148 +1,137 @@
<script lang="ts">
import { onMount } from 'svelte'
import { get } from 'svelte/store'
import { FontWeight, Tab, TextHintVariant, TextType } from 'shared/components/enums'
import {
ActivityInformation,
BasicActivityDetails,
Button,
ExpirationTimePicker,
KeyValueBox,
NftActivityDetails,
Text,
TextHint,
Toggle,
FontWeight,
TextType,
NftActivityDetails,
BasicActivityDetails,
ActivityInformation,
} from 'shared/components'
import { Tab, TextHintVariant } from 'shared/components/enums'
} from '@ui'
import { prepareOutput, selectedAccount } from '@core/account'
import { localize } from '@core/i18n'
import { checkActiveProfileAuth, isActiveLedgerProfile } from '@core/profile'
import { TimePeriod } from '@core/utils'
import { ActivityDirection, ActivityType, InclusionState, ActivityAction, TokenStandard } from '@core/wallet/enums'
import { newTransactionDetails, updateNewTransactionDetails, NewTransactionType } from '@core/wallet/stores'
import { TokenStandard } from '@core/wallet/enums'
import { newTransactionDetails, NewTransactionType, updateNewTransactionDetails } from '@core/wallet/stores'
import { sendOutput } from '@core/wallet/actions'
import {
getOutputParameters,
validateSendConfirmation,
getAddressFromSubject,
getStorageDepositFromOutput,
getOutputParameters,
getDefaultTransactionOptions,
} from '@core/wallet/utils'
import { closePopup, openPopup, PopupId, updatePopupProps } from '@auxiliary/popup'
import { Activity, NewTokenTransactionDetails } from '@core/wallet/types'
import { NewTokenTransactionDetails, NftActivity, TransactionActivity, VestingActivity } from '@core/wallet/types'
import { CommonOutput, Output } from '@iota/sdk/out/types'
import { ledgerPreparedOutput } from '@core/ledger'
import { getStorageDepositFromOutput } from '@core/wallet/utils/generateActivity/helper'
import { handleError } from '@core/error/handlers/handleError'
import {
ACCOUNTS_CONTRACT,
CONTRACT_FUNCTIONS,
getDestinationNetworkFromAddress,
TARGET_CONTRACTS,
TRANSFER_ALLOWANCE,
} from '@core/layer-2'
import { ToggleColor } from '@ui/inputs/Toggle.svelte'
import { getInitialExpirationDate, rebuildActivity } from '@core/wallet/utils/send/sendUtils'
export let _onMount: (..._: any[]) => Promise<void> = async () => {}
export let isSendAndClosePopup: boolean = false
export let disableBack = false
export let isCallbackFromUnlockStronghold: boolean = false
export let calculatedStorageDeposit: number = 0
export let preparedOutput: Output
let {
const transactionDetails = get(newTransactionDetails)
const {
type: transactionType,
recipient,
expirationDate,
giftStorageDeposit,
surplus,
disableChangeExpiration,
disableToggleGift,
layer2Parameters,
disableToggleGift,
disableChangeExpiration,
} = get(newTransactionDetails)
let storageDeposit = calculatedStorageDeposit
let minimumStorageDeposit = 0
let preparedOutput: Output
let expirationTimePicker: ExpirationTimePicker
let visibleSurplus: number | undefined = undefined
let { surplus, expirationDate, giftStorageDeposit } = get(newTransactionDetails)
let initialExpirationDate: TimePeriod
let activeTab: Tab
let activity: Partial<TransactionActivity | VestingActivity | NftActivity> | undefined = undefined
let expirationTimePicker: ExpirationTimePicker
let initialExpirationDate: TimePeriod
let storageDeposit: number = 0
let minimumStorageDeposit = 0
let visibleSurplus: number | undefined = undefined
$: transactionDetails = get(newTransactionDetails)
$: isInternal = recipient.type === 'account'
$: expirationTimePicker?.setNull(giftStorageDeposit)
$: isBaseTokenTransfer =
transactionDetails.type === NewTransactionType.TokenTransfer &&
transactionDetails.asset?.metadata?.standard === TokenStandard.BaseToken
$: hideGiftToggle = isBaseTokenTransfer || !!layer2Parameters || (disableToggleGift && !giftStorageDeposit)
$: expirationDate, giftStorageDeposit, refreshSendConfirmationState()
$: isInternal = recipient.type === 'account'
$: isTransferring = $selectedAccount.isTransferring
$: hideGiftToggle = isBaseTokenTransfer || !!layer2Parameters || (disableToggleGift && !giftStorageDeposit)
$: if (!isSendAndClosePopup) expirationDate, giftStorageDeposit, void rebuildTransactionOutput()
let activity: Activity | undefined = undefined
$: activity = {
...transactionDetails,
assetId: transactionDetails.type === NewTransactionType.TokenTransfer ? transactionDetails.asset.id : undefined,
$: activity = rebuildActivity(
transactionDetails,
recipient,
storageDeposit,
subject: recipient,
visibleSurplus,
isInternal,
id: undefined,
outputId: undefined,
transactionId: undefined,
time: undefined,
containsValue: true,
isAssetHidden: false,
asyncData: undefined,
giftedStorageDeposit: 0,
surplus: visibleSurplus,
type: ActivityType.Basic,
direction: ActivityDirection.Outgoing,
inclusionState: InclusionState.Pending,
action: ActivityAction.Send,
destinationNetwork: getDestinationNetworkFromAddress(layer2Parameters?.networkAddress),
...(layer2Parameters?.networkAddress && {
parsedLayer2Metadata: {
ethereumAddress: getAddressFromSubject(recipient),
targetContract: TARGET_CONTRACTS[ACCOUNTS_CONTRACT],
contractFunction: CONTRACT_FUNCTIONS[TRANSFER_ALLOWANCE],
gasBudget: layer2Parameters?.gasBudget ?? 0,
},
}),
}
layer2Parameters
)
onMount(async () => {
await updateStorageDeposit()
if (isSendAndClosePopup) {
// Needed after 'return from stronghold' to SHOW to correct expiration date before output is sent
initialExpirationDate = getInitialExpirationDate(expirationDate, storageDeposit, giftStorageDeposit)
function refreshSendConfirmationState(): void {
if (!isCallbackFromUnlockStronghold) {
if (
transactionDetails.type === NewTransactionType.NftTransfer &&
Number($selectedAccount.balances.baseCoin.available) === 0
) {
giftStorageDeposit = true
disableChangeExpiration = true
disableToggleGift = true
try {
await _onMount()
} catch (err) {
handleError(err)
}
updateNewTransactionDetails({ type: transactionDetails.type, expirationDate, giftStorageDeposit, surplus })
void prepareTransactionOutput()
} else {
initialExpirationDate = getInitialExpirationDate()
}
}
})
function getInitialExpirationDate(): TimePeriod {
if (expirationDate) {
return TimePeriod.Custom
} else if (storageDeposit && !giftStorageDeposit) {
return TimePeriod.OneDay
} else {
return TimePeriod.None
}
}
async function rebuildTransactionOutput(): Promise<void> {
updateNewTransactionDetails({
type: transactionType,
expirationDate,
giftStorageDeposit,
})
async function prepareTransactionOutput(): Promise<void> {
try {
const transactionDetails = get(newTransactionDetails)
const outputParams = await getOutputParameters(transactionDetails)
preparedOutput = await prepareOutput($selectedAccount.index, outputParams, getDefaultTransactionOptions())
await updateStorageDeposit()
// This potentially triggers a second 'prepareOutput',
// as it updates expiration date through the ExpirationTimePicker bind
// Could be avoided with a rework of ExpirationTimePicker
if (transactionDetails.expirationDate === undefined) {
initialExpirationDate = getInitialExpirationDate(expirationDate, storageDeposit, giftStorageDeposit)
}
} catch (err) {
handleError(err)
}
}
async function updateStorageDeposit(): Promise<void> {
if (preparedOutput) {
const { storageDeposit: _storageDeposit, giftedStorageDeposit: _giftedStorageDeposit } =
await getStorageDepositFromOutput($selectedAccount, preparedOutput as CommonOutput)
storageDeposit = minimumStorageDeposit = _storageDeposit > 0 ? _storageDeposit : _giftedStorageDeposit
if (isBaseTokenTransfer) {
const rawAmount = Number((transactionDetails as NewTokenTransactionDetails).rawAmount)
if (rawAmount >= storageDeposit) {
storageDeposit = 0
}
}
// Note: we need to adjust the surplus
// so we make sure that the surplus is always added on top of the minimum storage deposit
if (Number(surplus) > 0) {
Expand All @@ -156,36 +145,18 @@
hideGiftToggle = true
}
}
if (transactionDetails.expirationDate === undefined) {
initialExpirationDate = getInitialExpirationDate()
}
} catch (err) {
handleError(err)
}
}
async function updateStorageDeposit(): Promise<void> {
const { storageDeposit: _storageDeposit, giftedStorageDeposit: _giftedStorageDeposit } =
await getStorageDepositFromOutput($selectedAccount, preparedOutput as CommonOutput)
storageDeposit = minimumStorageDeposit = _storageDeposit > 0 ? _storageDeposit : _giftedStorageDeposit
if (isBaseTokenTransfer) {
const rawAmount = Number((transactionDetails as NewTokenTransactionDetails).rawAmount)
if (rawAmount >= storageDeposit) {
storageDeposit = 0
}
}
function toggleGiftStorageDeposit(): void {
giftStorageDeposit = !giftStorageDeposit
}
async function sendOutputAndClosePopup(): Promise<void> {
await sendOutput(preparedOutput)
closePopup()
}
function toggleGiftStorageDeposit(): void {
giftStorageDeposit = !giftStorageDeposit
}
async function onConfirmClick(): Promise<void> {
try {
validateSendConfirmation(preparedOutput as CommonOutput)
Expand All @@ -194,7 +165,7 @@
ledgerPreparedOutput.set(preparedOutput)
}
updatePopupProps({ isCallbackFromUnlockStronghold: true, calculatedStorageDeposit: storageDeposit })
updatePopupProps({ isSendAndClosePopup: true, preparedOutput })
await checkActiveProfileAuth(sendOutputAndClosePopup, { stronghold: true, ledger: false })
} catch (err) {
handleError(err)
Expand All @@ -212,14 +183,6 @@
function onCancelClick(): void {
closePopup()
}
onMount(async () => {
try {
await _onMount()
} catch (err) {
handleError(err)
}
})
</script>

<send-confirmation-popup class="w-full h-full space-y-6 flex flex-auto flex-col shrink-0">
Expand Down
Loading

0 comments on commit 3d9a52e

Please sign in to comment.