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",
|
"name": "bitfeed-client",
|
||||||
"version": "2.1.5",
|
"version": "2.2.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bitfeed-client",
|
"name": "bitfeed-client",
|
||||||
"version": "2.1.5",
|
"version": "2.2.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.16.5",
|
"@babel/core": "^7.16.5",
|
||||||
"@babel/preset-env": "^7.16.5",
|
"@babel/preset-env": "^7.16.5",
|
||||||
@ -21,6 +21,7 @@
|
|||||||
"d3-color": "^3.0.1",
|
"d3-color": "^3.0.1",
|
||||||
"d3-interpolate": "^3.0.1",
|
"d3-interpolate": "^3.0.1",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
|
"hash.js": "^1.1.7",
|
||||||
"locale-currency": "0.0.2",
|
"locale-currency": "0.0.2",
|
||||||
"qrcode": "^1.5.0",
|
"qrcode": "^1.5.0",
|
||||||
"rollup": "^2.62.0",
|
"rollup": "^2.62.0",
|
||||||
@ -2834,6 +2835,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
@ -3161,6 +3171,11 @@
|
|||||||
"node": ">=4"
|
"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": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||||
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
"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": {
|
"ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
|
"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": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"d3-color": "^3.0.1",
|
"d3-color": "^3.0.1",
|
||||||
"d3-interpolate": "^3.0.1",
|
"d3-interpolate": "^3.0.1",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
|
"hash.js": "^1.1.7",
|
||||||
"locale-currency": "0.0.2",
|
"locale-currency": "0.0.2",
|
||||||
"qrcode": "^1.5.0",
|
"qrcode": "^1.5.0",
|
||||||
"rollup": "^2.62.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 { exchangeRates, settings, sidebarToggle, newHighlightQuery, highlightingFull, detailTx, pageWidth } from '../stores.js'
|
||||||
import { formatCurrency } from '../utils/fx.js'
|
import { formatCurrency } from '../utils/fx.js'
|
||||||
import { hlToHex, mixColor, teal, purple } from '../utils/color.js'
|
import { hlToHex, mixColor, teal, purple } from '../utils/color.js'
|
||||||
|
import { SPKToAddress } from '../utils/encodings.js'
|
||||||
|
|
||||||
function onClose () {
|
function onClose () {
|
||||||
$detailTx = null
|
$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 sankeyLines
|
||||||
let sankeyHeight
|
let sankeyHeight
|
||||||
@ -230,10 +254,8 @@ function getMiterOffset (weight, dy, dx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flow-diagram {
|
.flow-diagram {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: row;
|
grid-template-columns: minmax(0px, 1fr) 380px minmax(0px, 1fr);
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
@ -244,8 +266,7 @@ function getMiterOffset (weight, dy, dx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
flex-grow: 1;
|
width: 100%;
|
||||||
flex-shrink: 1;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
@ -257,29 +278,42 @@ function getMiterOffset (weight, dy, dx) {
|
|||||||
border-top: solid 1px var(--grey);
|
border-top: solid 1px var(--grey);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: solid 1px var(--grey);
|
border-bottom: solid 1px var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.amount, .address {
|
.amount, .address {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
|
||||||
.amount {
|
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
.amount {
|
||||||
white-space: nowrap;
|
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 {
|
&.inputs {
|
||||||
.entry {
|
.entry {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
.address {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.diagram {
|
|
||||||
flex-grow: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sankey {
|
.sankey {
|
||||||
@ -301,23 +335,11 @@ function getMiterOffset (weight, dy, dx) {
|
|||||||
|
|
||||||
@media (max-width: 460px) {
|
@media (max-width: 460px) {
|
||||||
.flow-diagram {
|
.flow-diagram {
|
||||||
flex-direction: column;
|
display: block;
|
||||||
align-items: center;
|
|
||||||
font-size: 0.8em;
|
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin: 30px 0;
|
||||||
&.diagram {
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
&.inputs {
|
|
||||||
order: 2
|
|
||||||
}
|
|
||||||
&.outputs {
|
|
||||||
margin-top: 30px;
|
|
||||||
order: 3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -356,12 +378,12 @@ function getMiterOffset (weight, dy, dx) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Inputs & Outputs</h2>
|
<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">
|
<div class="column inputs">
|
||||||
<p class="header">{$detailTx.inputs.length} input{$detailTx.inputs.length > 1 ? 's' : ''}</p>
|
<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">
|
<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>
|
<p class="amount">{ formatBTC(input.value) }</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@ -399,9 +421,9 @@ function getMiterOffset (weight, dy, dx) {
|
|||||||
<p class="address">fee</p>
|
<p class="address">fee</p>
|
||||||
<p class="amount">{ formatBTC($detailTx.fee) }</p>
|
<p class="amount">{ formatBTC($detailTx.fee) }</p>
|
||||||
</div>
|
</div>
|
||||||
{#each $detailTx.outputs as output}
|
{#each outputs as output}
|
||||||
<div class="entry">
|
<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>
|
<p class="amount">{ formatBTC(output.value) }</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -205,9 +205,10 @@ export default class TxController {
|
|||||||
}
|
}
|
||||||
this.selectedTx = selected
|
this.selectedTx = selected
|
||||||
selectedTx.set(this.selectedTx)
|
selectedTx.set(this.selectedTx)
|
||||||
console.log(this.selectedTx)
|
if (sameTx && this.selectedTx) {
|
||||||
detailTx.set(this.selectedTx)
|
detailTx.set(this.selectedTx)
|
||||||
if (this.selectedTx) overlay.set('tx')
|
overlay.set('tx')
|
||||||
|
}
|
||||||
this.selectionLocked = !!this.selectedTx && !(this.selectionLocked && sameTx)
|
this.selectionLocked = !!this.selectedTx && !(this.selectionLocked && sameTx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Buffer } from 'buffer/'
|
import { Buffer } from 'buffer/'
|
||||||
window.Buffer = Buffer
|
window.Buffer = Buffer
|
||||||
import bech32 from 'bech32-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
|
// Extract a raw script hash from an address
|
||||||
export function addressToSPK (address) {
|
export function addressToSPK (address) {
|
||||||
@ -25,3 +26,36 @@ export function addressToSPK (address) {
|
|||||||
return prefix + Buffer.from(result).toString('hex').slice(2, -8) + postfix
|
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