Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address types #42

Merged
merged 2 commits into from
Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 86 additions & 7 deletions client/src/components/SearchBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,75 @@ import { fade } from 'svelte/transition'
import { flip } from 'svelte/animate'
import Icon from './Icon.svelte'
import SearchIcon from '../assets/icon/cil-search.svg'
import CrossIcon from '../assets/icon/cil-x.svg'
import CrossIcon from '../assets/icon/cil-x-circle.svg'
import AddressIcon from '../assets/icon/cil-wallet.svg'
import TxIcon from '../assets/icon/cil-arrow-circle-right.svg'
import BlockIcon from '../assets/icon/grid-icon.svg'
import { fly } from 'svelte/transition'
import { matchQuery, searchTx, searchBlock } from '../utils/search.js'
import { selectedTx, detailTx, overlay, loading } from '../stores.js'

const queryIcons = {
txid: TxIcon,
input: TxIcon,
output: TxIcon,
// address: AddressIcon,
blockhash: BlockIcon,
blockheight: BlockIcon,
}

let query
let matchedQuery
let errorMessage

$: {
if (query) {
matchedQuery = matchQuery(query)
} else {
matchedQuery = null
}
errorMessage = null
}

function clearInput () {
query = null
}

function handleSearchError (err) {
switch (err) {
case '404':
if (matchedQuery && matchedQuery.label) {
errorMessage = `${matchedQuery.label} not found`
}
break;
default:
errorMessage = 'server error'
}
}

async function searchSubmit (e) {
e.preventDefault()

if (matchedQuery) {
if (matchedQuery && matchedQuery.query !== 'address') {
$loading++
let searchErr
switch(matchedQuery.query) {
case 'txid':
await searchTx(matchedQuery.txid)
searchErr = await searchTx(matchedQuery.txid)
break;

case 'input':
await searchTx(matchedQuery.txid, matchedQuery.input, null)
searchErr = await searchTx(matchedQuery.txid, matchedQuery.input, null)
break;

case 'output':
await searchTx(matchedQuery.txid, null, matchedQuery.output)
searchErr = await searchTx(matchedQuery.txid, null, matchedQuery.output)
break;
}
if (searchErr != null) handleSearchError(searchErr)
$loading--
} else {
errorMessage = 'enter a transaction id, block hash or block height'
}

return false
Expand All @@ -58,6 +90,22 @@ async function searchSubmit (e) {
max-width: 600px;
margin: 0 1em;

.clear-button {
position: absolute;
right: 0;
bottom: .4em;
margin: 0;
color: var(--palette-bad);
font-size: 1.2em;
cursor: pointer;
opacity: 1;
transition: opacity 300ms;

&.disabled {
opacity: 0;
}
}

.input-icon {
font-size: 24px;
margin: 0 10px;
Expand Down Expand Up @@ -115,10 +163,28 @@ async function searchSubmit (e) {
transition: width 300ms;
}
}

.error-msg {
position: absolute;
left: 0;
top: 100%;
margin: 0;
font-size: 0.9em;
color: var(--palette-bad);
}

.input-icon.query-type {
position: absolute;
left: 0;
bottom: .4em;
margin: 0;
color: var(--palette-x);
font-size: 1.2em;
}
}

&:hover, &:active, &:focus {
.underline.active {
.search-input:active, .search-input:focus {
& ~ .underline.active {
width: 100%;
}
}
Expand All @@ -130,6 +196,8 @@ async function searchSubmit (e) {
margin: 0;
color: var(--input-color);
width: 100%;
padding-left: 1.5em;
padding-right: 1.5em;

&.disabled {
color: var(--palette-e);
Expand All @@ -143,9 +211,20 @@ async function searchSubmit (e) {
<div class="input-wrapper" transition:fly={{ y: -25 }}>
<form class="search-form" action="" on:submit={searchSubmit}>
<input class="search-input" type="text" bind:value={query} placeholder="Enter a txid">
<div class="clear-button" class:disabled={query == null || query === ''} on:click={clearInput} title="Clear">
<Icon icon={CrossIcon}/>
</div>
<div class="underline" />
<div class="underline active" />
<button type="submit" class="search-submit" />
{#if matchedQuery && matchedQuery.query && queryIcons[matchedQuery.query]}
<div class="input-icon query-type" transition:fade={{ duration: 300 }} title={matchedQuery.label}>
<Icon icon={queryIcons[matchedQuery.query]} />
</div>
{/if}
{#if errorMessage }
<p class="error-msg" transition:fade={{ duration: 300 }}>{ errorMessage }</p>
{/if}
</form>
<div class="input-icon search icon-button" on:click={searchSubmit} title="Search">
<Icon icon={SearchIcon}/>
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/SearchTab.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ setNextColor()
$: {
if ($newHighlightQuery) {
matchedQuery = matchQuery($newHighlightQuery)
if (matchedQuery && (matchedQuery.query === 'blockhash' || matchedQuery.query === 'blockheight')) matchedQuery = null
if (matchedQuery) {
matchedQuery.colorIndex = queryColorIndex
matchedQuery.color = highlightColors[queryColorIndex]
Expand All @@ -61,6 +62,7 @@ $: {
$: {
if (query) {
matchedQuery = matchQuery(query.trim())
if (matchedQuery && (matchedQuery.query === 'blockhash' || matchedQuery.query === 'blockheight')) matchedQuery = null
if (matchedQuery) {
matchedQuery.colorIndex = queryColorIndex
matchedQuery.color = highlightColors[queryColorIndex]
Expand Down Expand Up @@ -113,7 +115,7 @@ function clearUsedColor (colorIndex) {
}

async function add () {
if (matchedQuery && !$highlightingFull) {
if (matchedQuery && matchedQuery.query !== 'blockhash' && matchedQuery.query !== 'blockheight' && !$highlightingFull) {
watchlist.push({
...matchedQuery
})
Expand Down
12 changes: 8 additions & 4 deletions client/src/components/TransactionOverlay.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { searchTx } from '../utils/search.js'

function onClose () {
$detailTx = null
$highlightInOut = null
}

function formatBTC (sats) {
Expand Down Expand Up @@ -69,9 +70,12 @@ function expandAddresses(items, truncate) {
if (item.script_pub_key) {
address = SPKToAddress(item.script_pub_key) || "unknown"
if (address === 'OP_RETURN') {
title = item.script_pub_key.substring(2).match(/../g).reduce((parsed, hexChar) => {
title = item.script_pub_key.substring(4).match(/../g).reduce((parsed, hexChar) => {
return parsed + String.fromCharCode(parseInt(hexChar, 16))
}, "")
} else if (address === 'P2PK') {
if (item.script_pub_key.length === 70) title = 'compressed pubkey: ' + item.script_pub_key.substring(2,68)
else title = 'pubkey: ' + item.script_pub_key.substring(2,132)
}
}
return {
Expand Down Expand Up @@ -113,7 +117,7 @@ $: {
}
} else inputs = []
if ($detailTx && $detailTx.outputs) {
if ($detailTx.isCoinbase || !$detailTx.is_inflated || $detailTx.fee == null) {
if ($detailTx.isCoinbase || !$detailTx.is_inflated || !$detailTx.fee) {
outputs = expandAddresses($detailTx.outputs, truncate)
} else {
outputs = [{address: 'fee', value: $detailTx.fee, fee: true}, ...expandAddresses($detailTx.outputs, truncate)]
Expand Down Expand Up @@ -580,7 +584,7 @@ async function clickItem (item) {
<p class="header">{$detailTx.inputs.length} input{$detailTx.inputs.length > 1 ? 's' : ''}</p>
{#each inputs as input}
<div class="entry clickable" on:click={() => clickItem(input)}>
<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="address" title={input.title || input.address}><span class="truncatable">{input.address.slice(0,-6)}</span><span class="suffix">{input.address.slice(-6)}</span></p>
<p class="amount">{ input.value == null ? '???' : formatBTC(input.value) }</p>
</div>
{/each}
Expand Down Expand Up @@ -613,7 +617,7 @@ async function clickItem (item) {
{/if}
</div>
<div class="column outputs">
<p class="header">{$detailTx.outputs.length} output{$detailTx.outputs.length > 1 ? 's' : ''} {#if $detailTx.fee != null}+ fee{/if}</p>
<p class="header">{$detailTx.outputs.length} output{$detailTx.outputs.length > 1 ? 's' : ''} {#if $detailTx.fee}+ fee{/if}</p>
{#each outputs as output}
<div class="entry" class:clickable={output.rest || output.spend} class:unspent={!output.spend && !output.fee} class:highlight={highlight.out != null && highlight.out === output.index} on:click={() => clickItem(output)}>
<p class="address" title={output.title || output.address}><span class="truncatable">{output.address.slice(0,-6)}</span><span class="suffix">{output.address.slice(-6)}</span></p>
Expand Down
1 change: 1 addition & 0 deletions client/src/components/util/LoadingAnimation.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
box-sizing: border-box;
border-radius: 50%;
border: solid 2px var(--palette-x);
pointer-events: none;

.sizer {
width: 100%;
Expand Down
3 changes: 0 additions & 3 deletions client/src/controllers/TxController.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ export default class TxController {

dropTx (txid) {
if (this.txs[txid] && this.poolScene.drop(txid)) {
console.log('dropping tx', txid)
this.txs[txid].view.update({
display: {
position: {
Expand All @@ -123,8 +122,6 @@ export default class TxController {
this.destroyTx(txid)
}, 2000)
// this.poolScene.layoutAll()
} else {
console.log('dropped unknown tx', txid)
}
}

Expand Down
18 changes: 18 additions & 0 deletions client/src/utils/encodings.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ export function SPKToAddress (spk) {
} else if (spk.startsWith('6a')) {
// OP_RETURN
return 'OP_RETURN'
} else if (spk.length == 134 && spk.startsWith('41') && spk.endsWith('ac')) {
// uncompressed p2pk
return 'P2PK'
} else if (spk.length == 70 && spk.startsWith('21') && spk.endsWith('ac')) {
// compressed p2pk
return 'P2PK'
} else if (spk.endsWith('51ae') && spk.startsWith('51')) {
// possible p2ms (raw multisig)
return '1-of-1 P2MS'
} else if (spk.endsWith('52ae')) {
// possible p2ms (raw multisig)
if (spk.startsWith(51)) return '1-of-2 P2MS'
if (spk.startsWith(52)) return '2-of-2 P2MS'
} else if (spk.endsWith('53ae')) {
// possible p2ms (raw multisig)
if (spk.startsWith(51)) return '1-of-3 P2MS'
if (spk.startsWith(52)) return '2-of-3 P2MS'
if (spk.startsWith(53)) return '3-of-3 P2MS'
}
}

Expand Down
Loading