Feature update

-- added approximate transaction fees for BTC transactions
-- updated form component
This commit is contained in:
sairaj mote 2023-02-20 02:18:06 +05:30
parent ea6b9f5cb9
commit afd5f7af53
5 changed files with 231 additions and 134 deletions

View File

@ -52,9 +52,6 @@ body[data-theme=dark] {
--green: #00e676;
--yellow: rgb(255, 213, 5);
}
body[data-theme=dark] sm-popup::part(popup) {
background-color: rgba(var(--foreground-color), 1);
}
p,
strong {
@ -172,6 +169,9 @@ details[open] > summary .down-arrow {
transform: rotate(180deg);
}
sm-popup::part(popup) {
background-color: rgba(var(--foreground-color), 1);
}
sm-popup::part(backdrop) {
-webkit-backdrop-filter: blur(0.5rem);
backdrop-filter: blur(0.5rem);
@ -1015,16 +1015,31 @@ ol li::before {
min-height: 2rem;
}
#selected_fee_tip {
#selected_fee_tip,
#error_section {
font-weight: 500;
}
#selected_fee_tip.error {
.error {
color: var(--danger-color);
}
#selected_fee_tip.error .icon {
.error .icon {
fill: var(--danger-color);
}
#send_fee_wrapper {
display: grid;
}
#send_fee_wrapper > * {
grid-area: 1/1;
}
#send_fee_loader {
background-color: rgba(var(--foreground-color), 1);
z-index: 1;
width: 100%;
}
.receiver-card {
display: grid;
gap: 0.5rem;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -50,9 +50,6 @@ body[data-theme="dark"] {
--danger-color: rgb(255, 106, 106);
--green: #00e676;
--yellow: rgb(255, 213, 5);
sm-popup::part(popup) {
background-color: rgba(var(--foreground-color), 1);
}
}
p,
@ -160,6 +157,9 @@ details[open] {
}
}
sm-popup {
&::part(popup) {
background-color: rgba(var(--foreground-color), 1);
}
&::part(backdrop) {
backdrop-filter: blur(0.5rem);
}
@ -930,15 +930,27 @@ ol {
.remove-card-wrapper {
min-height: 2rem;
}
#selected_fee_tip {
#selected_fee_tip,
#error_section {
font-weight: 500;
&.error {
color: var(--danger-color);
.icon {
fill: var(--danger-color);
}
}
.error {
color: var(--danger-color);
.icon {
fill: var(--danger-color);
}
}
#send_fee_wrapper {
display: grid;
& > * {
grid-area: 1/1;
}
}
#send_fee_loader {
background-color: rgba(var(--foreground-color), 1);
z-index: 1;
width: 100%;
}
.receiver-card {
display: grid;
gap: 0.5rem;

View File

@ -1194,7 +1194,7 @@
</button>
<h3>Send BTC</h3>
</header>
<sm-form id="send_tx">
<sm-form id="send_tx" skip-submit>
<div>
<h4 class="margin-bottom-0-5">Receivers</h4>
<div id="receiver_container">
@ -1238,23 +1238,29 @@
<sm-chip value="custom">Custom</sm-chip>
</sm-chips>
</div>
<p id="selected_fee_tip">Estimated time of confirmation is 1hr</p>
<sm-input type="number" id="send_fee" placeholder="Fee" min="0.00000001" step="0.00000001"
error-text="Please enter valid fees" readonly animate required>
<div class="currency-symbol flex" slot="icon">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24"></rect>
</g>
<g>
<path
d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z">
</path>
</g>
</svg>
<p id="selected_fee_tip"></p>
<div id="send_fee_wrapper">
<sm-input type="number" id="send_fee" placeholder="Fee" min="0.00000001" step="0.00000001"
error-text="Please enter valid fees" readonly animate required>
<div class="currency-symbol flex" slot="icon">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24"></rect>
</g>
<g>
<path
d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z">
</path>
</g>
</svg>
</div>
</sm-input>
<div id="send_fee_loader" class="hidden flex align-center gap-0-5">
<sm-spinner></sm-spinner>
<span>Calculating fees...</span>
</div>
</sm-input>
</div>
</div>
<div id="error_section" class="hidden"></div>
<div class="multi-state-button">
@ -1565,6 +1571,11 @@
}
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(myFloID)
refreshBalance()
getExchangeRate().then(rate => {
getRef('conversion_rate').textContent = `1BTC = ${formatAmount(rate.inr)}`;
}).catch(error => {
console.error(error)
})
if (floGlobals.isSubAdmin) {
cashierUI.renderRequests(Cashier.Requests);
Cashier.init().then(result => {
@ -1704,6 +1715,9 @@
getRef('select_withdraw_upi_id').parentNode.classList.remove('hidden')
}
break;
case 'send_btc_popup':
calculateFees()
break;
case 'profile_popup':
renderElem(getRef('profile_popup__content'), render.profile())
renderSavedUpiIds('saved_upi_ids_list')
@ -2013,9 +2027,6 @@
})
render.savedIds()
}
getExchangeRate().then(rate => {
getRef('conversion_rate').textContent = `1BTC = ${formatAmount(rate.inr)}`;
})
break;
case 'contacts':
render.savedIds()
@ -2405,6 +2416,23 @@
}
]
const fadeIn = [
{
opacity: 0
},
{
opacity: 1
}
]
const fadeOut = [
{
opacity: 1
},
{
opacity: 0
}
]
function showChildElement(id, index, options = {}) {
return new Promise((resolve) => {
@ -2485,59 +2513,57 @@
});
floGlobals.prefersReducedMotion = reduceMotionQuery.matches
const generateKeys = document.createElement('template')
generateKeys.innerHTML = `
<style>
:host{
display: flex;
justify-content: center;
}
.generated-keys-wrapper {
padding: 1rem;
background-color: rgba(var(--foreground-color), 1);
border-radius: 0.5rem;
}
#flo_id_warning{
padding-bottom: 1.5rem;
}
#flo_id_warning .icon {
height: 3rem;
width: 3rem;
padding: 0.8rem;
overflow: visible;
background-color: #ffc107;
border-radius: 3rem;
fill: rgba(0, 0, 0, 0.8);
}
</style>
<section class="grid gap-1-5">
<div id="flo_id_warning" class="flex gap-1">
<svg class="icon" 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="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" /> </svg>
<div class="grid gap-0-5">
<strong>
<h3> Keep your keys safe! </h3>
</strong>
<p>Don't share with anyone. Once lost private key can't be recovered.</p>
</div>
</div>
<div class="grid gap-1-5 generated-keys-wrapper">
<div class="grid gap-0-5">
<h5>FLO address</h5>
<sm-copy id="generated_flo_address"></sm-copy>
</div>
<div class="grid gap-0-5">
<h5>Private key</h5>
<sm-copy id="generated_private_key"></sm-copy>
</div>
</div>
<button id="sign_up_button" class="button button--primary w-100">Sign in with these credentials</button>
<p class="margin-top-1">You can use these FLO credentials with other RanchiMall apps too. </p>
</section>
`
window.customElements.define('keys-generator', class extends HTMLElement {
constructor() {
super();
this.appendChild(generateKeys.content.cloneNode(true));
this.innerHTML = `
<style>
:host{
display: flex;
justify-content: center;
}
.generated-keys-wrapper {
padding: 1rem;
background-color: rgba(var(--foreground-color), 1);
border-radius: 0.5rem;
}
#flo_id_warning{
padding-bottom: 1.5rem;
}
#flo_id_warning .icon {
height: 3rem;
width: 3rem;
padding: 0.8rem;
overflow: visible;
background-color: #ffc107;
border-radius: 3rem;
fill: rgba(0, 0, 0, 0.8);
}
</style>
<section class="grid gap-1-5">
<div id="flo_id_warning" class="flex gap-1">
<svg class="icon" 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="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" /> </svg>
<div class="grid gap-0-5">
<strong>
<h3> Keep your keys safe! </h3>
</strong>
<p>Don't share with anyone. Once lost private key can't be recovered.</p>
</div>
</div>
<div class="grid gap-1-5 generated-keys-wrapper">
<div class="grid gap-0-5">
<h5>FLO address</h5>
<sm-copy id="generated_flo_address"></sm-copy>
</div>
<div class="grid gap-0-5">
<h5>Private key</h5>
<sm-copy id="generated_private_key"></sm-copy>
</div>
</div>
<button id="sign_up_button" class="button button--primary w-100">Sign in with these credentials</button>
<p class="margin-top-1">You can use these FLO credentials with other RanchiMall apps too. </p>
</section>
`
}
get keys() {
return {
@ -2616,8 +2642,7 @@
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M4.47 21h15.06c1.54 0 2.5-1.67 1.73-3L13.73 4.99c-.77-1.33-2.69-1.33-3.46 0L2.74 18c-.77 1.33.19 3 1.73 3zM12 14c-.55 0-1-.45-1-1v-2c0-.55.45-1 1-1s1 .45 1 1v2c0 .55-.45 1-1 1zm1 4h-2v-2h2v2z"/></svg>
<h1>Ad-Blocker Detected!</h1>
<p>
It seems like you are using an ad-blocker. Please disable it and reload the page.
We don't show any ads on this app and we don't track you.
Please disable your ad-blocker for optimal experience. Our app doesn't show ads or track activity.
</p>
${isBrave ? `<strong>If you have enabled Brave shield then disable it also.</strong>` : ''}
`;
@ -3694,12 +3719,19 @@
return;
}
getAddressDetails(floGlobals.myBtcID).then(({ txs }) => {
console.log(txs)
let allTransactions = []
const propToCheck = type === 'sent' ? 'out' : 'in';
let propToCheck = false
if (type === 'sent')
propToCheck = 'out';
else if (type === 'received')
propToCheck = 'in';
else if (type === 'self')
propToCheck = 'self';
for (let i = 0; i < txs.length; i++) {
const tx = txs[i];
tx.asset = 'btc';
if (type !== 'all')
if (propToCheck)
if (tx.type !== propToCheck)
continue;
allTransactions.push(tx)
@ -4136,7 +4168,7 @@
let d = {
txid: tx.txid,
time: tx.time,
block: tx.block_no
block: tx.block
}
if (tx.outgoing) {
d.type = "out";
@ -4173,30 +4205,10 @@
async function calculateBtcFees() {
const [senders, privKeys, receivers, amounts] = await getTransactionInputs().catch(e => {
console.error(e)
return [[], [], [], []]
return
});
if (!senders.length || !privKeys.length || !receivers.length || !amounts.length) return;
getRef('send_transaction').disabled = true;
getRef('fees_section').classList.add('hidden')
getRef('error_section').classList.remove('hidden')
renderElem(getRef('error_section'), html`<div class="flex align-center gap-0-5" style="font-size:0.9rem"><sm-spinner></sm-spinner> Calculating fees...</div>`)
btcOperator.createSignedTx(senders, privKeys, receivers, amounts).then(({ fee }) => {
getRef('send_fee').value = fee.toFixed(8);
getRef('fees_section').classList.remove('hidden')
getRef('error_section').classList.add('hidden')
getRef('send_transaction').disabled = false;
}).catch(e => {
getRef('fees_section').classList.add('hidden')
getRef('error_section').classList.remove('hidden')
renderElem(getRef('error_section'), html`
<p id="selected_fee_tip" class="error flex align-center gap-0-5">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M11 15h2v2h-2v-2zm0-8h2v6h-2V7zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/></svg>
${e}
</p>
`)
getRef('send_transaction').disabled = true;
console.error(e)
})
// if (!senders.length || !privKeys.length || !receivers.length || !amounts.length) return;
return btcOperator.createSignedTx(senders, privKeys, receivers, amounts)
}
const txParticipantsObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
@ -4227,7 +4239,10 @@
txParticipantsObserver.observe(getRef('receiver_container'), {
childList: true
})
let globalExchangeRate = {}
let globalExchangeRate = {
btc: 1,
inr: 1
}
async function getExchangeRate() {
return new Promise((resolve, reject) => {
Promise.all(['usd', 'inr'].map(cur => fetch(`https://bitpay.com/api/rates/btc/${cur}`))).then(responses => {
@ -4257,21 +4272,37 @@
})
getRef('fees_selector').addEventListener('change', e => {
if (e.target.value !== 'custom') {
getRef('send_fee').readOnly = true;
}
switch (e.target.value) {
case 'custom':
getRef('send_fee').readOnly = false;
getRef('send_fee').placeholder = 'Fee';
getRef('send_fee').focusIn();
getRef('selected_fee_tip').textContent = 'Set custom fee';
renderElem(getRef('selected_fee_tip'), html`Set custom fee`)
break;
case 'suggested':
getRef('selected_fee_tip').textContent = 'Estimated time of confirmation is 1hr'
calculateBtcFees();
calculateFees();
getRef('send_fee').readOnly = true;
break;
}
})
function calculateApproxFee() {
return new Promise((resolve, reject) => {
fetch('https://bitcoiner.live/api/fees/estimates/latest')
.then(res => {
res.json()
.then(data => {
const satPerByte = data.estimates['60'].sat_per_vbyte;
const legacyBytes = 200;
const segwitBytes = 77;
resolve((legacyBytes * satPerByte + (0.25 * satPerByte) * segwitBytes) / Math.pow(10, 8));
}).catch(e => {
reject(e)
})
}).catch(e => {
reject(e)
})
})
}
async function getTransactionInputs() {
const privateKey = await floDapps.user.private.catch(err => console.log(err));
const privKeys = btcOperator.convert.wif(privateKey);
@ -4283,14 +4314,58 @@
return [senders, privKeys, receivers, amounts]
}
getRef('receiver_container').addEventListener('input', debounce(e => {
getRef('receiver_container').addEventListener('input', debounce(calculateFees, 300))
function calculateFees() {
getRef('fees_selector').children[0].click();
getRef('fees_selector').classList.remove('hidden')
getRef('send_transaction').disabled = true;
getRef('send_fee').value = '';
getRef('send_fee_loader').classList.remove('hidden')
const animOptions = {
duration: 200,
easing: 'ease',
fill: 'forwards'
}
getRef('send_fee_loader').animate(fadeIn, animOptions)
getRef('fees_section').classList.remove('hidden')
getRef('error_section').classList.add('hidden')
const allValid = [...getRef('receiver_container').querySelectorAll('sm-input')].every(input => input.isValid)
if (allValid) {
calculateBtcFees()
getRef('send_fee').placeholder = 'Fee'
calculateBtcFees().then(({ fee }) => {
getRef('send_fee').value = fee.toFixed(8);
renderElem(getRef('selected_fee_tip'), html``)
getRef('send_transaction').disabled = false;
}).catch(e => {
getRef('fees_section').classList.add('hidden')
getRef('error_section').classList.remove('hidden')
renderElem(getRef('error_section'), html`
<p class="error flex align-center gap-0-5">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M11 15h2v2h-2v-2zm0-8h2v6h-2V7zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/></svg>
${e}
</p>
`)
console.error(e)
}).finally(_ => {
getRef('send_fee_loader').animate(fadeOut, animOptions).onfinish = _ =>
getRef('send_fee_loader').classList.add('hidden')
})
} else {
getRef('send_transaction').disabled = true;
getRef('send_fee').placeholder = 'Approximate fee'
renderElem(getRef('selected_fee_tip'), html` <p style="opacity: 0.8;">*Fill out all fields for exact fee!</p> `)
calculateApproxFee().then(fee => {
getRef('send_fee').value = fee.toFixed(8);
}).catch(e => {
getRef('fees_selector').children[1].click();
getRef('fees_selector').classList.add('hidden')
}).finally(_ => {
getRef('send_fee_loader').animate(fadeOut, animOptions).onfinish = _ =>
getRef('send_fee_loader').classList.add('hidden')
})
}
}, 300))
}
getRef('send_transaction').onclick = evt => {
buttonLoader('send_transaction', true)
@ -4298,17 +4373,12 @@
const [senders, privKeys, receivers, amounts] = await getTransactionInputs().catch(err => console.log(err));
const fee = parseFloat(getRef('send_fee').value.trim());
console.debug(senders, receivers, amounts, fee);
btcOperator.sendTx(senders, privKeys, receivers, amounts, fee).then(result => {
console.log(result);
btcOperator.sendTx(senders, privKeys, receivers, amounts, fee).then(txid => {
console.log(txid);
closePopup();
getRef('txid').value = result.txid;
getRef('txid').value = txid;
openPopup('txid_popup');
getRef('send_tx').reset()
getExchangeRate().then(() => {
calculateBtcFees()
}).catch(e => {
console.error(e)
})
}).catch(error => {
notify(`Error sending transaction \n ${error}`, 'error');
}).finally(_ => {

File diff suppressed because one or more lines are too long