Skip to content

Commit acb0400

Browse files
committed
frontend: Add received date to coin control UTXO list
Add the received date (block header timestamp) to the UTXO detail in the coin control view. This makes it easier for users to view the date, which can be helpful for various reasons, without having to open each UTXO individually in the block explorer.
1 parent f4d4769 commit acb0400

File tree

8 files changed

+47
-21
lines changed

8 files changed

+47
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Added support to show on the BitBox when a transaction's recipient is an address of a different account on the device.
1515
- Persist third party widget sessions
1616
- Change notes export file type to JSON Lines
17+
- Add received date to coin control transaction details
1718

1819
## v4.47.3
1920
- Upgrade Etherscan API to V2

backend/coins/btc/account.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,7 @@ func sortByAddresses(result []*SpendableOutput) []*SpendableOutput {
840840
for _, s := range sums {
841841
outputs := grouped[s.address]
842842
sort.Slice(outputs, func(i, j int) bool {
843-
return outputs[i].Value > outputs[j].Value
843+
return outputs[i].TxOut.Value > outputs[j].TxOut.Value
844844
})
845845
newResult = append(newResult, outputs...)
846846
}

backend/coins/btc/handlers/handlers.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -311,18 +311,24 @@ func (handlers *Handlers) getUTXOs(*http.Request) (interface{}, error) {
311311
for _, output := range spendableOutputs {
312312
address := output.Address.EncodeForHumans()
313313
addressReused := addressCounts[address] > 1
314-
314+
var formattedTime *string
315+
timestamp := output.HeaderTimestamp
316+
if timestamp != nil {
317+
t := timestamp.Format(time.RFC3339)
318+
formattedTime = &t
319+
}
315320
result = append(result,
316321
map[string]interface{}{
317-
"outPoint": output.OutPoint.String(),
318-
"txId": output.OutPoint.Hash.String(),
319-
"txOutput": output.OutPoint.Index,
320-
"amount": coin.ConvertBTCAmount(handlers.account.Coin(), btcutil.Amount(output.TxOut.Value), false, accountConfig.RateUpdater),
321-
"address": address,
322-
"scriptType": output.Address.AccountConfiguration.ScriptType(),
323-
"note": handlers.account.TxNote(output.OutPoint.Hash.String()),
324-
"addressReused": addressReused,
325-
"isChange": output.IsChange,
322+
"outPoint": output.OutPoint.String(),
323+
"txId": output.OutPoint.Hash.String(),
324+
"txOutput": output.OutPoint.Index,
325+
"amount": coin.ConvertBTCAmount(handlers.account.Coin(), btcutil.Amount(output.TxOut.Value), false, accountConfig.RateUpdater),
326+
"address": address,
327+
"scriptType": output.Address.AccountConfiguration.ScriptType(),
328+
"note": handlers.account.TxNote(output.OutPoint.Hash.String()),
329+
"addressReused": addressReused,
330+
"isChange": output.IsChange,
331+
"headerTimestamp": formattedTime,
326332
})
327333
}
328334

backend/coins/btc/transactions/transactions.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
package transactions
1616

1717
import (
18+
"time"
19+
1820
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts"
1921
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/btc/blockchain"
2022
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/btc/headers"
@@ -34,7 +36,8 @@ import (
3436

3537
// SpendableOutput is an unspent coin.
3638
type SpendableOutput struct {
37-
*wire.TxOut
39+
TxOut *wire.TxOut
40+
HeaderTimestamp *time.Time
3841
}
3942

4043
// ScriptHashHex returns the hash of the PkScript of the output, in hex format.
@@ -235,7 +238,8 @@ func (transactions *Transactions) SpendableOutputs() (map[wire.OutPoint]*Spendab
235238

236239
if confirmed || transactions.allInputsOurs(dbTx, txInfo.Tx) {
237240
result[outPoint] = &SpendableOutput{
238-
TxOut: txOut,
241+
TxOut: txOut,
242+
HeaderTimestamp: txInfo.HeaderTimestamp,
239243
}
240244
}
241245
}

backend/coins/btc/transactions/transactions_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,14 @@ func (s *transactionsSuite) TestUpdateAddressHistorySingleTxReceive() {
177177
}
178178
spendableOutputs, err := s.transactions.SpendableOutputs()
179179
s.Require().NoError(err)
180-
s.Require().Equal(
181-
map[wire.OutPoint]*transactions.SpendableOutput{
182-
{Hash: tx1.TxHash(), Index: 0}: utxo,
183-
},
184-
spendableOutputs,
185-
)
180+
expected := map[wire.OutPoint]*wire.TxOut{
181+
{Hash: tx1.TxHash(), Index: 0}: utxo.TxOut,
182+
}
183+
actual := make(map[wire.OutPoint]*wire.TxOut)
184+
for outpoint, spendable := range spendableOutputs {
185+
actual[outpoint] = spendable.TxOut
186+
}
187+
s.Require().Equal(expected, actual)
186188
transactions, err := s.transactions.Transactions(func(blockchainpkg.ScriptHashHex) bool { return false })
187189
s.Require().NoError(err)
188190
s.Require().Len(transactions, 1)

frontends/web/src/api/account.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ export type TUTXO = {
433433
scriptType: ScriptType;
434434
addressReused: boolean;
435435
isChange: boolean;
436+
headerTimestamp: string | null;
436437
};
437438

438439
export const getUTXOs = (code: AccountCode): Promise<TUTXO[]> => {

frontends/web/src/routes/account/send/utxos.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
}
7070

7171
.address,
72+
.date,
7273
.transaction {
7374
display: flex;
7475
font-size: var(--size-default);

frontends/web/src/routes/account/send/utxos.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ import { Amount } from '@/components/amount/amount';
3333
import { getScriptName } from '@/routes/account/utils';
3434
import { Message } from '@/components/message/message';
3535
import { Badge } from '@/components/badge/badge';
36-
import style from './utxos.module.css';
36+
import { parseTimeShort } from '@/utils/date';
3737
import { AmountWithUnit } from '@/components/amount/amount-with-unit';
38+
import style from './utxos.module.css';
3839

3940
export type TSelectedUTXOs = {
4041
[key: string]: boolean;
@@ -55,7 +56,7 @@ export const UTXOs = ({
5556
onChange,
5657
onClose,
5758
}: Props) => {
58-
const { t } = useTranslation();
59+
const { i18n, t } = useTranslation();
5960
const [utxos, setUtxos] = useState<TUTXO[]>([]);
6061
const [selectedUTXOs, setSelectedUTXOs] = useState<TSelectedUTXOs>({});
6162
const [reusedAddressUTXOs, setReusedAddressUTXOs] = useState(0);
@@ -128,6 +129,16 @@ export const UTXOs = ({
128129
</span>
129130
<AmountWithUnit alwaysShowAmounts amount={utxo.amount} convertToFiat/>
130131
</div>
132+
<div className={style.date}>
133+
<span className={style.label}>
134+
{t('transaction.details.date')}:
135+
</span>
136+
<span className={style.shrink}>
137+
{utxo.headerTimestamp
138+
? parseTimeShort(utxo.headerTimestamp, i18n.language)
139+
: t('transaction.status.pending')}
140+
</span>
141+
</div>
131142
<div className={style.address}>
132143
<span className={style.label}>
133144
{t('send.coincontrol.address')}:

0 commit comments

Comments
 (0)