Added transaction result popup
This commit is contained in:
parent
10662181c5
commit
fc8f42bd26
15
css/main.css
15
css/main.css
@ -632,13 +632,26 @@ ul {
|
|||||||
}
|
}
|
||||||
#confirmation_popup h4,
|
#confirmation_popup h4,
|
||||||
#prompt_popup h4 {
|
#prompt_popup h4 {
|
||||||
font-size: 1.2rem;
|
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
#confirmation_popup sm-button,
|
||||||
|
#prompt_popup sm-button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
#confirmation_popup .flex,
|
#confirmation_popup .flex,
|
||||||
#prompt_popup .flex {
|
#prompt_popup .flex {
|
||||||
|
padding: 0;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
#confirmation_popup .flex sm-button:first-of-type,
|
||||||
|
#prompt_popup .flex sm-button:first-of-type {
|
||||||
|
margin-right: 0.6rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#prompt_message {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.popup__header {
|
.popup__header {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -589,13 +589,22 @@ ul {
|
|||||||
#prompt_popup {
|
#prompt_popup {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 1.2rem;
|
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
sm-button {
|
||||||
.flex {
|
margin: 0;
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
}
|
||||||
|
.flex {
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
sm-button:first-of-type {
|
||||||
|
margin-right: 0.6rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#prompt_message {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup__header {
|
.popup__header {
|
||||||
|
|||||||
157
index.html
157
index.html
@ -10,6 +10,14 @@
|
|||||||
|
|
||||||
<body class="hidden">
|
<body class="hidden">
|
||||||
<sm-notifications id="notification_drawer"></sm-notifications>
|
<sm-notifications id="notification_drawer"></sm-notifications>
|
||||||
|
<sm-popup id="confirmation_popup">
|
||||||
|
<h4 id="confirm_title"></h4>
|
||||||
|
<p id="confirm_message" class="wrap-around"></p>
|
||||||
|
<div class="flex align-center gap-0-5 margin-left-auto">
|
||||||
|
<button class="button cancel-button">Cancel</button>
|
||||||
|
<button class="button button--primary confirm-button">OK</button>
|
||||||
|
</div>
|
||||||
|
</sm-popup>
|
||||||
<main class="page">
|
<main class="page">
|
||||||
<header id="main_header">
|
<header id="main_header">
|
||||||
<a href="#/home" id="logo" class="app-brand">
|
<a href="#/home" id="logo" class="app-brand">
|
||||||
@ -121,7 +129,10 @@
|
|||||||
</sm-input>
|
</sm-input>
|
||||||
</div>
|
</div>
|
||||||
<sm-input id="fee_input" placeholder="Fee" required></sm-input>
|
<sm-input id="fee_input" placeholder="Fee" required></sm-input>
|
||||||
<button class="button button--primary" type="submit" onclick="sendTx()">Send</button>
|
<div class="multi-state-button">
|
||||||
|
<button id="send_tx_button" class="button button--primary" type="submit" disabled
|
||||||
|
onclick="sendTx()">Send</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</sm-form>
|
</sm-form>
|
||||||
</main>
|
</main>
|
||||||
@ -202,6 +213,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</sm-popup>
|
</sm-popup>
|
||||||
|
<sm-popup id="transaction_result_popup">
|
||||||
|
<header slot="header" class="popup__header">
|
||||||
|
<button class="popup__header__close justify-self-start" onclick="closePopup()">
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
|
||||||
|
fill="#000000">
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<div id="transaction_result"></div>
|
||||||
|
</sm-popup>
|
||||||
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
||||||
<script src="scripts/components.min.js" type="text/javascript"></script>
|
<script src="scripts/components.min.js" type="text/javascript"></script>
|
||||||
<script src="scripts/btcwallet_scripts_lib.js" type="text/javascript"></script>
|
<script src="scripts/btcwallet_scripts_lib.js" type="text/javascript"></script>
|
||||||
@ -329,6 +353,31 @@
|
|||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// displays a popup for asking permission. Use this instead of JS confirm
|
||||||
|
const getConfirmation = (title, options = {}) => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const { message = '', cancelText = 'Cancel', confirmText = 'OK', danger = false } = options
|
||||||
|
openPopup('confirmation_popup', true)
|
||||||
|
getRef('confirm_title').innerText = title;
|
||||||
|
getRef('confirm_message').innerText = message;
|
||||||
|
const cancelButton = getRef('confirmation_popup').querySelector('.cancel-button');
|
||||||
|
const confirmButton = getRef('confirmation_popup').querySelector('.confirm-button')
|
||||||
|
confirmButton.textContent = confirmText
|
||||||
|
cancelButton.textContent = cancelText
|
||||||
|
if (danger)
|
||||||
|
confirmButton.classList.add('button--danger')
|
||||||
|
else
|
||||||
|
confirmButton.classList.remove('button--danger')
|
||||||
|
confirmButton.onclick = () => {
|
||||||
|
closePopup()
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
cancelButton.onclick = () => {
|
||||||
|
closePopup()
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
// detect browser version
|
// detect browser version
|
||||||
function detectBrowser() {
|
function detectBrowser() {
|
||||||
let ua = navigator.userAgent,
|
let ua = navigator.userAgent,
|
||||||
@ -519,7 +568,7 @@
|
|||||||
}
|
}
|
||||||
if (!amount)
|
if (!amount)
|
||||||
return '0';
|
return '0';
|
||||||
return amount.toLocaleString(null, { style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: 8 })
|
return amount.toLocaleString(undefined, { style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: 8 })
|
||||||
}
|
}
|
||||||
function togglePrivateKeyVisibility(input) {
|
function togglePrivateKeyVisibility(input) {
|
||||||
const target = input.closest('sm-input')
|
const target = input.closest('sm-input')
|
||||||
@ -575,6 +624,69 @@
|
|||||||
convert()
|
convert()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async function sendTx() {
|
||||||
|
const senderPrivateKey = getRef('private_key_input').value.trim(),
|
||||||
|
receiverAddress = getRef('receiver_address_input').value.trim(),
|
||||||
|
amount = getRef('amount_input').value.trim(),
|
||||||
|
fee = getRef('fee_input').value.trim();
|
||||||
|
if (!senderPrivateKey || !receiverAddress || !amount || !fee)
|
||||||
|
return notify('Please fill all the fields', 'error')
|
||||||
|
const senderAddress = getTaprootAddress(senderPrivateKey).tr.address
|
||||||
|
if (btcOperator.validateAddress(senderAddress) !== 'bech32m')
|
||||||
|
return notify('Sender address is not a Taproot address', 'error')
|
||||||
|
const confirmation = await getConfirmation('Confirm transaction', {
|
||||||
|
message: `Send ${formatAmount(amount)} to ${receiverAddress} with a fee of ${formatAmount(fee)}?`,
|
||||||
|
confirmText: 'Send',
|
||||||
|
})
|
||||||
|
if (!confirmation)
|
||||||
|
return;
|
||||||
|
buttonLoader('send_tx_button', true)
|
||||||
|
createTx(senderPrivateKey, receiverAddress, amount, fee).then(txHex => {
|
||||||
|
btcOperator.broadcastTx(txHex).then(txid => {
|
||||||
|
notify(`Transaction sent successfully. Txid: ${txid}`, 'success')
|
||||||
|
showTransactionResult(true, txid)
|
||||||
|
}).catch(err => {
|
||||||
|
notify(err, 'error')
|
||||||
|
showTransactionResult(false, err)
|
||||||
|
}).finally(() => {
|
||||||
|
buttonLoader('send_tx_button', false)
|
||||||
|
})
|
||||||
|
}).catch(err => {
|
||||||
|
notify(err, 'error')
|
||||||
|
buttonLoader('send_tx_button', false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function showTransactionResult(success, result, options = {}) {
|
||||||
|
let { title, description } = options
|
||||||
|
if (!title)
|
||||||
|
title = success ? 'Transaction initiated' : 'Transaction failed'
|
||||||
|
if (!description)
|
||||||
|
description = success ? 'This might take upto 30 mins to complete and reflect on blockchain.' : result
|
||||||
|
|
||||||
|
renderElem(getRef('transaction_result'), html`
|
||||||
|
${success ? html`
|
||||||
|
<svg class="icon icon--success" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <path d="M0 0h24v24H0z" fill="none" /> <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" /> </svg>
|
||||||
|
` : ''}
|
||||||
|
${!success ? html`
|
||||||
|
<svg class="icon icon--failed" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <path d="M0 0h24v24H0z" fill="none" /> <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" /> </svg>
|
||||||
|
` : ''}
|
||||||
|
<h3 id="transaction_result__title">${title}</h3>
|
||||||
|
<div id="transaction_result__description"> ${description} </div>
|
||||||
|
${success && result ? html`
|
||||||
|
<div class="grid gap-1">
|
||||||
|
<a id="transaction_link" class="flex align-center button--colored" href=${`https://ranchimall.github.io/btcwallet/#/check_details?query=${result}`} style="margin-top: 1.5rem;" target="_blank">
|
||||||
|
See transaction on blockchain
|
||||||
|
<svg class="icon margin-left-0-5" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <path d="M0 0h24v24H0z" fill="none"></path> <path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"></path> </svg>
|
||||||
|
</a>
|
||||||
|
<div class="grid">
|
||||||
|
<span class="label">Transaction ID</span>
|
||||||
|
<sm-copy class="justify-self-center" value=${result}></sm-copy>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
`)
|
||||||
|
openPopup('transaction_result_popup')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
//This library uses API provided by chain.so (https://chain.so/)
|
//This library uses API provided by chain.so (https://chain.so/)
|
||||||
@ -607,45 +719,6 @@
|
|||||||
util.Sat_to_BTC = value => BigInt(parseFloat((value / SATOSHI_IN_BTC).toFixed(8)));
|
util.Sat_to_BTC = value => BigInt(parseFloat((value / SATOSHI_IN_BTC).toFixed(8)));
|
||||||
util.BTC_to_Sat = value => BigInt(parseInt(value * SATOSHI_IN_BTC));
|
util.BTC_to_Sat = value => BigInt(parseInt(value * SATOSHI_IN_BTC));
|
||||||
|
|
||||||
function get_fee_rate() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fetch('https://api.blockchain.info/mempool/fees').then(response => {
|
|
||||||
if (response.ok)
|
|
||||||
response.json()
|
|
||||||
.then(result => resolve(util.Sat_to_BTC(result.regular)))
|
|
||||||
.catch(error => reject(error));
|
|
||||||
else
|
|
||||||
reject(response);
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const broadcastTx = rawTxHex => new Promise((resolve, reject) => {
|
|
||||||
let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction';
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
|
||||||
},
|
|
||||||
body: "rawtx=" + rawTxHex
|
|
||||||
}).then(response => {
|
|
||||||
response.text().then(resultText => {
|
|
||||||
let r = resultText.match(/<result>.*<\/result>/);
|
|
||||||
if (!r)
|
|
||||||
reject(resultText);
|
|
||||||
else {
|
|
||||||
r = r.pop().replace('<result>', '').replace('</result>', '');
|
|
||||||
if (r == '1') {
|
|
||||||
let txid = resultText.match(/<txid>.*<\/txid>/).pop().replace('<txid>', '').replace('</txid>', '');
|
|
||||||
resolve(txid);
|
|
||||||
} else if (r == '0') {
|
|
||||||
let error = resultText.match(/<response>.*<\/response>/).pop().replace('<response>', '').replace('</response>', '');
|
|
||||||
reject(decodeURIComponent(error.replace(/\+/g, " ")));
|
|
||||||
} else reject(resultText);
|
|
||||||
}
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
});
|
|
||||||
function getTaprootAddress(wif) {
|
function getTaprootAddress(wif) {
|
||||||
if (!wif)
|
if (!wif)
|
||||||
wif = coinjs.newKeys().wif
|
wif = coinjs.newKeys().wif
|
||||||
@ -711,7 +784,7 @@
|
|||||||
tx.sign(privKey_arrayform, undefined, new Uint8Array(32));
|
tx.sign(privKey_arrayform, undefined, new Uint8Array(32));
|
||||||
|
|
||||||
tx.finalize()
|
tx.finalize()
|
||||||
console.log(tx.hex);
|
return tx.hex
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user