Skip to content

Commit

Permalink
Broadcast txs over block explorer via tor (#1183)
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-kaufman authored May 25, 2021
1 parent 9184801 commit c18f65c
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 10 deletions.
55 changes: 55 additions & 0 deletions src/cryptoadvance/specter/server_endpoints/wallets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,61 @@ def broadcast(wallet_alias):
return jsonify(success=False, error="broadcast tx request must use POST")


@wallets_endpoint.route(
"/wallet/<wallet_alias>/broadcast_blockexplorer_tor/", methods=["GET", "POST"]
)
@login_required
def broadcast_blockexplorer_tor(wallet_alias):
wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
if request.method == "POST":
tx = request.form.get("tx")
explorer = request.form.get("explorer")
res = wallet.rpc.testmempoolaccept([tx])[0]
if res["allowed"]:
try:
if app.specter.chain == "main":
url_network = ""
elif app.specter.chain == "liquidv1":
url_network = "liquid/"
elif app.specter.chain == "test" or app.specter.chain == "testnet":
url_network = "testnet/"
elif app.specter.chain == "signet":
url_network = "signet/"
else:
return jsonify(
success=False,
error=f"Failed to broadcast transaction. Network not supported.",
)
if explorer == "mempool":
explorer = "MEMPOOL_SPACE_ONION"
elif explorer == "blockstream":
explorer = "BLOCKSTREAM_INFO_ONION"
else:
return jsonify(
success=False,
error=f"Failed to broadcast transaction. Block explorer not supported.",
)
requests_session = app.specter.requests_session(force_tor=True)
requests_session.post(
f"{app.config['EXPLORERS_LIST'][explorer]['url']}{url_network}api/tx",
data=tx,
)
wallet.delete_pending_psbt(get_txid(tx))
return jsonify(success=True)
except Exception as e:
return jsonify(
success=False,
error=f"Failed to broadcast transaction with error: {e}",
)
else:
return jsonify(
success=False,
error="Failed to broadcast transaction: transaction is invalid\n%s"
% res["reject-reason"],
)
return jsonify(success=False, error="broadcast tx request must use POST")


@wallets_endpoint.route("/wallet/<wallet_alias>/decoderawtx/", methods=["GET", "POST"])
@login_required
@app.csrf.exempt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
<h1>Transaction is Ready to Broadcast</h1>
<div class="flex-center flex-column">
<button id="broadcast_local_btn" class="btn flex-item" style="width: 190px;">Send transaction</button>
{% if specter.chain != "regtest" %}
<button type="button" id="broadcast_blockstream_btn" class="btn flex-item" style="width: 190px;">Send via blockstream.info</button>
{% endif %}
<button type="button" id="broadcast_block_explorer_btn" class="btn flex-item {% if specter.chain != 'regtest' %}hidden{% endif %}" style="width: 190px;">Send via block explorer</button>
<button type="button" id="broadcast_blockstream_btn" class="btn flex-item hidden" style="width: 190px;">Send via blockstream.info</button>
<button type="button" id="broadcast_mempool_btn" class="btn flex-item hidden" style="width: 190px;">Send via mempool.space</button>
<button type="button" id="broadcast_blockstream_tor_btn" class="btn flex-item hidden" style="width: 190px;">Send via blockstream.info<br>(over Tor hidden service)</button>
<button type="button" id="broadcast_mempool_tor_btn" class="btn flex-item hidden" style="width: 190px;">Send via mempool.space<br>(over Tor hidden service)</button>
<button type="button" id="broadcast_options_btn" class="btn flex-item hidden" style="width: 190px;">Back to options</button>
<button id="save_psbt_btn" class="btn flex-item" style="width: 190px;">Save as pending</button>
<button id="copy_final_tx_btn" class="btn flex-item" style="width: 190px;">Copy raw transaction</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,32 @@
showPageOverlay("tx_broadcast");
});
document.getElementById("broadcast_block_explorer_btn").addEventListener("click", async function(e) {
e.preventDefault();
document.getElementById("broadcast_local_btn").classList.add("hidden");
document.getElementById("broadcast_block_explorer_btn").classList.add("hidden");
document.getElementById("save_psbt_btn").classList.add("hidden");
document.getElementById("copy_final_tx_btn").classList.add("hidden");
document.getElementById("broadcast_blockstream_btn").classList.remove("hidden");
document.getElementById("broadcast_mempool_btn").classList.remove("hidden");
document.getElementById("broadcast_blockstream_tor_btn").classList.remove("hidden");
document.getElementById("broadcast_mempool_tor_btn").classList.remove("hidden");
document.getElementById("broadcast_options_btn").classList.remove("hidden");
});
document.getElementById("broadcast_options_btn").addEventListener("click", async function(e) {
e.preventDefault();
document.getElementById("broadcast_local_btn").classList.remove("hidden");
document.getElementById("broadcast_block_explorer_btn").classList.remove("hidden");
document.getElementById("save_psbt_btn").classList.remove("hidden");
document.getElementById("copy_final_tx_btn").classList.remove("hidden");
document.getElementById("broadcast_blockstream_btn").classList.add("hidden");
document.getElementById("broadcast_mempool_btn").classList.add("hidden");
document.getElementById("broadcast_blockstream_tor_btn").classList.add("hidden");
document.getElementById("broadcast_mempool_tor_btn").classList.add("hidden");
document.getElementById("broadcast_options_btn").classList.add("hidden");
});
document.getElementById("broadcast_local_btn").addEventListener("click", async function(e) {
e.preventDefault();
broadcastLocal(raw);
Expand All @@ -271,7 +297,22 @@
if ("{{ specter.chain }}" != "regtest") {
document.getElementById("broadcast_blockstream_btn").addEventListener("click", async function(e) {
e.preventDefault();
broadcastBlockstream(raw);
broadcastBlockExplorer(raw, 'blockstream');
});
document.getElementById("broadcast_mempool_btn").addEventListener("click", async function(e) {
e.preventDefault();
broadcastBlockExplorer(raw, 'mempool');
});
document.getElementById("broadcast_blockstream_tor_btn").addEventListener("click", async function(e) {
e.preventDefault();
broadcastBlockExplorerTor(raw, 'blockstream');
});
document.getElementById("broadcast_mempool_tor_btn").addEventListener("click", async function(e) {
e.preventDefault();
broadcastBlockExplorerTor(raw, 'mempool');
});
}
Expand Down Expand Up @@ -391,22 +432,59 @@
}
}
function broadcastBlockstream(tx) {
showNotification("Sending transaction via blockstream...");
async function broadcastBlockExplorerTor(tx, explorer) {
showNotification("Sending transaction...", 0);
let url="{{ url_for('wallets_endpoint.broadcast_blockexplorer_tor', wallet_alias=wallet.alias) }}";
var formData = new FormData();
formData.append("csrf_token", "{{ csrf_token() }}");
formData.append("tx", tx);
formData.append("explorer", explorer);
try {
const response = await fetch(
url,
{
method: 'POST',
body: formData
}
);
const jsonResponse = await response.json();
if (jsonResponse.success) {
hidePageOverlay();
window.location.replace("{{ url_for('wallets_endpoint.history', wallet_alias=wallet.alias) }}");
} else {
showError("Server failed to broadcast transactions!\n" + jsonResponse.error);
}
} catch(e) {
showError("Server failed to broadcast transactions!\n" + e);
}
}
function broadcastBlockExplorer(tx, explorer) {
showNotification("Sending transaction via " + explorer + "...", 0);
// TODO: Use fetch instead of XMLHttpRequest
let url;
if (explorer == "blockstream") {
url = "https://blockstream.info/"
} else if (explorer == "mempool") {
url = "https://mempool.space/"
}
if ("{{ specter.chain }}" == "main") {
url="https://blockstream.info/api/tx";
url += "api/tx";
} else if("{{specter.chain}}" == "liquidv1"){
url="https://blockstream.info/liquid/api/tx";
url += "liquid/api/tx";
} else if("{{specter.chain}}" == "signet"){
url += "signet/api/tx";
} else {
url="https://blockstream.info/testnet/api/tx";
url += "testnet/api/tx";
}
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if(xmlHttp.readyState === 4 && xmlHttp.status === 200) {
let o = JSON.parse(this.responseText);
var deletePSBTBtn = document.getElementById("deletepsbt_btn");
deletePSBTBtn.click();
showNotification("Transaction was sent successfully!");
Expand Down

0 comments on commit c18f65c

Please sign in to comment.