feat(@desktop/wallet): Fetch multi tx details (#12002)

closes #11897
This commit is contained in:
Cuteivist 2023-08-30 14:10:59 +02:00 committed by GitHub
parent a42ac076ca
commit db350dc36e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 375 additions and 140 deletions

View File

@ -3,6 +3,7 @@ import tables, stint, sets
import model
import entry
import entry_details
import recipients_model
import events_handler
import status
@ -41,6 +42,7 @@ QtObject:
currentActivityFilter: backend_activity.ActivityFilter
currencyService: currency_service.Service
tokenService: token_service.Service
activityDetails: ActivityDetails
eventsHandler: EventsHandler
status: Status
@ -183,6 +185,38 @@ QtObject:
error "failed to find pending transaction with identity: ", identity
ptIndex += 1
proc fetchTxDetails*(self: Controller, id: string, isMultiTx: bool, isPending: bool) {.slot.} =
self.activityDetails = newActivityDetails(id, isMultiTx)
if isPending:
return
try:
let amountToCurrencyConvertor = proc(amount: UInt256, symbol: string): CurrencyAmount =
return currencyAmountToItem(self.currencyService.parseCurrencyValue(symbol, amount),
self.currencyService.getCurrencyFormat(symbol))
if isMultiTx:
let res = backend_activity.getMultiTxDetails(parseInt(id))
if res.error != nil:
error "failed to fetch multi tx details: ", id
return
self.activityDetails = newActivityDetails(res.result, amountToCurrencyConvertor)
else:
let res = backend_activity.getTxDetails(id)
if res.error != nil:
error "failed to fetch tx details: ", id
return
self.activityDetails = newActivityDetails(res.result, amountToCurrencyConvertor)
except Exception as e:
let errDescription = e.msg
error "error: ", errDescription
return
proc getActivityDetails(self: Controller): QVariant {.slot.} =
return newQVariant(self.activityDetails)
QtProperty[QVariant] activityDetails:
read = getActivityDetails
proc processResponse(self: Controller, response: JsonNode) =
defer: self.status.setLoadingData(false)

View File

@ -47,7 +47,6 @@ QtObject:
extradata: ExtraData
totalFees: CurrencyAmount
maxTotalFees: CurrencyAmount
amountCurrency: CurrencyAmount
noAmount: CurrencyAmount
@ -57,7 +56,6 @@ QtObject:
proc delete*(self: ActivityEntry) =
self.QObject.delete
proc newMultiTransactionActivityEntry*(mt: MultiTransactionDto, metadata: backend.ActivityEntry, extradata: ExtraData, valueConvertor: AmountToCurrencyConvertor): ActivityEntry =
new(result, delete)
result.multi_transaction = mt
@ -83,7 +81,6 @@ QtObject:
result.extradata = extradata
result.totalFees = valueConvertor(stint.fromHex(UInt256, tr.totalFees), "Gwei")
result.maxTotalFees = valueConvertor(stint.fromHex(UInt256, tr.maxTotalFees), "Gwei")
result.amountCurrency = valueConvertor(
if metadata.activityType == backend.ActivityType.Receive: metadata.amountIn else: metadata.amountOut,
tr.symbol
@ -122,6 +119,16 @@ QtObject:
raise newException(Defect, "ActivityEntry is not a MultiTransaction")
return self.multi_transaction
proc getId*(self: ActivityEntry): string {.slot.} =
if self.isMultiTransaction():
return $self.multi_transaction.id
elif self.transaction != nil:
return self.transaction[].id
return ""
QtProperty[string] id:
read = getId
proc getSender*(self: ActivityEntry): string {.slot.} =
return if self.metadata.sender.isSome(): "0x" & self.metadata.sender.unsafeGet().toHex() else: ""
@ -218,26 +225,6 @@ QtObject:
QtProperty[QVariant] totalFees:
read = getTotalFees
proc getMaxTotalFees*(self: ActivityEntry): QVariant {.slot.} =
if self.transaction == nil:
error "getMaxTotalFees: ActivityEntry is not an transaction entry"
return newQVariant(self.noAmount)
return newQVariant(self.maxTotalFees)
# TODO: used only in details, move it to a entry_details.nim. See #11598
QtProperty[QVariant] maxTotalFees:
read = getMaxTotalFees
proc getInput*(self: ActivityEntry): string {.slot.} =
if self.transaction == nil:
error "getInput: ActivityEntry is not an transactio entry"
return ""
return self.transaction[].input
# TODO: used only in details, move it to a entry_details.nim. See #11598
QtProperty[string] input:
read = getInput
proc getTxType*(self: ActivityEntry): int {.slot.} =
return self.metadata.activityType.int
@ -245,6 +232,7 @@ QtObject:
read = getTxType
proc getTokenType*(self: ActivityEntry): string {.slot.} =
let s = if self.transaction != nil: self.transaction[].symbol else: ""
if self.transaction != nil:
return self.transaction[].typeValue
if self.isInTransactionType() and self.metadata.tokenOut.isSome:
@ -285,23 +273,6 @@ QtObject:
QtProperty[string] tokenAddress:
read = getTokenAddress
proc getContract*(self: ActivityEntry): string {.slot.} =
return if self.metadata.contractAddress.isSome(): "0x" & self.metadata.contractAddress.unsafeGet().toHex() else: ""
QtProperty[string] contract:
read = getContract
proc getTxHash*(self: ActivityEntry): string {.slot.} =
if self.transaction != nil and len(self.transaction[].txHash) > 0:
return self.transaction[].txHash
if self.metadata.transaction.isSome:
return self.metadata.transaction.unsafeGet().hash
return ""
# TODO: used only in details, move it to a entry_details.nim. See #11598
QtProperty[string] txHash:
read = getTxHash
proc getTokenID*(self: ActivityEntry): string {.slot.} =
if self.metadata.payloadType == backend.PayloadType.MultiTransaction:
error "getTokenID: ActivityEntry is not a transaction"
@ -321,26 +292,6 @@ QtObject:
QtProperty[string] tokenID:
read = getTokenID
# TODO: used only in details, move it to a entry_details.nim. See #11598
proc getNonce*(self: ActivityEntry): string {.slot.} =
if self.transaction == nil:
error "getNonce: ActivityEntry is not an transaction entry"
return ""
return $self.transaction[].nonce
QtProperty[string] nonce:
read = getNonce
proc getBlockNumber*(self: ActivityEntry): string {.slot.} =
if self.transaction == nil:
error "getBlockNumber: ActivityEntry is not an transaction entry"
return ""
return $self.transaction[].blockNumber
# TODO: used only in details, move it to a entry_details.nim. See #11598
QtProperty[string] blockNumber:
read = getBlockNumber
proc getOutAmount*(self: ActivityEntry): float {.slot.} =
return float(self.extradata.outAmount)

View File

@ -0,0 +1,120 @@
import NimQml, json, stint, strutils
import backend/activity as backend
import app/modules/shared_models/currency_amount
import web3/ethtypes as eth
import web3/conversions
type
AmountToCurrencyConvertor* = proc (amount: UInt256, symbol: string): CurrencyAmount
QtObject:
type
ActivityDetails* = ref object of QObject
id*: string
multiTxId: int
nonce*: int
blockNumber*: int
protocolType*: Option[backend.ProtocolType]
txHash*: string
input*: string
contractAddress: Option[eth.Address]
maxTotalFees: CurrencyAmount
proc setup(self: ActivityDetails) =
self.QObject.setup
proc delete*(self: ActivityDetails) =
self.QObject.delete
proc getMaxTotalFees(maxFee: string, gasLimit: string): string =
return (stint.fromHex(Uint256, maxFee) * stint.fromHex(Uint256, gasLimit)).toHex
proc newActivityDetails*(id: string, isMultiTx: bool): ActivityDetails =
new(result, delete)
if isMultiTx:
result.multiTxId = parseInt(id)
else:
result.id = id
result.maxTotalFees = newCurrencyAmount()
result.setup()
proc newActivityDetails*(e: JsonNode, valueConvertor: AmountToCurrencyConvertor): ActivityDetails =
new(result, delete)
const protocolTypeField = "protocolType"
const hashField = "hash"
const contractAddressField = "contractAddress"
const inputField = "input"
result = ActivityDetails(
id: e["id"].getStr(),
multiTxId: e["multiTxId"].getInt(),
nonce: e["nonce"].getInt(),
blockNumber: e["blockNumber"].getInt()
)
let maxFeePerGas = e["maxFeePerGas"].getStr()
let gasLimit = e["gasLimit"].getStr()
if len(maxFeePerGas) > 0 and len(gasLimit) > 0:
let maxTotalFees = getMaxTotalFees(maxFeePerGas, gasLimit)
result.maxTotalFees = valueConvertor(stint.fromHex(UInt256, maxTotalFees), "Gwei")
else:
result.maxTotalFees = newCurrencyAmount()
if e.hasKey(hashField) and e[hashField].kind != JNull:
result.txHash = e[hashField].getStr()
if e.hasKey(protocolTypeField) and e[protocolTypeField].kind != JNull:
result.protocolType = some(fromJson(e[protocolTypeField], backend.ProtocolType))
if e.hasKey(inputField) and e[inputField].kind != JNull:
result.input = e[inputField].getStr()
if e.hasKey(contractAddressField) and e[contractAddressField].kind != JNull:
var contractAddress: eth.Address
fromJson(e[contractAddressField], contractAddressField, contractAddress)
result.contractAddress = some(contractAddress)
result.setup()
proc getNonce*(self: ActivityDetails): int {.slot.} =
return self.nonce
QtProperty[int] nonce:
read = getNonce
proc getBlockNumber*(self: ActivityDetails): int {.slot.} =
return self.blockNumber
QtProperty[int] blockNumber:
read = getBlockNumber
proc getProtocol*(self: ActivityDetails): string {.slot.} =
if self.protocolType.isSome():
return $self.protocolType.unsafeGet()
return ""
QtProperty[string] protocol:
read = getProtocol
proc getTxHash*(self: ActivityDetails): string {.slot.} =
return self.txHash
QtProperty[string] txHash:
read = getTxHash
proc getInput*(self: ActivityDetails): string {.slot.} =
return self.input
QtProperty[string] input:
read = getInput
proc getContract*(self: ActivityDetails): string {.slot.} =
return if self.contractAddress.isSome(): "0x" & self.contractAddress.unsafeGet().toHex() else: ""
QtProperty[string] contract:
read = getContract
proc getMaxTotalFees*(self: ActivityDetails): QVariant {.slot.} =
return newQVariant(self.maxTotalFees)
QtProperty[QVariant] maxTotalFees:
read = getMaxTotalFees

View File

@ -30,28 +30,28 @@ type
id*: string
typeValue*: string
address*: string
blockNumber*: string
blockNumber*: string # TODO remove, fetched separately in details
blockHash*: string
contract*: string
timestamp*: UInt256
gasPrice*: string
gasLimit*: string
gasLimit*: string # TODO remove, fetched separately in details
gasUsed*: string
nonce*: string
nonce*: string # TODO remove, fetched separately in details
txStatus*: string
value*: string
tokenId*: UInt256
fromAddress*: string
to*: string
chainId*: int
maxFeePerGas*: string
maxFeePerGas*: string # TODO remove, fetched separately in details
maxPriorityFeePerGas*: string
input*: string
txHash*: string
input*: string # TODO remove, fetched separately in details
txHash*: string # TODO remove, fetched separately in details
multiTransactionID*: int
baseGasFees*: string
totalFees*: string
maxTotalFees*: string
maxTotalFees*: string # TODO remove, fetched separately in details
additionalData*: string
symbol*: string

View File

@ -18,6 +18,7 @@ const noLimitTimestampForPeriod* = 0
const eventActivityFilteringDone*: string = "wallet-activity-filtering-done"
const eventActivityGetRecipientsDone*: string = "wallet-activity-get-recipients-result"
const eventActivityGetOldestTimestampDone*: string = "wallet-activity-get-oldest-timestamp-result"
const eventActivityFetchTransactionDetails*: string = "wallet-activity-fetch-transaction-details-result"
type
Period* = object
@ -218,6 +219,29 @@ proc `%`*(pt: PayloadType): JsonNode {.inline.} =
proc fromJson*(jn: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} =
return cast[PayloadType](jn.getInt())
# Mirrors status-go/services/wallet/activity/activity.go ProtocolType
type
ProtocolType* {.pure} = enum
Hop = 1
Uniswap
# Define toJson proc for ProtocolType
proc `%`*(pt: ProtocolType): JsonNode {.inline.} =
return newJInt(ord(pt))
# Define fromJson proc for ProtocolType
proc fromJson*(jn: JsonNode, T: typedesc[ProtocolType]): ProtocolType {.inline.} =
return cast[ProtocolType](jn.getInt())
proc `$`*(pt: ProtocolType): string {.inline.} =
case pt:
of Hop:
return "Hop"
of Uniswap:
return "Uniswap"
else:
return ""
# Mirrors status-go/services/wallet/activity/activity.go TransferType
type
TransferType* {.pure.} = enum
@ -259,7 +283,6 @@ type
chainIdOut*: Option[ChainId]
chainIdIn*: Option[ChainId]
transferType*: Option[TransferType]
contractAddress*: Option[eth.Address]
# Mirrors services/wallet/activity/service.go ErrorCode
ErrorCode* = enum
@ -287,7 +310,6 @@ proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.
const chainIdOutField = "chainIdOut"
const chainIdInField = "chainIdIn"
const transferTypeField = "transferType"
const contractAddressField = "contractAddress"
result = T(
payloadType: fromJson(e["payloadType"], PayloadType),
transaction: if e.hasKey("transaction"):
@ -325,10 +347,6 @@ proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.
result.chainIdIn = some(fromJson(e[chainIdInField], ChainId))
if e.hasKey(transferTypeField) and e[transferTypeField].kind != JNull:
result.transferType = some(fromJson(e[transferTypeField], TransferType))
if e.hasKey(contractAddressField) and e[contractAddressField].kind != JNull:
var address: eth.Address
fromJson(e[contractAddressField], contractAddressField, address)
result.contractAddress = some(address)
proc `$`*(self: ActivityEntry): string =
let transactionStr = if self.transaction.isSome: $self.transaction.get()
@ -420,3 +438,9 @@ proc fromJson*(e: JsonNode, T: typedesc[GetOldestTimestampResponse]): GetOldestT
rpc(getOldestActivityTimestampAsync, "wallet"):
requestId: int32
addresses: seq[string]
rpc(getMultiTxDetails, "wallet"):
id: int
rpc(getTxDetails, "wallet"):
id: string

View File

@ -33,18 +33,27 @@ Item {
onTransactionChanged: {
d.decodedInputData = ""
if (!transaction || !transaction.input)
if (!transaction)
return
d.loadingInputDate = true
RootStore.fetchDecodedTxData(transaction.txHash, transaction.input)
RootStore.fetchTxDetails(transaction.id, transaction.isMultiTransaction, transaction.isPending)
d.details = RootStore.getTxDetails()
if (!!d.details && !!d.details.input) {
d.loadingInputDate = true
RootStore.fetchDecodedTxData(d.details.txHash, d.details.input)
}
}
QtObject {
id: d
property var details: null
readonly property bool isDetailsValid: details !== undefined && !!details
readonly property bool isIncoming: transactionType === Constants.TransactionType.Received
readonly property string networkShortName: root.isTransactionValid ? RootStore.getNetworkShortName(transaction.chainId) : ""
readonly property string networkIcon: isTransactionValid ? RootStore.getNetworkIcon(transaction.chainId) : "network/Network=Custom"
readonly property int blockNumber: root.isTransactionValid ? RootStore.hex2Dec(root.transaction.blockNumber) : 0
readonly property int blockNumber: isDetailsValid ? details.blockNumber : 0
readonly property int toBlockNumber: 0 // TODO fill when bridge data is implemented
readonly property string networkShortNameOut: networkShortName
readonly property string networkShortNameIn: transactionHeader.isMultiTransaction ? RootStore.getNetworkShortName(transaction.chainIdOut) : ""
@ -61,7 +70,7 @@ Item {
if (!root.isTransactionValid || transactionHeader.isMultiTransaction)
return ""
const formatted = RootStore.formatCurrencyAmount(transaction.amount, transaction.symbol)
return symbol || !transaction.contract ? formatted : "%1 (%2)".arg(formatted).arg(Utils.compactAddress(transaction.tokenAddress, 4))
return symbol || (!d.isDetailsValid || !d.details.contract) ? formatted : "%1 (%2)".arg(formatted).arg(Utils.compactAddress(transaction.tokenAddress, 4))
}
readonly property string outFiatValueFormatted: {
if (!root.isTransactionValid || !transactionHeader.isMultiTransaction || !outSymbol)
@ -74,7 +83,7 @@ Item {
const formatted = RootStore.formatCurrencyAmount(transaction.outAmount, transaction.outSymbol)
return outSymbol || !transaction.tokenOutAddress ? formatted : "%1 (%2)".arg(formatted).arg(Utils.compactAddress(transaction.tokenOutAddress, 4))
}
readonly property real feeEthValue: root.isTransactionValid ? RootStore.getGasEthValue(transaction.totalFees.amount, 1) : 0 // TODO use directly?
readonly property real feeEthValue: root.isTransactionValid ? RootStore.getFeeEthValue(transaction.totalFees) : 0
readonly property real feeFiatValue: root.isTransactionValid ? RootStore.getFiatValue(d.feeEthValue, Constants.ethToken, RootStore.currentCurrency) : 0 // TODO use directly?
readonly property int transactionType: root.isTransactionValid ? transaction.txType : Constants.TransactionType.Send
@ -89,7 +98,7 @@ Item {
Connections {
target: RootStore.walletSectionInst
function onTxDecoded(txHash: string, dataDecoded: string) {
if (!root.isTransactionValid || txHash !== root.transaction.txHash)
if (!root.isTransactionValid || (d.isDetailsValid && txHash !== d.details.txHash))
return
if (!dataDecoded) {
d.loadingInputDate = false
@ -168,7 +177,7 @@ Item {
nftUrl: root.isTransactionValid && !!transaction.nftImageUrl ? transaction.nftImageUrl : ""
strikethrough: d.transactionType === Constants.TransactionType.Destroy
tokenId: root.isTransactionValid ? transaction.tokenID : ""
contractAddress: root.isTransactionValid ? transaction.contract : ""
contractAddress: d.isDetailsValid ? d.details.contract : ""
}
Column {
@ -269,7 +278,7 @@ Item {
}
TransactionDataTile {
id: contractDeploymentTile
readonly property bool hasValue: root.isTransactionValid && !!root.transaction.contract
readonly property bool hasValue: d.isDetailsValid && !!d.details.contract
&& transactionHeader.transactionStatus !== Constants.TransactionStatus.Pending
&& transactionHeader.transactionStatus !== Constants.TransactionStatus.Failed
width: parent.width
@ -281,11 +290,11 @@ Item {
} else if (!hasValue) {
return qsTr("Awaiting contract address...")
}
return qsTr("Contract created") + "\n" + transaction.contract
return qsTr("Contract created") + "\n" + d.details.contract
}
buttonIconName: hasValue ? "more" : ""
statusListItemSubTitle.customColor: hasValue ? Theme.palette.directColor1 : Theme.palette.directColor5
onButtonClicked: addressMenu.openContractMenu(this, transaction.contract, transactionHeader.networkName, d.symbol)
onButtonClicked: addressMenu.openContractMenu(this, d.details.contract, transactionHeader.networkName, d.symbol)
components: [
Loader {
anchors.verticalCenter: parent.verticalCenter
@ -308,18 +317,16 @@ Item {
TransactionDataTile {
width: parent.width
title: qsTr("Using")
buttonIconName: "external"
subTitle: "" // TODO fill protocol name for Swap and Bridge
asset.name: "" // TODO fill protocol icon for Bridge and Swap e.g. Style.svg("network/Network=Arbitrum")
onButtonClicked: {
// TODO handle
}
subTitle: d.isDetailsValid ? d.details.protocol : ""
asset.name: d.isDetailsValid && d.details.protocol ? Style.svg("protocol/Protocol=%1".arg(d.details.protocol)) : Style.svg("network/Network=Custom")
iconSettings.bgRadius: iconSettings.bgWidth / 2
// buttonIconName: "external" // TODO handle external link #11982
visible: !!subTitle
}
TransactionDataTile {
width: parent.width
title: qsTr("%1 Tx hash").arg(transactionHeader.networkName)
subTitle: root.isTransactionValid ? root.transaction.txHash : ""
subTitle: d.isDetailsValid ? d.details.txHash : ""
visible: !!subTitle
buttonIconName: "more"
onButtonClicked: addressMenu.openTxMenu(this, subTitle, d.networkShortName)
@ -342,7 +349,7 @@ Item {
}
TransactionContractTile {
// Used to display contract address for any network
address: root.isTransactionValid ? transaction.contract : ""
address: d.isDetailsValid ? d.details.contract : ""
symbol: {
if (!root.isTransactionValid)
return ""
@ -453,7 +460,7 @@ Item {
Layout.fillHeight: true
Layout.fillWidth: true
title: qsTr("Nonce")
subTitle: root.isTransactionValid ? RootStore.hex2Dec(root.transaction.nonce) : ""
subTitle: d.isDetailsValid ? d.details.nonce : ""
visible: !!subTitle
}
}
@ -467,8 +474,8 @@ Item {
return ""
} else if (!!d.decodedInputData) {
return d.decodedInputData.substring(0, 200)
} else if (root.isTransactionValid) {
return String(root.transaction.input).substring(0, 200)
} else if (d.isDetailsValid) {
return String(d.details.input).substring(0, 200)
}
return ""
}
@ -492,7 +499,7 @@ Item {
statusListItemTertiaryTitle.anchors.top: undefined
statusListItemTertiaryTitle.anchors.baseline: statusListItemTitle.baseline
statusListItemTertiaryTitle.font: statusListItemTitle.font
onButtonClicked: addressMenu.openInputDataMenu(this, !!d.decodedInputData ? d.decodedInputData : root.transaction.input)
onButtonClicked: addressMenu.openInputDataMenu(this, !!d.decodedInputData ? d.decodedInputData : d.details.input)
Loader {
anchors {
@ -610,10 +617,10 @@ Item {
width: parent.width
title: d.symbol ? qsTr("Fees") : qsTr("Estimated max fee")
subTitle: {
if (!root.isTransactionValid || transactionHeader.isNFT)
if (!root.isTransactionValid || transactionHeader.isNFT || !!d.isDetailsValid)
return ""
if (!d.symbol) {
const maxFeeEth = RootStore.getGasEthValue(transaction.maxTotalFees.amount, 1)
const maxFeeEth = RootStore.getFeeEthValue(d.details.maxTotalFees)
return RootStore.formatCurrencyAmount(maxFeeEth, Constants.ethToken)
}
@ -631,7 +638,7 @@ Item {
return ""
let fiatValue
if (!d.symbol) {
const maxFeeEth = RootStore.getGasEthValue(transaction.maxTotalFees.amount, 1)
const maxFeeEth = RootStore.getFeeEthValue(d.details.maxTotalFees)
fiatValue = RootStore.getFiatValue(maxFeeEth, Constants.ethToken, RootStore.currentCurrency)
} else {
fiatValue = d.feeFiatValue
@ -659,7 +666,7 @@ Item {
if (fieldIsHidden)
return ""
if (showMaxFee) {
const maxFeeEth = RootStore.getGasEthValue(transaction.maxTotalFees.amount, 1)
const maxFeeEth = RootStore.getFeeEthValue(d.details.maxTotalFees)
return RootStore.formatCurrencyAmount(maxFeeEth, Constants.ethToken)
} else if (showFee) {
return RootStore.formatCurrencyAmount(d.feeEthValue, Constants.ethToken)
@ -673,7 +680,7 @@ Item {
if (fieldIsHidden)
return ""
if (showMaxFee) {
const maxFeeEth = RootStore.getGasEthValue(transaction.maxTotalFees.amount, 1)
const maxFeeEth = RootStore.getFeeEthValue(d.details.maxTotalFees)
const maxFeeFiat = RootStore.getFiatValue(d.feeEthValue, "ETH", RootStore.currentCurrency)
return RootStore.formatCurrencyAmount(maxFeeFiat, RootStore.currentCurrency)
} else if (showFee) {
@ -718,7 +725,7 @@ Item {
icon.width: 20
icon.height: 20
size: StatusButton.Small
onClicked: RootStore.copyToClipboard(transactionHeader.getDetailsString())
onClicked: RootStore.copyToClipboard(transactionHeader.getDetailsString(d.details))
}
}
}

View File

@ -0,0 +1,26 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="18" cy="18" r="17.5" fill="white" stroke="#F6F8FA"/>
<g clip-path="url(#clip0_16679_212870)">
<path d="M27.1385 14.4178L23.0668 8.48738C22.4274 7.55788 20.9813 8.05131 21.0286 9.18059C21.0719 10.21 21.4561 11.2995 22.3757 12.6366L22.3762 12.6391L22.3751 12.6413L22.3729 12.6426L22.3704 12.6422C21.9943 12.3614 16.3226 8.09704 9.58082 10.3676H9.57321C8.82916 10.6468 8.44931 11.2562 8.63763 11.9707C8.81192 12.6322 9.48947 13.0293 10.163 12.9174C11.595 12.6787 13.0162 12.5652 14.4138 12.688C15.8114 12.8107 17.1785 13.1694 18.4514 13.7956C19.7244 14.4218 20.8775 15.308 21.9373 16.3229C23.1293 17.4634 24.6083 19.8957 24.6083 19.8957C24.6949 20.0498 24.8332 20.1683 24.9986 20.2303C25.1641 20.2923 25.3461 20.2939 25.5126 20.2347C25.9133 20.091 26.0999 19.6285 25.9493 19.2317C25.6351 18.4024 25.2505 17.6016 24.7998 16.838L24.7988 16.8353L24.7997 16.8327L24.8022 16.8313L24.805 16.832C25.3034 17.0197 25.7646 17.2536 26.3103 17.1257C26.7555 17.0209 27.1461 16.7177 27.3276 16.2932C27.4591 15.9905 27.5107 15.6592 27.4776 15.3308C27.4444 15.0024 27.3277 14.688 27.1385 14.4178Z" fill="url(#paint0_linear_16679_212870)"/>
<path d="M8.34877 20.9696L12.4205 26.9C13.0599 27.8295 14.506 27.3361 14.4587 26.2068C14.4154 25.1774 14.0312 24.0878 13.1116 22.7508L13.1111 22.7483L13.1122 22.746L13.1144 22.7448L13.1168 22.7451C13.4931 23.0259 19.1647 27.2903 25.9065 25.0198H25.9141C26.6581 24.7406 27.038 24.1312 26.8497 23.4167C26.6754 22.7552 25.9978 22.358 25.3243 22.4699C23.8922 22.7086 22.4711 22.8222 21.0735 22.6994C19.6759 22.5766 18.3088 22.218 17.0359 21.5918C15.7629 20.9656 14.6097 20.0794 13.55 19.0644C12.358 17.9239 10.8791 15.4917 10.8791 15.4917C10.7924 15.3376 10.6541 15.2191 10.4887 15.157C10.3232 15.095 10.1412 15.0935 9.97472 15.1527C9.57404 15.2963 9.38733 15.7589 9.53798 16.1556C9.85218 16.9849 10.2368 17.7858 10.6875 18.5494L10.6886 18.552L10.6876 18.5547L10.6851 18.5561L10.6823 18.5554C10.1839 18.3676 9.7227 18.1338 9.17697 18.2618C8.73182 18.3664 8.34116 18.6698 8.15965 19.0941C8.02825 19.3968 7.97663 19.7282 8.00974 20.0566C8.04286 20.3849 8.15959 20.6993 8.34877 20.9696Z" fill="url(#paint1_linear_16679_212870)"/>
</g>
<defs>
<linearGradient id="paint0_linear_16679_212870" x1="28.4876" y1="25.8943" x2="4.57995" y2="-3.51434" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#B32EFF"/>
<stop offset="0.42" stop-color="#CE60D3"/>
<stop offset="0.65" stop-color="#E185B3"/>
<stop offset="0.84" stop-color="#EE9C9F"/>
<stop offset="0.96" stop-color="#F2A498"/>
</linearGradient>
<linearGradient id="paint1_linear_16679_212870" x1="6.99969" y1="9.4931" x2="30.9074" y2="38.9018" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#B32EFF"/>
<stop offset="0.42" stop-color="#CE60D3"/>
<stop offset="0.65" stop-color="#E185B3"/>
<stop offset="0.84" stop-color="#EE9C9F"/>
<stop offset="0.96" stop-color="#F2A498"/>
</linearGradient>
<clipPath id="clip0_16679_212870">
<rect width="20" height="20" fill="white" transform="translate(8 8)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 3.3 MiB

View File

@ -1,4 +1,5 @@
import QtQuick 2.13
import QtGraphicalEffects 1.15
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
@ -55,6 +56,15 @@ StatusListItem {
*/
property string buttonIconName
property StatusAssetSettings iconSettings: StatusAssetSettings {
name: root.asset.name
color: "transparent"
width: root.smallIcon ? 20 : 36
height: root.smallIcon ? 20 : 36
bgWidth: width
bgHeight: height
}
signal buttonClicked()
leftPadding: 12
@ -94,13 +104,14 @@ StatusListItem {
Component {
id: iconComponent
StatusRoundIcon {
asset: StatusAssetSettings {
name: root.asset.name
color: "transparent"
width: root.smallIcon ? 20 : 36
height: root.smallIcon ? 20 : 36
bgWidth: width
bgHeight: height
asset: root.iconSettings
layer.enabled: asset.bgRadius > 0
layer.effect: OpacityMask {
maskSource: Rectangle {
width: root.iconSettings.bgWidth
height: root.iconSettings.bgHeight
radius: root.iconSettings.bgRadius
}
}
}
}

View File

@ -143,11 +143,13 @@ StatusListItem {
}
property StatusAssetSettings tokenIconAsset: StatusAssetSettings {
width: 18
height: 18
bgWidth: width
bgHeight: height
bgColor: "transparent"
width: 20
height: 20
bgWidth: width + 2
bgHeight: height + 2
bgRadius: bgWidth / 2
bgColor: Style.current.name === Constants.lightThemeName && Constants.isDefaultTokenIcon(root.tokenImage) ?
Theme.palette.white : "transparent"
color: "transparent"
isImage: !loading
name: root.tokenImage
@ -164,7 +166,12 @@ StatusListItem {
property bool showRetryButton: false
}
function getDetailsString() {
function getDetailsString(detailsObj) {
if (!detailsObj) {
rootStore.fetchTxDetails(modelData.id, modelData.isMultiTransaction, modelData.isPending)
detailsObj = rootStore.getTxDetails()
}
let details = ""
const endl = "\n"
const endl2 = endl + endl
@ -246,7 +253,7 @@ StatusListItem {
case Constants.TransactionStatus.Verified: {
const timestampString = LocaleUtils.formatDateTime(modelData.timestamp * 1000, Locale.LongFormat)
details += qsTr("Status") + endl
const epoch = parseFloat(Math.abs(walletRootStore.getLatestBlockNumber(modelData.chainId) - rootStore.hex2Dec(modelData.blockNumber)).toFixed(0)).toLocaleString()
const epoch = parseFloat(Math.abs(walletRootStore.getLatestBlockNumber(modelData.chainId) - detailsObj.blockNumber).toFixed(0)).toLocaleString()
details += qsTr("Finalised in epoch %1").arg(epoch.toFixed(0)) + endl2
details += qsTr("Signed") + endl + root.timestampString + endl2
details += qsTr("Confirmed") + endl
@ -256,7 +263,7 @@ StatusListItem {
case Constants.TransactionStatus.Finished: {
const timestampString = LocaleUtils.formatDateTime(modelData.timestamp * 1000, Locale.LongFormat)
details += qsTr("Status") + endl
const epoch = Math.abs(walletRootStore.getLatestBlockNumber(modelData.chainId) - rootStore.hex2Dec(modelData.blockNumber))
const epoch = Math.abs(walletRootStore.getLatestBlockNumber(modelData.chainId) - detailsObj.blockNumber)
details += qsTr("Finalised in epoch %1").arg(epoch.toFixed(0)) + endl2
details += qsTr("Signed") + endl + timestampString + endl2
details += qsTr("Confirmed") + endl
@ -298,9 +305,8 @@ StatusListItem {
details += qsTr("To") + endl + modelData.recipient + endl2
break
}
const protocolName = "" // TODO fill protocol name for Bridge and Swap
if (!!protocolName) {
details += qsTr("Using") + endl + protocolName + endl2
if (!!detailsObj.protocol) {
details += qsTr("Using") + endl + detailsObj.protocol + endl2
}
if (!!modelData.txHash) {
details += qsTr("%1 Tx hash").arg(root.networkName) + endl + modelData.txHash + endl2
@ -310,18 +316,18 @@ StatusListItem {
details += qsTr("%1 Tx hash").arg(networkNameOut) + endl + bridgeTxHash + endl2
}
const protocolFromContractAddress = "" // TODO fill protocol contract address for 'from' network for Bridge and Swap
if (!!protocolName && !!protocolFromContractAddress) {
details += qsTr("%1 %2 contract address").arg(root.networkName).arg(protocolName) + endl
if (!!detailsObj.protocol && !!protocolFromContractAddress) {
details += qsTr("%1 %2 contract address").arg(root.networkName).arg(detailsObj.protocol) + endl
details += protocolFromContractAddress + endl2
}
if (!!modelData.contract && type !== Constants.TransactionType.ContractDeployment && !/0x0+$/.test(modelData.contract)) {
if (!!detailsObj.contract && type !== Constants.TransactionType.ContractDeployment && !/0x0+$/.test(detailsObj.contract)) {
let symbol = !!modelData.symbol || !modelData.tokenAddress ? modelData.symbol : "(%1)".arg(Utils.compactAddress(modelData.tokenAddress, 4))
details += qsTr("%1 %2 contract address").arg(root.networkName).arg(symbol) + endl
details += modelData.contract + endl2
details += detailsObj.contract + endl2
}
const protocolToContractAddress = "" // TODO fill protocol contract address for 'to' network for Bridge
if (!!protocolToContractAddress && !!protocolName) {
details += qsTr("%1 %2 contract address").arg(networkNameOut).arg(protocolName) + endl
if (!!protocolToContractAddress && !!detailsObj.protocol) {
details += qsTr("%1 %2 contract address").arg(networkNameOut).arg(detailsObj.protocol) + endl
details += protocolToContractAddress + endl2
}
const swapContractAddress = "" // TODO fill swap contract address for Swap
@ -348,15 +354,17 @@ StatusListItem {
details += qsTr("Network") + endl + networkName + endl2
}
details += qsTr("Token format") + endl + modelData.tokenType.toUpperCase() + endl2
details += qsTr("Nonce") + endl + rootStore.hex2Dec(modelData.nonce) + endl2
details += qsTr("Nonce") + endl + detailsObj.nonce + endl2
if (type === Constants.TransactionType.Bridge) {
details += qsTr("Included in Block on %1").arg(networkName) + endl
details += rootStore.hex2Dec(modelData.blockNumber) + endl2
details += qsTr("Included in Block on %1").arg(networkNameOut) + endl
details += detailsObj.blockNumber + endl2
const bridgeBlockNumber = 0 // TODO fill when bridge data is implemented
details += rootStore.hex2Dec(bridgeBlockNumber) + endl2
if (bridgeBlockNumber > 0) {
details += qsTr("Included in Block on %1").arg(networkNameOut) + endl
details += bridgeBlockNumber + endl2
}
} else {
details += qsTr("Included in Block") + endl + rootStore.hex2Dec(modelData.blockNumber) + endl2
details += qsTr("Included in Block") + endl + detailsObj.blockNumber + endl2
}
// VALUES
@ -409,7 +417,7 @@ StatusListItem {
} else if (type === Constants.TransactionType.ContractDeployment) {
const isPending = root.transactionStatus === Constants.TransactionStatus.Pending
if (isPending) {
const maxFeeEthValue = rootStore.getGasEthValue(modelData.maxTotalFees.amount, 1)
const maxFeeEthValue = rootStore.getFeeEthValue(detailsObj.maxTotalFees.amount)
const maxFeeCrypto = rootStore.formatCurrencyAmount(maxFeeEthValue, "ETH")
const maxFeeFiat = rootStore.formatCurrencyAmount(maxFeeCrypto, root.currentCurrency)
valuesString += qsTr("Estimated max fee %1 (%2)").arg(maxFeeCrypto).arg(maxFeeFiat) + endl2
@ -462,8 +470,7 @@ StatusListItem {
return qsTr("%1 from %2 to %3").arg(inTransactionValue).arg(networkNameOut).arg(networkNameIn)
case Constants.TransactionType.ContractDeployment:
const name = addressNameTo || addressNameFrom
return !!modelData.contract ? qsTr("Contract %1 via %2 on %3").arg(Utils.compactAddress(modelData.contract, 4)).arg(name).arg(networkName)
: qsTr("Via %1 on %2").arg(name).arg(networkName)
return qsTr("Via %1 on %2").arg(name).arg(networkName)
case Constants.TransactionType.Mint:
if (allAccounts)
return qsTr("%1 via %2 in %4").arg(transactionValue).arg(networkName).arg(toAddress)
@ -591,7 +598,7 @@ StatusListItem {
bgWidth: width + 2
bgHeight: height + 2
bgRadius: bgWidth / 2
bgColor: root.color
bgColor: Theme.palette.white
isImage:root.tokenIconAsset.isImage
color: root.tokenIconAsset.color
name: root.inTokenImage
@ -801,11 +808,6 @@ StatusListItem {
width: 17
height: 17
}
PropertyChanges {
target: root.tokenIconAsset
width: 20
height: 20
}
PropertyChanges {
target: d
titlePixelSize: 17

View File

@ -230,6 +230,10 @@ QtObject {
return currencyStore.getGasEthValue(gweiValue, gasLimit)
}
function getFeeEthValue(feeCurrency) {
return currencyStore.getGasEthValue(feeCurrency.amount / Math.pow(10, feeCurrency.displayDecimals), 1)
}
function formatCurrencyAmount(amount, symbol, options = null, locale = null) {
return currencyStore.formatCurrencyAmount(amount, symbol, options, locale)
}
@ -243,6 +247,14 @@ QtObject {
walletSectionInst.fetchDecodedTxData(txHash, input)
}
function fetchTxDetails(id, isMultiTx, isPending) {
walletSectionInst.activityController.fetchTxDetails(id, isMultiTx, isPending)
}
function getTxDetails() {
return walletSectionInst.activityController.activityDetails
}
property bool marketHistoryIsLoading: Global.appIsReady? walletSectionAllTokens.marketHistoryIsLoading : false
function fetchHistoricalBalanceForTokenAsJson(address, tokenSymbol, currencySymbol, timeIntervalEnum) {

View File

@ -1114,6 +1114,10 @@ QtObject {
return ""
}
function isDefaultTokenIcon(url) {
return url.indexOf("DEFAULT-TOKEN") !== -1
}
// Message outgoing status
readonly property string sending: "sending"
readonly property string sent: "sent"