diff --git a/frontend/src/app/components/transaction/liquid-ublinding.ts b/frontend/src/app/components/transaction/liquid-ublinding.ts index 338e9013c..a53edeb4c 100644 --- a/frontend/src/app/components/transaction/liquid-ublinding.ts +++ b/frontend/src/app/components/transaction/liquid-ublinding.ts @@ -126,9 +126,13 @@ export class LiquidUnblinding { } async checkUnblindedTx(tx: Transaction) { - const windowLocationHash = window.location.hash.substring('#blinded='.length); - if (windowLocationHash.length > 0) { - const blinders = this.parseBlinders(windowLocationHash); + if (!window.location.hash?.length) { + return tx; + } + const fragmentParams = new URLSearchParams(window.location.hash.slice(1) || ''); + const blinderStr = fragmentParams.get('blinded'); + if (blinderStr && blinderStr.length) { + const blinders = this.parseBlinders(blinderStr); if (blinders) { this.commitments = await this.makeCommitmentMap(blinders); return this.tryUnblindTx(tx); diff --git a/frontend/src/app/components/transaction/transaction-preview.component.html b/frontend/src/app/components/transaction/transaction-preview.component.html index f023a77b1..cb273b16c 100644 --- a/frontend/src/app/components/transaction/transaction-preview.component.html +++ b/frontend/src/app/components/transaction/transaction-preview.component.html @@ -29,7 +29,7 @@
- +

diff --git a/frontend/src/app/components/transaction/transaction-preview.component.scss b/frontend/src/app/components/transaction/transaction-preview.component.scss index 65c0ca75e..75eceb99e 100644 --- a/frontend/src/app/components/transaction/transaction-preview.component.scss +++ b/frontend/src/app/components/transaction/transaction-preview.component.scss @@ -69,7 +69,7 @@ .graph-wrapper { position: relative; background: #181b2d; - padding: 10px; + padding: 10px 0; padding-bottom: 0; .above-bow { diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 1822f5d35..cd0cd716d 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -209,6 +209,7 @@ [maxStrands]="graphExpanded ? maxInOut : 24" [network]="network" [tooltip]="true" + [connectors]="true" [inputIndex]="inputIndex" [outputIndex]="outputIndex" > diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss index 7127a898a..619cac89a 100644 --- a/frontend/src/app/components/transaction/transaction.component.scss +++ b/frontend/src/app/components/transaction/transaction.component.scss @@ -86,7 +86,7 @@ position: relative; width: 100%; background: #181b2d; - padding: 10px; + padding: 10px 0; padding-bottom: 0; } diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 6b01cedae..f5a22d929 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -404,7 +404,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { @HostListener('window:resize', ['$event']) setGraphSize(): void { if (this.graphContainer) { - this.graphWidth = this.graphContainer.nativeElement.clientWidth - 24; + this.graphWidth = this.graphContainer.nativeElement.clientWidth; } } diff --git a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html index cbf2f7d5a..6262c56a7 100644 --- a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html +++ b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html @@ -22,13 +22,13 @@ -

Peg In

+

Peg In

-

Peg Out

+

Peg Out

{{ line.pegout.slice(0, -4) }} @@ -38,7 +38,7 @@ -

+

Input Output @@ -46,6 +46,17 @@ #{{ line.index + 1 }}

+ +

+ Transaction  + {{ line.txid.slice(0, 8) }}... + {{ line.txid.slice(-4) }} +

+ +

Output  #{{ line.vout + 1 }}

+

Input  #{{ line.vin + 1 }}

+
+

Confidential

diff --git a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts index 4e450a7dc..54c58ffab 100644 --- a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts +++ b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts @@ -5,6 +5,9 @@ interface Xput { type: 'input' | 'output' | 'fee'; value?: number; index?: number; + txid?: string; + vin?: number; + vout?: number; address?: string; rest?: number; coinbase?: boolean; @@ -21,6 +24,7 @@ interface Xput { export class TxBowtieGraphTooltipComponent implements OnChanges { @Input() line: Xput | void; @Input() cursorPosition: { x: number, y: number }; + @Input() isConnector: boolean = false; tooltipPosition = { x: 0, y: 0 }; diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html index a85f62c65..23346f405 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html @@ -29,6 +29,14 @@ + + + + + + + + @@ -41,6 +49,14 @@ + + + + + + + + @@ -65,6 +81,22 @@ + + + +

diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.scss b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.scss index 5a71ee421..7c9ecf0ce 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.scss +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.scss @@ -22,19 +22,46 @@ stroke: url(#output-highlight-gradient); } } + } - &:hover { - z-index: 10; - cursor: pointer; - &.input { - stroke: url(#input-hover-gradient); - } - &.output { - stroke: url(#output-hover-gradient); - } - &.fee { - stroke: url(#fee-hover-gradient); - } + .line:hover, .marker-target:hover + .line { + z-index: 10; + cursor: pointer; + &.input { + stroke: url(#input-hover-gradient); + } + &.output { + stroke: url(#output-hover-gradient); + } + &.fee { + stroke: url(#fee-hover-gradient); } } -} + + .connector { + stroke: none; + opacity: 0.75; + cursor: pointer; + &.input { + fill: url(#input-connector-gradient); + } + &.output { + fill: url(#output-connector-gradient); + } + } + + .connector:hover { + &.input { + fill: url(#input-hover-connector-gradient); + } + &.output { + fill: url(#output-hover-connector-gradient); + } + } + + .marker-target { + stroke: none; + fill: transparent; + cursor: pointer; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts index fa2faea8c..39164314a 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts @@ -11,12 +11,17 @@ interface SvgLine { path: string; style: string; class?: string; + connectorPath?: string; + markerPath?: string; } interface Xput { type: 'input' | 'output' | 'fee'; value?: number; index?: number; + txid?: string; + vin?: number; + vout?: number; address?: string; rest?: number; coinbase?: boolean; @@ -40,6 +45,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { @Input() minWeight = 2; // @Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen. @Input() tooltip = false; + @Input() connectors = false; @Input() inputIndex: number; @Input() outputIndex: number; @@ -49,9 +55,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { outputs: SvgLine[]; middle: SvgLine; midWidth: number; + txWidth: number; + connectorWidth: number; combinedWeight: number; isLiquid: boolean = false; hoverLine: Xput | void = null; + hoverConnector: boolean = false; tooltipPosition = { x: 0, y: 0 }; outspends: Outspend[] = []; @@ -59,16 +68,16 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { refreshOutspends$: ReplaySubject = new ReplaySubject(); gradientColors = { - '': ['#9339f4', '#105fb0'], - bisq: ['#9339f4', '#105fb0'], + '': ['#9339f4', '#105fb0', '#9339f400'], + bisq: ['#9339f4', '#105fb0', '#9339f400'], // liquid: ['#116761', '#183550'], - liquid: ['#09a197', '#0f62af'], + liquid: ['#09a197', '#0f62af', '#09a19700'], // 'liquidtestnet': ['#494a4a', '#272e46'], - 'liquidtestnet': ['#d2d2d2', '#979797'], + 'liquidtestnet': ['#d2d2d2', '#979797', '#d2d2d200'], // testnet: ['#1d486f', '#183550'], - testnet: ['#4edf77', '#10a0af'], + testnet: ['#4edf77', '#10a0af', '#4edf7700'], // signet: ['#6f1d5d', '#471850'], - signet: ['#d24fc8', '#a84fd2'], + signet: ['#d24fc8', '#a84fd2', '#d24fc800'], }; gradient: string[] = ['#105fb0', '#105fb0']; @@ -118,7 +127,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet'); this.gradient = this.gradientColors[this.network]; this.midWidth = Math.min(10, Math.ceil(this.width / 100)); - this.combinedWeight = Math.min(this.maxCombinedWeight, Math.floor((this.width - (2 * this.midWidth)) / 6)); + this.txWidth = this.connectors ? Math.max(this.width - 200, this.width * 0.8) : this.width - 20; + this.combinedWeight = Math.min(this.maxCombinedWeight, Math.floor((this.txWidth - (2 * this.midWidth)) / 6)); + this.connectorWidth = (this.width - this.txWidth) / 2; const totalValue = this.calcTotalValue(this.tx); let voutWithFee = this.tx.vout.map((v, i) => { @@ -141,6 +152,8 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { return { type: 'input', value: v?.prevout?.value, + txid: v.txid, + vout: v.vout, address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(), index: i, coinbase: v?.is_coinbase, @@ -268,7 +281,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { // required to prevent this line overlapping its neighbor if (this.tooltip || !xputs[i].rest) { - const w = (this.width - Math.max(lastWeight, line.weight)) / 2; // approximate horizontal width of the curved section of the line + const w = (this.width - Math.max(lastWeight, line.weight) - (2 * this.connectorWidth)) / 2; // approximate horizontal width of the curved section of the line const y1 = line.outerY; const y2 = line.innerY; const t = (lastWeight + line.weight) / 2; // distance between center of this line and center of previous line @@ -308,13 +321,15 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { return { path: this.makePath(side, line.outerY, line.innerY, line.thickness, line.offset, pad + maxOffset), style: this.makeStyle(line.thickness, xputs[i].type), - class: xputs[i].type + class: xputs[i].type, + connectorPath: this.connectors ? this.makeConnectorPath(side, line.outerY, line.innerY, line.thickness): null, + markerPath: this.makeMarkerPath(side, line.outerY, line.innerY, line.thickness), }; }); } makePath(side: 'in' | 'out', outer: number, inner: number, weight: number, offset: number, pad: number): string { - const start = (weight * 0.5); + const start = (weight * 0.5) + this.connectorWidth; const curveStart = Math.max(start + 1, pad - offset); const end = this.width / 2 - (this.midWidth * 0.9) + 1; const curveEnd = end - offset - 10; @@ -332,6 +347,40 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { } } + makeConnectorPath(side: 'in' | 'out', y: number, inner, weight: number): string { + const halfWidth = weight * 0.5; + const offset = 10; //Math.max(2, halfWidth * 0.2); + const lineEnd = this.connectorWidth; + + // align with for svg horizontal gradient bug correction + if (Math.round(y) === Math.round(inner)) { + y -= 1; + } + + if (side === 'in') { + return `M ${lineEnd - offset} ${y - halfWidth} L ${halfWidth + lineEnd - offset} ${y} L ${lineEnd - offset} ${y + halfWidth} L -${10} ${ y + halfWidth} L -${10} ${y - halfWidth}`; + } else { + return `M ${this.width - halfWidth - lineEnd + offset} ${y - halfWidth} L ${this.width - lineEnd + offset} ${y} L ${this.width - halfWidth - lineEnd + offset} ${y + halfWidth} L ${this.width + 10} ${ y + halfWidth} L ${this.width + 10} ${y - halfWidth}`; + } + } + + makeMarkerPath(side: 'in' | 'out', y: number, inner, weight: number): string { + const halfWidth = weight * 0.5; + const offset = 10; //Math.max(2, halfWidth * 0.2); + const lineEnd = this.connectorWidth; + + // align with for svg horizontal gradient bug correction + if (Math.round(y) === Math.round(inner)) { + y -= 1; + } + + if (side === 'in') { + return `M ${lineEnd - offset} ${y - halfWidth} L ${halfWidth + lineEnd - offset} ${y} L ${lineEnd - offset} ${y + halfWidth} L ${weight + lineEnd} ${ y + halfWidth} L ${weight + lineEnd} ${y - halfWidth}`; + } else { + return `M ${this.width - halfWidth - lineEnd + offset} ${y - halfWidth} L ${this.width - lineEnd + offset} ${y} L ${this.width - halfWidth - lineEnd + offset} ${y + halfWidth} L ${this.width - halfWidth - lineEnd} ${ y + halfWidth} L ${this.width - halfWidth - lineEnd} ${y - halfWidth}`; + } + } + makeStyle(minWeight, type): string { if (type === 'fee') { return `stroke-width: ${minWeight}`; @@ -346,26 +395,31 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { } onHover(event, side, index): void { - if (side === 'input') { + if (side.startsWith('input')) { this.hoverLine = { ...this.inputData[index], index }; + this.hoverConnector = (side === 'input-connector'); + } else { this.hoverLine = { - ...this.outputData[index] + ...this.outputData[index], + ...this.outspends[this.outputData[index].index] }; + this.hoverConnector = (side === 'output-connector'); } } onBlur(event, side, index): void { this.hoverLine = null; + this.hoverConnector = false; } onClick(event, side, index): void { - if (side === 'input') { + if (side.startsWith('input')) { const input = this.tx.vin[index]; - if (input && !input.is_coinbase && !input.is_pegin && input.txid && input.vout != null) { + if (side === 'input-connector' && input && !input.is_coinbase && !input.is_pegin && input.txid && input.vout != null) { this.router.navigate([this.relativeUrlPipe.transform('/tx'), input.txid], { queryParamsHandling: 'merge', fragment: (new URLSearchParams({ @@ -385,7 +439,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { } else { const output = this.tx.vout[index]; const outspend = this.outspends[index]; - if (output && outspend && outspend.spent && outspend.txid) { + if (side === 'output-connector' && output && outspend && outspend.spent && outspend.txid) { this.router.navigate([this.relativeUrlPipe.transform('/tx'), outspend.txid], { queryParamsHandling: 'merge', fragment: (new URLSearchParams({