feat(@desktop/wallet): Adapt invocations to New Simple Send Modal

fixes #17023
This commit is contained in:
Khushboo Mehta 2025-01-07 20:06:51 +01:00 committed by Khushboo-dev-cpp
parent 2098300d8f
commit 4b2457a17b
12 changed files with 584 additions and 230 deletions

View File

@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Backpressure 0.1
import Models 1.0
@ -120,9 +121,12 @@ SplitView {
closePolicy: Popup.CloseOnEscape
interactive: interactiveCheckbox.checked
displayOnlyAssets: displayOnlyAssetsCheckbox.checked
transferOwnership: transferOwnershipCheckbox.checked
accountsModel: accountsSelectorAdaptor.processedWalletAccounts
assetsModel: assetsSelectorViewAdaptor.outputAssetsModel
flatCollectiblesModel: collectiblesSelectionAdaptor.filteredFlatModel
collectiblesModel: collectiblesSelectionAdaptor.model
networksModel: d.filteredNetworksModel
@ -548,6 +552,17 @@ SplitView {
checked: true
}
CheckBox {
id: transferOwnershipCheckbox
text: "transfer ownership"
checked: false
}
CheckBox {
id: displayOnlyAssetsCheckbox
text: "displayOnlyAssets"
}
Text {
text: "Select an accounts"
}
@ -586,23 +601,93 @@ SplitView {
}
ComboBox {
id: tokensCombobox
Layout.preferredWidth: 200
model: ConcatModel {
sources: [
SourceModel {
model: assetsSelectorViewAdaptor.outputAssetsModel
model: ObjectProxyModel {
sourceModel: d.walletAssetStore.walletTokensStore.plainTokensBySymbolModel
delegate: SortFilterProxyModel {
readonly property var addressPerChain: this
sourceModel: LeftJoinModel {
leftModel: model.addressPerChain
rightModel: d.filteredNetworksModel
joinRole: "chainId"
}
filters: ValueFilter {
roleName: "isTest"
value: testNetworksCheckbox.checked
}
}
expectedRoles: "addressPerChain"
exposedRoles: "addressPerChain"
}
markerRoleValue: "first_model"
},
SourceModel {
model: collectiblesKeyModel
model: RolesRenamingModel {
sourceModel: collectiblesBySymbolModel
mapping: [
RoleRename {
from: "symbol"
to: "key"
}
]
}
markerRoleValue: "second_model"
}
]
markerRoleName: "which_model"
expectedRoles: ["tokensKey", "name"]
expectedRoles: ["key", "name", "addressPerChain", "chainId", "ownership", "type", "tokenType", "addressPerChain"]
}
delegate: ItemDelegate {
contentItem: RowLayout {
Text {
text: model.name
}
StatusIcon {
icon: {
const iconUrl = ModelUtils.getByKey(NetworksModel.flatNetworks, "chainId", model.chainId, "iconUrl")
if(!!iconUrl)
return Theme.svg(iconUrl)
else return ""
}
}
}
onClicked: {
let tokenType = model.type ?? model.tokenType
if (tokenType === Constants.TokenType.ERC721) {
simpleSend.sendType = Constants.SendType.ERC721Transfer
simpleSend.selectedChainId = model.chainId
const firstAccountOwningSelectedCollectible = ModelUtils.get(model.ownership, 0, "accountAddress")
if(!!firstAccountOwningSelectedCollectible)
simpleSend.selectedAccountAddress = firstAccountOwningSelectedCollectible
}
else if (tokenType === Constants.TokenType.ERC1155) {
simpleSend.sendType = Constants.SendType.ERC1155Transfer
simpleSend.selectedChainId = model.chainId
const firstAccountOwningSelectedCollectible = ModelUtils.get(model.ownership, 0, "accountAddress")
if(!!firstAccountOwningSelectedCollectible)
simpleSend.selectedAccountAddress = firstAccountOwningSelectedCollectible
}
else {
let selectedChainId = ModelUtils.getByKey(model.addressPerChain, "layer", "1", "chainId")
if (!selectedChainId) {
selectedChainId = ModelUtils.get(model.addressPerChain, 0, "chainId")
}
simpleSend.selectedChainId = selectedChainId
simpleSend.sendType = Constants.SendType.Transfer
}
}
highlighted: tokensCombobox.highlightedIndex === index
}
textRole: "name"
valueRole: "tokensKey"
valueRole: "key"
}
RowLayout {
@ -616,13 +701,10 @@ SplitView {
}
}
Button {
text: "update in modal"
onClicked: simpleSend.selectedAmount = amountInput.text
text: "update raw value in modal"
onClicked: simpleSend.selectedRawAmount = amountInput.text
}
}
Text {
text: "amount selected in base unit: " + simpleSend.selectedAmountInBaseUnit
}
Text {
text: "Select a recipient"
@ -651,23 +733,11 @@ SplitView {
text: "token selected is: " + simpleSend.selectedTokenKey
}
Text {
text: "amount entered is: " + simpleSend.selectedAmount
text: "raw amount entered is: " + simpleSend.selectedRawAmount
}
Text {
text: "selected recipient is: \n" + simpleSend.selectedRecipientAddress
}
RolesRenamingModel {
id: collectiblesKeyModel
sourceModel: collectiblesSelectionAdaptor.model
mapping: [
RoleRename {
from: "symbol"
to: "tokensKey"
}
]
}
}
}
}

View File

@ -322,11 +322,8 @@ Item {
const bottomItemText = findChild(amountToSendInput, "bottomItemText")
verify(!!bottomItemText)
verify(!bottomItemText.visible)
const bottomItemTextLoadingComponent = findChild(amountToSendInput, "bottomItemTextLoadingComponent")
verify(!!bottomItemTextLoadingComponent)
verify(bottomItemTextLoadingComponent.visible)
verify(bottomItemText.visible)
verify(bottomItemText.loading)
}
function test_max_button_when_different_tokens_clicked() {

View File

@ -49,7 +49,11 @@ QtObject {
return value
}
return value - Math.max(0.0001, Math.min(0.01, value * 0.1))
const estFee = Math.max(0.0001, Math.min(0.01, value * 0.1))
const result = value - estFee
// Ensure the result is not negative
return Math.max(result, 0)
}
function getLabelForEstimatedTxTime(estimatedFlag) {

View File

@ -75,6 +75,12 @@ QObject {
**/
readonly property alias model: communityGroupsGrouppedByCollection
/** output model which follows same structure as the input collectiblesModel
The only add on here is that this model is not grouped and is filtered
based on account and chainId
**/
readonly property alias filteredFlatModel: initiallyFilteredAndSorted
// In case collectibles are to be shown only on specific networks
property var enabledChainIds: []

View File

@ -27,6 +27,8 @@ Rectangle {
property alias selectedRecipientAddress: recipientInputLoader.selectedRecipientAddress
property alias selectedRecipientType: recipientInputLoader.selectedRecipientType
property bool interactive: true
signal resolveENS(string ensName, string uuid)
function ensNameResolved(resolvedPubKey, resolvedAddress, uuid) {
@ -48,6 +50,8 @@ Rectangle {
Layout.fillWidth: true
interactive: root.interactive
savedAddressesModel: root.savedAddressesModel
myAccountsModel: root.myAccountsModel

View File

@ -55,6 +55,12 @@ RowLayout {
/** input property holds if the header is the sticky header **/
property bool isStickyHeader
/** input property to show only ERC20 assets and no collectibles **/
property bool displayOnlyAssets
/** input property to decide if the header can be interacted with **/
property bool interactive: true
/** input property for programatic selection of network **/
property int selectedChainId
@ -106,8 +112,10 @@ RowLayout {
TokenSelectorButton.Size.Small:
TokenSelectorButton.Size.Normal
enabled: root.interactive
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
collectiblesModel: root.displayOnlyAssets ? null: root.collectiblesModel
onCollectibleSelected: root.collectibleSelected(key)
onCollectionSelected: root.collectionSelected(key)
@ -142,6 +150,7 @@ RowLayout {
multiSelection: false
showSelectionIndicator: false
showTitle: false
selectionAllowed: root.interactive
Binding on selection {
value: [root.selectedChainId]

View File

@ -54,6 +54,12 @@ Control {
/** input property for programatic selection of network **/
property int selectedChainId
/** input property to decide if the header can be interacted with **/
property bool interactive: true
/** input property to show only ERC20 assets and no collectibles **/
property bool displayOnlyAssets
/** signal to propagate that an asset was selected **/
signal assetSelected(string key)
/** signal to propagate that a collection was selected **/
@ -119,6 +125,8 @@ Control {
isStickyHeader: true
isScrolling: root.stickyHeaderVisible
interactive: root.interactive
displayOnlyAssets: root.displayOnlyAssets
networksModel: root.networksModel
assetsModel: root.assetsModel

View File

@ -62,6 +62,29 @@ StatusDialog {
- icon: icon of the subitem
**/
required property var collectiblesModel
/**
This model is needed here not to be used in any visual item but
to evaluate the collectible selected by using the selectedTokenKey.
To do this with the grouped and nested collectiblesModel is very
complex and is adding unnecessary edge cases that need to be handled
expicity.
This model should be already filtered by account and chainId selected
Expected model structure:
symbol [string] - unique identifier of a collectible
chainId [int] - unique identifier of a network
collectionUid [string] - unique identifier of a collection
contractAddress [string] - collectible's contract address
name [string] - collectible's name e.g. "Magicat"
collectionName [string] - collection name e.g. "Crypto Kitties"
mediaUrl [url] - collectible's media url
imageUrl [url] - collectible's image url
communityId [string] - unique identifier of a community for community collectible or empty
ownership [model] - submodel of balances per chain/account
balance [int] - balance (always 1 for ERC-721)
accountAddress [string] - unique identifier of an account
**/
required property var flatCollectiblesModel
/**
Expected model structure:
- chainId: network chain id
@ -78,6 +101,11 @@ StatusDialog {
/** input property to decide if send modal is interactive or prefilled **/
property bool interactive: true
/** input property to show only ERC20 assets and no collectibles **/
property bool displayOnlyAssets
/** input property true if a community owner token is being transferred **/
property bool transferOwnership
/** input property to decide if routes are being fetched **/
property bool routesLoading
@ -103,24 +131,26 @@ StatusDialog {
property int selectedChainId
/** property to set and expose currently selected token key **/
property string selectedTokenKey
/** property to set and expose the amount to send from outside without any localization **/
property string selectedAmount
/** output property to set currently set amount to send
/** property to set and expose the raw amount to send from outside without any localization
Crypto value in a base unit as a string integer,
e.g. 1000000000000000000 for 1 ETH **/
readonly property string selectedAmountInBaseUnit: amountToSend.amount
property string selectedRawAmount
/** input property holds the publicKey of the user for registering an ENS name **/
property string publicKey
/** input property holds the selected ens name to be registered **/
property string ensName
/** input property holds the selected sticker pack id to purchase **/
property string stickersPackId
/** property to check if the form is filled correctly **/
readonly property bool allValuesFilledCorrectly: !!root.selectedAccountAddress &&
root.selectedChainId !== 0 &&
!!root.selectedTokenKey &&
!!root.selectedRecipientAddress &&
!!root.selectedAmount &&
!amountToSend.markAsInvalid &&
amountToSend.valid
/** Output property if a collectible is selected in the the send modal **/
readonly property bool isCollectibleSelected: d.isCollectibleSelected
root.selectedChainId !== 0 &&
!!root.selectedTokenKey &&
!!root.selectedRecipientAddress &&
!!root.selectedRawAmount &&
!amountToSend.markAsInvalid &&
amountToSend.valid
/** TODO: replace with new and improved recipient selector StatusDateRangePicker
TBD under https://github.com/status-im/status-desktop/issues/16916 **/
@ -160,37 +190,64 @@ StatusDialog {
sourceModel: root.assetsModel
key: "tokensKey"
value: root.selectedTokenKey
onItemChanged: d.setAssetInTokenSelector()
onAvailableChanged: d.setAssetInTokenSelector()
}
// Holds if the asset entry is valid
readonly property bool selectedAssetEntryValid: selectedAssetEntry.available &&
!!selectedAssetEntry.item
// Used to set selected asset in token selector
function setAssetInTokenSelector() {
if(selectedAssetEntry.available && !!selectedAssetEntry.item) {
d.setTokenOnBothHeaders(selectedAssetEntry.item.symbol,
Constants.tokenIcon(selectedAssetEntry.item.symbol),
selectedAssetEntry.item.key)
}
}
// Used to get collectible entry if selected token is a collectible
readonly property var selectedCollectibleEntry: ModelEntry {
sourceModel: root.collectiblesModel
sourceModel: root.flatCollectiblesModel
key: "symbol"
value: root.selectedTokenKey
onItemChanged: d.setCollectibleInTokenSelector()
onAvailableChanged: d.setCollectibleInTokenSelector()
}
/** exposes the currently selected token entry **/
readonly property var selectedTokenEntry: selectedAssetEntry.available ?
selectedAssetEntry.item :
selectedCollectibleEntry.available ?
selectedCollectibleEntry.item: null
onSelectedTokenEntryChanged: {
if(!selectedAssetEntry.available && !selectedCollectibleEntry.available) {
// Holds if the collectible entry is valid
readonly property bool selectedCollectibleEntryValid: selectedCollectibleEntry.available &&
!!selectedCollectibleEntry.item
/** Handling the case when an asset is selcted from dropdown
to reset the harcoded "1" set for collectibles
**/
onSelectedCollectibleEntryValidChanged: {
if(!selectedCollectibleEntryValid && root.selectedRawAmount === "1") {
amountToSend.clear()
}
}
// Used to set selected collectible in token selector
function setCollectibleInTokenSelector() {
if(selectedCollectibleEntry.available && !!selectedCollectibleEntry.item) {
const id = selectedCollectibleEntry.item.communityId ?
selectedCollectibleEntry.item.collectionUid :
selectedCollectibleEntry.item.uid
d.setTokenOnBothHeaders(selectedCollectibleEntry.item.name,
selectedCollectibleEntry.item.imageUrl ||
selectedCollectibleEntry.item.mediaUrl,
id)
}
}
// In case no token is found in the models, we reset the token selector
readonly property bool noTokenSelected: !selectedAssetEntryValid && !selectedCollectibleEntryValid
onNoTokenSelectedChanged: {
if(noTokenSelected) {
d.debounceResetTokenSelector()
}
if(selectedAssetEntry.available && !!selectedTokenEntry) {
d.setTokenOnBothHeaders(selectedTokenEntry.symbol,
Constants.tokenIcon(selectedTokenEntry.symbol),
selectedTokenEntry.tokensKey)
}
else if(selectedCollectibleEntry.available && !!selectedTokenEntry) {
const id = selectedTokenEntry.communityId ?
selectedTokenEntry.collectionUid :
selectedTokenEntry.uid
d.setTokenOnBothHeaders(selectedTokenEntry.name,
selectedTokenEntry.imageUrl || selectedTokenEntry.mediaUrl,
id)
}
}
function setTokenOnBothHeaders(name, icon, key) {
@ -198,8 +255,8 @@ StatusDialog {
stickySendModalHeader.setToken(name, icon, key)
}
readonly property var debounceResetTokenSelector: Backpressure.debounce(root, 0, function() {
if(!selectedAssetEntry.available && !selectedCollectibleEntry.available) {
readonly property var debounceResetTokenSelector: Backpressure.debounce(root, 200, function() {
if(!selectedAssetEntryValid && !selectedCollectibleEntryValid) {
// reset token selector in case selected tokens doesnt exist in either models
d.setTokenOnBothHeaders("", "", "")
root.selectedTokenKey = ""
@ -207,25 +264,25 @@ StatusDialog {
})
readonly property var debounceSetSelectedAmount: Backpressure.debounce(root, 1000, function() {
root.selectedAmount = amountToSend.text
if(amountToSend.amount !== "0" && amountToSend.amount !== root.selectedRawAmount)
root.selectedRawAmount = amountToSend.amount
})
readonly property bool isCollectibleSelected: {
if(!selectedTokenEntry)
return false
const type = selectedAssetEntry.available ? selectedAssetEntry.item.type :
selectedCollectibleEntry.available ? selectedCollectibleEntry.item.tokenType :
Constants.TokenType.Unknown
return (type === Constants.TokenType.ERC721 || type === Constants.TokenType.ERC1155)
}
readonly property string selectedCryptoTokenSymbol: !!d.selectedTokenEntry ?
d.selectedTokenEntry.symbol: ""
readonly property string selectedCryptoTokenSymbol: selectedAssetEntryValid ?
selectedAssetEntry.item.symbol:
selectedCollectibleEntryValid ?
selectedCollectibleEntry.item.symbol: ""
readonly property double maxSafeCryptoValue: {
const maxCryptoBalance = !!d.selectedTokenEntry && !!d.selectedTokenEntry.currentBalance ?
d.selectedTokenEntry.currentBalance : 0
return WalletUtils.calculateMaxSafeSendAmount(maxCryptoBalance, d.selectedCryptoTokenSymbol)
if (selectedCollectibleEntryValid) {
let collectibleBalance = SQUtils.ModelUtils.getByKey(selectedCollectibleEntry.item.ownership, "accountAddress", root.selectedAccountAddress, "balance")
return !!collectibleBalance ? collectibleBalance: 0
} else if (selectedAssetEntryValid) {
const maxCryptoBalance = !!d.selectedAssetEntry.item.currentBalance ?
d.selectedAssetEntry.item.currentBalance : 0
return WalletUtils.calculateMaxSafeSendAmount(maxCryptoBalance, d.selectedCryptoTokenSymbol)
}
return 0
}
// handle multiple property changes from single changed signal
@ -234,10 +291,33 @@ StatusDialog {
root.selectedChainId,
root.selectedTokenKey,
root.selectedRecipientAddress,
root.selectedAmount]
root.selectedRawAmount,
root.allValuesFilledCorrectly]
onCombinedPropertyChangedHandlerChanged: Qt.callLater(() => root.formChanged())
readonly property bool errNotEnoughGas: root.routerErrorCode === Constants.routerErrorCodes.router.errNotEnoughNativeBalance
function setRawValue() {
if(!!selectedRawAmount && (amountToSend.amount !== root.selectedRawAmount || amountToSend.empty)) {
amountToSend.setRawValue(root.selectedRawAmount)
}
}
function setSelectedCollectible(key) {
let tokenType = SQUtils.ModelUtils.getByKey(root.flatCollectiblesModel, "symbol", key, "tokenType")
if(tokenType === Constants.TokenType.ERC1155) {
root.sendType = Constants.SendType.ERC1155Transfer
} else if(tokenType === Constants.TokenType.ERC721) {
root.sendType = Constants.SendType.ERC721Transfer
}
root.selectedRawAmount = "1"
root.selectedTokenKey = key
}
function setSelectedAsset(key) {
root.sendType = Constants.SendType.Transfer
root.selectedTokenKey = key
}
}
width: 556
@ -250,11 +330,7 @@ StatusDialog {
}
// Bindings needed for exposing and setting raw values from AmountToSend
onSelectedAmountChanged: {
if(!!selectedAmount && amountToSend.text !== root.selectedAmount) {
amountToSend.setValue(root.selectedAmount)
}
}
onSelectedRawAmountChanged: d.setRawValue()
Item {
id: sendModalcontentItem
@ -310,15 +386,18 @@ StatusDialog {
stickyHeaderVisible: d.stickyHeaderVisible
interactive: root.interactive && !root.transferOwnership
displayOnlyAssets: root.displayOnlyAssets
networksModel: root.networksModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
selectedChainId: root.selectedChainId
onCollectibleSelected: root.selectedTokenKey = key
onCollectionSelected: root.selectedTokenKey = key
onAssetSelected: root.selectedTokenKey = key
onCollectibleSelected: d.setSelectedCollectible(key)
onCollectionSelected: d.setSelectedCollectible(key)
onAssetSelected: d.setSelectedAsset(key)
onNetworkSelected: root.selectedChainId = chainId
}
}
@ -353,6 +432,8 @@ StatusDialog {
Layout.topMargin: 28
isScrolling: d.stickyHeaderVisible
interactive: root.interactive && !root.transferOwnership
displayOnlyAssets: root.displayOnlyAssets
networksModel: root.networksModel
assetsModel: root.assetsModel
@ -360,9 +441,9 @@ StatusDialog {
selectedChainId: root.selectedChainId
onCollectibleSelected: root.selectedTokenKey = key
onCollectionSelected: root.selectedTokenKey = key
onAssetSelected: root.selectedTokenKey = key
onCollectibleSelected: d.setSelectedCollectible(key)
onCollectionSelected: d.setSelectedCollectible(key)
onAssetSelected: d.setSelectedAsset(key)
onNetworkSelected: root.selectedChainId = chainId
}
@ -375,9 +456,7 @@ StatusDialog {
interactive: root.interactive
dividerVisible: true
progressivePixelReduction: false
/** TODO: connect this with suggested routes being fetched as price
gets updated each time a new proposal is fetched
bottomTextLoading: root.suggestedRoutesLoading **/
bottomTextLoading: root.routesLoading
/** TODO: connect to max safe value for eth.
For now simply checking balance in case of both eth and other ERC20's **/
@ -386,29 +465,27 @@ StatusDialog {
selectedSymbol: amountToSend.fiatMode ?
root.currentCurrency:
d.selectedCryptoTokenSymbol
price: !!d.selectedTokenEntry &&
!!d.selectedTokenEntry.marketDetails ?
d.selectedTokenEntry.marketDetails.currencyPrice.amount : 1
multiplierIndex: !!d.selectedTokenEntry &&
!!d.selectedTokenEntry.decimals ?
d.selectedTokenEntry.decimals : 0
price: !!d.selectedAssetEntryValid &&
!!d.selectedAssetEntry.item.marketDetails ?
d.selectedAssetEntry.item.marketDetails.currencyPrice.amount : 1
multiplierIndex: !!d.selectedAssetEntryValid &&
!!d.selectedAssetEntry.item.decimals ?
d.selectedAssetEntry.item.decimals : 0
formatFiat: amount => root.fnFormatCurrencyAmount(
amount, root.currentCurrency)
formatBalance: amount => root.fnFormatCurrencyAmount(
amount, d.selectedCryptoTokenSymbol)
visible: !!root.selectedTokenKey && !d.isCollectibleSelected
visible: d.selectedAssetEntryValid
onVisibleChanged: if(visible) forceActiveFocus()
onTextChanged: d.debounceSetSelectedAmount()
onAmountChanged: d.debounceSetSelectedAmount()
bottomRightComponent: MaxSendButton {
id: maxButton
formattedValue: {
const price = !!d.selectedTokenEntry && !!d.selectedTokenEntry.marketDetails ?
d.selectedTokenEntry.marketDetails.currencyPrice.amount : 0
let maxSafeValue = amountToSend.fiatMode ? d.maxSafeCryptoValue * price : d.maxSafeCryptoValue
let maxSafeValue = amountToSend.fiatMode ? d.maxSafeCryptoValue * amountToSend.price : d.maxSafeCryptoValue
return root.fnFormatCurrencyAmount(
maxSafeValue,
amountToSend.selectedSymbol,
@ -448,6 +525,8 @@ StatusDialog {
Layout.fillHeight: true
Layout.bottomMargin: feesLayout.visible ? 0 : Theme.xlPadding
interactive: root.interactive
savedAddressesModel: root.savedAddressesModel
myAccountsModel: root.accountsModel
recentRecipientsModel: root.recentRecipientsModel

View File

@ -94,7 +94,7 @@ Loader {
implicitWidth: parent.width
modelData: d.savedAddrSelectedEntry.item
radius: 8
clearVisible: true
clearVisible: root.interactive
color: Theme.palette.indirectColor1
sensor.enabled: false
subTitle: {
@ -127,7 +127,7 @@ Loader {
width: parent.width
radius: 8
clearVisible: true
clearVisible: root.interactive
color: Theme.palette.indirectColor1
sensor.enabled: false
subTitle: {

View File

@ -660,13 +660,13 @@ Item {
// for simple send
walletAccountsModel: WalletStores.RootStore.accounts
filteredFlatNetworksModel: WalletStores.RootStore.filteredFlatModel
flatNetworksModel: WalletStores.RootStore.flatNetworks
areTestNetworksEnabled: WalletStores.RootStore.areTestNetworksEnabled
groupedAccountAssetsModel: appMain.walletAssetsStore.groupedAccountAssetsModel
plainTokensBySymbolModel: appMain.tokensStore.plainTokensBySymbolModel
showCommunityAssetsInSend: appMain.tokensStore.showCommunityAssetsInSend
collectiblesBySymbolModel: WalletStores.RootStore.collectiblesStore.jointCollectiblesBySymbolModel
tokenBySymbolModel: appMain.tokensStore.plainTokensBySymbolModel
savedAddressesModel: WalletStores.RootStore.savedAddresses
recentRecipientsModel: appMain.transactionStore.tempActivityController1Model

View File

@ -83,13 +83,6 @@ QtObject {
- accountAddress [string] - unique identifier of an account
**/
required property var collectiblesBySymbolModel
/** Expected model structure:
- key [string] - unique identifier of an asset
- decimals [int] - decimals of the token
- marketDetails [QObject] - collectible's contract address
- currencyPrice [CurrencyAmount] - assets market price in CurrencyAmount
**/
required property var tokenBySymbolModel
/**
Expected model structure:
- chainId: network chain id
@ -98,6 +91,14 @@ QtObject {
networks on both mainnet & testnet
**/
required property var flatNetworksModel
/**
Expected model structure:
- chainId: network chain id
- chainName: name of network
- iconUrl: network icon url
networks on either mainnet OR testnet
**/
required property var filteredFlatNetworksModel
/** true if testnet mode is on **/
required property bool areTestNetworksEnabled
/** whether community tokens are shown in send modal
@ -148,87 +149,171 @@ QtObject {
**/
required property var fnGetOpenSeaUrl
/** property to store the params to be updated in the send modal until it is launched **/
property var simpleSendParams
/** signal to request launch of buy crypto modal **/
signal launchBuyFlowRequested(string accountAddress, int chainId, string tokenKey)
function openSend(params = {}) {
function openSend(params = {}, forceLaunchOldSend = false) {
// TODO remove once simple send is feature complete
let sendModalCmp = root.simpleSendEnabled ? simpleSendModalComponent: sendModalComponent
let sendModalInst = sendModalCmp.createObject(popupParent, params)
sendModalInst.open()
if(root.simpleSendEnabled && !forceLaunchOldSend) {
root.simpleSendParams = params
let sendModalInst = simpleSendModalComponent.createObject(popupParent)
sendModalInst.open()
} else {
let sendModalInst = sendModalComponent.createObject(popupParent, params)
sendModalInst.open()
}
}
function connectUsername(ensName) {
let params = {
preSelectedSendType: Constants.SendType.ENSSetPubKey,
preSelectedHoldingID: Constants.ethToken ,
preSelectedHoldingType: Constants.TokenType.Native,
preDefinedAmountToSend: LocaleUtils.numberToLocaleString(0),
preSelectedRecipient: root.ensRegisteredAddress,
interactive: false,
publicKey: root.myPublicKey,
ensName: ensName
let params = {}
if (root.simpleSendEnabled) {
params = {
sendType: Constants.SendType.ENSSetPubKey,
selectedTokenKey: Constants.ethToken ,
selectedRawAmount: "0",
selectedRecipientAddress: root.ensRegisteredAddress,
interactive: false,
publicKey: root.myPublicKey,
ensName: ensName
}
} else {
params = {
preSelectedSendType: Constants.SendType.ENSSetPubKey,
preSelectedHoldingID: Constants.ethToken ,
preSelectedHoldingType: Constants.TokenType.Native,
preDefinedAmountToSend: LocaleUtils.numberToLocaleString(0),
preSelectedRecipient: root.ensRegisteredAddress,
interactive: false,
publicKey: root.myPublicKey,
ensName: ensName
}
}
openSend(params)
}
function registerUsername(ensName) {
let params = {
preSelectedSendType: Constants.SendType.ENSRegister,
preSelectedHoldingID: root.getStatusTokenKey(),
preSelectedHoldingType: Constants.TokenType.ERC20,
preDefinedAmountToSend: LocaleUtils.numberToLocaleString(10),
preSelectedRecipient: root.ensRegisteredAddress,
interactive: false,
publicKey: root.myPublicKey,
ensName: ensName
let params = {}
if (root.simpleSendEnabled) {
params = {
sendType: Constants.SendType.ENSRegister,
selectedTokenKey: root.getStatusTokenKey(),
// TODO this should come from backend.To be fixed when ENS is reworked
selectedRawAmount: SQUtils.AmountsArithmetic.fromNumber(10, 18).toString(),
selectedRecipientAddress: root.ensRegisteredAddress,
interactive: false,
publicKey: root.myPublicKey,
ensName: ensName
}
} else {
params = {
preSelectedSendType: Constants.SendType.ENSRegister,
preSelectedHoldingID: root.getStatusTokenKey(),
preSelectedHoldingType: Constants.TokenType.ERC20,
preDefinedAmountToSend: LocaleUtils.numberToLocaleString(10),
preSelectedRecipient: root.ensRegisteredAddress,
interactive: false,
publicKey: root.myPublicKey,
ensName: ensName
}
}
openSend(params)
}
function releaseUsername(ensName, senderAddress, chainId) {
let params = {
preSelectedSendType: Constants.SendType.ENSRelease,
preSelectedAccountAddress: senderAddress,
preSelectedHoldingID: Constants.ethToken ,
preSelectedHoldingType: Constants.TokenType.Native,
preDefinedAmountToSend: LocaleUtils.numberToLocaleString(0),
preSelectedChainId: chainId,
preSelectedRecipient: root.ensRegisteredAddress,
interactive: false,
publicKey: root.myPublicKey,
ensName: ensName
let params = {}
if (root.simpleSendEnabled) {
params = {
sendType: Constants.SendType.ENSRelease,
selectedAccountAddress: senderAddress,
selectedTokenKey: Constants.ethToken ,
selectedRawAmount: "0",
selectedChainId: chainId,
selectedRecipientAddress: root.ensRegisteredAddress,
interactive: false,
publicKey: root.myPublicKey,
ensName: ensName
}
} else {
params = {
preSelectedSendType: Constants.SendType.ENSRelease,
preSelectedAccountAddress: senderAddress,
preSelectedHoldingID: Constants.ethToken ,
preSelectedHoldingType: Constants.TokenType.Native,
preDefinedAmountToSend: LocaleUtils.numberToLocaleString(0),
preSelectedChainId: chainId,
preSelectedRecipient: root.ensRegisteredAddress,
interactive: false,
publicKey: root.myPublicKey,
ensName: ensName
}
}
openSend(params)
}
function buyStickerPack(packId, price) {
let params = {
preSelectedSendType: Constants.SendType.StickersBuy,
preSelectedHoldingID: root.getStatusTokenKey(),
preSelectedHoldingType: Constants.TokenType.ERC20,
preDefinedAmountToSend: LocaleUtils.numberToLocaleString(price),
preSelectedChainId: root.stickersNetworkId,
preSelectedRecipient: root.stickersMarketAddress,
interactive: false,
stickersPackId: packId
let params = {}
if (root.simpleSendEnabled) {
params = {
sendType: Constants.SendType.StickersBuy,
selectedTokenKey: root.getStatusTokenKey(),
selectedRawAmount: SQUtils.AmountsArithmetic.fromNumber(price, 18).toString(),
selectedChainId: root.stickersNetworkId,
selectedRecipientAddress: root.stickersMarketAddress,
interactive: false,
stickersPackId: packId
}
} else {
params = {
preSelectedSendType: Constants.SendType.StickersBuy,
preSelectedHoldingID: root.getStatusTokenKey(),
preSelectedHoldingType: Constants.TokenType.ERC20,
preDefinedAmountToSend: LocaleUtils.numberToLocaleString(price),
preSelectedChainId: root.stickersNetworkId,
preSelectedRecipient: root.stickersMarketAddress,
interactive: false,
stickersPackId: packId
}
}
openSend(params)
}
function transferOwnership(tokenId, senderAddress) {
let params = {
preSelectedSendType: Constants.SendType.ERC721Transfer,
preSelectedAccountAddress: senderAddress,
preSelectedHoldingID: tokenId,
preSelectedHoldingType: Constants.TokenType.ERC721,
let selectedChainId =
SQUtils.ModelUtils.getByKey(root.collectiblesBySymbolModel, "symbol", tokenId, "chainId")
let params = {}
if (root.simpleSendEnabled) {
params = {
sendType: Constants.SendType.ERC721Transfer,
selectedAccountAddress: senderAddress,
selectedTokenKey: tokenId,
selectedRawAmount: "1",
selectedChainId: selectedChainId,
transferOwnership: true
}
} else {
params = {
preSelectedSendType: Constants.SendType.ERC721Transfer,
preSelectedAccountAddress: senderAddress,
preSelectedHoldingID: tokenId,
preSelectedHoldingType: Constants.TokenType.ERC721,
}
}
openSend(params)
}
function sendToRecipient(recipientAddress) {
let params = {
preSelectedRecipient: recipientAddress
let params = {}
if (root.simpleSendEnabled) {
params = {
selectedRecipientAddress: recipientAddress
}
} else {
params = {
preSelectedRecipient: recipientAddress
}
}
openSend(params)
}
@ -240,32 +325,79 @@ QtObject {
preSelectedHoldingType: tokenType,
onlyAssets: true
}
openSend(params)
openSend(params, true)
}
function sendToken(senderAddress, tokenId, tokenType) {
let sendType = Constants.SendType.Transfer
let selectedChainId = 0
let selectedRawAmount = ""
if (tokenType === Constants.TokenType.ERC721) {
sendType = Constants.SendType.ERC721Transfer
} else if(tokenType === Constants.TokenType.ERC1155) {
sendType = Constants.SendType.ERC1155Transfer
selectedRawAmount = "1"
selectedChainId =
SQUtils.ModelUtils.getByKey(root.collectiblesBySymbolModel, "symbol", tokenId, "chainId")
}
let params = {
preSelectedSendType: sendType,
preSelectedAccountAddress: senderAddress,
preSelectedHoldingID: tokenId ,
preSelectedHoldingType: tokenType,
else if(tokenType === Constants.TokenType.ERC1155) {
sendType = Constants.SendType.ERC1155Transfer
selectedRawAmount = "1"
selectedChainId =
SQUtils.ModelUtils.getByKey(root.collectiblesBySymbolModel, "symbol", tokenId, "chainId")
}
else {
let layer1chainId = SQUtils.ModelUtils.getByKey(root.filteredFlatNetworksModel, "layer", "1", "chainId")
let networksChainIdArray = SQUtils.ModelUtils.modelToFlatArray(root.filteredFlatNetworksModel, "chainId")
let selectedAssetAddressPerChain =
SQUtils.ModelUtils.getByKey(root.plainTokensBySymbolModel, "key", tokenId, "addressPerChain")
// check if layer address is found
selectedChainId = SQUtils.ModelUtils.getByKey(selectedAssetAddressPerChain, "chainId", layer1chainId, "chainId")
// if not layer 1 chain id found, select the first one is list
if (!selectedChainId) {
selectedChainId = SQUtils.ModelUtils.getFirstModelEntryIf(
selectedAssetAddressPerChain,
(addPerChain) => {
return networksChainIdArray.includes(addPerChain.chainId)
})
}
}
let params = {}
if (root.simpleSendEnabled) {
params = {
sendType: sendType,
selectedAccountAddress: senderAddress,
selectedTokenKey: tokenId,
selectedRawAmount: selectedRawAmount,
selectedChainId: selectedChainId
}
} else {
params = {
preSelectedSendType: sendType,
preSelectedAccountAddress: senderAddress,
preSelectedHoldingID: tokenId,
preSelectedHoldingType: tokenType,
}
}
openSend(params)
}
function openTokenPaymentRequest(recipientAddress, symbol, rawAmount, chainId) {
const params = {
preSelectedHoldingID: symbol,
preSelectedHoldingType: Constants.TokenType.ERC20,
preDefinedRawAmountToSend: rawAmount,
preSelectedChainId: chainId,
preSelectedRecipient: recipientAddress
let params = {}
if (root.simpleSendEnabled) {
params = {
selectedTokenKey: symbol,
selectedRawAmount: rawAmount,
selectedChainId: chainId,
selectedRecipientAddress: recipientAddress,
interactive: false
}
} else {
params = {
preSelectedHoldingID: symbol,
preSelectedHoldingType: Constants.TokenType.ERC20,
preDefinedRawAmountToSend: rawAmount,
preSelectedChainId: chainId,
preSelectedRecipient: recipientAddress
}
}
openSend(params)
}
@ -289,8 +421,9 @@ QtObject {
accountsModel: handler.accountsSelectorAdaptor.processedWalletAccounts
assetsModel: handler.assetsSelectorViewAdaptor.outputAssetsModel
flatCollectiblesModel: handler.collectiblesSelectionAdaptor.filteredFlatModel
collectiblesModel: handler.collectiblesSelectionAdaptor.model
networksModel: handler.filteredFlatNetworksModel
networksModel: root.filteredFlatNetworksModel
savedAddressesModel: root.savedAddressesModel
recentRecipientsModel: root.recentRecipientsModel
@ -298,6 +431,46 @@ QtObject {
fnFormatCurrencyAmount: root.fnFormatCurrencyAmount
fnResolveENS: root.fnResolveENS
onOpened: {
if(root.simpleSendParams.interactive !== undefined) {
interactive = root.simpleSendParams.interactive
}
if(root.simpleSendParams.displayOnlyAssets !== undefined) {
displayOnlyAssets = root.simpleSendParams.displayOnlyAssets
}
if(root.simpleSendParams.sendType !== undefined) {
sendType = root.simpleSendParams.sendType
}
if(root.simpleSendParams.selectedAccountAddress !== undefined &&
!!root.simpleSendParams.selectedAccountAddress) {
selectedAccountAddress = root.simpleSendParams.selectedAccountAddress
}
if(root.simpleSendParams.selectedTokenKey !== undefined) {
selectedTokenKey = root.simpleSendParams.selectedTokenKey
}
if(root.simpleSendParams.selectedChainId !== undefined) {
selectedChainId = root.simpleSendParams.selectedChainId
}
if(root.simpleSendParams.selectedRawAmount !== undefined) {
selectedRawAmount = root.simpleSendParams.selectedRawAmount
}
if(root.simpleSendParams.selectedRecipientAddress !== undefined) {
selectedRecipientAddress = root.simpleSendParams.selectedRecipientAddress
}
if(root.simpleSendParams.publicKey !== undefined) {
publicKey = root.simpleSendParams.publicKey
}
if(root.simpleSendParams.ensName !== undefined) {
ensName = root.simpleSendParams.ensName
}
if(root.simpleSendParams.stickersPackId !== undefined) {
stickersPackId = root.simpleSendParams.stickersPackId
}
if(root.simpleSendParams.transferOwnership !== undefined) {
transferOwnership = root.simpleSendParams.transferOwnership
}
}
onClosed: {
destroy()
root.transactionStoreNew.stopUpdatesForSuggestedRoute()
@ -308,22 +481,47 @@ QtObject {
if(allValuesFilledCorrectly) {
handler.uuid = Utils.uuid()
simpleSendModal.routesLoading = true
let tokenKey = selectedTokenKey
/** TODO: This special handling for collectibles should ideally not
be needed, howver is needed because of current implementation and
collectible token id is contractAddress:tokenId **/
if(sendType === Constants.SendType.ERC1155Transfer ||
sendType === Constants.SendType.ERC721Transfer) {
const selectedCollectible =
SQUtils.ModelUtils.getByKey(root.collectiblesBySymbolModel, "symbol", selectedTokenKey)
if(!!selectedCollectible &&
!!selectedCollectible.contractAddress &&
!!selectedCollectible.tokenId) {
tokenKey = "%1:%2".arg(
selectedCollectible.contractAddress).arg(
selectedCollectible.tokenId)
}
}
root.transactionStoreNew.fetchSuggestedRoutes(handler.uuid,
sendType,
selectedChainId,
selectedAccountAddress,
selectedRecipientAddress,
selectedAmountInBaseUnit,
selectedTokenKey)
selectedRawAmount,
tokenKey,
/*amountOut = */ "0",
/*toToken =*/ "",
handler.extraParamsJson)
}
}
onReviewSendClicked: {
if (handler.selectedCollectibleEntry.available &&
!!handler.selectedCollectibleEntry.item) {
root.fnGetDetailedCollectible(simpleSendModal.selectedChainId ,
handler.selectedCollectibleEntry.item.contractAddress,
handler.selectedCollectibleEntry.item.tokenId)
if(sendType === Constants.SendType.ERC1155Transfer ||
sendType === Constants.SendType.ERC721Transfer) {
const selectedCollectible =
SQUtils.ModelUtils.getByKey(root.collectiblesBySymbolModel, "symbol", selectedTokenKey)
if(!!selectedCollectible &&
!!selectedCollectible.contractAddress &&
!!selectedCollectible.tokenId) {
root.fnGetDetailedCollectible(simpleSendModal.selectedChainId ,
selectedCollectible.contractAddress,
selectedCollectible.tokenId)
}
}
Global.openPopup(sendSignModalCmp)
}
@ -336,10 +534,15 @@ QtObject {
id: handler
property string uuid
property var fetchedPathModel
readonly property var filteredFlatNetworksModel: SortFilterProxyModel {
sourceModel: root.flatNetworksModel
filters: ValueFilter { roleName: "isTest"; value: root.areTestNetworksEnabled }
readonly property string extraParamsJson: {
if (!!simpleSendModal.stickersPackId) {
return JSON.stringify({[Constants.suggestedRoutesExtraParamsProperties.packId]: simpleSendModal.stickersPackId})
}
if (!!simpleSendModal.ensName && !!simpleSendModal.publicKey) {
return JSON.stringify({[Constants.suggestedRoutesExtraParamsProperties.username]: simpleSendModal.ensName,
[Constants.suggestedRoutesExtraParamsProperties.publicKey]: simpleSendModal.publicKey})
}
return ""
}
function routesFetched(returnedUuid, pathModel, errCode, errDescription) {
@ -374,7 +577,7 @@ QtObject {
accounts: root.walletAccountsModel
assetsModel: root.groupedAccountAssetsModel
tokensBySymbolModel: root.plainTokensBySymbolModel
filteredFlatNetworksModel: handler.filteredFlatNetworksModel
filteredFlatNetworksModel: root.filteredFlatNetworksModel
selectedTokenKey: simpleSendModal.selectedTokenKey
selectedNetworkChainId: simpleSendModal.selectedChainId
@ -397,7 +600,7 @@ QtObject {
accountKey: simpleSendModal.selectedAccountAddress
enabledChainIds: [simpleSendModal.selectedChainId]
networksModel: handler.filteredFlatNetworksModel
networksModel: root.filteredFlatNetworksModel
collectiblesModel: SortFilterProxyModel {
sourceModel: root.collectiblesBySymbolModel
filters: ValueFilter {
@ -405,7 +608,7 @@ QtObject {
value: false
}
}
filterCommunityOwnerAndMasterTokens: true
filterCommunityOwnerAndMasterTokens: !simpleSendModal.transferOwnership
}
readonly property var totalFeesAggregator: FunctionAggregator {
@ -419,8 +622,9 @@ QtObject {
SQUtils.AmountsArithmetic.fromString(value)).toString()
onValueChanged: {
let decimals = !!handler.ethTokenEntry.item ? handler.ethTokenEntry.item.decimals: 18
let ethFiatValue = !!handler.ethTokenEntry.item ? handler.ethTokenEntry.item.marketDetails.currencyPrice.amount: 1
const ethToken = SQUtils.ModelUtils.getByKey(root.plainTokensBySymbolModel, "key", Constants.ethToken)
let decimals = !!ethToken ? ethToken.decimals: 18
let ethFiatValue = !!ethToken ? ethToken.marketDetails.currencyPrice.amount: 1
let totalFees = SQUtils.AmountsArithmetic.div(SQUtils.AmountsArithmetic.fromString(value), SQUtils.AmountsArithmetic.fromNumber(1, decimals))
let totalFeesInFiat = root.fnFormatCurrencyAmount(ethFiatValue*totalFees, root.currentCurrency).toString()
simpleSendModal.estimatedCryptoFees = root.fnFormatCurrencyAmount(totalFees.toString(), Constants.ethToken)
@ -428,7 +632,6 @@ QtObject {
}
}
readonly property var estimatedTimeAggregator: FunctionAggregator {
model: !!handler.fetchedPathModel ?
handler.fetchedPathModel: null
@ -442,25 +645,6 @@ QtObject {
}
}
readonly property var selectedTokenEntry: ModelEntry {
sourceModel: root.tokenBySymbolModel
key: "key"
value: simpleSendModal.selectedTokenKey
}
readonly property var ethTokenEntry: ModelEntry {
sourceModel: root.tokenBySymbolModel
key: "key"
value: Constants.ethToken
}
readonly property var selectedCollectibleEntry: ModelEntry {
sourceModel: simpleSendModal.isCollectibleSelected ?
root.collectiblesBySymbolModel: null
value: simpleSendModal.selectedTokenKey
key: "symbol"
}
Component.onCompleted: {
root.ensNameResolved.connect(ensNameResolved)
root.transactionStoreNew.suggestedRoutesReady.connect(routesFetched)
@ -486,8 +670,8 @@ QtObject {
chainId: simpleSendModal.selectedChainId
networksModel: root.flatNetworksModel
tokenKey: simpleSendModal.selectedTokenKey
tokenBySymbolModel: root.tokenBySymbolModel
selectedAmountInBaseUnit: simpleSendModal.selectedAmountInBaseUnit
tokenBySymbolModel: root.plainTokensBySymbolModel
selectedAmountInBaseUnit: simpleSendModal.selectedRawAmount
}
Component {
@ -523,7 +707,8 @@ QtObject {
loginType: root.loginType
isCollectible: simpleSendModal.isCollectibleSelected
isCollectible: simpleSendModal.sendType === Constants.SendType.ERC1155Transfer ||
simpleSendModal.sendType === Constants.SendType.ERC721Transfer
isCollectibleLoading: root.isDetailedCollectibleLoading
collectibleContractAddress: root.detailedCollectible.contractAddress
collectibleTokenId: root.detailedCollectible.tokenId

View File

@ -295,7 +295,7 @@ Control {
RowLayout {
Layout.fillWidth: true
StatusBaseText {
StatusTextWithLoadingState {
id: bottomItem
objectName: "bottomItemText"
@ -317,7 +317,8 @@ Control {
elide: Text.ElideMiddle
font.pixelSize: 13
color: Theme.palette.directColor5
customColor: Theme.palette.directColor5
loading: root.bottomTextLoading
MouseArea {
objectName: "amountToSend_mouseArea"
@ -352,8 +353,6 @@ Control {
}
HoverHandler { id: hoverHandler }
visible: !root.bottomTextLoading
}
StatusIcon {
Layout.preferredWidth: 16
@ -371,12 +370,5 @@ Control {
Layout.alignment: Qt.AlignVCenter
}
}
LoadingComponent {
objectName: "bottomItemTextLoadingComponent"
Layout.preferredWidth: bottomItem.width
Layout.preferredHeight: bottomItem.height
visible: root.bottomTextLoading
}
}
}