diff --git a/rin/wallet/web/server.py b/rin/wallet/web/server.py index 6ca5e8c..1e64a8d 100644 --- a/rin/wallet/web/server.py +++ b/rin/wallet/web/server.py @@ -140,13 +140,26 @@ def get_network_info(): mempool = rpc_call("getrawmempool") blockchain_info = rpc_call("getblockchaininfo") + # Format peer information + peers = [] + for peer in peer_info: + peers.append({ + "addr": peer.get("addr", ""), + "services": peer.get("servicesnames", []), + "relaytxes": peer.get("relaytxes", False), + "synced_blocks": peer.get("synced_blocks", 0), + "last_transaction": peer.get("last_transaction", 0), + "version": peer.get("subver", ""), + "pingtime": round(peer.get("pingtime", 0) * 1000, 1), + }) + return jsonify({ "network": { "connections": network_info.get("connections", 0), "networkactive": network_info.get("networkactive", False), "relayfee": network_info.get("relayfee", 0), }, - "peers": len(peer_info), + "peers": peers, "mempool_size": len(mempool), "blockchain": { "blocks": blockchain_info.get("blocks", 0), @@ -159,6 +172,34 @@ def get_network_info(): return jsonify({"error": str(exc)}), 500 +@app.route("/api/rebroadcast", methods=["POST"]) +@require_token +def rebroadcast_pending(): + try: + # Get all pending transactions + txs = rpc_call("listtransactions", "*", 100, 0, True) + pending = [tx for tx in txs if tx.get("confirmations", 0) == 0] + + rebroadcasted = [] + for tx in pending: + txid = tx.get("txid") + if txid: + try: + # Get raw transaction + tx_data = rpc_call("gettransaction", txid, True) + raw_hex = tx_data.get("hex") + if raw_hex: + # Rebroadcast + rpc_call("sendrawtransaction", raw_hex) + rebroadcasted.append(txid) + except Exception: + pass # Already in mempool or other error + + return jsonify({"rebroadcasted": rebroadcasted, "count": len(rebroadcasted)}) + except RuntimeError as exc: + return jsonify({"error": str(exc)}), 500 + + @app.route("/") def root(): return send_from_directory(app.static_folder, "index.html") diff --git a/rin/wallet/web/static/index.html b/rin/wallet/web/static/index.html index 560a95a..ef27804 100644 --- a/rin/wallet/web/static/index.html +++ b/rin/wallet/web/static/index.html @@ -280,15 +280,39 @@ function renderTx(tx) { const li = document.createElement('li'); - const amount = Number(tx.amount).toFixed(8); + const amount = Math.abs(Number(tx.amount)).toFixed(8); const type = tx.category === 'send' ? 'Sent' : 'Received'; const confirmations = tx.confirmations || 0; let status, statusClass, tooltip = ''; + let ageInfo = ''; + if (confirmations === 0) { status = 'Pending'; statusClass = 'status-pending'; - tooltip = 'title="0 confirmations - waiting for network confirmation"'; + + // Calculate transaction age + const txTime = tx.time || tx.timereceived; + if (txTime) { + const now = Math.floor(Date.now() / 1000); + const ageSeconds = now - txTime; + const ageMinutes = Math.floor(ageSeconds / 60); + const ageHours = Math.floor(ageMinutes / 60); + + let ageText = ''; + if (ageHours > 0) { + ageText = `${ageHours}h ${ageMinutes % 60}m ago`; + } else if (ageMinutes > 0) { + ageText = `${ageMinutes}m ago`; + } else { + ageText = `${ageSeconds}s ago`; + } + + ageInfo = `
Age: ${ageText}
`; + tooltip = `title="0 confirmations - waiting for network confirmation (${ageText})"`; + } else { + tooltip = 'title="0 confirmations - waiting for network confirmation"'; + } } else if (confirmations < 20) { status = `Immature (${confirmations}/20)`; statusClass = 'status-immature'; @@ -302,6 +326,7 @@ li.innerHTML = `
${type} ${amount} RIN ${status}
${tx.time ? new Date(tx.time * 1000).toLocaleString() : ''}
+ ${ageInfo}
${tx.txid}
`; return li; @@ -403,55 +428,94 @@ } async function fetchNetworkInfo() { - let res = await fetch('/api/network', { headers: authHeaders() }); - let data = await res.json(); + try { + let res = await fetch('/api/network', { headers: authHeaders() }); + let data = await res.json(); - // If token is invalid, refresh and retry - if (data.error === 'invalid_token') { - await refreshToken(); - res = await fetch('/api/network', { headers: authHeaders() }); - data = await res.json(); + // If token is invalid, refresh and retry + if (data.error === 'invalid_token') { + await refreshToken(); + res = await fetch('/api/network', { headers: authHeaders() }); + data = await res.json(); + } + + if (data.error) { + networkInfo.innerHTML = `
Error: ${data.error}
`; + return; + } + + const network = data.network; + const blockchain = data.blockchain; + + // Build peers list + let peersHtml = ''; + if (data.peers && data.peers.length > 0) { + peersHtml = '
Connected Peers
'; + data.peers.forEach(peer => { + const relayStatus = peer.relaytxes ? '✓ Relay' : '✗ No Relay'; + const syncStatus = peer.synced_blocks === blockchain.blocks ? '✓ Synced' : `${peer.synced_blocks}`; + peersHtml += ` +
+
${peer.addr}
+
${relayStatus} | ${peer.version} | Ping: ${peer.pingtime}ms | Blocks: ${syncStatus}
+
+ `; + }); + peersHtml += '
'; + } + + networkInfo.innerHTML = ` +
+
+
Network Connections
+
${network.connections}
+
+
+
Mempool Size
+
${data.mempool_size}
+
+
+
Block Height
+
${blockchain.blocks}
+
+
+
Chain
+
${blockchain.chain}
+
+
+ ${peersHtml} +
+ Network Active: ${network.networkactive ? 'Yes' : 'No'} | + Relay Fee: ${network.relayfee} RIN | + Difficulty: ${Number(blockchain.difficulty).toFixed(2)} +
+ `; + } catch (err) { + console.error('Network info fetch failed:', err); + networkInfo.innerHTML = `
Error loading network info: ${err.message}
`; } + } - if (data.error) { - networkInfo.innerHTML = `
Error: ${data.error}
`; - return; + async function rebroadcastPending() { + try { + const res = await fetch('/api/rebroadcast', { + method: 'POST', + headers: authHeaders(), + }); + const data = await res.json(); + console.log(`Rebroadcasted ${data.count} pending transactions`); + } catch (err) { + console.error('Rebroadcast failed:', err); } - - const network = data.network; - const blockchain = data.blockchain; - - networkInfo.innerHTML = ` -
-
-
Network Connections
-
${network.connections}
-
-
-
Mempool Size
-
${data.mempool_size}
-
-
-
Block Height
-
${blockchain.blocks}
-
-
-
Chain
-
${blockchain.chain}
-
-
-
- Network Active: ${network.networkactive ? 'Yes' : 'No'} | - Relay Fee: ${network.relayfee} RIN | - Difficulty: ${blockchain.difficulty.toFixed(2)} -
- `; } async function refresh() { await Promise.all([fetchBalances(), fetchTransactions(), fetchNetworkInfo()]); } + // Auto-rebroadcast pending transactions every minute + setInterval(rebroadcastPending, 60000); + document.getElementById('refreshButton').addEventListener('click', refresh); document.getElementById('generateButton').addEventListener('click', generateAddress); document.getElementById('sendButton').addEventListener('click', sendCoins);