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);
|
--background: rgba(var(--text-color), 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sm-popup::part(popup) {
|
||||||
|
background-color: rgba(var(--foreground-color), 1);
|
||||||
|
}
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
ol {
|
ol {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@ -1099,6 +1103,7 @@ ol li::before {
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
.contact .menu {
|
.contact .menu {
|
||||||
|
flex-shrink: 0;
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
fill: rgba(var(--text-color), 1);
|
fill: rgba(var(--text-color), 1);
|
||||||
@ -2237,6 +2242,31 @@ sm-chip .badge {
|
|||||||
border-bottom: solid thin rgba(var(--text-color), 0.3);
|
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) {
|
@media screen and (max-width: 640px) {
|
||||||
sm-popup {
|
sm-popup {
|
||||||
--border-radius: 1rem 1rem 0 0;
|
--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);
|
--background: rgba(var(--text-color), 0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sm-popup {
|
||||||
|
&::part(popup) {
|
||||||
|
background-color: rgba(var(--foreground-color), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
ol {
|
ol {
|
||||||
@ -1121,6 +1126,7 @@ ol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
|
flex-shrink: 0;
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
fill: rgba(var(--text-color), 1);
|
fill: rgba(var(--text-color), 1);
|
||||||
@ -2305,6 +2311,27 @@ sm-chip {
|
|||||||
border-bottom: solid thin rgba(var(--text-color), 0.3);
|
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) {
|
@media screen and (max-width: 640px) {
|
||||||
sm-popup {
|
sm-popup {
|
||||||
|
|||||||
174
index.html
174
index.html
@ -1044,32 +1044,39 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Add receiver</button>
|
Add receiver</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-0-5">
|
<div id="fees_section" class="grid gap-0-5">
|
||||||
<div class="flex align-center space-between">
|
<div class="flex align-center space-between">
|
||||||
<h4>Fees</h4>
|
<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="suggested" selected>Suggested</sm-chip>
|
||||||
<sm-chip value="custom">Custom</sm-chip>
|
<sm-chip value="custom">Custom</sm-chip>
|
||||||
</sm-chips>
|
</sm-chips>
|
||||||
</div>
|
</div>
|
||||||
<p id="selected_fee_tip" class="hidden">Estimated time of confirmation is 1hr</p>
|
<p id="selected_fee_tip"></p>
|
||||||
<sm-input type="number" id="send_fee" placeholder="Fee" min="0.00000001" step="0.00000001"
|
<div id="send_fee_wrapper">
|
||||||
error-text="Please enter valid fees" animate required>
|
<sm-input type="number" id="send_fee" placeholder="Fee" min="0.00000001" step="0.00000001"
|
||||||
<div class="currency-symbol flex" slot="icon">
|
error-text="Please enter valid fees" readonly animate required>
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
|
<div class="currency-symbol flex" slot="icon">
|
||||||
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
|
||||||
<g>
|
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
||||||
<rect fill="none" height="24" width="24"></rect>
|
<g>
|
||||||
</g>
|
<rect fill="none" height="24" width="24"></rect>
|
||||||
<g>
|
</g>
|
||||||
<path
|
<g>
|
||||||
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
|
||||||
</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">
|
||||||
</g>
|
</path>
|
||||||
</svg>
|
</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>
|
</div>
|
||||||
</sm-input>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="error_section" class="hidden"></div>
|
||||||
<div class="multi-state-button margin-bottom-1-5">
|
<div class="multi-state-button margin-bottom-1-5">
|
||||||
<button id="initiate_transaction" type="submit" class="button button--primary cta w-100"
|
<button id="initiate_transaction" type="submit" class="button button--primary cta w-100"
|
||||||
disabled>Initiate</button>
|
disabled>Initiate</button>
|
||||||
@ -1405,6 +1412,9 @@
|
|||||||
renderCreationList()
|
renderCreationList()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'multisig_tx_popup':
|
||||||
|
calculateFees()
|
||||||
|
break;
|
||||||
case 'compose_mail_popup':
|
case 'compose_mail_popup':
|
||||||
const mailingContacts = []
|
const mailingContacts = []
|
||||||
for (const floID in floGlobals.contacts) {
|
for (const floID in floGlobals.contacts) {
|
||||||
@ -2199,6 +2209,22 @@
|
|||||||
transform: 'translateY(-1.5rem)'
|
transform: 'translateY(-1.5rem)'
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
const fadeIn = [
|
||||||
|
{
|
||||||
|
opacity: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const fadeOut = [
|
||||||
|
{
|
||||||
|
opacity: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
function showChildElement(id, index, options = {}) {
|
function showChildElement(id, index, options = {}) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@ -4422,7 +4448,7 @@
|
|||||||
const widthDelta = changedWidth - originalWidth
|
const widthDelta = changedWidth - originalWidth
|
||||||
const leftMove = -widthDelta / 2;
|
const leftMove = -widthDelta / 2;
|
||||||
const rightMove = 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('receiver_name').textContent = originalName
|
||||||
getRef('pseudo_background').animate([
|
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>
|
<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>
|
</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) {
|
function initTransaction(e) {
|
||||||
@ -4547,6 +4579,108 @@
|
|||||||
delegate(getRef('receiver_container'), 'click', '.remove-card', e => {
|
delegate(getRef('receiver_container'), 'click', '.remove-card', e => {
|
||||||
e.target.closest('.receiver-card').remove()
|
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 => {
|
getRef('initiate_transaction').onclick = async evt => {
|
||||||
buttonLoader('initiate_transaction', true)
|
buttonLoader('initiate_transaction', true)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user