From bf60cf721e2f09fdc148b35e493a990dedac574c Mon Sep 17 00:00:00 2001 From: rodribp Date: Tue, 9 Jun 2026 00:21:51 -0600 Subject: [PATCH] fix: False positive coinjoin (#6464) --- backend/src/api/common.ts | 10 +++++++++- frontend/src/app/shared/transaction.utils.ts | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 8fcd2b27b..6786bf1c8 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -749,7 +749,15 @@ export class Common { // fast but bad heuristic to detect possible coinjoins // (at least 5 inputs and 5 outputs, less than half of which are unique amounts, with no address reuse) const addressReuse = Object.keys(reusedOutputAddresses).reduce((acc, key) => Math.max(acc, (reusedInputAddresses[key] || 0) + (reusedOutputAddresses[key] || 0)), 0) > 1; - if (!addressReuse && tx.vin.length >= 5 && tx.vout.length >= 5 && (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 ) { + const tokenRelated = (flags & (TransactionFlags.inscription | TransactionFlags.op_return)) !== 0n; + if (!addressReuse && + tx.vin.length >= 5 && + tx.vout.length >= 5 && + (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 && + !tokenRelated && + tx.vin.length / tx.vout.length < 5 && + tx.vin.length / tx.vout.length > 0.2 + ) { flags |= TransactionFlags.coinjoin; } // more than 5:1 input:output ratio diff --git a/frontend/src/app/shared/transaction.utils.ts b/frontend/src/app/shared/transaction.utils.ts index 6ed112404..8bb4fcbc4 100644 --- a/frontend/src/app/shared/transaction.utils.ts +++ b/frontend/src/app/shared/transaction.utils.ts @@ -935,7 +935,15 @@ export function getTransactionFlags(tx: Transaction, cpfpInfo?: CpfpInfo, replac // fast but bad heuristic to detect possible coinjoins // (at least 5 inputs and 5 outputs, less than half of which are unique amounts, with no address reuse) const addressReuse = Object.keys(reusedOutputAddresses).reduce((acc, key) => Math.max(acc, (reusedInputAddresses[key] || 0) + (reusedOutputAddresses[key] || 0)), 0) > 1; - if (!addressReuse && tx.vin.length >= 5 && tx.vout.length >= 5 && (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 ) { + const tokenRelated = (flags & (TransactionFlags.inscription | TransactionFlags.op_return)) !== 0n; + if (!addressReuse && + tx.vin.length >= 5 && + tx.vout.length >= 5 && + (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 && + !tokenRelated && + tx.vin.length / tx.vout.length < 5 && + tx.vin.length / tx.vout.length > 0.2 + ) { flags |= TransactionFlags.coinjoin; } // more than 5:1 input:output ratio