Added currency conversion

This commit is contained in:
sairaj mote 2023-10-24 16:12:33 +05:30
parent 354abe7cd6
commit 553590832b
6 changed files with 152 additions and 18 deletions

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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;

View File

@ -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

File diff suppressed because one or more lines are too long