mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
Merge 877f3cd5b2 into 79e79bcadb
This commit is contained in:
commit
55c2e651af
7 changed files with 432 additions and 3 deletions
|
|
@ -135,6 +135,19 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="network !== 'liquid' && network !== 'liquidtestnet' && !address.electrum && addressTypeInfo && spendableUtxoCount > 0">
|
||||
<br>
|
||||
<div class="title-tx">
|
||||
<h2 *ngIf="showCostToSpend" class="text-start float-start" i18n="address.cost-to-spend">Cost to Spend</h2>
|
||||
<button type="button" class="btn btn-outline-info btn-sm float-end" (click)="showCostToSpend = !showCostToSpend">
|
||||
<ng-container *ngIf="!showCostToSpend" i18n="address.show-cost-to-spend|Show cost to spend">Cost to Spend</ng-container>
|
||||
<ng-container *ngIf="showCostToSpend" i18n="address.hide-cost-to-spend|Hide cost to spend">Hide</ng-container>
|
||||
</button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<app-cost-to-spend *ngIf="showCostToSpend" [addressTypeInfo]="addressTypeInfo" [utxoCount]="spendableUtxoCount" [balance]="chainStats.balance + mempoolStats.balance"></app-cost-to-spend>
|
||||
</ng-container>
|
||||
|
||||
<br>
|
||||
<div class="title-tx">
|
||||
<h2 class="text-start">
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { of, merge, Subscription, Observable, forkJoin } from 'rxjs';
|
|||
import { SeoService } from '@app/services/seo.service';
|
||||
import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
||||
import { AddressInformation } from '@interfaces/node-api.interface';
|
||||
import { AddressTypeInfo } from '@app/shared/address-utils';
|
||||
import { AddressTypeInfo, observedInputVsize } from '@app/shared/address-utils';
|
||||
import { extractTapLeaves, fillTapTree, convertTextToBuffer, PsbtKeyValue } from '@app/shared/transaction.utils';
|
||||
|
||||
class AddressStats implements ChainStats {
|
||||
|
|
@ -124,6 +124,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||
addressTypeInfo: null | AddressTypeInfo;
|
||||
tapTreeIncomplete: boolean = false;
|
||||
taprootPsbtExpanded: boolean = false;
|
||||
showCostToSpend: boolean = false;
|
||||
psbtForm: UntypedFormGroup;
|
||||
psbtError?: string;
|
||||
accelerationsSubscription: Subscription;
|
||||
|
|
@ -159,7 +160,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||
this.network = network;
|
||||
this.updateAccelerationSubscription();
|
||||
});
|
||||
this.websocketService.want(['blocks']);
|
||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||
this.psbtForm = this.formBuilder.group({ psbt: [''], tapleaf: [''], taptree: [''], ikey: [''] });
|
||||
|
||||
this.onResize();
|
||||
|
|
@ -326,6 +327,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
this.addressTypeInfo.processInputs(addressVin, vinIds);
|
||||
this.addressTypeInfo.observedInputVsize = observedInputVsize(addressVin);
|
||||
if (this.addressTypeInfo.type === 'v1_p2tr' && !this.addressTypeInfo.tapscript) {
|
||||
this.setTapTreeIncomplete(true);
|
||||
}
|
||||
|
|
@ -520,6 +522,10 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||
this.mempoolStats = new AddressStats(this.address.mempool_stats, this.address.address);
|
||||
}
|
||||
|
||||
get spendableUtxoCount(): number {
|
||||
return this.chainStats.utxos + this.mempoolStats.utxos;
|
||||
}
|
||||
|
||||
setBalancePeriod(period: 'all' | '1m'): boolean {
|
||||
this.balancePeriod = period;
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
<ng-container *ngIf="{ cost: costToSpend$ | async } as state">
|
||||
<div class="box" *ngIf="state.cost as cost; else notReady">
|
||||
<div class="row align-items-start">
|
||||
<div class="col-md">
|
||||
<div
|
||||
class="text-muted small text-uppercase mb-1"
|
||||
i18n="address.cost-to-spend.parameters|Cost to spend parameters group"
|
||||
>
|
||||
Parameters
|
||||
</div>
|
||||
<table class="table table-borderless table-striped cost-to-spend-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td i18n="address.cost-to-spend.input-vsize">vsize per input</td>
|
||||
<td class="wrap-cell">
|
||||
<span
|
||||
*ngIf="cost.estimated"
|
||||
class="estimate-marker"
|
||||
i18n-ngbTooltip="address.cost-to-spend.estimated-tooltip"
|
||||
ngbTooltip="Estimated for this script type; actual size varies"
|
||||
placement="bottom"
|
||||
>~</span
|
||||
>{{ cost.inputVsize | number }} <span class="symbol">vB</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="address.cost-to-spend.utxos">Spendable UTXOs</td>
|
||||
<td class="wrap-cell">{{ utxoCount | number }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="address.cost-to-spend.fee-rate">Fee rate (~30 min)</td>
|
||||
<td class="wrap-cell">
|
||||
<app-fee-rate [fee]="cost.feeRate"></app-fee-rate>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div
|
||||
class="text-muted small text-uppercase mb-1"
|
||||
i18n="address.cost-to-spend.group-cost|Cost to spend group header"
|
||||
>
|
||||
Cost to spend
|
||||
</div>
|
||||
<table
|
||||
class="table table-borderless table-striped cost-to-spend-table mb-3"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
i18n-ngbTooltip="address.cost-to-spend.min-cost-tooltip"
|
||||
ngbTooltip="Best case: all UTXOs consolidated into a single transaction, so the per-transaction overhead is shared"
|
||||
placement="bottom"
|
||||
>
|
||||
<span i18n="address.cost-to-spend.min-cost"
|
||||
>Spend all together</span
|
||||
>
|
||||
</td>
|
||||
<td class="wrap-cell">
|
||||
<app-amount
|
||||
[satoshis]="cost.minCost"
|
||||
[noFiat]="true"
|
||||
></app-amount>
|
||||
<span class="fiat"
|
||||
><app-fiat [value]="cost.minCost"></app-fiat
|
||||
></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
i18n-ngbTooltip="address.cost-to-spend.max-cost-tooltip"
|
||||
ngbTooltip="Worst case: each UTXO is spent separately, each in its own transaction with one typical output, so every spend pays full overhead"
|
||||
placement="bottom"
|
||||
>
|
||||
<span i18n="address.cost-to-spend.max-cost"
|
||||
>Spend separately</span
|
||||
>
|
||||
</td>
|
||||
<td class="wrap-cell">
|
||||
<app-amount
|
||||
[satoshis]="cost.maxCost"
|
||||
[noFiat]="true"
|
||||
></app-amount>
|
||||
<span class="fiat"
|
||||
><app-fiat [value]="cost.maxCost"></app-fiat
|
||||
></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div
|
||||
class="text-muted small text-uppercase mb-1"
|
||||
i18n="
|
||||
address.cost-to-spend.group-effective-balance|Effective balance
|
||||
group header
|
||||
"
|
||||
>
|
||||
Effective balance
|
||||
</div>
|
||||
<table class="table table-borderless table-striped cost-to-spend-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
i18n-ngbTooltip="
|
||||
address.cost-to-spend.min-effective-balance-tooltip
|
||||
"
|
||||
ngbTooltip="Balance minus the maximum cost to spend (each UTXO spent separately)"
|
||||
placement="bottom"
|
||||
>
|
||||
<span i18n="address.cost-to-spend.min-effective-balance"
|
||||
>Min effective balance</span
|
||||
>
|
||||
</td>
|
||||
<td class="wrap-cell">
|
||||
<app-amount
|
||||
[satoshis]="cost.minEffectiveBalance"
|
||||
[noFiat]="true"
|
||||
></app-amount>
|
||||
<span class="fiat"
|
||||
><app-fiat [value]="cost.minEffectiveBalance"></app-fiat
|
||||
></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
i18n-ngbTooltip="
|
||||
address.cost-to-spend.max-effective-balance-tooltip
|
||||
"
|
||||
ngbTooltip="Balance minus the minimum cost to spend (all UTXOs consolidated)"
|
||||
placement="bottom"
|
||||
>
|
||||
<span i18n="address.cost-to-spend.max-effective-balance"
|
||||
>Max effective balance</span
|
||||
>
|
||||
</td>
|
||||
<td class="wrap-cell">
|
||||
<app-amount
|
||||
[satoshis]="cost.maxEffectiveBalance"
|
||||
[noFiat]="true"
|
||||
></app-amount>
|
||||
<span class="fiat"
|
||||
><app-fiat [value]="cost.maxEffectiveBalance"></app-fiat
|
||||
></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #notReady>
|
||||
<ng-container *ngIf="state.cost === null; else loading">
|
||||
<div class="box">
|
||||
<div class="text-center" i18n="address.cost-to-spend.unavailable">
|
||||
Fee data unavailable
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #loading>
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
<div class="col-md" *ngFor="let col of [1, 2]">
|
||||
<table
|
||||
class="table table-borderless table-striped cost-to-spend-table"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let i of [1, 2, 3, 4]">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
.cost-to-spend-table {
|
||||
margin-bottom: 0;
|
||||
|
||||
tr td {
|
||||
&:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.wrap-cell {
|
||||
white-space: nowrap;
|
||||
@media (max-width: 575.98px) {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fiat {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.estimate-marker {
|
||||
cursor: help;
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { BehaviorSubject, Observable, combineLatest, of } from 'rxjs';
|
||||
import { map, timeout, catchError, startWith } from 'rxjs/operators';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
import {
|
||||
AddressTypeInfo,
|
||||
TX_OVERHEAD_VSIZE,
|
||||
TYPICAL_OUTPUT_VSIZE,
|
||||
estimateInputVsize,
|
||||
} from '@app/shared/address-utils';
|
||||
|
||||
// give up waiting for fees over the websocket after this long
|
||||
const FEE_TIMEOUT_MS = 10000;
|
||||
|
||||
interface CostToSpend {
|
||||
feeRate: number;
|
||||
inputVsize: number;
|
||||
estimated: boolean;
|
||||
minCost: number;
|
||||
maxCost: number;
|
||||
minEffectiveBalance: number;
|
||||
maxEffectiveBalance: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-cost-to-spend',
|
||||
templateUrl: './cost-to-spend.component.html',
|
||||
styleUrls: ['./cost-to-spend.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class CostToSpendComponent implements OnInit, OnChanges {
|
||||
@Input() addressTypeInfo: AddressTypeInfo;
|
||||
@Input() utxoCount: number;
|
||||
@Input() balance: number;
|
||||
|
||||
costToSpend$: Observable<CostToSpend | null | undefined>;
|
||||
// Bridges @Input changes into the reactive pipeline so cost recalculates on live balance updates
|
||||
private inputs$ = new BehaviorSubject<void>(undefined);
|
||||
|
||||
constructor(private stateService: StateService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.costToSpend$ = combineLatest([
|
||||
this.stateService.recommendedFees$,
|
||||
this.inputs$,
|
||||
]).pipe(
|
||||
map(([fees]) => this.calculate(fees.halfHourFee)),
|
||||
timeout({ first: FEE_TIMEOUT_MS }), // fees never arrived (websocket failure)
|
||||
catchError(() => of(null)), // null = unavailable; stream closes, but *ngIf recreates the component on navigation so the state can't persist across addresses
|
||||
startWith(undefined),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.inputs$.next();
|
||||
}
|
||||
|
||||
private calculate(feeRate: number): CostToSpend {
|
||||
const { vsize: inputVsize, estimated } = estimateInputVsize(
|
||||
this.addressTypeInfo,
|
||||
this.addressTypeInfo.observedInputVsize
|
||||
);
|
||||
const overhead = TX_OVERHEAD_VSIZE + TYPICAL_OUTPUT_VSIZE;
|
||||
// consolidate all UTXOs into one tx overhead paid once (theoretical lower bound)
|
||||
const minCost = Math.ceil(
|
||||
(this.utxoCount * inputVsize + overhead) * feeRate
|
||||
);
|
||||
// max spend each UTXO in its own transaction, rounding each individual
|
||||
const maxCost =
|
||||
this.utxoCount * Math.ceil((inputVsize + overhead) * feeRate);
|
||||
return {
|
||||
feeRate,
|
||||
inputVsize,
|
||||
estimated,
|
||||
minCost,
|
||||
maxCost,
|
||||
// min effective balance subtracts the max cost & max subtracts the min
|
||||
minEffectiveBalance: Math.max(0, this.balance - maxCost),
|
||||
maxEffectiveBalance: Math.max(0, this.balance - minCost),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ import { HashrateChartComponent } from '@components/hashrate-chart/hashrate-char
|
|||
import { HashrateChartPoolsComponent } from '@components/hashrates-chart-pools/hashrate-chart-pools.component';
|
||||
import { BlockHealthGraphComponent } from '@components/block-health-graph/block-health-graph.component';
|
||||
import { AddressComponent } from '@components/address/address.component';
|
||||
import { CostToSpendComponent } from '@components/address/cost-to-spend/cost-to-spend.component';
|
||||
import { WalletComponent } from '@components/wallet/wallet.component';
|
||||
import { WalletPreviewComponent } from '@components/wallet/wallet-preview.component';
|
||||
import { AddressGraphComponent } from '@components/address-graph/address-graph.component';
|
||||
|
|
@ -55,6 +56,7 @@ import { CommonModule } from '@angular/common';
|
|||
CustomDashboardComponent,
|
||||
MempoolBlockComponent,
|
||||
AddressComponent,
|
||||
CostToSpendComponent,
|
||||
WalletComponent,
|
||||
WalletPreviewComponent,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import '@angular/localize/init';
|
||||
import { ScriptInfo } from '@app/shared/script.utils';
|
||||
import { ScriptInfo, getVarIntLength } from '@app/shared/script.utils';
|
||||
import { Vin, Vout } from '@interfaces/electrs.interface';
|
||||
import { BECH32_CHARS_LW, BASE58_CHARS, HEX_CHARS } from '@app/shared/regex.utils';
|
||||
import { parseTaproot } from './transaction.utils';
|
||||
|
|
@ -137,6 +137,7 @@ export class AddressTypeInfo {
|
|||
isMultisig?: { m: number, n: number };
|
||||
tapscript?: boolean;
|
||||
simplicity?: boolean;
|
||||
observedInputVsize?: number; // median realized input vsize from previous spends
|
||||
|
||||
constructor (network: string, address: string, type?: AddressType, vin?: Vin[], vout?: Vout) {
|
||||
this.network = network;
|
||||
|
|
@ -159,6 +160,7 @@ export class AddressTypeInfo {
|
|||
cloned.isMultisig = this.isMultisig;
|
||||
cloned.tapscript = this.tapscript;
|
||||
cloned.simplicity = this.simplicity;
|
||||
cloned.observedInputVsize = this.observedInputVsize;
|
||||
return cloned;
|
||||
}
|
||||
|
||||
|
|
@ -256,6 +258,120 @@ export class AddressTypeInfo {
|
|||
}
|
||||
}
|
||||
|
||||
// vsize of typical transaction overhead when spending a UTXO in its own
|
||||
// transaction: version + locktime + in/out counts + segwit marker/flag (~11 vB)
|
||||
// plus one typical output (~31 vB for a p2wpkh recipient).
|
||||
export const TX_OVERHEAD_VSIZE = 11;
|
||||
export const TYPICAL_OUTPUT_VSIZE = 31;
|
||||
|
||||
// estimated vsize (vB) of a single input spending each address type, for the
|
||||
// common single-key case. Derived from the per-component byte costs documented
|
||||
// in fillUnsignedInput (transaction.utils.ts): DER sig 72 B, pubkey 34 B,
|
||||
// Schnorr sig 65 B, with the 4x witness discount applied.
|
||||
const INPUT_VSIZE: Partial<Record<AddressType, number>> = {
|
||||
p2pk: 114,
|
||||
p2pkh: 148,
|
||||
'p2sh-p2wpkh': 91,
|
||||
v0_p2wpkh: 68,
|
||||
v1_p2tr: 58, // keyspend
|
||||
};
|
||||
|
||||
// rough vsize of an m-of-n multisig input. `wrapped` adds the p2sh redeemscript
|
||||
// pushed in the scriptsig (p2sh-p2wsh); otherwise native p2wsh.
|
||||
function multisigWitnessInputVsize(m: number, n: number, wrapped: boolean): number {
|
||||
// wrapped (p2sh-p2wsh) scriptsig pushes the 34-byte witness program: 1-byte push opcode + 34 bytes
|
||||
const nonWitness = wrapped ? 76 : 41; // outpoint + sequence (+ redeemscript push)
|
||||
const witnessScript = 3 + n * 34; // OP_m + n*push33 + OP_n + OP_CHECKMULTISIG
|
||||
// stack: item count + dummy + m*(length prefix + signature) + script length prefix + script
|
||||
const witnessBytes = 1 + 1 + m * (1 + 72) + getVarIntLength(witnessScript) + witnessScript;
|
||||
return Math.ceil(nonWitness + witnessBytes / 4); // consensus vsize rounds up
|
||||
}
|
||||
|
||||
// realized weight (WU) of a spent input, reconstructed from its on-chain scriptsig + witness
|
||||
function vinWeightUnits(vin: Vin): number | null {
|
||||
if (!vin || vin.is_coinbase) {
|
||||
return null;
|
||||
}
|
||||
const scriptsigLen = (vin.scriptsig?.length || 0) / 2;
|
||||
const base = 36 + getVarIntLength(scriptsigLen) + scriptsigLen + 4;
|
||||
let witnessBytes = 0;
|
||||
// count witness only when present; legacy inputs carry no witness section
|
||||
if (vin.witness && vin.witness.length) {
|
||||
witnessBytes += getVarIntLength(vin.witness.length);
|
||||
for (const item of vin.witness) {
|
||||
const itemLen = item.length / 2;
|
||||
witnessBytes += getVarIntLength(itemLen) + itemLen;
|
||||
}
|
||||
}
|
||||
return base * 4 + witnessBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Median realized input vsize (vB) across previous spends, or undefined if none
|
||||
* are measurable. Kept fractional so rounding error doesn't accumulate per UTXO.
|
||||
*/
|
||||
export function observedInputVsize(vins: Vin[]): number | undefined {
|
||||
const vsizes = (vins || [])
|
||||
.map((v) => vinWeightUnits(v))
|
||||
.filter((wu): wu is number => wu !== null && wu > 0)
|
||||
.map((wu) => wu / 4);
|
||||
if (!vsizes.length) {
|
||||
return undefined;
|
||||
}
|
||||
vsizes.sort((a, b) => a - b);
|
||||
const mid = Math.floor(vsizes.length / 2);
|
||||
return vsizes.length % 2 ? vsizes[mid] : (vsizes[mid - 1] + vsizes[mid]) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimates the vsize (vB) of a single input spending from this address.
|
||||
*
|
||||
* `estimated` flags address types whose input size is variable (multisig,
|
||||
* p2wsh, tapscript script-path, unknown) and therefore only approximated.
|
||||
*
|
||||
* `observedVsize`, when provided, is the realized vsize of an input extracted
|
||||
* from a previous spend and always takes priority over the type-based estimate.
|
||||
*/
|
||||
export function estimateInputVsize(info: AddressTypeInfo, observedVsize?: number): { vsize: number; estimated: boolean } {
|
||||
if (observedVsize !== undefined) {
|
||||
return { vsize: observedVsize, estimated: false };
|
||||
}
|
||||
|
||||
switch (info.type) {
|
||||
case 'p2pk':
|
||||
case 'p2pkh':
|
||||
case 'p2sh-p2wpkh':
|
||||
case 'v0_p2wpkh':
|
||||
return { vsize: INPUT_VSIZE[info.type], estimated: false };
|
||||
case 'v1_p2tr':
|
||||
// 58 vB is a typical key-path estimate; without an observed spend we
|
||||
// can't tell key-path from a (variable) script-path spend, so it stays
|
||||
// approximate
|
||||
return { vsize: INPUT_VSIZE.v1_p2tr, estimated: true };
|
||||
case 'v0_p2wsh':
|
||||
if (info.isMultisig) {
|
||||
return { vsize: multisigWitnessInputVsize(info.isMultisig.m, info.isMultisig.n, false), estimated: true };
|
||||
}
|
||||
return { vsize: 105, estimated: true }; // assume 2-of-3
|
||||
case 'p2sh-p2wsh':
|
||||
if (info.isMultisig) {
|
||||
return { vsize: multisigWitnessInputVsize(info.isMultisig.m, info.isMultisig.n, true), estimated: true };
|
||||
}
|
||||
return { vsize: 139, estimated: true }; // assume 2-of-3
|
||||
case 'multisig':
|
||||
if (info.isMultisig) {
|
||||
// bare multisig: signatures + redeemscript in the scriptsig (no discount)
|
||||
return { vsize: 41 + info.isMultisig.m * 73 + (3 + info.isMultisig.n * 34), estimated: true };
|
||||
}
|
||||
return { vsize: 252, estimated: true }; // assume 2-of-3
|
||||
case 'p2sh':
|
||||
// unresolved p2sh (no spend seen yet): assume nested segwit single-key
|
||||
return { vsize: INPUT_VSIZE['p2sh-p2wpkh'], estimated: true };
|
||||
default:
|
||||
return { vsize: INPUT_VSIZE.v0_p2wpkh, estimated: true };
|
||||
}
|
||||
}
|
||||
|
||||
export interface AddressMatch {
|
||||
prefix: string;
|
||||
postfix: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue