Added transaction Fee suggestion
This commit is contained in:
parent
087ddd0ed8
commit
d20381da36
30
css/main.css
30
css/main.css
@ -304,6 +304,10 @@ sm-chip[selected] {
|
||||
--background: rgba(var(--text-color), 0.9);
|
||||
}
|
||||
|
||||
sm-popup::part(popup) {
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
@ -1099,6 +1103,7 @@ ol li::before {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.contact .menu {
|
||||
flex-shrink: 0;
|
||||
justify-self: flex-end;
|
||||
padding: 0.2rem;
|
||||
fill: rgba(var(--text-color), 1);
|
||||
@ -2237,6 +2242,31 @@ sm-chip .badge {
|
||||
border-bottom: solid thin rgba(var(--text-color), 0.3);
|
||||
}
|
||||
|
||||
#selected_fee_tip,
|
||||
#error_section {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
.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%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
sm-popup {
|
||||
--border-radius: 1rem 1rem 0 0;
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -310,6 +310,11 @@ sm-chip {
|
||||
--background: rgba(var(--text-color), 0.9);
|
||||
}
|
||||
}
|
||||
sm-popup {
|
||||
&::part(popup) {
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
@ -1121,6 +1126,7 @@ ol {
|
||||
}
|
||||
|
||||
.menu {
|
||||
flex-shrink: 0;
|
||||
justify-self: flex-end;
|
||||
padding: 0.2rem;
|
||||
fill: rgba(var(--text-color), 1);
|
||||
@ -2305,6 +2311,27 @@ sm-chip {
|
||||
border-bottom: solid thin rgba(var(--text-color), 0.3);
|
||||
}
|
||||
}
|
||||
#selected_fee_tip,
|
||||
#error_section {
|
||||
font-weight: 500;
|
||||
}
|
||||
.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%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
sm-popup {
|
||||
|
||||
174
index.html
174
index.html
@ -1044,32 +1044,39 @@
|
||||
</svg>
|
||||
Add receiver</button>
|
||||
</div>
|
||||
<div class="grid gap-0-5">
|
||||
<div id="fees_section" class="grid gap-0-5">
|
||||
<div class="flex align-center space-between">
|
||||
<h4>Fees</h4>
|
||||
<sm-chips id="fees_selector" class="hidden">
|
||||
<sm-chips id="fees_selector">
|
||||
<sm-chip value="suggested" selected>Suggested</sm-chip>
|
||||
<sm-chip value="custom">Custom</sm-chip>
|
||||
</sm-chips>
|
||||
</div>
|
||||
<p id="selected_fee_tip" class="hidden">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" 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 margin-bottom-1-5">
|
||||
<button id="initiate_transaction" type="submit" class="button button--primary cta w-100"
|
||||
disabled>Initiate</button>
|
||||
@ -1405,6 +1412,9 @@
|
||||
renderCreationList()
|
||||
break;
|
||||
}
|
||||
case 'multisig_tx_popup':
|
||||
calculateFees()
|
||||
break;
|
||||
case 'compose_mail_popup':
|
||||
const mailingContacts = []
|
||||
for (const floID in floGlobals.contacts) {
|
||||
@ -2199,6 +2209,22 @@
|
||||
transform: 'translateY(-1.5rem)'
|
||||
},
|
||||
]
|
||||
const fadeIn = [
|
||||
{
|
||||
opacity: 0
|
||||
},
|
||||
{
|
||||
opacity: 1
|
||||
}
|
||||
]
|
||||
const fadeOut = [
|
||||
{
|
||||
opacity: 1
|
||||
},
|
||||
{
|
||||
opacity: 0
|
||||
}
|
||||
]
|
||||
|
||||
function showChildElement(id, index, options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
@ -4422,7 +4448,7 @@
|
||||
const widthDelta = changedWidth - originalWidth
|
||||
const leftMove = -widthDelta / 2;
|
||||
const rightMove = widthDelta / 2;
|
||||
const scale = (changedWidth / originalWidth).toFixed(2);
|
||||
const scale = (changedWidth / (originalWidth || 1)).toFixed(2) || 1;
|
||||
getRef('receiver_name').textContent = originalName
|
||||
getRef('pseudo_background').animate([
|
||||
{
|
||||
@ -4482,7 +4508,13 @@
|
||||
<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="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
|
||||
</button>
|
||||
`);
|
||||
}).catch(err => notify(err, 'error'))
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
notify('Failed to check balance', 'error')
|
||||
renderElem(multisig.querySelector('.multisig-option__balance'), html`
|
||||
<button class="button button--small" onclick=${checkBalance}>Check balance</button>
|
||||
`)
|
||||
})
|
||||
}
|
||||
|
||||
function initTransaction(e) {
|
||||
@ -4547,6 +4579,108 @@
|
||||
delegate(getRef('receiver_container'), 'click', '.remove-card', e => {
|
||||
e.target.closest('.receiver-card').remove()
|
||||
})
|
||||
getRef('fees_selector').addEventListener('change', e => {
|
||||
switch (e.target.value) {
|
||||
case 'custom':
|
||||
getRef('send_fee').readOnly = false;
|
||||
getRef('send_fee').placeholder = 'Fee';
|
||||
renderElem(getRef('selected_fee_tip'), html`Set custom fee`)
|
||||
break;
|
||||
case 'suggested':
|
||||
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 calculateBtcFees() {
|
||||
const [senders, privKeys, receivers, amounts] = await getTransactionInputs().catch(e => {
|
||||
console.error(e)
|
||||
return
|
||||
});
|
||||
// if (!senders.length || !privKeys.length || !receivers.length || !amounts.length) return;
|
||||
return btcOperator.createSignedTx(senders, privKeys, receivers, amounts)
|
||||
}
|
||||
async function getTransactionInputs() {
|
||||
const privateKey = await floDapps.user.private.catch(err => console.log(err));
|
||||
const privKeys = btcOperator.convert.wif(privateKey);
|
||||
const senders = floGlobals.myBtcID;
|
||||
const receivers = [...getRef('receiver_container').querySelectorAll('.receiver-input')].filter(input => input.value.trim() !== '').map(input => input.value.trim());
|
||||
const amounts = [...getRef('receiver_container').querySelectorAll('.amount-input')].filter(input => input.value.trim() !== '').map(input => {
|
||||
return parseFloat(input.value.trim())
|
||||
});
|
||||
return [senders, privKeys, receivers, amounts]
|
||||
}
|
||||
|
||||
getRef('receiver_container').addEventListener('input', debounce(calculateFees, 300))
|
||||
|
||||
function calculateFees() {
|
||||
getRef('fees_selector').children[0].click();
|
||||
getRef('fees_selector').classList.remove('hidden')
|
||||
getRef('initiate_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) {
|
||||
getRef('send_fee').placeholder = 'Fee'
|
||||
calculateBtcFees().then(({ fee }) => {
|
||||
getRef('send_fee').value = fee.toFixed(8);
|
||||
renderElem(getRef('selected_fee_tip'), html``)
|
||||
getRef('initiate_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_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')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getRef('initiate_transaction').onclick = async evt => {
|
||||
buttonLoader('initiate_transaction', true)
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user