Added currency conversion
This commit is contained in:
parent
354abe7cd6
commit
553590832b
14
css/main.css
14
css/main.css
@ -700,6 +700,20 @@ menu {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#loading_page {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 20;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
|
||||
#main_header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -650,6 +650,19 @@ menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#loading_page {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 20;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
#main_header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
|
||||
136
index.html
136
index.html
@ -18,6 +18,10 @@
|
||||
<button class="button button--primary confirm-button">OK</button>
|
||||
</div>
|
||||
</sm-popup>
|
||||
<div id="loading_page">
|
||||
<sm-spinner></sm-spinner>
|
||||
<strong>Getting Taproot Wallet ready</strong>
|
||||
</div>
|
||||
<main id="main_card">
|
||||
<header id="main_header">
|
||||
<a href="#/home" id="logo" class="app-brand">
|
||||
@ -33,7 +37,14 @@
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
<theme-toggle></theme-toggle>
|
||||
<div class="flex align-center gap-0-3">
|
||||
<sm-select id="currency_selector" class="margin-right-0-5">
|
||||
<sm-option value="btc">BTC</sm-option>
|
||||
<sm-option value="inr">INR</sm-option>
|
||||
<sm-option value="usd">USD</sm-option>
|
||||
</sm-select>
|
||||
<theme-toggle></theme-toggle>
|
||||
</div>
|
||||
</header>
|
||||
<div id="page_container"> </div>
|
||||
<nav id="main_navbar">
|
||||
@ -369,7 +380,33 @@
|
||||
createRipple(e, e.target.closest("button, .interactive"));
|
||||
}
|
||||
});
|
||||
router.routeTo(location.hash)
|
||||
getExchangeRate()
|
||||
.then(() => {
|
||||
selectedCurrency = localStorage.getItem('taproot-wallet-currency') || 'btc'
|
||||
setTimeout(() => {
|
||||
document.getElementById('currency_selector').value = selectedCurrency
|
||||
}, 100)
|
||||
})
|
||||
.catch(e => {
|
||||
selectedCurrency = 'btc'
|
||||
// console.error(e)
|
||||
getRef('currency_selector').classList.add('hidden')
|
||||
}).finally(() => {
|
||||
router.routeTo(location.hash)
|
||||
setTimeout(() => {
|
||||
getRef('loading_page').animate([
|
||||
{ transform: 'translateY(0)', },
|
||||
{ transform: 'translateY(-100%)', }
|
||||
], {
|
||||
duration: 300,
|
||||
fill: 'forwards',
|
||||
easing: 'ease'
|
||||
}).onfinish = () => {
|
||||
getRef('loading_page').remove()
|
||||
}
|
||||
}, 500);
|
||||
document.querySelectorAll('.currency-symbol').forEach(el => el.innerHTML = currencyIcons[selectedCurrency])
|
||||
})
|
||||
});
|
||||
function createRipple(event, target) {
|
||||
const circle = document.createElement("span");
|
||||
@ -650,15 +687,82 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
function formatAmount(amount = 0, currency = 'btc') {
|
||||
const currencyIcons = {
|
||||
btc: ` <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> `,
|
||||
usd: `<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="M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z"/></svg>`,
|
||||
inr: `<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"/></g><g><g><path d="M13.66,7C13.1,5.82,11.9,5,10.5,5L6,5V3h12v2l-3.26,0c0.48,0.58,0.84,1.26,1.05,2L18,7v2l-2.02,0c-0.25,2.8-2.61,5-5.48,5 H9.77l6.73,7h-2.77L7,14v-2h3.5c1.76,0,3.22-1.3,3.46-3L6,9V7L13.66,7z"/></g></g></svg>`
|
||||
}
|
||||
function formatAmount(amount = 0) {
|
||||
// check if amount is a string and convert it to a number
|
||||
if (typeof amount === 'string') {
|
||||
amount = parseFloat(amount)
|
||||
}
|
||||
if (!amount)
|
||||
return '0';
|
||||
return amount.toLocaleString(undefined, { style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: 8 })
|
||||
return amount.toLocaleString(undefined, { style: 'currency', currency: selectedCurrency, minimumFractionDigits: 0, maximumFractionDigits: selectedCurrency === 'btc' ? 8 : 2 })
|
||||
}
|
||||
let globalExchangeRate = {}
|
||||
async function getExchangeRate() {
|
||||
return new Promise((resolve, reject) => {
|
||||
Promise.all(['usd', 'inr'].map(cur => fetch(`https://bitpay.com/api/rates/btc/${cur}`))).then(responses => {
|
||||
Promise.all(responses.map(res => res.json())).then(rates => {
|
||||
rates.forEach(rate => {
|
||||
globalExchangeRate[rate.code.toLowerCase()] = rate.rate
|
||||
})
|
||||
globalExchangeRate.btc = 1
|
||||
resolve(globalExchangeRate)
|
||||
}).catch(err => reject(err))
|
||||
}).catch(err => reject(err))
|
||||
})
|
||||
}
|
||||
function getConvertedAmount(amount) {
|
||||
// check if amount is a string and convert it to a number
|
||||
if (typeof amount === 'string') {
|
||||
amount = parseFloat(amount)
|
||||
}
|
||||
if (globalExchangeRate[selectedCurrency])
|
||||
return parseFloat((amount * globalExchangeRate[selectedCurrency]).toFixed(8))
|
||||
else return amount
|
||||
}
|
||||
function roundUp(amount, precision = 2) {
|
||||
return parseFloat((Math.ceil(amount * Math.pow(10, precision)) / Math.pow(10, precision)).toFixed(precision))
|
||||
}
|
||||
let previouslySelectedCurrency = localStorage.getItem('taproot-wallet-currency') || 'btc';
|
||||
getRef('currency_selector').addEventListener('change', e => {
|
||||
selectedCurrency = e.target.value;
|
||||
localStorage.setItem('taproot-wallet-currency', selectedCurrency);
|
||||
document.querySelectorAll('.currency-symbol').forEach(el => el.innerHTML = currencyIcons[selectedCurrency])
|
||||
document.querySelectorAll('.amount-shown').forEach(el => {
|
||||
if (el.tagName.includes('SM-')) {
|
||||
const originalAmount = parseFloat(el.value.trim());
|
||||
let convertedAmount
|
||||
const rupeeRate = (globalExchangeRate.inr / globalExchangeRate.usd);
|
||||
switch (previouslySelectedCurrency) {
|
||||
case 'usd':
|
||||
if (selectedCurrency === 'inr')
|
||||
convertedAmount = roundUp(originalAmount * rupeeRate)
|
||||
else
|
||||
convertedAmount = roundUp((originalAmount / globalExchangeRate.usd), 8)
|
||||
break;
|
||||
case 'inr':
|
||||
if (selectedCurrency === 'usd')
|
||||
convertedAmount = roundUp((originalAmount / rupeeRate))
|
||||
else
|
||||
convertedAmount = roundUp((originalAmount / globalExchangeRate.inr), 8)
|
||||
break;
|
||||
case 'btc':
|
||||
convertedAmount = roundUp(originalAmount * globalExchangeRate[selectedCurrency])
|
||||
break;
|
||||
}
|
||||
el.value = convertedAmount
|
||||
el.isValid // trigger validation
|
||||
} else {
|
||||
if (el.dataset.btcAmount === undefined) return
|
||||
el.textContent = formatAmount(getConvertedAmount(el.dataset.btcAmount))
|
||||
}
|
||||
})
|
||||
previouslySelectedCurrency = selectedCurrency
|
||||
})
|
||||
function togglePrivateKeyVisibility(input) {
|
||||
const target = input.closest('sm-input')
|
||||
target.type = target.type === 'password' ? 'text' : 'password';
|
||||
@ -702,12 +806,12 @@
|
||||
feeSection = html`<p class="error">${message}</p>`
|
||||
} else if (status === 'success') {
|
||||
feeSection = html`
|
||||
<sm-input id="fee_input" placeholder="Suggested fee" type="number" value=${suggestedFee()} step="0.00000001" min="0.0000001" readonly animate required></sm-input>
|
||||
<sm-input id="fee_input" class="amount-shown" placeholder="Suggested fee" type="number" value=${suggestedFee()} step="0.00000001" min="0.0000001" readonly animate required></sm-input>
|
||||
`
|
||||
}
|
||||
} else {
|
||||
feeSection = html`
|
||||
<sm-input id="fee_input" placeholder="Custom fee" type="number" step="0.00000001" min="0.0000001" error-text="Minimum fee is 0.0000001BTC" animate required>
|
||||
<sm-input id="fee_input" class="amount-shown" placeholder="Custom fee" type="number" step="0.00000001" min="0.0000001" error-text="Minimum fee is 0.0000001BTC" animate required>
|
||||
</sm-input>
|
||||
`
|
||||
}
|
||||
@ -754,7 +858,7 @@
|
||||
<ul id="receivers_container" class="grid gap-1">
|
||||
<li class="grid gap-0-5 receiver-wrapper">
|
||||
<sm-input class="receiver-address" placeholder="Receiver's address" data-btc-address animate required></sm-input>
|
||||
<sm-input class="receiver-amount" placeholder="Amount" type="number" step="0.00000001" min="0.0000001" error-text="Amount should be grater than 0.0000001 BTC" animate required>
|
||||
<sm-input class="receiver-amount amount-shown" placeholder="Amount" type="number" step="0.00000001" min="0.0000001" error-text="Amount should be grater than 0.0000001 BTC" 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>
|
||||
@ -839,7 +943,7 @@
|
||||
<sm-copy value=${address}><p>${address}<p></sm-copy>
|
||||
</div>
|
||||
<p>
|
||||
Balance: <b>${formatAmount(balance)}</b>
|
||||
Balance: <b class="amount-shown" data-btc-amount=${balance}>${formatAmount(getConvertedAmount(balance))}</b>
|
||||
</p>
|
||||
</div>
|
||||
`)
|
||||
@ -882,7 +986,7 @@
|
||||
<sm-input class="receiver-address" placeholder="Receiver's address" data-btc-address
|
||||
required></sm-input>
|
||||
<div class="flex gap-0-5">
|
||||
<sm-input class="receiver-amount flex-1" placeholder="Amount" type="number" step="0.00000001" min="0.0000001" error-text="Amount should be grater than 0.0000001 BTC" required>
|
||||
<sm-input class="receiver-amount flex-1 amount-shown" placeholder="Amount" type="number" step="0.00000001" min="0.0000001" error-text="Amount should be grater than 0.0000001 BTC" 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>
|
||||
@ -902,7 +1006,8 @@
|
||||
return notify('Sender address is not a Taproot address', 'error')
|
||||
const receivers = [...getRef('receivers_container').children].reduce((receivers, receiver) => {
|
||||
const receiverAddress = receiver.querySelector('.receiver-address').value.trim()
|
||||
const amount = parseFloat(receiver.querySelector('.receiver-amount').value.trim())
|
||||
let amount = parseFloat(receiver.querySelector('.receiver-amount').value.trim())
|
||||
amount = amount / (globalExchangeRate[selectedCurrency] || 1) // convert to btc
|
||||
if (!receivers[receiverAddress])
|
||||
receivers[receiverAddress] = 0
|
||||
receivers[receiverAddress] += amount
|
||||
@ -910,7 +1015,8 @@
|
||||
}, {})
|
||||
const receiverAddresses = Object.keys(receivers)
|
||||
const receiverAmounts = Object.values(receivers)
|
||||
const fee = parseFloat(getRef('fee_input')?.value.trim()) || 0
|
||||
let fee = parseFloat(getRef('fee_input')?.value.trim()) || 0
|
||||
fee = fee / (globalExchangeRate[selectedCurrency] || 1)
|
||||
return { senderPrivateKey, senderAddress, receivers, receiverAddresses, receiverAmounts, fee }
|
||||
}
|
||||
async function sendTx() {
|
||||
@ -926,17 +1032,17 @@
|
||||
<div class="grid">
|
||||
<span class="label">Receivers</span>
|
||||
<div class="grid gap-0-3">
|
||||
${Object.entries(receivers).map(([address, amount]) => html.node`
|
||||
${Object.entries(receivers).map(([address, amount]) => html.node/*html*/`
|
||||
<div class="grid gap-0-5" style="padding:0.5rem;border:solid thin rgba(var(--text-color),0.3);border-radius: 0.3rem;">
|
||||
<b>${address}</b>
|
||||
<b>${formatAmount(amount)}</b>
|
||||
<b class="amount-shown" data-btc-amount=${amount}>${formatAmount(getConvertedAmount(amount))}</b>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<span class="label">Fee</span>
|
||||
<b>${formatAmount(fee)}</b>
|
||||
<b class="amount-shown" data-btc-amount=${fee}>${formatAmount(getConvertedAmount(fee))}</b>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@ -1114,7 +1220,7 @@
|
||||
// add change address
|
||||
const changeAmount = senderBalance - (totalAmount + fee)
|
||||
if (changeAmount < 0)
|
||||
return reject(`Insufficient balance. Required: ${totalAmount + fee} BTC, Available: ${util.Sat_to_BTC(senderBalance)} BTC`)
|
||||
return reject(`Insufficient balance. Required: ${formatAmount(getConvertedAmount(totalAmount + fee))}, Available: ${formatAmount(getConvertedAmount(util.Sat_to_BTC(senderBalance)))}`)
|
||||
tx.addOutputAddress(address, BigInt(util.BTC_to_Sat(changeAmount)));
|
||||
const privKey = coinjs.wif2privkey(senderPrivateKey).privkey;
|
||||
const privKey_arrayForm = hex.decode(privKey);
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
scripts/components.min.js
vendored
2
scripts/components.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user