feat: Add Bitcoin support, BTC chips, and improve UX
Some checks failed
Workflow push to Dappbundle / Build (push) Has been cancelled
Some checks failed
Workflow push to Dappbundle / Build (push) Has been cancelled
- Implement Bitcoin address generation (Bech32) and WIF display. - Add BTC chips to searched addresses list. - Add input validation: reject FLO/BTC addresses with clear intent messages. - Update search placeholder and errors to use 'MNT'. - Fix desktop notification panel positioning.
This commit is contained in:
parent
72351dd5b8
commit
6be4402f6f
70
index.html
70
index.html
@ -468,9 +468,10 @@
|
|||||||
const apply = () => {
|
const apply = () => {
|
||||||
if (window.matchMedia('(min-width: 640px)').matches) {
|
if (window.matchMedia('(min-width: 640px)').matches) {
|
||||||
Object.assign(panel.style, {
|
Object.assign(panel.style, {
|
||||||
left: 'calc(10rem + 1rem)',
|
// Desktop: top-right to avoid overlapping "Searched addresses" (which is left-side)
|
||||||
bottom: '1rem',
|
left: 'auto',
|
||||||
top: 'auto',
|
bottom: 'auto',
|
||||||
|
top: '1rem',
|
||||||
right: '1rem',
|
right: '1rem',
|
||||||
zIndex: '1000'
|
zIndex: '1000'
|
||||||
});
|
});
|
||||||
@ -663,7 +664,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<sm-form oninvalid="handleInvalidSearch()">
|
<sm-form oninvalid="handleInvalidSearch()">
|
||||||
<div id="input_wrapper">
|
<div id="input_wrapper">
|
||||||
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="Address, private key, or tx hash"
|
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="MNT Address, private key, or tx hash"
|
||||||
type="password" animate>
|
type="password" animate>
|
||||||
<svg class="icon" slot="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="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"> </path> </g> </svg>
|
<svg class="icon" slot="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="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"> </path> </g> </svg>
|
||||||
<label slot="right" class="interact">
|
<label slot="right" class="interact">
|
||||||
@ -734,13 +735,14 @@
|
|||||||
}
|
}
|
||||||
const renderedContacts = []
|
const renderedContacts = []
|
||||||
for (const floAddress in contacts) {
|
for (const floAddress in contacts) {
|
||||||
const { ethAddress } = contacts[floAddress]
|
const { ethAddress, btcAddress } = contacts[floAddress]
|
||||||
renderedContacts.push(html`
|
renderedContacts.push(html`
|
||||||
<li class="contact" .dataset=${{ floAddress, ethAddress }}>
|
<li class="contact" .dataset=${{ floAddress, ethAddress, btcAddress }}>
|
||||||
${floAddress === ethAddress ? html`
|
${floAddress === ethAddress ? html`
|
||||||
`: html`
|
`: html`
|
||||||
<sm-chips onchange=${e => e.target.closest('.contact').querySelector('sm-copy').value = e.target.value}>
|
<sm-chips onchange=${e => e.target.closest('.contact').querySelector('sm-copy').value = e.target.value}>
|
||||||
<sm-chip value=${floAddress} selected>FLO</sm-chip>
|
<sm-chip value=${floAddress} selected>FLO</sm-chip>
|
||||||
|
${btcAddress ? html`<sm-chip value=${btcAddress}>BTC</sm-chip>` : ''}
|
||||||
<sm-chip value=${ethAddress}>MNT</sm-chip>
|
<sm-chip value=${ethAddress}>MNT</sm-chip>
|
||||||
</sm-chips>
|
</sm-chips>
|
||||||
`}
|
`}
|
||||||
@ -777,6 +779,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The search bar is versatile: it accepts addresses, private keys, or transaction hashes.
|
// The search bar is versatile: it accepts addresses, private keys, or transaction hashes.
|
||||||
|
|
||||||
|
// Reject FLO addresses (start with F)
|
||||||
|
if (/^F[a-km-zA-HJ-NP-Z1-9]{26,34}$/.test(keyToConvert)) {
|
||||||
|
notify('FLO addresses are not supported. Please use a MNT address or private key.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject BTC addresses (legacy: 1/3, segwit: bc1)
|
||||||
|
if (/^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(keyToConvert) || /^bc1[a-z0-9]{39,59}$/i.test(keyToConvert)) {
|
||||||
|
notify('BTC addresses are not supported. Please use a MNT address or private key.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ethOperator.isValidAddress(keyToConvert)) {
|
if (ethOperator.isValidAddress(keyToConvert)) {
|
||||||
ethAddress = keyToConvert
|
ethAddress = keyToConvert
|
||||||
}
|
}
|
||||||
@ -787,12 +802,37 @@
|
|||||||
// Otherwise, try to convert as private key
|
// Otherwise, try to convert as private key
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
|
let isHex = false;
|
||||||
|
let btcAddress;
|
||||||
|
|
||||||
if (/^[0-9a-fA-F]{64}$/.test(keyToConvert)) {
|
if (/^[0-9a-fA-F]{64}$/.test(keyToConvert)) {
|
||||||
keyToConvert = coinjs.privkey2wif(keyToConvert)
|
keyToConvert = coinjs.privkey2wif(keyToConvert)
|
||||||
|
isHex = true;
|
||||||
}
|
}
|
||||||
const ethPrivateKey = coinjs.wif2privkey(keyToConvert).privkey;
|
const ethPrivateKey = coinjs.wif2privkey(keyToConvert).privkey;
|
||||||
ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
||||||
floAddress = floCrypto.getFloID(keyToConvert)
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!isHex) {
|
||||||
|
floAddress = floCrypto.getFloID(keyToConvert)
|
||||||
|
try {
|
||||||
|
btcAddress = btcOperator.bech32Address(keyToConvert)
|
||||||
|
} catch (e) { console.error(e) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to indexed DB
|
||||||
|
compactIDB.readData('contacts', floAddress || ethAddress).then(result => {
|
||||||
|
if (result) return
|
||||||
|
compactIDB.addData('contacts', {
|
||||||
|
ethAddress,
|
||||||
|
btcAddress
|
||||||
|
}, floAddress || ethAddress).then(() => {
|
||||||
|
renderSearchedAddressList()
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notify('Invalid input. Please enter a valid Ethereum address or private key.', 'error');
|
notify('Invalid input. Please enter a valid Ethereum address or private key.', 'error');
|
||||||
return;
|
return;
|
||||||
@ -1962,8 +2002,24 @@
|
|||||||
const { floID, privKey } = floCrypto.generateNewID();
|
const { floID, privKey } = floCrypto.generateNewID();
|
||||||
const ethPrivateKey = coinjs.wif2privkey(privKey).privkey;
|
const ethPrivateKey = coinjs.wif2privkey(privKey).privkey;
|
||||||
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
||||||
|
|
||||||
|
// Bitcoin support
|
||||||
|
const btcBech32 = btcOperator.bech32Address(privKey);
|
||||||
|
// Convert to Bitcoin WIF format
|
||||||
|
const btcPrivKey = btcOperator.convert.wif(privKey);
|
||||||
|
|
||||||
renderElem(getRef('created_address_wrapper'), html`
|
renderElem(getRef('created_address_wrapper'), html`
|
||||||
<ul id="generated_addresses" class="grid gap-1-5">
|
<ul id="generated_addresses" class="grid gap-1-5">
|
||||||
|
<li class="grid gap-0-5">
|
||||||
|
<div>
|
||||||
|
<h5>Bitcoin Address</h5>
|
||||||
|
<sm-copy value="${btcBech32}"></sm-copy>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5>Private Key</h5>
|
||||||
|
<sm-copy value="${btcPrivKey}"></sm-copy>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li class="grid gap-0-5">
|
<li class="grid gap-0-5">
|
||||||
<div>
|
<div>
|
||||||
<h5>FLO Address</h5>
|
<h5>FLO Address</h5>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user