Workflow updating files of floethereum
This commit is contained in:
parent
139dba4cd9
commit
2278af46ed
@ -11,7 +11,7 @@
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
|
||||
rel="stylesheet">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body class="hidden">
|
||||
@ -92,66 +92,65 @@
|
||||
</button>
|
||||
<theme-toggle></theme-toggle>
|
||||
</header>
|
||||
<nav id="main_navbar">
|
||||
<ul>
|
||||
<li>
|
||||
<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"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<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" />
|
||||
<circle cx="16" cy="12" r="1.5" />
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Balance
|
||||
</span>
|
||||
<div class="nav-item__indicator"></div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/send">
|
||||
<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>
|
||||
<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">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Send
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/create">
|
||||
<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>
|
||||
<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">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Create
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/retrieve">
|
||||
<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="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Retrieve
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav id="main_navbar">
|
||||
<ul>
|
||||
<li>
|
||||
<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"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<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" />
|
||||
<circle cx="16" cy="12" r="1.5" />
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Balance
|
||||
</span>
|
||||
<div class="nav-item__indicator"></div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/send">
|
||||
<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>
|
||||
<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">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Send
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/create">
|
||||
<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>
|
||||
<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">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Create
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/retrieve">
|
||||
<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="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Retrieve
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div id="page_container"></div>
|
||||
</main>
|
||||
@ -178,7 +177,7 @@
|
||||
expirationDays: 60,
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<!-- ethers.js version 5.6 -->
|
||||
|
||||
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
||||
@ -259,7 +258,7 @@
|
||||
if (mode === 'error') {
|
||||
console.error(message)
|
||||
}
|
||||
|
||||
|
||||
// Ensure notification drawer element exists and is fully initialized
|
||||
const notificationDrawer = getRef("notification_drawer");
|
||||
if (!notificationDrawer || typeof notificationDrawer.push !== 'function') {
|
||||
@ -271,7 +270,7 @@
|
||||
window._pendingNotifications.push({ message, mode, options, icon });
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return notificationDrawer.push(message, { icon, ...options });
|
||||
}
|
||||
// displays a popup for asking permission. Use this instead of JS confirm
|
||||
@ -440,8 +439,8 @@
|
||||
else
|
||||
page = path
|
||||
this.state = { page, wildcards, lastPage: this.lastPage, params }
|
||||
|
||||
|
||||
|
||||
|
||||
if (queryString) {
|
||||
params = new URLSearchParams(queryString)
|
||||
this.state.params = Object.fromEntries(params)
|
||||
@ -460,37 +459,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
//Moving notification draw so that it does not overlap the menu item
|
||||
(function placeToasts() {
|
||||
const drawer = document.getElementById('notification_drawer');
|
||||
const panel = drawer?.shadowRoot?.querySelector('.notification-panel');
|
||||
if (!panel) return;
|
||||
//Moving notification draw so that it does not overlap the menu item
|
||||
(function placeToasts() {
|
||||
const drawer = document.getElementById('notification_drawer');
|
||||
const panel = drawer?.shadowRoot?.querySelector('.notification-panel');
|
||||
if (!panel) return;
|
||||
|
||||
const apply = () => {
|
||||
if (window.matchMedia('(min-width: 640px)').matches) {
|
||||
// Desktop: offset from left navbar
|
||||
Object.assign(panel.style, {
|
||||
left: 'calc(10rem + 1rem)',
|
||||
bottom: '1rem',
|
||||
top: 'auto',
|
||||
right: '1rem', // optional, lets long toasts wrap before content edge
|
||||
zIndex: '1000'
|
||||
});
|
||||
} else {
|
||||
// Mobile: keep top-right or top-left as you prefer
|
||||
Object.assign(panel.style, {
|
||||
left: '0.5rem',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
bottom: 'auto',
|
||||
zIndex: '1000'
|
||||
});
|
||||
}
|
||||
};
|
||||
const apply = () => {
|
||||
if (window.matchMedia('(min-width: 640px)').matches) {
|
||||
// Desktop: top-right corner
|
||||
Object.assign(panel.style, {
|
||||
left: 'auto',
|
||||
top: '1rem',
|
||||
right: '1rem',
|
||||
bottom: 'auto',
|
||||
zIndex: '1000'
|
||||
});
|
||||
} else {
|
||||
// Mobile: top-center
|
||||
Object.assign(panel.style, {
|
||||
left: '0.5rem',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
bottom: 'auto',
|
||||
zIndex: '1000'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
apply();
|
||||
window.addEventListener('resize', apply);
|
||||
})();
|
||||
apply();
|
||||
window.addEventListener('resize', apply);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<script>
|
||||
@ -562,87 +561,87 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Initialize IndexedDB for storing contact addresses
|
||||
let idbReady;
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
// Initialize the database before any routing or data reads
|
||||
idbReady = compactIDB.initDB('floEthereum', { contacts: {} })
|
||||
.then((res) => { console.log(res); })
|
||||
.catch((err) => { console.error(err); });
|
||||
// Initialize the database before any routing or data reads
|
||||
idbReady = compactIDB.initDB('floEthereum', { contacts: {} })
|
||||
.then((res) => { console.log(res); })
|
||||
.catch((err) => { console.error(err); });
|
||||
|
||||
// Set up event listeners and routing after database is ready
|
||||
idbReady.then(() => {
|
||||
const routeNow = () => router.routeTo(location.hash);
|
||||
// Set up event listeners and routing after database is ready
|
||||
idbReady.then(() => {
|
||||
const routeNow = () => router.routeTo(location.hash);
|
||||
|
||||
// Set up UI event listeners for copy notifications and ripple effects
|
||||
document.addEventListener('copy', () => notify('copied', 'success'));
|
||||
document.addEventListener('pointerdown', (e) => {
|
||||
const target = e.target.closest('button:not(:disabled), .interactive:not(:disabled)');
|
||||
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();
|
||||
// Set up UI event listeners for copy notifications and ripple effects
|
||||
document.addEventListener('copy', () => notify('copied', 'success'));
|
||||
document.addEventListener('pointerdown', (e) => {
|
||||
const target = e.target.closest('button:not(:disabled), .interactive:not(:disabled)');
|
||||
if (target) createRipple(e, target);
|
||||
});
|
||||
|
||||
// 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();
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
|
||||
// 3) Reveal UI only after we’re safe to render
|
||||
document.body.classList.remove('hidden');
|
||||
});
|
||||
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
|
||||
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 we’re safe to render
|
||||
document.body.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// Process pending notifications after a delay to ensure custom elements are ready
|
||||
setTimeout(() => {
|
||||
if (window._pendingNotifications && window._pendingNotifications.length > 0) {
|
||||
const notificationDrawer = getRef("notification_drawer");
|
||||
if (notificationDrawer && typeof notificationDrawer.push === 'function') {
|
||||
window._pendingNotifications.forEach(({ message, icon, options }) => {
|
||||
notificationDrawer.push(message, { icon, ...options });
|
||||
});
|
||||
window._pendingNotifications = [];
|
||||
if (window._pendingNotifications && window._pendingNotifications.length > 0) {
|
||||
const notificationDrawer = getRef("notification_drawer");
|
||||
if (notificationDrawer && typeof notificationDrawer.push === 'function') {
|
||||
window._pendingNotifications.forEach(({ message, icon, options }) => {
|
||||
notificationDrawer.push(message, { icon, ...options });
|
||||
});
|
||||
window._pendingNotifications = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000); // Give custom elements time to fully initialize
|
||||
|
||||
|
||||
|
||||
router.addRoute('404', () => {
|
||||
renderElem(getRef('page_container'), html`
|
||||
<h1>Page not found</h1>
|
||||
@ -665,7 +664,7 @@
|
||||
</h2>
|
||||
<sm-form oninvalid="handleInvalidSearch()">
|
||||
<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>
|
||||
<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">
|
||||
@ -689,7 +688,7 @@
|
||||
renderError('Please switch MetaMask to Ethereum Mainnet')
|
||||
}
|
||||
renderSearchedAddressList()
|
||||
|
||||
|
||||
// Handle URL parameters after page is rendered
|
||||
// Use setTimeout to ensure DOM is fully ready
|
||||
setTimeout(() => {
|
||||
@ -770,17 +769,29 @@
|
||||
function checkBalance(ethAddress, floAddress) {
|
||||
if (!ethAddress) {
|
||||
let keyToConvert = document.querySelector('#check_balance_input').value.trim()
|
||||
|
||||
|
||||
// Check if input is empty
|
||||
if (!keyToConvert) {
|
||||
notify('Please enter an Ethereum address, private key, or transaction hash', 'error');
|
||||
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
|
||||
if (ethOperator.isValidAddress(keyToConvert)) {
|
||||
ethAddress = keyToConvert
|
||||
}
|
||||
}
|
||||
// Check if it's a transaction hash (0x followed by 64 hex characters)
|
||||
else if (/^0x[0-9a-fA-F]{64}$/.test(keyToConvert)) {
|
||||
viewTransactionDetails(keyToConvert);
|
||||
@ -802,24 +813,24 @@
|
||||
}
|
||||
}
|
||||
if (!ethAddress) return
|
||||
|
||||
|
||||
// Reset pagination when checking new address
|
||||
currentPage = 1;
|
||||
currentAddress = ethAddress;
|
||||
currentFloAddress = floAddress;
|
||||
|
||||
|
||||
loadTransactionsPage(ethAddress, floAddress, currentPage);
|
||||
}
|
||||
|
||||
async function loadTransactionsPage(ethAddress, floAddress, page) {
|
||||
buttonLoader('check_balance_button', true);
|
||||
|
||||
|
||||
try {
|
||||
const results = await Promise.allSettled([
|
||||
ethOperator.getBalance(ethAddress),
|
||||
ethOperator.getTokenBalance(ethAddress, 'usdc'),
|
||||
ethOperator.getTokenBalance(ethAddress, 'usdt'),
|
||||
ethOperator.getTransactionHistory(ethAddress, {
|
||||
ethOperator.getTransactionHistory(ethAddress, {
|
||||
page: page,
|
||||
offset: TRANSACTIONS_PER_PAGE,
|
||||
sort: 'desc'
|
||||
@ -859,9 +870,9 @@
|
||||
console.error(error)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
renderBalanceAndTransactions(ethAddress, floAddress, etherBalance, usdcBalance, usdtBalance, transactions, page);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
notify(error.message || error, 'error');
|
||||
} finally {
|
||||
@ -877,7 +888,7 @@
|
||||
url.searchParams.delete('page');
|
||||
// Update browser URL without reloading the page
|
||||
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
||||
|
||||
|
||||
// Determine if pagination buttons should be enabled
|
||||
const hasNextPage = transactions.length >= TRANSACTIONS_PER_PAGE;
|
||||
const hasPrevPage = page > 1;
|
||||
@ -914,9 +925,9 @@
|
||||
<div class="flex align-center space-between">
|
||||
<h4>Transactions</h4>
|
||||
<sm-chips id="tx_filter_chips" onchange=${(e) => {
|
||||
const selectedValue = e.detail?.value || e.target.value || 'all';
|
||||
filterTransactions(selectedValue, transactions, ethAddress);
|
||||
}}>
|
||||
const selectedValue = e.detail?.value || e.target.value || 'all';
|
||||
filterTransactions(selectedValue, transactions, ethAddress);
|
||||
}}>
|
||||
<sm-chip value="all" selected>All</sm-chip>
|
||||
<sm-chip value="sent">Sent</sm-chip>
|
||||
<sm-chip value="received">Received</sm-chip>
|
||||
@ -944,7 +955,7 @@
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
|
||||
getRef('eth_balance_wrapper').classList.remove('hidden');
|
||||
getRef('eth_balance_wrapper').animate([
|
||||
{
|
||||
@ -1006,9 +1017,9 @@
|
||||
const amountPrefix = isReceived ? '+' : '-';
|
||||
const displayAddress = isReceived ? tx.from : tx.to;
|
||||
const directionText = isReceived ? 'Received from' : 'Sent to';
|
||||
|
||||
|
||||
// Arrow icons matching BTC wallet
|
||||
const icon = isReceived
|
||||
const icon = isReceived
|
||||
? 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="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`
|
||||
: svg`<svg class="icon sent" 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="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`;
|
||||
|
||||
@ -1048,22 +1059,22 @@
|
||||
async function viewTransactionDetails(txHash, preserveAddress = false) {
|
||||
try {
|
||||
buttonLoader('check_balance_button', true);
|
||||
|
||||
|
||||
// Update URL to show the transaction hash
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('tx', txHash);
|
||||
url.searchParams.delete('page');
|
||||
|
||||
|
||||
// Remove address from URL unless viewing from transaction history
|
||||
if (!preserveAddress) {
|
||||
url.searchParams.delete('address');
|
||||
}
|
||||
|
||||
|
||||
// Update browser URL without reloading the page
|
||||
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
||||
|
||||
|
||||
const txDetails = await ethOperator.getTransactionDetails(txHash);
|
||||
|
||||
|
||||
let formattedDate = 'Pending';
|
||||
if (txDetails.timestamp) {
|
||||
const date = new Date(txDetails.timestamp * 1000);
|
||||
@ -1086,13 +1097,13 @@
|
||||
<div class="flex align-center space-between">
|
||||
<h3>Transaction Details</h3>
|
||||
<button class="button button--small" onclick=${() => {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.delete('tx');
|
||||
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
||||
|
||||
// Go back to address view
|
||||
handleUrlParams();
|
||||
}}>
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.delete('tx');
|
||||
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
||||
|
||||
// Go back to address view
|
||||
handleUrlParams();
|
||||
}}>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
@ -1167,7 +1178,7 @@
|
||||
// Function to handle URL parameters and load appropriate data
|
||||
function handleUrlParams() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
// Check for transaction hash parameter
|
||||
const txHash = urlParams.get('tx');
|
||||
if (txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash)) {
|
||||
@ -1184,14 +1195,14 @@
|
||||
viewTransactionDetails(txHash);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check for address parameter
|
||||
const address = urlParams.get('address');
|
||||
if (address && ethOperator.isValidAddress(address)) {
|
||||
// Populate the input field - target both custom element and native input
|
||||
const searchInput = document.querySelector('#check_balance_input');
|
||||
const nativeInput = searchInput?.querySelector('input');
|
||||
|
||||
|
||||
if (nativeInput) {
|
||||
nativeInput.value = address;
|
||||
nativeInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
@ -1200,7 +1211,7 @@
|
||||
searchInput.value = address;
|
||||
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
|
||||
// Load the address (checkBalance handles its own loading state)
|
||||
checkBalance(address);
|
||||
} else {
|
||||
@ -1352,38 +1363,38 @@
|
||||
const receiver = getRef('send_tx_form').querySelector('.receiver-address').value.trim();
|
||||
const amount = getRef('send_tx_form').querySelector('.receiver-amount').value.trim();
|
||||
const asset = getRef('asset_selector').value;
|
||||
|
||||
|
||||
try {
|
||||
// First, get basic confirmation
|
||||
const initialConfirmation = await getConfirmation('Confirm transaction', {
|
||||
message: `Calculating gas fees for sending ${amount} ${asset.toUpperCase()} to ${receiver}...`,
|
||||
confirmText: 'Continue',
|
||||
});
|
||||
|
||||
|
||||
if (!initialConfirmation) return;
|
||||
|
||||
|
||||
// Show loading state while calculating gas
|
||||
buttonLoader('send_tx_button', true);
|
||||
|
||||
|
||||
// Get private key
|
||||
let privateKey = getRef('private_key_input').value.trim();
|
||||
if (/^[0-9a-fA-F]{64}$/.test(privateKey)) {
|
||||
privateKey = coinjs.privkey2wif(privateKey);
|
||||
}
|
||||
privateKey = coinjs.wif2privkey(privateKey).privkey;
|
||||
|
||||
|
||||
// Calculate gas fees
|
||||
let gasEstimate, feeData, estimatedGasFee, maxGasFee, totalCostETH;
|
||||
|
||||
|
||||
try {
|
||||
// Get provider for gas estimation
|
||||
const provider = ethOperator.getProvider(true);
|
||||
|
||||
|
||||
if (!provider) throw new Error('Provider not available');
|
||||
|
||||
|
||||
// Get fee data
|
||||
feeData = await provider.getFeeData();
|
||||
|
||||
|
||||
// Estimate gas limit
|
||||
if (asset === 'ether') {
|
||||
gasEstimate = await ethOperator.estimateGas({
|
||||
@ -1395,43 +1406,43 @@
|
||||
// For token transfers, estimate is typically higher
|
||||
gasEstimate = ethers.BigNumber.from('65000'); // Typical ERC20 transfer gas
|
||||
}
|
||||
|
||||
|
||||
// Calculate priority fee and max fee
|
||||
const priorityFee = feeData.maxPriorityFeePerGas || ethers.utils.parseUnits("1.5", "gwei");
|
||||
let maxFee = feeData.maxFeePerGas;
|
||||
|
||||
|
||||
if (!maxFee || maxFee.lt(priorityFee)) {
|
||||
const block = await provider.getBlock("latest");
|
||||
const baseFee = block.baseFeePerGas || ethers.utils.parseUnits("1", "gwei");
|
||||
maxFee = baseFee.mul(2).add(priorityFee);
|
||||
}
|
||||
|
||||
|
||||
const minMaxFee = priorityFee.mul(15).div(10);
|
||||
if (maxFee.lt(minMaxFee)) {
|
||||
maxFee = minMaxFee;
|
||||
}
|
||||
|
||||
|
||||
// Calculate estimated gas fee (using base fee + priority fee for estimation)
|
||||
const block = await provider.getBlock("latest");
|
||||
const baseFee = block.baseFeePerGas || ethers.utils.parseUnits("1", "gwei");
|
||||
const estimatedGasPrice = baseFee.add(priorityFee);
|
||||
estimatedGasFee = parseFloat(ethers.utils.formatEther(gasEstimate.mul(estimatedGasPrice)));
|
||||
|
||||
|
||||
// Calculate max possible gas fee
|
||||
maxGasFee = parseFloat(ethers.utils.formatEther(gasEstimate.mul(maxFee)));
|
||||
|
||||
|
||||
// Calculate total cost in ETH
|
||||
totalCostETH = asset === 'ether' ? (parseFloat(amount) + estimatedGasFee) : estimatedGasFee;
|
||||
|
||||
|
||||
} catch (gasError) {
|
||||
console.error('Gas estimation error:', gasError);
|
||||
buttonLoader('send_tx_button', false);
|
||||
notify('Failed to estimate gas fees. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
buttonLoader('send_tx_button', false);
|
||||
|
||||
|
||||
// Show detailed confirmation with gas fees
|
||||
const gasConfirmationPopup = html.node`
|
||||
<sm-popup id="gas_confirmation_popup">
|
||||
@ -1487,9 +1498,9 @@
|
||||
</div>
|
||||
</sm-popup>
|
||||
`;
|
||||
|
||||
|
||||
document.body.appendChild(gasConfirmationPopup);
|
||||
|
||||
|
||||
// Store transaction data for confirmation
|
||||
window.pendingTxData = {
|
||||
receiver,
|
||||
@ -1497,7 +1508,7 @@
|
||||
asset,
|
||||
privateKey
|
||||
};
|
||||
|
||||
|
||||
// Define close function
|
||||
window.closeGasConfirmation = () => {
|
||||
closePopup();
|
||||
@ -1508,19 +1519,19 @@
|
||||
delete window.confirmAndSend;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
|
||||
// Define confirm and send function
|
||||
window.confirmAndSend = async () => {
|
||||
closePopup();
|
||||
gasConfirmationPopup.remove();
|
||||
|
||||
|
||||
const { receiver, amount, asset, privateKey } = window.pendingTxData;
|
||||
delete window.pendingTxData;
|
||||
delete window.closeGasConfirmation;
|
||||
delete window.confirmAndSend;
|
||||
|
||||
|
||||
buttonLoader('send_tx_button', true);
|
||||
|
||||
|
||||
try {
|
||||
switch (asset) {
|
||||
case 'ether': {
|
||||
@ -1568,9 +1579,9 @@
|
||||
buttonLoader('send_tx_button', false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
openPopup('gas_confirmation_popup');
|
||||
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
notify(e.message || 'Transaction failed', 'error');
|
||||
@ -1625,13 +1636,13 @@
|
||||
}
|
||||
openPopup('transaction_result_popup')
|
||||
}
|
||||
|
||||
|
||||
// ROUTE: Retrieve
|
||||
router.addRoute('retrieve', (state) => {
|
||||
const container = getRef('page_container');
|
||||
container.dataset.page = 'retrieve';
|
||||
const container = getRef('page_container');
|
||||
container.dataset.page = 'retrieve';
|
||||
|
||||
renderElem(container, html`
|
||||
renderElem(container, html`
|
||||
<sm-form id="retrieve_form" style="width: min(32rem, 100%)">
|
||||
<!-- Block 1 (like Sender block) -->
|
||||
<fieldset class="flex flex-direction-column gap-0-5">
|
||||
@ -1678,70 +1689,70 @@
|
||||
}
|
||||
|
||||
function retrieveAddress() {
|
||||
const outEl = getRef('retrieve_result');
|
||||
const inpEl = getRef('retrieve_key_input');
|
||||
let input = getRef('retrieve_key_input').value?.trim();
|
||||
let usedSecret = false;
|
||||
const outEl = getRef('retrieve_result');
|
||||
const inpEl = getRef('retrieve_key_input');
|
||||
let input = getRef('retrieve_key_input').value?.trim();
|
||||
let usedSecret = false;
|
||||
let wif = '';
|
||||
let ethPriv = '';
|
||||
|
||||
if (!input) {
|
||||
notify('Please enter an ETH address, WIF, or 64-hex private key', 'error');
|
||||
renderElem(outEl, html``);
|
||||
outEl.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
if (!input) {
|
||||
notify('Please enter an ETH address, WIF, or 64-hex private key', 'error');
|
||||
renderElem(outEl, html``);
|
||||
outEl.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Loading hint
|
||||
renderElem(outEl, html`<div class="muted">Resolving…</div>`);
|
||||
outEl.classList.remove('hidden');
|
||||
try {
|
||||
// Loading hint
|
||||
renderElem(outEl, html`<div class="muted">Resolving…</div>`);
|
||||
outEl.classList.remove('hidden');
|
||||
|
||||
// Detect ETH address (without private key)
|
||||
const isEthAddrRegex = /^0x[0-9a-fA-F]{40}$/;
|
||||
const isEthAddress = (typeof ethOperator !== 'undefined' && ethOperator.isValidAddress?.(input))
|
||||
|| isEthAddrRegex.test(input);
|
||||
// Detect ETH address (without private key)
|
||||
const isEthAddrRegex = /^0x[0-9a-fA-F]{40}$/;
|
||||
const isEthAddress = (typeof ethOperator !== 'undefined' && ethOperator.isValidAddress?.(input))
|
||||
|| isEthAddrRegex.test(input);
|
||||
|
||||
if (isEthAddress) {
|
||||
// We cannot derive BTC/FLO from an address alone (no private key)
|
||||
const normalizedEth = input.toLowerCase();
|
||||
renderElem(outEl, html`
|
||||
if (isEthAddress) {
|
||||
// We cannot derive BTC/FLO from an address alone (no private key)
|
||||
const normalizedEth = input.toLowerCase();
|
||||
renderElem(outEl, html`
|
||||
<div class="card grid gap-0-5">
|
||||
<span class="label">Resolved</span>
|
||||
<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>
|
||||
`);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize to WIF if the input is a raw 64-hex private key
|
||||
usedSecret = true;
|
||||
// Normalize to WIF if the input is a raw 64-hex private key
|
||||
usedSecret = true;
|
||||
|
||||
let wif = input;
|
||||
const is64Hex = /^[0-9a-fA-F]{64}$/.test(input);
|
||||
if (is64Hex) {
|
||||
wif = coinjs.privkey2wif(input); // hex → WIF
|
||||
}
|
||||
let wif = input;
|
||||
const is64Hex = /^[0-9a-fA-F]{64}$/.test(input);
|
||||
if (is64Hex) {
|
||||
wif = coinjs.privkey2wif(input); // hex → WIF
|
||||
}
|
||||
|
||||
// From WIF, derive ETH private key (hex)
|
||||
const ethPriv = coinjs.wif2privkey(wif).privkey;
|
||||
// From WIF, derive ETH private key (hex)
|
||||
const ethPriv = coinjs.wif2privkey(wif).privkey;
|
||||
|
||||
|
||||
// ETH address from private key
|
||||
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPriv);
|
||||
// ETH address from private key
|
||||
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPriv);
|
||||
|
||||
// BTC bech32 from WIF
|
||||
const btcBech32 = btcOperator.bech32Address(wif);
|
||||
//const btcPriv = btcOperator.convert.wif(wif);
|
||||
//const floPriv = btcOperator.convert.wif(wif, 0xa3);
|
||||
// BTC bech32 from WIF
|
||||
const btcBech32 = btcOperator.bech32Address(wif);
|
||||
//const btcPriv = btcOperator.convert.wif(wif);
|
||||
//const floPriv = btcOperator.convert.wif(wif, 0xa3);
|
||||
|
||||
// FLO from WIF
|
||||
const floAddress = floCrypto.getFloID(wif);
|
||||
// FLO from WIF
|
||||
const floAddress = floCrypto.getFloID(wif);
|
||||
|
||||
|
||||
// Render results
|
||||
renderElem(outEl, html`
|
||||
// Render results
|
||||
renderElem(outEl, html`
|
||||
<div class="card grid gap-1">
|
||||
|
||||
|
||||
@ -1755,29 +1766,29 @@
|
||||
</div>
|
||||
`);
|
||||
|
||||
} catch (e) {
|
||||
notify('Could not retrieve from the provided value', 'error');
|
||||
renderElem(outEl, html``);
|
||||
outEl.classList.add('hidden');
|
||||
}
|
||||
} catch (e) {
|
||||
notify('Could not retrieve from the provided value', 'error');
|
||||
renderElem(outEl, html``);
|
||||
outEl.classList.add('hidden');
|
||||
}
|
||||
|
||||
finally {
|
||||
if (usedSecret && inpEl) {
|
||||
const wipe = (s) => (typeof s === 'string' ? s.replace(/./g, '\0') : s);
|
||||
input = typeof input === 'string' && input.length ? (wipe(input), null) : input;
|
||||
wif = typeof wif === 'string' && wif.length ? (wipe(wif), null) : wif;
|
||||
ethPriv = typeof ethPriv === 'string' && ethPriv.length ? (wipe(ethPriv), null) : ethPriv;
|
||||
finally {
|
||||
if (usedSecret && inpEl) {
|
||||
const wipe = (s) => (typeof s === 'string' ? s.replace(/./g, '\0') : s);
|
||||
input = typeof input === 'string' && input.length ? (wipe(input), null) : input;
|
||||
wif = typeof wif === 'string' && wif.length ? (wipe(wif), null) : wif;
|
||||
ethPriv = typeof ethPriv === 'string' && ethPriv.length ? (wipe(ethPriv), null) : ethPriv;
|
||||
|
||||
const inner = inpEl.shadowRoot?.querySelector('input[part="input"]');
|
||||
if (inner) {
|
||||
inner.value = '';
|
||||
inner.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
||||
if (typeof handleRetrieveInput === 'function') {
|
||||
const inner = inpEl.shadowRoot?.querySelector('input[part="input"]');
|
||||
if (inner) {
|
||||
inner.value = '';
|
||||
inner.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
||||
if (typeof handleRetrieveInput === 'function') {
|
||||
handleRetrieveInput({ target: inpEl });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1812,6 +1823,9 @@
|
||||
const { floID, privKey } = floCrypto.generateNewID();
|
||||
const ethPrivateKey = coinjs.wif2privkey(privKey).privkey;
|
||||
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`
|
||||
<ul id="generated_addresses" class="grid gap-1-5">
|
||||
<li class="grid gap-0-5">
|
||||
@ -1824,6 +1838,16 @@
|
||||
<sm-copy value="${privKey}"></sm-copy>
|
||||
</div>
|
||||
</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">
|
||||
<div>
|
||||
<h5>Equivalent Ethereum Address</h5>
|
||||
@ -1842,4 +1866,4 @@
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user