Skip to content

frontend: add received date to coin control UTXO list #3249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Reduced support for BitBox01
- Fix a bug that would prevent the app to perform firmware upgrade when offline.
- Replace sidebar with bottom navigation bar for mobile devices
- Add received date to coin control transaction details

# v4.47.2
- Linux: fix compatiblity with some versions of Mesa that are incompatible with the bundled wayland libraries
Expand Down
13 changes: 10 additions & 3 deletions backend/coins/btc/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"path"
"sort"
"sync/atomic"
"time"

"github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts"
accountsTypes "github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts/types"
Expand Down Expand Up @@ -840,9 +841,10 @@ func sortByAddresses(result []*SpendableOutput) []*SpendableOutput {
// SpendableOutput is an unspent coin.
type SpendableOutput struct {
*transactions.SpendableOutput
OutPoint wire.OutPoint
Address *addresses.AccountAddress
IsChange bool
OutPoint wire.OutPoint
Address *addresses.AccountAddress
IsChange bool
HeaderTimestamp *time.Time
}

// SpendableOutputs returns the utxo set, sorted by the value descending.
Expand All @@ -856,13 +858,18 @@ func (account *Account) SpendableOutputs() []*SpendableOutput {
}
for outPoint, txOut := range utxos {
scriptHashHex := blockchain.NewScriptHashHex(txOut.TxOut.PkScript)
headerTimestamp, err := account.transactions.GetHeaderTimestamp(outPoint.Hash)
if err != nil {
headerTimestamp = nil
}
result = append(
result,
&SpendableOutput{
OutPoint: outPoint,
SpendableOutput: txOut,
Address: account.getAddress(scriptHashHex),
IsChange: account.IsChange(scriptHashHex),
HeaderTimestamp: headerTimestamp,
})
}
return sortByAddresses(result)
Expand Down
19 changes: 10 additions & 9 deletions backend/coins/btc/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,15 +365,16 @@ func (handlers *Handlers) getUTXOs(*http.Request) (interface{}, error) {

result = append(result,
map[string]interface{}{
"outPoint": output.OutPoint.String(),
"txId": output.OutPoint.Hash.String(),
"txOutput": output.OutPoint.Index,
"amount": handlers.formatBTCAmountAsJSON(btcutil.Amount(output.TxOut.Value), false),
"address": address,
"scriptType": output.Address.Configuration.ScriptType(),
"note": handlers.account.TxNote(output.OutPoint.Hash.String()),
"addressReused": addressReused,
"isChange": output.IsChange,
"outPoint": output.OutPoint.String(),
"txId": output.OutPoint.Hash.String(),
"txOutput": output.OutPoint.Index,
"amount": handlers.formatBTCAmountAsJSON(btcutil.Amount(output.TxOut.Value), false),
"address": address,
"scriptType": output.Address.Configuration.ScriptType(),
"note": handlers.account.TxNote(output.OutPoint.Hash.String()),
"addressReused": addressReused,
"isChange": output.IsChange,
"headerTimestamp": output.HeaderTimestamp,
Copy link
Contributor

Choose a reason for hiding this comment

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

})
}

Expand Down
14 changes: 14 additions & 0 deletions backend/coins/btc/transactions/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package transactions

import (
"time"

"github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts"
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/btc/blockchain"
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/btc/headers"
Expand Down Expand Up @@ -221,6 +223,18 @@ func (transactions *Transactions) SpendableOutputs() (map[wire.OutPoint]*Spendab
})
}

// GetHeaderTimestamp retrieves the header timestamp for a given transaction hash.
func (transactions *Transactions) GetHeaderTimestamp(txHash chainhash.Hash) (*time.Time, error) {
transactions.synchronizer.WaitSynchronized()
return DBView(transactions.db, func(dbTx DBTxInterface) (*time.Time, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This adds one dbTx per UTXO. In transactions.SpendableOutputs() we aleady call TxInfo() on each UTXO, so it would be more natural to simply fetch the timestamp there.

txInfo, err := dbTx.TxInfo(txHash)
if err != nil {
return nil, err
}
return txInfo.HeaderTimestamp, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

HeaderTimestamp is nil when the utxo is unconfirmed. If nil you should take the CreatedTimestamp, like here:

https://github.com/BitBoxSwiss/bitbox-wallet-app/blob/master/backend/coins/btc/handlers/handlers.go#L205-L208

It would probably be good to add a method Timestamp() to the TransactionData struct and add this logic there.

})
}

func (transactions *Transactions) isInputSpent(dbTx DBTxInterface, outPoint wire.OutPoint) bool {
input, err := dbTx.Input(outPoint)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions frontends/web/src/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ export type TUTXO = {
scriptType: ScriptType;
addressReused: boolean;
isChange: boolean;
headerTimestamp: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think in theory both the HeaderTimestamp and the CreatedTimestamp could be nil, so that means headerTimestamp should be string | null.

};

export const getUTXOs = (code: AccountCode): Promise<TUTXO[]> => {
Expand Down
2 changes: 1 addition & 1 deletion frontends/web/src/components/transactions/transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ type TDateProps = {
time: string | null;
}

const Date = ({
export const Date = ({
time,
}: TDateProps) => {
const { i18n } = useTranslation();
Expand Down
1 change: 1 addition & 0 deletions frontends/web/src/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,7 @@
"addressReused": "Address re-used",
"change": "Change",
"outpoint": "Outpoint",
"receivedDate": "Received date",
"title": "Send from output"
},
"confirm": {
Expand Down
1 change: 1 addition & 0 deletions frontends/web/src/routes/account/send/utxos.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
}

.address,
.date,
.transaction {
display: flex;
font-size: var(--size-default);
Expand Down
9 changes: 9 additions & 0 deletions frontends/web/src/routes/account/send/utxos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { FiatConversion } from '@/components/rates/rates';
import { getScriptName } from '@/routes/account/utils';
import { Message } from '@/components/message/message';
import { Badge } from '@/components/badge/badge';
import { Date } from '@/components/transactions/transaction';
import style from './utxos.module.css';

export type TSelectedUTXOs = {
Expand Down Expand Up @@ -127,6 +128,14 @@ export const UTXOs = ({
</span>
<FiatConversion alwaysShowAmounts amount={utxo.amount} unstyled noAction/>
</div>
<div className={style.date}>
<span className={style.label}>
{t('send.coincontrol.receivedDate')}:
</span>
<span className={style.shrink}>
<Date time={utxo.headerTimestamp} />
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know this date format is used in the tx list of the account, but there the transactions are sorted by time. Here I find it a bit confusing to see dates like Saturday, March 22 or Wednesday, October 30, 2024. I think it would be easier to have a dd/mm/YYYY format or similar.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please dont' hardcode dd/mm/YYYY but use {parseTimeShort(utxo.headerTimestamp, i18n.language)}, so the date is localized and short.

new Date().toLocaleString('en-US', {
      month: 'short',
      day: 'numeric',
      year: 'numeric'
    })
"Apr 4, 2025" 

new Date().toLocaleString('de-CH', {
      month: 'short',
      day: 'numeric',
      year: 'numeric'
    })
"4. Apr. 2025" 

</span>
</div>
<div className={style.address}>
<span className={style.label}>
{t('send.coincontrol.address')}:
Expand Down
Loading