Decode input/output addresses. Optimize for mobile

This commit is contained in:
Mononaut 2022-03-11 13:34:44 -06:00
parent 1db488d350
commit 987c2c801b
5 changed files with 124 additions and 37 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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 &amp; Outputs</h2> <h2>Inputs &amp; 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}

View File

@ -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)
} }
} }

View File

@ -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')
}