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

show inputs and ouputs that belong to the users wallet #989

Merged
merged 1 commit into from
Mar 4, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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