Workflow updating files of floethereum

This commit is contained in:
RanchiMall Dev 2026-01-23 18:57:41 +00:00
parent 139dba4cd9
commit 2278af46ed

View File

@ -92,66 +92,65 @@
</button> </button>
<theme-toggle></theme-toggle> <theme-toggle></theme-toggle>
</header> </header>
<nav id="main_navbar"> <nav id="main_navbar">
<ul> <ul>
<li> <li>
<a class="nav-item nav-item--active interactive" href="#/balance"> <a class="nav-item nav-item--active interactive" href="#/balance">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" <svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000"> width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" /> <path d="M0 0h24v24H0V0z" fill="none" />
<path <path
d="M21 7.28V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-2.28c.59-.35 1-.98 1-1.72V9c0-.74-.41-1.37-1-1.72zM20 9v6h-7V9h7zM5 19V5h14v2h-6c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6v2H5z" /> d="M21 7.28V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-2.28c.59-.35 1-.98 1-1.72V9c0-.74-.41-1.37-1-1.72zM20 9v6h-7V9h7zM5 19V5h14v2h-6c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6v2H5z" />
<circle cx="16" cy="12" r="1.5" /> <circle cx="16" cy="12" r="1.5" />
</svg> </svg>
<span class="nav-item__title"> <span class="nav-item__title">
Balance Balance
</span> </span>
<div class="nav-item__indicator"></div> <div class="nav-item__indicator"></div>
</a> </a>
</li> </li>
<li> <li>
<a class="nav-item interactive" href="#/send"> <a class="nav-item interactive" href="#/send">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" <svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000"> width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none"></path> <path d="M0 0h24v24H0V0z" fill="none"></path>
<path <path
d="M4.01 6.03l7.51 3.22-7.52-1 .01-2.22m7.5 8.72L4 17.97v-2.22l7.51-1M2.01 3L2 10l15 2-15 2 .01 7L23 12 2.01 3z"> d="M4.01 6.03l7.51 3.22-7.52-1 .01-2.22m7.5 8.72L4 17.97v-2.22l7.51-1M2.01 3L2 10l15 2-15 2 .01 7L23 12 2.01 3z">
</path> </path>
</svg> </svg>
<span class="nav-item__title"> <span class="nav-item__title">
Send Send
</span> </span>
</a> </a>
</li> </li>
<li> <li>
<a class="nav-item interactive" href="#/create"> <a class="nav-item interactive" href="#/create">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" <svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000"> width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none"></path> <path d="M0 0h24v24H0V0z" fill="none"></path>
<path <path
d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"> d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z">
</path> </path>
</svg> </svg>
<span class="nav-item__title"> <span class="nav-item__title">
Create Create
</span> </span>
</a> </a>
</li> </li>
<li> <li>
<a class="nav-item interactive" href="#/retrieve"> <a class="nav-item interactive" href="#/retrieve">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" <svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000"> width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" /> <path d="M0 0h24v24H0V0z" fill="none" />
<path <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" /> </svg>
</svg> <span class="nav-item__title">
<span class="nav-item__title"> Retrieve
Retrieve </span>
</span> </a>
</a> </li>
</li> </ul>
</ul> </nav>
</nav>
<div id="page_container"></div> <div id="page_container"></div>
</main> </main>
@ -460,37 +459,37 @@
} }
} }
//Moving notification draw so that it does not overlap the menu item //Moving notification draw so that it does not overlap the menu item
(function placeToasts() { (function placeToasts() {
const drawer = document.getElementById('notification_drawer'); const drawer = document.getElementById('notification_drawer');
const panel = drawer?.shadowRoot?.querySelector('.notification-panel'); const panel = drawer?.shadowRoot?.querySelector('.notification-panel');
if (!panel) return; if (!panel) return;
const apply = () => { const apply = () => {
if (window.matchMedia('(min-width: 640px)').matches) { if (window.matchMedia('(min-width: 640px)').matches) {
// Desktop: offset from left navbar // Desktop: top-right corner
Object.assign(panel.style, { Object.assign(panel.style, {
left: 'calc(10rem + 1rem)', left: 'auto',
bottom: '1rem', top: '1rem',
top: 'auto', right: '1rem',
right: '1rem', // optional, lets long toasts wrap before content edge bottom: 'auto',
zIndex: '1000' zIndex: '1000'
}); });
} else { } else {
// Mobile: keep top-right or top-left as you prefer // Mobile: top-center
Object.assign(panel.style, { Object.assign(panel.style, {
left: '0.5rem', left: '0.5rem',
top: '0.5rem', top: '0.5rem',
right: '0.5rem', right: '0.5rem',
bottom: 'auto', bottom: 'auto',
zIndex: '1000' zIndex: '1000'
}); });
} }
}; };
apply(); apply();
window.addEventListener('resize', apply); window.addEventListener('resize', apply);
})(); })();
</script> </script>
<script> <script>
@ -567,79 +566,79 @@
let idbReady; let idbReady;
window.addEventListener('load', () => { window.addEventListener('load', () => {
// Initialize the database before any routing or data reads // Initialize the database before any routing or data reads
idbReady = compactIDB.initDB('floEthereum', { contacts: {} }) idbReady = compactIDB.initDB('floEthereum', { contacts: {} })
.then((res) => { console.log(res); }) .then((res) => { console.log(res); })
.catch((err) => { console.error(err); }); .catch((err) => { console.error(err); });
// Set up event listeners and routing after database is ready // Set up event listeners and routing after database is ready
idbReady.then(() => { idbReady.then(() => {
const routeNow = () => router.routeTo(location.hash); const routeNow = () => router.routeTo(location.hash);
// Set up UI event listeners for copy notifications and ripple effects // Set up UI event listeners for copy notifications and ripple effects
document.addEventListener('copy', () => notify('copied', 'success')); document.addEventListener('copy', () => notify('copied', 'success'));
document.addEventListener('pointerdown', (e) => { document.addEventListener('pointerdown', (e) => {
const target = e.target.closest('button:not(:disabled), .interactive:not(:disabled)'); const target = e.target.closest('button:not(:disabled), .interactive:not(:disabled)');
if (target) createRipple(e, target); if (target) createRipple(e, target);
});
// Handle Ethereum provider and MetaMask connection
if (window.ethereum) {
window.ethereum.on('chainChanged', (chainId) => {
window.currentChainId = chainId;
if (chainId !== '0x1') {
renderError('Please switch MetaMask to Ethereum Mainnet');
} else {
routeNow();
}
});
window.ethereum.request({ method: 'eth_chainId' })
.then((chainId) => {
window.currentChainId = chainId;
if (chainId !== '0x1') {
renderError('Please switch MetaMask to Ethereum Mainnet');
} else {
routeNow();
}
})
.catch(() => {
// If reading chain id fails, still render the app
routeNow();
}); });
// Listen for MetaMask account changes // Handle Ethereum provider and MetaMask connection
ethereum.on('accountsChanged', (accounts) => { if (window.ethereum) {
getRef('eth_balance_wrapper').classList.add('hidden'); window.ethereum.on('chainChanged', (chainId) => {
setMetaMaskStatus(accounts.length > 0); window.currentChainId = chainId;
}); if (chainId !== '0x1') {
ethereum.on('connect', (accounts) => { renderError('Please switch MetaMask to Ethereum Mainnet');
setMetaMaskStatus(accounts.length > 0); } else {
}); routeNow();
ethereum.on('disconnect', (accounts) => { }
setMetaMaskStatus(false); });
});
} else {
// No MetaMask detected, proceed with normal routing
routeNow();
}
// 3) Reveal UI only after were safe to render window.ethereum.request({ method: 'eth_chainId' })
document.body.classList.remove('hidden'); .then((chainId) => {
}); window.currentChainId = chainId;
if (chainId !== '0x1') {
renderError('Please switch MetaMask to Ethereum Mainnet');
} else {
routeNow();
}
})
.catch(() => {
// If reading chain id fails, still render the app
routeNow();
});
// Listen for MetaMask account changes
ethereum.on('accountsChanged', (accounts) => {
getRef('eth_balance_wrapper').classList.add('hidden');
setMetaMaskStatus(accounts.length > 0);
});
ethereum.on('connect', (accounts) => {
setMetaMaskStatus(accounts.length > 0);
});
ethereum.on('disconnect', (accounts) => {
setMetaMaskStatus(false);
});
} else {
// No MetaMask detected, proceed with normal routing
routeNow();
}
// 3) Reveal UI only after were safe to render
document.body.classList.remove('hidden');
});
}); });
// Process pending notifications after a delay to ensure custom elements are ready // Process pending notifications after a delay to ensure custom elements are ready
setTimeout(() => { setTimeout(() => {
if (window._pendingNotifications && window._pendingNotifications.length > 0) { if (window._pendingNotifications && window._pendingNotifications.length > 0) {
const notificationDrawer = getRef("notification_drawer"); const notificationDrawer = getRef("notification_drawer");
if (notificationDrawer && typeof notificationDrawer.push === 'function') { if (notificationDrawer && typeof notificationDrawer.push === 'function') {
window._pendingNotifications.forEach(({ message, icon, options }) => { window._pendingNotifications.forEach(({ message, icon, options }) => {
notificationDrawer.push(message, { icon, ...options }); notificationDrawer.push(message, { icon, ...options });
}); });
window._pendingNotifications = []; window._pendingNotifications = [];
}
} }
}
}, 1000); // Give custom elements time to fully initialize }, 1000); // Give custom elements time to fully initialize
@ -665,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="ETH 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">
@ -777,6 +776,18 @@
return; return;
} }
// 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 an ETH 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 an ETH address or private key.', 'error');
return;
}
// Check if it's a valid Ethereum address // Check if it's a valid Ethereum address
if (ethOperator.isValidAddress(keyToConvert)) { if (ethOperator.isValidAddress(keyToConvert)) {
ethAddress = keyToConvert ethAddress = keyToConvert
@ -914,9 +925,9 @@
<div class="flex align-center space-between"> <div class="flex align-center space-between">
<h4>Transactions</h4> <h4>Transactions</h4>
<sm-chips id="tx_filter_chips" onchange=${(e) => { <sm-chips id="tx_filter_chips" onchange=${(e) => {
const selectedValue = e.detail?.value || e.target.value || 'all'; const selectedValue = e.detail?.value || e.target.value || 'all';
filterTransactions(selectedValue, transactions, ethAddress); filterTransactions(selectedValue, transactions, ethAddress);
}}> }}>
<sm-chip value="all" selected>All</sm-chip> <sm-chip value="all" selected>All</sm-chip>
<sm-chip value="sent">Sent</sm-chip> <sm-chip value="sent">Sent</sm-chip>
<sm-chip value="received">Received</sm-chip> <sm-chip value="received">Received</sm-chip>
@ -1086,13 +1097,13 @@
<div class="flex align-center space-between"> <div class="flex align-center space-between">
<h3>Transaction Details</h3> <h3>Transaction Details</h3>
<button class="button button--small" onclick=${() => { <button class="button button--small" onclick=${() => {
const url = new URL(window.location); const url = new URL(window.location);
url.searchParams.delete('tx'); url.searchParams.delete('tx');
window.history.pushState({}, '', url.pathname + url.search + url.hash); window.history.pushState({}, '', url.pathname + url.search + url.hash);
// Go back to address view // Go back to address view
handleUrlParams(); handleUrlParams();
}}> }}>
Back Back
</button> </button>
</div> </div>
@ -1628,10 +1639,10 @@
// ROUTE: Retrieve // ROUTE: Retrieve
router.addRoute('retrieve', (state) => { router.addRoute('retrieve', (state) => {
const container = getRef('page_container'); const container = getRef('page_container');
container.dataset.page = 'retrieve'; container.dataset.page = 'retrieve';
renderElem(container, html` renderElem(container, html`
<sm-form id="retrieve_form" style="width: min(32rem, 100%)"> <sm-form id="retrieve_form" style="width: min(32rem, 100%)">
<!-- Block 1 (like Sender block) --> <!-- Block 1 (like Sender block) -->
<fieldset class="flex flex-direction-column gap-0-5"> <fieldset class="flex flex-direction-column gap-0-5">
@ -1678,70 +1689,70 @@
} }
function retrieveAddress() { function retrieveAddress() {
const outEl = getRef('retrieve_result'); const outEl = getRef('retrieve_result');
const inpEl = getRef('retrieve_key_input'); const inpEl = getRef('retrieve_key_input');
let input = getRef('retrieve_key_input').value?.trim(); let input = getRef('retrieve_key_input').value?.trim();
let usedSecret = false; let usedSecret = false;
let wif = ''; let wif = '';
let ethPriv = ''; let ethPriv = '';
if (!input) { if (!input) {
notify('Please enter an ETH address, WIF, or 64-hex private key', 'error'); notify('Please enter an ETH address, WIF, or 64-hex private key', 'error');
renderElem(outEl, html``); renderElem(outEl, html``);
outEl.classList.add('hidden'); outEl.classList.add('hidden');
return; return;
} }
try { try {
// Loading hint // Loading hint
renderElem(outEl, html`<div class="muted">Resolving…</div>`); renderElem(outEl, html`<div class="muted">Resolving…</div>`);
outEl.classList.remove('hidden'); outEl.classList.remove('hidden');
// Detect ETH address (without private key) // Detect ETH address (without private key)
const isEthAddrRegex = /^0x[0-9a-fA-F]{40}$/; const isEthAddrRegex = /^0x[0-9a-fA-F]{40}$/;
const isEthAddress = (typeof ethOperator !== 'undefined' && ethOperator.isValidAddress?.(input)) const isEthAddress = (typeof ethOperator !== 'undefined' && ethOperator.isValidAddress?.(input))
|| isEthAddrRegex.test(input); || isEthAddrRegex.test(input);
if (isEthAddress) { if (isEthAddress) {
// We cannot derive BTC/FLO from an address alone (no private key) // We cannot derive BTC/FLO from an address alone (no private key)
const normalizedEth = input.toLowerCase(); const normalizedEth = input.toLowerCase();
renderElem(outEl, html` renderElem(outEl, html`
<div class="card grid gap-0-5"> <div class="card grid gap-0-5">
<span class="label">Resolved</span> <span class="label">Resolved</span>
<div><strong>ETH address</strong>: <sm-copy value="${normalizedEth}"></sm-copy></div> <div><strong>ETH address</strong>: <sm-copy value="${normalizedEth}"></sm-copy></div>
<div class="muted">BTC/FLO cannot be derived from an address without the private key.</div> <div class="muted">BTC/FLO cannot be derived from an address without the private key.</div>
</div> </div>
`); `);
return; return;
} }
// Normalize to WIF if the input is a raw 64-hex private key // Normalize to WIF if the input is a raw 64-hex private key
usedSecret = true; usedSecret = true;
let wif = input; let wif = input;
const is64Hex = /^[0-9a-fA-F]{64}$/.test(input); const is64Hex = /^[0-9a-fA-F]{64}$/.test(input);
if (is64Hex) { if (is64Hex) {
wif = coinjs.privkey2wif(input); // hex → WIF wif = coinjs.privkey2wif(input); // hex → WIF
} }
// From WIF, derive ETH private key (hex) // From WIF, derive ETH private key (hex)
const ethPriv = coinjs.wif2privkey(wif).privkey; const ethPriv = coinjs.wif2privkey(wif).privkey;
// ETH address from private key // ETH address from private key
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPriv); const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPriv);
// BTC bech32 from WIF // BTC bech32 from WIF
const btcBech32 = btcOperator.bech32Address(wif); const btcBech32 = btcOperator.bech32Address(wif);
//const btcPriv = btcOperator.convert.wif(wif); //const btcPriv = btcOperator.convert.wif(wif);
//const floPriv = btcOperator.convert.wif(wif, 0xa3); //const floPriv = btcOperator.convert.wif(wif, 0xa3);
// FLO from WIF // FLO from WIF
const floAddress = floCrypto.getFloID(wif); const floAddress = floCrypto.getFloID(wif);
// Render results // Render results
renderElem(outEl, html` renderElem(outEl, html`
<div class="card grid gap-1"> <div class="card grid gap-1">
@ -1755,29 +1766,29 @@
</div> </div>
`); `);
} catch (e) { } catch (e) {
notify('Could not retrieve from the provided value', 'error'); notify('Could not retrieve from the provided value', 'error');
renderElem(outEl, html``); renderElem(outEl, html``);
outEl.classList.add('hidden'); outEl.classList.add('hidden');
} }
finally { finally {
if (usedSecret && inpEl) { if (usedSecret && inpEl) {
const wipe = (s) => (typeof s === 'string' ? s.replace(/./g, '\0') : s); const wipe = (s) => (typeof s === 'string' ? s.replace(/./g, '\0') : s);
input = typeof input === 'string' && input.length ? (wipe(input), null) : input; input = typeof input === 'string' && input.length ? (wipe(input), null) : input;
wif = typeof wif === 'string' && wif.length ? (wipe(wif), null) : wif; wif = typeof wif === 'string' && wif.length ? (wipe(wif), null) : wif;
ethPriv = typeof ethPriv === 'string' && ethPriv.length ? (wipe(ethPriv), null) : ethPriv; ethPriv = typeof ethPriv === 'string' && ethPriv.length ? (wipe(ethPriv), null) : ethPriv;
const inner = inpEl.shadowRoot?.querySelector('input[part="input"]'); const inner = inpEl.shadowRoot?.querySelector('input[part="input"]');
if (inner) { if (inner) {
inner.value = ''; inner.value = '';
inner.dispatchEvent(new Event('input', { bubbles: true, composed: true })); inner.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
if (typeof handleRetrieveInput === 'function') { if (typeof handleRetrieveInput === 'function') {
handleRetrieveInput({ target: inpEl }); handleRetrieveInput({ target: inpEl });
} }
} }
} }
} }
} }
@ -1812,6 +1823,9 @@
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)
const btcBech32 = btcOperator.bech32Address(privKey);
// Convert FLO WIF to Bitcoin WIF format (L/K prefix)
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"> <li class="grid gap-0-5">
@ -1824,6 +1838,16 @@
<sm-copy value="${privKey}"></sm-copy> <sm-copy value="${privKey}"></sm-copy>
</div> </div>
</li> </li>
<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>Equivalent Ethereum Address</h5> <h5>Equivalent Ethereum Address</h5>