diff --git a/client/src/components/SearchBar.svelte b/client/src/components/SearchBar.svelte
index 869f8f6..471ca46 100644
--- a/client/src/components/SearchBar.svelte
+++ b/client/src/components/SearchBar.svelte
@@ -7,12 +7,23 @@ import SearchIcon from '../assets/icon/cil-search.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) {
@@ -20,31 +31,48 @@ $: {
} 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
@@ -65,7 +93,7 @@ async function searchSubmit (e) {
.clear-button {
position: absolute;
right: 0;
- bottom: .3em;
+ bottom: .4em;
margin: 0;
color: var(--palette-bad);
font-size: 1.2em;
@@ -135,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%;
}
}
@@ -150,6 +196,7 @@ async function searchSubmit (e) {
margin: 0;
color: var(--input-color);
width: 100%;
+ padding-left: 1.5em;
padding-right: 1.5em;
&.disabled {
@@ -170,6 +217,14 @@ async function searchSubmit (e) {
+ {#if matchedQuery && matchedQuery.query && queryIcons[matchedQuery.query]}
+
+
+
+ {/if}
+ {#if errorMessage }
+ { errorMessage }
+ {/if}
diff --git a/client/src/components/SearchTab.svelte b/client/src/components/SearchTab.svelte
index 12b0a51..8427d60 100644
--- a/client/src/components/SearchTab.svelte
+++ b/client/src/components/SearchTab.svelte
@@ -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]
@@ -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]
@@ -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
})
diff --git a/client/src/components/util/LoadingAnimation.svelte b/client/src/components/util/LoadingAnimation.svelte
index 371a9ad..c7f8ef1 100644
--- a/client/src/components/util/LoadingAnimation.svelte
+++ b/client/src/components/util/LoadingAnimation.svelte
@@ -6,6 +6,7 @@
box-sizing: border-box;
border-radius: 50%;
border: solid 2px var(--palette-x);
+ pointer-events: none;
.sizer {
width: 100%;
diff --git a/client/src/controllers/TxController.js b/client/src/controllers/TxController.js
index b5219fe..a95dbf8 100644
--- a/client/src/controllers/TxController.js
+++ b/client/src/controllers/TxController.js
@@ -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: {
@@ -123,8 +122,6 @@ export default class TxController {
this.destroyTx(txid)
}, 2000)
// this.poolScene.layoutAll()
- } else {
- console.log('dropped unknown tx', txid)
}
}
diff --git a/client/src/utils/search.js b/client/src/utils/search.js
index 7dee2bb..7b35778 100644
--- a/client/src/utils/search.js
+++ b/client/src/utils/search.js
@@ -14,20 +14,22 @@ function matchQuery (query) {
const asInt = parseInt(q)
// Remember to update the bounds in
if (!isNaN(asInt) && asInt >= 0 && `${asInt}` === q) {
- return null /*{
+ return {
query: 'blockheight',
+ label: 'block height',
height: asInt,
value: asInt
- }*/
+ }
}
// Looks like a block hash?
if (/^0{8}[a-f0-9]{56}$/.test(q)) {
- return null /* {
+ return {
query: 'blockhash',
+ label: 'block hash',
hash: query,
value: query,
- }*/
+ }
}
// Looks like a transaction input?
@@ -35,6 +37,7 @@ function matchQuery (query) {
const parts = q.split(':')
return {
query: 'input',
+ label: 'transaction input',
txid: parts[1],
output: parts[0],
value: q
@@ -46,6 +49,7 @@ function matchQuery (query) {
const parts = q.split(':')
return {
query: 'output',
+ label: 'transaction output',
txid: parts[0],
output: parts[1],
value: q
@@ -56,6 +60,7 @@ function matchQuery (query) {
if (/^[a-f0-9]{64}$/.test(q)) {
return {
query: 'txid',
+ label: 'transaction',
txid: q,
value: q
}
@@ -72,6 +77,7 @@ function matchQuery (query) {
return {
query: 'address',
+ label: 'address',
encoding: 'base58',
addressType,
address: query,
@@ -93,6 +99,7 @@ function matchQuery (query) {
return {
query: 'address',
+ label: 'address',
encoding: 'bech32',
addressType,
address: query,
@@ -108,30 +115,37 @@ export {matchQuery as matchQuery}
async function fetchTx (txid) {
if (!txid) return
- try {
- const response = await fetch(`${api.uri}/api/tx/${txid}`, {
- method: 'GET'
- })
+ const response = await fetch(`${api.uri}/api/tx/${txid}`, {
+ method: 'GET'
+ })
+ if (!response) throw new Error('null response')
+ if (response.status == 200) {
const result = await response.json()
const txData = result.tx
- txData.block = { height: result.blockheight, hash: result.blockhash, time: result.time * 1000 }
+ if (result.blockheight != null && result.blockhash != null) {
+ txData.block = { height: result.blockheight, hash: result.blockhash, time: result.time * 1000 }
+ }
return new BitcoinTx(txData, null, (txData.inputs && txData.inputs[0] && txData.inputs[0].prev_txid === "0000000000000000000000000000000000000000000000000000000000000000"))
- } catch (err) {
- console.log("failed to fetch tx ", txid)
- return null
+ } else {
+ throw new Error(response.status)
}
}
export async function searchTx(txid, input, output) {
- const searchResult = await fetchTx(txid)
- if (searchResult) {
- selectedTx.set(searchResult)
- detailTx.set(searchResult)
- overlay.set('tx')
- if (input != null || output != null) highlightInOut.set({txid, input, output})
- return true
- } else {
- return false
+ try {
+ const searchResult = await fetchTx(txid)
+ if (searchResult) {
+ selectedTx.set(searchResult)
+ detailTx.set(searchResult)
+ overlay.set('tx')
+ if (input != null || output != null) highlightInOut.set({txid, input, output})
+ return null
+ } else {
+ return '500'
+ }
+ } catch (err) {
+ console.log('error fetching tx ', err)
+ return err.message
}
}
diff --git a/server/lib/router.ex b/server/lib/router.ex
index 1518d6f..ab93286 100644
--- a/server/lib/router.ex
+++ b/server/lib/router.ex
@@ -55,8 +55,9 @@ defmodule BitcoinStream.Router do
end
defp get_tx(txid) do
- with {:ok, 200, %{"hex" => hex, "blockhash" => blockhash}} <- RPC.request(:rpc, "getrawtransaction", [txid, true]),
- {:ok, 200, %{"height" => height, "time" => time}} <- RPC.request(:rpc, "getblockheader", [blockhash, true]),
+ with {:ok, 200, verbosetx} <- RPC.request(:rpc, "getrawtransaction", [txid, true]),
+ %{"hex" => hex, "blockhash" => blockhash} <- Map.merge(%{"blockhash" => nil}, verbosetx),
+ {:ok, 200, %{"height" => height, "time" => time}} <- (if blockhash != nil do RPC.request(:rpc, "getblockheader", [blockhash, true]) else {:ok, 200, %{"height" => nil, "time" => nil}} end),
rawtx <- Base.decode16!(hex, case: :lower),
{:ok, txn } <- BitcoinTx.decode(rawtx),
inflated_txn <- BitcoinTx.inflate(txn, false),