Skip to content

Commit

Permalink
show inputs and ouputs that belong to the users wallets by changing t…
Browse files Browse the repository at this point in the history
…he background color of that input/output (using colors based on the send/receive svg icons) (#989)
  • Loading branch information
djpnewton committed Mar 4, 2021
1 parent cf079d2 commit cb01072
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 76 deletions.
1 change: 1 addition & 0 deletions src/cryptoadvance/specter/server_endpoints/wallets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,7 @@ def addressinfo(wallet_alias):
"address": address,
"descriptor": descriptor,
"walletName": wallet.name,
"isMine": not address_info.is_external,
**address_info,
}
except Exception as e:
Expand Down
214 changes: 143 additions & 71 deletions src/cryptoadvance/specter/templates/includes/tx-data.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ <h2>Transaction details</h2><br>
shadow.appendChild(clone);
}
}
async function fetchTxData(self) {
async function fetchRawTx(self, txid) {
let url = `{{ url_for('wallets_endpoint.decoderawtx', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", self.wallet);
var formData = new FormData();
formData.append('txid', self.txid);
formData.append('txid', txid);
formData.append('csrf_token', '{{ csrf_token() }}');
try {
const response = await fetch(
Expand All @@ -58,72 +58,73 @@ <h2>Transaction details</h2><br>
);
if(response.status != 200){
showError(await response.text());
return;
return null;
}
const jsonResponse = await response.json();
if (jsonResponse.success) {
console.log(jsonResponse);
let rawtx = jsonResponse.rawtx;
let walletName = jsonResponse.walletName;
let tx = jsonResponse.tx;
let walletLink = `{{ url_for('wallets_endpoint.wallet', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", self.wallet);
let rawtxHTML = `
<table class="tx-data-table">
<tbody>
<tr><td><span class="optional">Transaction id:</span><span class="mobile-only">TxID:</span></td><td style="word-break: break-all;"><explorer-link data-type="tx" data-value="${self.txid}"></explorer-link></td></tr>
<tr><td>From wallet:</td><td><a href=${walletLink}>${walletName}<a></td></tr>
<tr><td>Size:</td><td>${rawtx.vsize} vbytes <span class="note">(${rawtx.size} bytes)</span></td></tr>
`;
if (tx.fee) {
rawtxHTML += `
<tr><td>Fee:</td><td>${parseInt(-1e8 * tx.fee)} sats</td></tr>
<tr><td>Fee rate:</td><td>${parseFloat((-1e8 * tx.fee / rawtx.vsize).toFixed(2)).toString()} sat/vbyte</td></tr>
`;
}
if (tx.confirmations) {
rawtxHTML += `
<tr><td>Mined at block:</td><td>${tx.blockheight}</td></tr>
<tr><td>Block hash:</td><td style="word-break: break-all;">${tx.blockhash}</td></tr>
<tr><td>Block time:</td><td>${new Date(tx.blocktime * 1000)} <span style="font-size: 0.9em;color: #ccc;">(${tx.blocktime})</span></td></tr>
`;
}
return jsonResponse;
} catch(e) {
self.note.innerText = "Failed to load transaction details...";
showError(e);
}
return null;
}
async function fetchTxData(self) {
let jsonResponse = await fetchRawTx(self, self.txid);
if (jsonResponse !== null && jsonResponse.success) {
console.log(jsonResponse);
let rawtx = jsonResponse.rawtx;
let walletName = jsonResponse.walletName;
let tx = jsonResponse.tx;
let walletLink = `{{ url_for('wallets_endpoint.wallet', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", self.wallet);
let rawtxHTML = `
<table class="tx-data-table">
<tbody>
<tr><td><span class="optional">Transaction id:</span><span class="mobile-only">TxID:</span></td><td style="word-break: break-all;"><explorer-link data-type="tx" data-value="${self.txid}"></explorer-link></td></tr>
<tr><td>From wallet:</td><td><a href=${walletLink}>${walletName}<a></td></tr>
<tr><td>Size:</td><td>${rawtx.vsize} vbytes <span class="note">(${rawtx.size} bytes)</span></td></tr>
`;
if (tx.fee) {
rawtxHTML += `
<tr><td>Inputs count:</td><td>${rawtx.vin.length}</td></tr>
<tr><td>Outputs count:</td><td>${rawtx.vout.length}</td></tr>
<tr><td>Fee:</td><td>${parseInt(-1e8 * tx.fee)} sats</td></tr>
<tr><td>Fee rate:</td><td>${parseFloat((-1e8 * tx.fee / rawtx.vsize).toFixed(2)).toString()} sat/vbyte</td></tr>
`;
}
if (tx.confirmations) {
rawtxHTML += `
</tbody>
</table>
<tr><td>Mined at block:</td><td>${tx.blockheight}</td></tr>
<tr><td>Block hash:</td><td style="word-break: break-all;">${tx.blockhash}</td></tr>
<tr><td>Block time:</td><td>${new Date(tx.blocktime * 1000)} <span style="font-size: 0.9em;color: #ccc;">(${tx.blocktime})</span></td></tr>
`;
rawtxHTML += `<h3>Inputs</h3>`;
for (let i in rawtx.vin) {
if ('coinbase' in rawtx.vin[i]) {
rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: #131a24;">
<b>Input #${i}</b><br><br>
<b>Coinbase transaction</b>
<br>
</p>
`;
continue;
}
rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: #131a24;">
<b>Input #${i}</b><br><br>
Transaction id: <explorer-link style="word-break: break-all;" data-type="tx" data-value="${rawtx.vin[i].txid}"></explorer-link><br>
Output # ${rawtx.vin[i].vout}
</p>
`;
}
}
rawtxHTML += `
<tr><td>Inputs count:</td><td>${rawtx.vin.length}</td></tr>
<tr><td>Outputs count:</td><td>${rawtx.vout.length}</td></tr>
`;
rawtxHTML += `
</tbody>
</table>
`;
rawtxHTML += `<h3>Inputs</h3>`;
for (let i in rawtx.vin) {
console.log(JSON.stringify(rawtx.vin[i]));

rawtxHTML += `<h3>Outputs</h3>`;
for (let i in rawtx.vout) {
let address = ('addresses' in rawtx.vout[i] && rawtx.vout[i].addresses.length == 1) ? rawtx.vout[i].addresses[0] : 'Unknown';
let bgColor = '#131a24';
let addressAndValue = '';
let jsonResponse = await fetchRawTx(self, rawtx.vin[i].txid);
if (jsonResponse !== null && jsonResponse.success) {
let spentOutput = jsonResponse.rawtx.vout[rawtx.vin[i].vout];
let address = ('addresses' in spentOutput && spentOutput.addresses.length == 1) ? spentOutput.addresses[0] : 'Unknown';
if (address == 'Unknown') {
address = ('addresses' in rawtx.vout[i].scriptPubKey && rawtx.vout[i].scriptPubKey.addresses.length == 1) ? rawtx.vout[i].scriptPubKey.addresses[0] : 'Unknown';
address = ('addresses' in spentOutput.scriptPubKey && spentOutput.scriptPubKey.addresses.length == 1) ? spentOutput.scriptPubKey.addresses[0] : 'Unknown';
}
let isMine = await fetchAddressIsMine(self, address);
if (isMine) {
bgColor = '#925d07';
}
let value = parseFloat(rawtx.vout[i].value.toFixed(8));
let price = '';


let value = parseFloat(spentOutput.value.toFixed(8));
let price = ''
if (self.price && self.symbol) {
price = `<span class="note">(${self.symbol}${numberWithCommas((parseFloat(self.price) * value).toFixed(2))})</span>`;
}
Expand All @@ -134,25 +135,96 @@ <h2>Transaction details</h2><br>

value = `${numberWithCommas(value.toString())}`;

rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: #131a24;">
<b>Output #${i}</b><br><br>
addressAndValue = `<br>
Address: <address-label data-copy-hidden="true" data-address="${address}" data-wallet="${self.wallet}"></address-label><br>
Value: ${value} ${price}
Value: ${value} ${price}`;
}

if ('coinbase' in rawtx.vin[i]) {
rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
<b>Input #${i}</b><br><br>
<b>Coinbase transaction</b>
<br>
</p>
`;
continue;
}

self.info.innerHTML = rawtxHTML;
self.note.innerText = "";
return
rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
<b>Input #${i}</b><br><br>
Transaction id: <explorer-link style="word-break: break-all;" data-type="tx" data-value="${rawtx.vin[i].txid}"></explorer-link><br>
Output # ${rawtx.vin[i].vout}
${addressAndValue}
</p>
`;
}

};
self.note.innerText = "Failed to load transaction details...";
} catch(e) {
self.note.innerText = "Failed to load transaction details...";
rawtxHTML += `<h3>Outputs</h3>`;
for (let i in rawtx.vout) {
let address = ('addresses' in rawtx.vout[i] && rawtx.vout[i].addresses.length == 1) ? rawtx.vout[i].addresses[0] : 'Unknown';
if (address == 'Unknown') {
address = ('addresses' in rawtx.vout[i].scriptPubKey && rawtx.vout[i].scriptPubKey.addresses.length == 1) ? rawtx.vout[i].scriptPubKey.addresses[0] : 'Unknown';
}
let value = parseFloat(rawtx.vout[i].value.toFixed(8));
let price = '';
if (self.price && self.symbol) {
price = `<span class="note">(${self.symbol}${numberWithCommas((parseFloat(self.price) * value).toFixed(2))})</span>`;
}

if (self.btcUnit == 'sat') {
value = parseInt(value * 1e8)
}

value = `${numberWithCommas(value.toString())}`;

let bgColor = '#131a24';
let isMine = await fetchAddressIsMine(self, address);
if (isMine) {
bgColor = '#154984';
}

rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
<b>Output #${i}</b><br><br>
Address: <address-label data-copy-hidden="true" data-address="${address}" data-wallet="${self.wallet}"></address-label><br>
Value: ${value} ${price}
</p>
`;
}

self.info.innerHTML = rawtxHTML;
self.note.innerText = "";
return;
};
self.note.innerText = "Failed to load transaction details...";
}
async function fetchAddressIsMine(self, address) {
let url = `{{ url_for('wallets_endpoint.addressinfo', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", self.wallet);
var formData = new FormData();
formData.append('address', address)
formData.append('csrf_token', '{{ csrf_token() }}');
try {
const response = await fetch(
url,
{
method: 'POST',
body: formData
}
);
if(response.status != 200){
showError(await response.text());
return false;
}
const jsonResponse = await response.json();
if (jsonResponse.success) {
return jsonResponse.isMine;
}
} catch(e) {
showError("Failed to load address data...");
showError(e);
}
return false;
}
customElements.define('tx-data', TxDataElement);
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,21 @@
</p>
<h2 class="tx_details_header">Inputs ({{psbt['tx']['vin'] | length}})</h2>
{% for input in psbt['tx']['vin'] %}
<p class="tx_info" style="text-align: left; background-color: #131a24;">
{% set tx = wallet.gettransaction(input['txid'], decode=true) %}
{% set bg_color = '#131a24' %}
{% if tx and tx.vout and tx.vout|length > input['vout'] and 'addresses' in tx.vout[input['vout']] and tx.vout[input['vout']].addresses|length > 0 %}
{% set address = tx.vout[input['vout']].addresses[0] %}
{% set bg_color = '#925d07' if wallet.is_address_mine(address) else bg_color %}
{% endif %}
<p class="tx_info" style="text-align: left; background-color: {{ bg_color }};">
<b style="margin: auto;">Input #{{loop.index0}}</b><br><br>
<b>Transaction id:</b><br>
{{ explorer_link('tx', input['txid'], input['txid'], specter.explorer) }}
(Output #{{ input['vout'] }})<br>
{% set tx = wallet.gettransaction(input['txid'], decode=true) %}
{% if tx %}
{% if tx.vout and tx.vout|length > input['vout'] and 'addresses' in tx.vout[input['vout']] and tx.vout[input['vout']].addresses|length > 0 %}
{% if address %}
<b>Address:</b>
<address-label data-address="{{ tx.vout[input['vout']].addresses[0] }}" data-wallet="{{ wallet_alias }}"></address-label>
<address-label data-address="{{ address }}" data-wallet="{{ wallet_alias }}"></address-label>
<br>
{% endif %}
<b>Amount:</b> {{ psbt['inputs'][loop.index0]['witness_utxo']['amount'] }} BTC {% if specter.price_check %}<span class="note">&nbsp;({{ psbt['inputs'][loop.index0]['witness_utxo']['amount'] | altunit }})</span>{% endif %}
Expand All @@ -138,7 +143,9 @@
{% endfor %}
<h2 class="tx_details_header">Outputs ({{psbt['tx']['vout']|length}})</h2>
{% for output in psbt['tx']['vout'] %}
<p class="tx_info" style="text-align: left; background-color: #131a24;">
{% set address = output['scriptPubKey']['addresses'][0] %}
{% set bg_color = '#154984' if wallet.is_address_mine(address) else '#131a24' %}
<p class="tx_info" style="text-align: left; background-color: {{ bg_color }};">
<b>Output #{{loop.index0}}</b> {% if output['scriptPubKey']['addresses'][0] not in psbt['address'] %}(Change){% endif %}<br><br>
<b>Address:</b>
<address-label data-address="{{ output['scriptPubKey']['addresses'][0] }}" data-wallet="{{ wallet_alias }}"></address-label>
Expand Down
4 changes: 4 additions & 0 deletions src/cryptoadvance/specter/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,10 @@ def get_address_info(self, address):
except:
return None

def is_address_mine(self, address):
addrinfo = self.get_address_info(address)
return addrinfo and not addrinfo.is_external

def get_electrum_file(self):
""" Exports the wallet data as Electrum JSON format """
electrum_devices = [
Expand Down

0 comments on commit cb01072

Please sign in to comment.