mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-12 19:20:46 +02:00
Decode input/output addresses. Optimize for mobile
This commit is contained in:
parent
1db488d350
commit
987c2c801b
33
client/package-lock.json
generated
33
client/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bitfeed-client",
|
||||
"version": "2.1.5",
|
||||
"version": "2.2.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bitfeed-client",
|
||||
"version": "2.1.5",
|
||||
"version": "2.2.1",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.5",
|
||||
"@babel/preset-env": "^7.16.5",
|
||||
@ -21,6 +21,7 @@
|
||||
"d3-color": "^3.0.1",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"hash.js": "^1.1.7",
|
||||
"locale-currency": "0.0.2",
|
||||
"qrcode": "^1.5.0",
|
||||
"rollup": "^2.62.0",
|
||||
@ -2834,6 +2835,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hash.js": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@ -3161,6 +3171,11 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
@ -6383,6 +6398,15 @@
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@ -6628,6 +6652,11 @@
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
|
||||
},
|
||||
"minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"d3-color": "^3.0.1",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"hash.js": "^1.1.7",
|
||||
"locale-currency": "0.0.2",
|
||||
"qrcode": "^1.5.0",
|
||||
"rollup": "^2.62.0",
|
||||
|
@ -6,6 +6,7 @@ import { longBtcFormat, numberFormat, feeRateFormat } from '../utils/format.js'
|
||||
import { exchangeRates, settings, sidebarToggle, newHighlightQuery, highlightingFull, detailTx, pageWidth } from '../stores.js'
|
||||
import { formatCurrency } from '../utils/fx.js'
|
||||
import { hlToHex, mixColor, teal, purple } from '../utils/color.js'
|
||||
import { SPKToAddress } from '../utils/encodings.js'
|
||||
|
||||
function onClose () {
|
||||
$detailTx = null
|
||||
@ -50,6 +51,29 @@ $: {
|
||||
}
|
||||
}
|
||||
|
||||
function expandAddresses(items) {
|
||||
return items.map(item => {
|
||||
let address = 'unknown'
|
||||
if (item.script_pub_key) {
|
||||
address = SPKToAddress(item.script_pub_key) || ""
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
address
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let inputs = []
|
||||
let outputs = []
|
||||
$: {
|
||||
if ($detailTx && $detailTx.inputs) {
|
||||
inputs = expandAddresses($detailTx.inputs)
|
||||
} else inputs = []
|
||||
if ($detailTx && $detailTx.outputs) {
|
||||
outputs = expandAddresses($detailTx.outputs)
|
||||
} else outputs = []
|
||||
}
|
||||
|
||||
let sankeyLines
|
||||
let sankeyHeight
|
||||
@ -230,10 +254,8 @@ function getMiterOffset (weight, dy, dx) {
|
||||
}
|
||||
|
||||
.flow-diagram {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0px, 1fr) 380px minmax(0px, 1fr);
|
||||
|
||||
.header {
|
||||
height: 60px;
|
||||
@ -244,8 +266,7 @@ function getMiterOffset (weight, dy, dx) {
|
||||
}
|
||||
|
||||
.column {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
|
||||
.entry {
|
||||
@ -257,29 +278,42 @@ function getMiterOffset (weight, dy, dx) {
|
||||
border-top: solid 1px var(--grey);
|
||||
box-sizing: border-box;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
&:last-child {
|
||||
border-bottom: solid 1px var(--grey);
|
||||
}
|
||||
|
||||
.amount, .address {
|
||||
margin: 0;
|
||||
}
|
||||
.amount {
|
||||
font-family: monospace;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.amount {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.address {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
}
|
||||
.truncatable {
|
||||
max-width: calc(100% - 4em);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.inputs {
|
||||
.entry {
|
||||
align-items: flex-start;
|
||||
.address {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.diagram {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sankey {
|
||||
@ -301,23 +335,11 @@ function getMiterOffset (weight, dy, dx) {
|
||||
|
||||
@media (max-width: 460px) {
|
||||
.flow-diagram {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 0.8em;
|
||||
display: block;
|
||||
|
||||
.column {
|
||||
width: 100%;
|
||||
|
||||
&.diagram {
|
||||
order: 1;
|
||||
}
|
||||
&.inputs {
|
||||
order: 2
|
||||
}
|
||||
&.outputs {
|
||||
margin-top: 30px;
|
||||
order: 3
|
||||
}
|
||||
margin: 30px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -356,12 +378,12 @@ function getMiterOffset (weight, dy, dx) {
|
||||
</div>
|
||||
|
||||
<h2>Inputs & Outputs</h2>
|
||||
<div class="pane flow-diagram">
|
||||
<div class="pane flow-diagram" style="grid-template-columns: minmax(0px, 1fr) {svgWidth}px minmax(0px, 1fr);">
|
||||
<div class="column inputs">
|
||||
<p class="header">{$detailTx.inputs.length} input{$detailTx.inputs.length > 1 ? 's' : ''}</p>
|
||||
{#each $detailTx.inputs as input}
|
||||
{#each inputs as input}
|
||||
<div class="entry">
|
||||
<p class="address">address</p>
|
||||
<p class="address" title={input.address}><span class="truncatable">{input.address.slice(0,-6)}</span><span class="suffix">{input.address.slice(-6)}</span></p>
|
||||
<p class="amount">{ formatBTC(input.value) }</p>
|
||||
</div>
|
||||
{/each}
|
||||
@ -399,9 +421,9 @@ function getMiterOffset (weight, dy, dx) {
|
||||
<p class="address">fee</p>
|
||||
<p class="amount">{ formatBTC($detailTx.fee) }</p>
|
||||
</div>
|
||||
{#each $detailTx.outputs as output}
|
||||
{#each outputs as output}
|
||||
<div class="entry">
|
||||
<p class="address">address</p>
|
||||
<p class="address" title={output.address}><span class="truncatable">{output.address.slice(0,-6)}</span><span class="suffix">{output.address.slice(-6)}</span></p>
|
||||
<p class="amount">{ formatBTC(output.value) }</p>
|
||||
</div>
|
||||
{/each}
|
||||
|
@ -205,9 +205,10 @@ export default class TxController {
|
||||
}
|
||||
this.selectedTx = selected
|
||||
selectedTx.set(this.selectedTx)
|
||||
console.log(this.selectedTx)
|
||||
detailTx.set(this.selectedTx)
|
||||
if (this.selectedTx) overlay.set('tx')
|
||||
if (sameTx && this.selectedTx) {
|
||||
detailTx.set(this.selectedTx)
|
||||
overlay.set('tx')
|
||||
}
|
||||
this.selectionLocked = !!this.selectedTx && !(this.selectionLocked && sameTx)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Buffer } from 'buffer/'
|
||||
window.Buffer = Buffer
|
||||
import bech32 from 'bech32-buffer'
|
||||
import { base58_to_binary } from 'base58-js'
|
||||
import { base58_to_binary, binary_to_base58 } from 'base58-js'
|
||||
import { sha256 } from 'hash.js'
|
||||
|
||||
// Extract a raw script hash from an address
|
||||
export function addressToSPK (address) {
|
||||
@ -25,3 +26,36 @@ export function addressToSPK (address) {
|
||||
return prefix + Buffer.from(result).toString('hex').slice(2, -8) + postfix
|
||||
}
|
||||
}
|
||||
|
||||
// Extract an address from a raw scriptpubkey
|
||||
export function SPKToAddress (spk) {
|
||||
if (spk.startsWith('5120')) {
|
||||
// taproot
|
||||
return (new bech32.BitcoinAddress('bc', 1, hexToUintArray(spk.slice(4)))).encode()
|
||||
} else if (spk.startsWith('0020') || spk.startsWith('0014')) {
|
||||
// p2wsh or p2wpkh
|
||||
return (new bech32.BitcoinAddress('bc', 0, hexToUintArray(spk.slice(4)))).encode()
|
||||
} else if (spk.startsWith('76a914')) {
|
||||
// p2pkh
|
||||
const payload = "00" + spk.slice(6, -4)
|
||||
const checksum = hash(hash(payload)).slice(0, 8)
|
||||
return binary_to_base58(hexToUintArray(payload + checksum))
|
||||
} else if (spk.startsWith('a914')) {
|
||||
// p2sh
|
||||
const payload = "05" + spk.slice(4, -2)
|
||||
const checksum = hash(hash(payload)).slice(0, 8)
|
||||
return binary_to_base58(hexToUintArray(payload + checksum))
|
||||
}
|
||||
}
|
||||
|
||||
function hexToUintArray(hex) {
|
||||
let a = new Uint8Array(hex.length / 2)
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
a[i] = parseInt(hex.substr(2 * i, 2), 16)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
function hash (hex) {
|
||||
return sha256().update(hexToUintArray(hex)).digest('hex')
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user