Skip to content

Commit 523a702

Browse files
committed
Merge branch 'coincontrol'
2 parents 96555be + bba49bd commit 523a702

File tree

12 files changed

+66
-19
lines changed

12 files changed

+66
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Fix Wallet Connect issue where account unspecified by the connecting dapp caused a UI crash
1313
- Fix Wallet Connect issue with required/optionalNamespace and handling all possible namespace definitions
1414
- Add Satoshi as an option in active currencies
15+
- Show address re-use warning and group UTXOs with the same address together in coin control.
1516

1617
## 4.42.0
1718
- Preselect backup when there's only one backup available

backend/coins/btc/handlers/handlers.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -324,16 +324,27 @@ func (handlers *Handlers) getUTXOs(*http.Request) (interface{}, error) {
324324
return result, errp.New("Interface must be of type btc.Account")
325325
}
326326

327+
addressCounts := make(map[string]int)
328+
327329
for _, output := range t.SpendableOutputs() {
330+
address := output.Address.EncodeForHumans()
331+
addressCounts[address]++
332+
}
333+
334+
for _, output := range t.SpendableOutputs() {
335+
address := output.Address.EncodeForHumans()
336+
addressReused := addressCounts[address] > 1
337+
328338
result = append(result,
329339
map[string]interface{}{
330-
"outPoint": output.OutPoint.String(),
331-
"txId": output.OutPoint.Hash.String(),
332-
"txOutput": output.OutPoint.Index,
333-
"amount": handlers.formatBTCAmountAsJSON(btcutil.Amount(output.TxOut.Value), false),
334-
"address": output.Address.EncodeForHumans(),
335-
"scriptType": output.Address.Configuration.ScriptType(),
336-
"note": handlers.account.TxNote(output.OutPoint.Hash.String()),
340+
"outPoint": output.OutPoint.String(),
341+
"txId": output.OutPoint.Hash.String(),
342+
"txOutput": output.OutPoint.Index,
343+
"amount": handlers.formatBTCAmountAsJSON(btcutil.Amount(output.TxOut.Value), false),
344+
"address": address,
345+
"scriptType": output.Address.Configuration.ScriptType(),
346+
"note": handlers.account.TxNote(output.OutPoint.Hash.String()),
347+
"addressReused": addressReused,
337348
})
338349
}
339350

frontends/web/src/api/account.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ export type TUTXO = {
369369
amount: IAmount;
370370
note: string;
371371
scriptType: ScriptType;
372+
addressReused: boolean;
372373
};
373374

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

frontends/web/src/components/badge/badge.module.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
border: 1px solid;
44
display: inline-block;
55
font-size: var(--size-small);
6-
line-height: 14px;
6+
line-height: 1;
77
padding: var(--space-eight) 10px;
88
white-space: nowrap;
99
}
@@ -42,3 +42,10 @@
4242
border-color: var(--color-darkyellow);
4343
color: var(--color-olive);
4444
}
45+
46+
/* swiss-red with different opacities */
47+
.danger {
48+
background: rgba(255, 0, 0, 0.2);
49+
border-color: rgba(255, 0, 0, 0.33);
50+
color: rgba(255, 0, 0, 1);
51+
}

frontends/web/src/components/badge/badge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import { ReactElement, ReactNode } from 'react';
1818
import style from './badge.module.css';
1919

20-
type TBadgeStyles = 'success' | 'warning'; // TODO: not yet implemented 'info' | 'danger'
20+
type TBadgeStyles = 'success' | 'warning' | 'danger'; // TODO: not yet implemented 'info'
2121

2222
type TProps = {
2323
children?: ReactNode;

frontends/web/src/components/forms/radio.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.radio {
22
--size-default: 14px;
3+
line-height: 1.5;
34
}
45

56
.radio input {
@@ -11,7 +12,6 @@
1112
display: inline-flex;
1213
flex-direction: column;
1314
font-size: var(--size-default);
14-
line-height: 1.5;
1515
margin-right: var(--space-half);
1616
padding-left: calc(var(--space-half) + var(--space-quarter));
1717
position: relative;

frontends/web/src/locales/en/app.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,6 +1525,7 @@
15251525
"button": "Review",
15261526
"coincontrol": {
15271527
"address": "Address",
1528+
"addressReused": "Address re-used",
15281529
"outpoint": "Outpoint",
15291530
"title": "Send from output"
15301531
},
@@ -1790,6 +1791,7 @@
17901791
"walletConnect": "WalletConnect"
17911792
},
17921793
"warning": {
1794+
"coincontrol": "One or more UTXOs have a reused address. Be aware the receiver will see all UTXOs associated with those addresses.",
17931795
"receivePairing": "Please pair the BitBox to enable secure address verification. Go to 'Manage device' in the sidebar.",
17941796
"sdcard": "Keep the microSD card stored separate from the BitBox, unless you want to manage backups.",
17951797
"sendPairing": "Please pair the BitBox to securely verify transaction details. Go to 'Manage device' in the sidebar."

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
.transaction {
7575
display: flex;
7676
font-size: var(--size-default);
77-
line-height: 1.4;
77+
line-height: 1.5;
7878
white-space: nowrap;
7979
}
8080

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

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { ExternalLink } from '../../../components/icon';
3232
import { Amount } from '../../../components/amount/amount';
3333
import { FiatConversion } from '../../../components/rates/rates';
3434
import { getScriptName } from '../utils';
35+
import { Message } from '../../../components/message/message';
36+
import { Badge } from '../../../components/badge/badge';
3537
import style from './utxos.module.css';
3638

3739
export type TSelectedUTXOs = {
@@ -56,6 +58,7 @@ export const UTXOs = ({
5658
const { t } = useTranslation();
5759
const [utxos, setUtxos] = useState<TUTXO[]>([]);
5860
const [selectedUTXOs, setSelectedUTXOs] = useState<TSelectedUTXOs>({});
61+
const [reusedAddressUTXOs, setReusedAddressUTXOs] = useState(0);
5962

6063
useEffect(() => {
6164
getUTXOs(accountCode).then(setUtxos);
@@ -71,14 +74,22 @@ export const UTXOs = ({
7174
return () => unsubscribe();
7275
}, [accountCode]);
7376

74-
const handleUTXOChange = (event: ChangeEvent<HTMLInputElement>) => {
77+
const handleUTXOChange = (
78+
event: ChangeEvent<HTMLInputElement>,
79+
utxo: TUTXO,
80+
) => {
7581
const target = event.target;
76-
const outPoint = target.dataset.outpoint as string;
7782
const proposedUTXOs = Object.assign({}, selectedUTXOs);
7883
if (target.checked) {
79-
proposedUTXOs[outPoint] = true;
84+
proposedUTXOs[utxo.outPoint] = true;
85+
if (utxo.addressReused) {
86+
setReusedAddressUTXOs(reusedAddressUTXOs + 1);
87+
}
8088
} else {
81-
delete proposedUTXOs[outPoint];
89+
delete proposedUTXOs[utxo.outPoint];
90+
if (utxo.addressReused) {
91+
setReusedAddressUTXOs(reusedAddressUTXOs - 1);
92+
}
8293
}
8394
setSelectedUTXOs(proposedUTXOs);
8495
onChange(proposedUTXOs);
@@ -98,8 +109,7 @@ export const UTXOs = ({
98109
<Checkbox
99110
checked={!!selectedUTXOs[utxo.outPoint]}
100111
id={'utxo-' + utxo.outPoint}
101-
data-outpoint={utxo.outPoint}
102-
onChange={handleUTXOChange}>
112+
onChange={event => handleUTXOChange(event, utxo)}>
103113
{utxo.note && (
104114
<div className={style.note}>
105115
<strong>{utxo.note}{' '}</strong>
@@ -124,6 +134,14 @@ export const UTXOs = ({
124134
<span className={style.shrink}>
125135
{utxo.address}
126136
</span>
137+
<div className="m-left-quarter">
138+
{utxo.addressReused ?
139+
<Badge type="danger">
140+
{t('send.coincontrol.addressReused')}
141+
</Badge> :
142+
null
143+
}
144+
</div>
127145
</div>
128146
<div className={style.transaction}>
129147
<span className={style.label}>
@@ -155,6 +173,13 @@ export const UTXOs = ({
155173
title={t('send.coincontrol.title')}
156174
large
157175
onClose={onClose}>
176+
<div>
177+
{(reusedAddressUTXOs > 0) && (
178+
<Message type="warning">
179+
{t('warning.coincontrol')}
180+
</Message>
181+
)}
182+
</div>
158183
<div>
159184
{ allScriptTypes.map(renderUTXOs) }
160185
<div className="buttons text-center m-top-none m-bottom-half">

frontends/web/src/routes/account/summary/accountssummary.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.accountName {
22
display: flex;
3-
align-items: center;
3+
align-items: baseline;
44
}
55

66
.accountName p {

frontends/web/src/routes/buy/components/exchangeselectionradio.module.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
color: var(--color-default);
4949
display: inline-block;
5050
font-weight: 400;
51-
line-height: 22px;
5251
margin: 0;
5352
}
5453
.paymentMethodName img {

frontends/web/src/routes/buy/exchange.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
}
7676

7777
.radioButtonsContainer {
78+
line-height: 1.5;
7879
min-height: 180px;
7980
}
8081

0 commit comments

Comments
 (0)