feat: coldcard warning on transaction page

This commit is contained in:
rodribp 2026-08-12 00:42:43 -06:00
parent 63c4330174
commit 279741517e
No known key found for this signature in database
GPG key ID: DBB99FAE51E608F0
3 changed files with 57 additions and 0 deletions

View file

@ -21,6 +21,12 @@
<span i18n="transaction.poison.warning">Warning! This transaction involves deceptively similar addresses. It may be an address poisoning attack.</span>
</div>
}
@if (showColdcardWarning) {
<div class="alert alert-danger alert-dismissible coldcard-warning" role="alert">
<a [routerLink]="['/docs/faq' | relativeUrl]" fragment="why-may-my-multisig-funds-be-at-risk" i18n="transaction.coldcard.warning">Warning for ColdCard users: If this multisig wallet uses default setup coldcard devices, the funds may be at risk while this transaction is unconfirmed. Click this warning to learn more.</a>
<button type="button" class="btn-close btn-close-white" aria-label="Close" i18n-aria-label (click)="dismissWarning()"></button>
</div>
}
<div class="row">
<div class="col">
<table class="table table-fixed table-borderless smaller-text table-sm table-tx-vin">

View file

@ -416,6 +416,22 @@ h2 {
}
}
.coldcard-warning {
display: flex;
align-items: center;
position: relative;
background-color: #b71c1c;
border-color: #b71c1c;
a {
color: #fff;
}
}
.btn-close:hover,
.btn-close:focus {
opacity: 1;
}
@keyframes acceleratePulse {
0% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;}

View file

@ -72,6 +72,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
showTaprootControlBlock: { [vinIndex: number]: boolean } = {};
showOrdData: { [key: string]: { show: boolean; inscriptions?: Inscription[]; runestone?: Runestone, runeInfo?: { [id: string]: { etching: Etching; txid: string; } }; } } = {};
similarityMatches: Map<string, Map<string, { score: number, match: AddressMatch, group: number }>> = new Map();
showColdcardWarning: boolean = false;
selectedSig: { txIndex: number, vindex: number, sig: SigInfo } | null = null;
sigHighlights: { vin: boolean[], vout: boolean[] } = { vin: [], vout: [] };
@ -186,6 +187,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
});
this.updateAddressSimilarities();
this.updateShowColdcardWarning();
}
refreshPrice(): void {
@ -476,6 +478,33 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
}
}
updateShowColdcardWarning(): void {
const showCCWarningPref = this.storageService.getValue('showTxColdcardWarning');
// there's only one tx, it's the tx page, it's unconfirmed, it contains inputs that are p2sh, v0_p2wsh or p2sh-p2wsh, is multisig and network is mainnet
if (showCCWarningPref && showCCWarningPref === 'false') {
return;
}
if (!this.transactionPage || this.txPreview) {
return;
}
if (this.transactions.length > 1 || this.transactions[0].status.confirmed) {
return;
}
const vinPossibleCC: Vin[] = this.transactions[0].vin.filter((input: Vin) => {
const addressTypeInfo = new AddressTypeInfo(this.network || 'mainnet', input.prevout.scriptpubkey_address, input.prevout?.scriptpubkey_type as AddressType, [input]);
return ['p2sh', 'v0_p2wsh', 'p2sh-p2wsh'].includes(addressTypeInfo.type) && !!addressTypeInfo.isMultisig && addressTypeInfo.network === 'mainnet';
});
if (vinPossibleCC.length === 0) {
return;
}
this.showColdcardWarning = true;
}
// assume any address with 12 or more contiguous repeated substrings is fake
fakeScriptHashRegex = new RegExp(/(.+?)\1{11,}/);
isFakeScripthash(vout: Vout): boolean {
@ -713,6 +742,12 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
}
}
dismissWarning(): void {
this.storageService.setValue('showTxColdcardWarning', 'false');
this.showColdcardWarning = false;
this.ref.markForCheck();
}
ngOnDestroy(): void {
this.outspendsSubscription.unsubscribe();
this.currencyChangeSubscription?.unsubscribe();