status-desktop/ui/app/mainui/SendModalHandler.qml

401 lines
17 KiB
QML

import QtQuick 2.15
import SortFilterProxyModel 0.2
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import AppLayouts.Wallet.stores 1.0 as WalletStores
import AppLayouts.Wallet.popups.simpleSend 1.0
import AppLayouts.Wallet.adaptors 1.0
import AppLayouts.Wallet 1.0
import shared.popups.send 1.0
import shared.stores.send 1.0
import utils 1.0
QtObject {
id: root
required property var popupParent
required property int loginType
required property TransactionStore transactionStore
required property WalletStores.CollectiblesStore walletCollectiblesStore
required property WalletStores.TransactionStoreNew transactionStoreNew
/** for ens flows **/
required property string myPublicKey
required property string ensRegisteredAddress
/** TODO: This should probably be a property and not
a function. Needs changes on backend side **/
property var getStatusTokenKey: function() {}
/** for sticker flows **/
required property string stickersMarketAddress
required property string stickersNetworkId
/** Feature flag for single network send until its feature complete **/
required property bool simpleSendEnabled
/** For simple send modal flows, decoupling from transaction store **/
/** curently selected fiat currency symbol **/
required property string currentCurrency
/** Expected model structure:
- name: name of account
- address: wallet address
- color: color of the account
- emoji: emoji selected for the account
- currencyBalance: total currency balance in CurrencyAmount
- accountBalance: balance of selected token + selected chain
**/
required property var walletAccountsModel
/** Expected model structure:
- tokensKey: unique string ID of the token (asset); e.g. "ETH" or contract address
- name: user visible token name (e.g. "Ethereum")
- symbol: user visible token symbol (e.g. "ETH")
- decimals: number of decimal places
- communityId: optional; ID of the community this token belongs to, if any
- marketDetails: object containing props like `currencyPrice` for the computed values below
- balances: submodel[ chainId:int, account:string, balance:BigIntString, iconUrl:string ]
**/
required property var groupedAccountAssetsModel
/** Expected model structure:
- symbol [string] - unique identifier of a collectible
- 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 collectiblesBySymbolModel
/** Expected model structure:
- key [string] - unique identifier of an asset
- decimals [int] - decimals of the token
- marketDetails [QObject] - collectible's contract address
- currencyPrice [CurrenctAmount] - assets market price in CurrencyAmount
**/
required property var tokenBySymbolModel
/**
Expected model structure:
- chainId: network chain id
- chainName: name of network
- iconUrl: network icon url
networks on both mainnet & testnet
**/
required property var flatNetworksModel
/** true if testnet mode is on **/
required property bool areTestNetworksEnabled
/** whether community tokens are shown in send modal
based on a global setting **/
required property bool showCommunityAssetsInSend
/** required function to format currency amount to locale string **/
required property var fnFormatCurrencyAmount
required property var savedAddressesModel
required property var recentRecipientsModel
/** required function to resolve an ens name **/
required property var fnResolveENS
/** required signal to receive resolved ens name address **/
signal ensNameResolved(string resolvedPubKey, string resolvedAddress, string uuid)
signal launchBuyFlowRequested(string accountAddress, string chainId, string tokenKey)
function openSend(params = {}) {
// TODO remove once simple send is feature complete
let sendModalCmp = root.simpleSendEnabled ? simpleSendModalComponent: sendModalComponent
let sendModalInst = sendModalCmp.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
}
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
}
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
}
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
}
openSend(params)
}
function transferOwnership(tokenId, senderAddress) {
let params = {
preSelectedSendType: Constants.SendType.ERC721Transfer,
preSelectedAccountAddress: senderAddress,
preSelectedHoldingID: tokenId,
preSelectedHoldingType: Constants.TokenType.ERC721,
}
openSend(params)
}
function sendToRecipient(recipientAddress) {
let params = {
preSelectedRecipient: recipientAddress
}
openSend(params)
}
function bridgeToken(tokenId, tokenType) {
let params = {
preSelectedSendType: Constants.SendType.Bridge,
preSelectedHoldingID: tokenId ,
preSelectedHoldingType: tokenType,
onlyAssets: true
}
openSend(params)
}
function sendToken(senderAddress, tokenId, tokenType) {
let sendType = Constants.SendType.Transfer
if (tokenType === Constants.TokenType.ERC721) {
sendType = Constants.SendType.ERC721Transfer
} else if(tokenType === Constants.TokenType.ERC1155) {
sendType = Constants.SendType.ERC1155Transfer
}
let params = {
preSelectedSendType: sendType,
preSelectedAccountAddress: senderAddress,
preSelectedHoldingID: tokenId ,
preSelectedHoldingType: tokenType,
}
openSend(params)
}
readonly property Component sendModalComponent: Component {
SendModal {
loginType: root.loginType
store: root.transactionStore
collectiblesStore: root.walletCollectiblesStore
showCustomRoutingMode: !production
onClosed: destroy()
}
}
readonly property Component simpleSendModalComponent: Component {
SimpleSendModal {
id: simpleSendModal
/** TODO: use the newly defined WalletAccountsSelectorAdaptor
in https://github.com/status-im/status-desktop/pull/16834 **/
accountsModel: root.walletAccountsModel
assetsModel: backendHandler.assetsSelectorViewAdaptor.outputAssetsModel
collectiblesModel: backendHandler.collectiblesSelectionAdaptor.model
networksModel: backendHandler.filteredFlatNetworksModel
savedAddressesModel: root.savedAddressesModel
recentRecipientsModel: root.recentRecipientsModel
currentCurrency: root.currentCurrency
fnFormatCurrencyAmount: root.fnFormatCurrencyAmount
fnResolveENS: root.fnResolveENS
onClosed: {
destroy()
root.transactionStoreNew.stopUpdatesForSuggestedRoute()
}
onFormChanged: {
backendHandler.resetRouterValues()
if(allValuesFilledCorrectly()) {
backendHandler.uuid = Utils.uuid()
simpleSendModal.routesLoading = true
root.transactionStoreNew.fetchSuggestedRoutes(backendHandler.uuid,
sendType,
selectedChainId,
selectedAccountAddress,
selectedRecipientAddress,
selectedAmountInBaseUnit,
selectedTokenKey)
}
}
// TODO: this should be called from the Reiew and Sign Modal instead
onReviewSendClicked: {
root.transactionStoreNew.authenticateAndTransfer(backendHandler.uuid, selectedAccountAddress)
}
onLaunchBuyFlow: {
root.launchBuyFlowRequested(selectedAccountAddress, selectedChainId, selectedTokenKey)
}
readonly property var backendHandler: QtObject {
property string uuid
property var fetchedPathModel
readonly property var filteredFlatNetworksModel: SortFilterProxyModel {
sourceModel: root.flatNetworksModel
filters: ValueFilter { roleName: "isTest"; value: root.areTestNetworksEnabled }
}
function routesFetched(returnedUuid, pathModel, errCode, errDescription) {
simpleSendModal.routesLoading = false
if(returnedUuid !== uuid) {
// Suggested routes for a different fetch, ignore
return
}
simpleSendModal.routerErrorCode = errCode
simpleSendModal.routerError = WalletUtils.getRouterErrorBasedOnCode(errCode)
simpleSendModal.routerErrorDetails = "%1 - %2".arg(errCode).arg(
WalletUtils.getRouterErrorDetailsOnCode(errCode, errDescription))
fetchedPathModel = pathModel
// TODO: Handle errors here
}
function transactionSent(returnedUuid, chainId, approvalTx, txHash, error) {
if(returnedUuid !== uuid) {
// Suggested routes for a different fetch, ignore
return
}
if (!!error) {
if (error.includes(Constants.walletSection.authenticationCanceled)) {
return
}
// TODO: handle error here
return
}
close()
}
readonly property var assetsSelectorViewAdaptor: TokenSelectorViewAdaptor {
// TODO: remove all store dependecies and add specific properties to the handler instead
assetsModel: root.groupedAccountAssetsModel
flatNetworksModel: root.flatNetworksModel
currentCurrency: root.currentCurrency
showCommunityAssets: root.showCommunityAssetsInSend
accountAddress: simpleSendModal.selectedAccountAddress
enabledChainIds: [simpleSendModal.selectedChainId]
}
readonly property var collectiblesSelectionAdaptor: CollectiblesSelectionAdaptor {
accountKey: simpleSendModal.selectedAccountAddress
enabledChainIds: [simpleSendModal.selectedChainId]
networksModel: backendHandler.filteredFlatNetworksModel
collectiblesModel: SortFilterProxyModel {
sourceModel: root.collectiblesBySymbolModel
filters: ValueFilter {
roleName: "soulbound"
value: false
}
}
}
readonly property var totalBalanceAggregator: FunctionAggregator {
model: !!backendHandler.fetchedPathModel ?
backendHandler.fetchedPathModel: null
initialValue: "0"
roleName: "txTotalFee"
aggregateFunction: (aggr, value) => SQUtils.AmountsArithmetic.sum(
SQUtils.AmountsArithmetic.fromString(aggr),
SQUtils.AmountsArithmetic.fromString(value)).toString()
onValueChanged: {
let decimals = !!backendHandler.selectedTokenEntry.item ? backendHandler.selectedTokenEntry.item.decimals: 18
let ethFiatValue = !!backendHandler.ethTokenEntry.item ? backendHandler.ethTokenEntry.item.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)
simpleSendModal.estimatedFiatFees = totalFeesInFiat
}
}
readonly property var estimatedTimeAggregator: SumAggregator {
model: !!backendHandler.fetchedPathModel ?
backendHandler.fetchedPathModel: null
roleName: "estimatedTime"
onValueChanged: {
simpleSendModal.estimatedTime = WalletUtils.getLabelForEstimatedTxTime(value)
}
}
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
}
Component.onCompleted: {
root.ensNameResolved.connect(ensNameResolved)
root.transactionStoreNew.suggestedRoutesReady.connect(routesFetched)
root.transactionStoreNew.transactionSent.connect(transactionSent)
}
function resetRouterValues() {
simpleSendModal.estimatedCryptoFees = ""
simpleSendModal.estimatedFiatFees = ""
simpleSendModal.estimatedTime = ""
simpleSendModal.routerErrorCode = ""
simpleSendModal.routerError = ""
simpleSendModal.routerErrorDetails = ""
}
}
}
}
}