Add searched address database functionality and UI integration

- Implemented SearchedAddressDB class for managing searched addresses using IndexedDB.
- Added methods for saving, retrieving, deleting, and clearing searched addresses.
- Integrated searched addresses history into the main UI with display and interaction features.
- Updated styles for searched addresses section to enhance user experience.
- Modified existing functions to save searched addresses with source information when translating from other blockchains.
This commit is contained in:
void-57 2025-09-29 03:39:08 +05:30
parent 0eb12a71d5
commit 62c0a7aaf2
3 changed files with 1028 additions and 274 deletions

124
doge/dogeSearchDB.js Normal file
View File

@ -0,0 +1,124 @@
class SearchedAddressDB {
constructor() {
this.dbName = "DogeWalletDB";
this.version = 1;
this.storeName = "searchedAddresses";
this.db = null;
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.storeName)) {
const store = db.createObjectStore(this.storeName, {
keyPath: "address",
});
store.createIndex("timestamp", "timestamp", { unique: false });
}
};
});
}
async saveSearchedAddress(
address,
balance,
timestamp = Date.now(),
sourceInfo = null
) {
if (!this.db) await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], "readwrite");
const store = transaction.objectStore(this.storeName);
// First, check if this address already exists
const getRequest = store.get(address);
getRequest.onsuccess = () => {
const existingRecord = getRequest.result;
let finalSourceInfo = sourceInfo;
// If record exists and has sourceInfo, preserve it unless we're providing new sourceInfo
if (existingRecord && existingRecord.sourceInfo && !sourceInfo) {
finalSourceInfo = existingRecord.sourceInfo;
}
// If existing record has sourceInfo and new one doesn't, keep the existing one
else if (
existingRecord &&
existingRecord.sourceInfo &&
sourceInfo === null
) {
finalSourceInfo = existingRecord.sourceInfo;
}
const data = {
address, // This will be the DOGE address
balance,
timestamp,
formattedBalance: `${balance} DOGE`,
sourceInfo: finalSourceInfo, // Contains original blockchain info if translated from another chain
};
const putRequest = store.put(data);
putRequest.onsuccess = () => resolve();
putRequest.onerror = () => reject(putRequest.error);
};
getRequest.onerror = () => reject(getRequest.error);
});
}
async getSearchedAddresses() {
if (!this.db) await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], "readonly");
const store = transaction.objectStore(this.storeName);
const index = store.index("timestamp");
const request = index.getAll();
request.onsuccess = () => {
const results = request.result.sort(
(a, b) => b.timestamp - a.timestamp
);
resolve(results);
};
request.onerror = () => reject(request.error);
});
}
async deleteSearchedAddress(address) {
if (!this.db) await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], "readwrite");
const store = transaction.objectStore(this.storeName);
const request = store.delete(address);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async clearAllSearchedAddresses() {
if (!this.db) await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], "readwrite");
const store = transaction.objectStore(this.storeName);
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}

View File

@ -26,6 +26,7 @@
<script src="doge/lib.dogecoin.js"></script> <script src="doge/lib.dogecoin.js"></script>
<script src="doge/dogeCrypto.js"></script> <script src="doge/dogeCrypto.js"></script>
<script src="doge/dogeBlockchainAPI.js"></script> <script src="doge/dogeBlockchainAPI.js"></script>
<script src="doge/dogeSearchDB.js"></script>
</head> </head>
<body> <body>
<!-- Confirmation Popup --> <!-- Confirmation Popup -->
@ -344,14 +345,14 @@
<div id="balanceHistorySearch"> <div id="balanceHistorySearch">
<div class="form-group"> <div class="form-group">
<label <label
><i class="fas fa-map-marker-alt"></i> Dogecoin Address / ><i class="fas fa-map-marker-alt"></i> DOGE/BTC/FLO/LTC
Private Key:</label address:</label
> >
<div class="input-with-actions"> <div class="input-with-actions">
<input <input
id="transactionAddr" id="transactionAddr"
class="form-input" class="form-input"
placeholder="Enter DOGE address or private key (DOGE/BTC/FLO/LTC)" placeholder="Enter DOGE/BTC/FLO/LTC address"
/> />
<button <button
type="button" type="button"
@ -362,10 +363,6 @@
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
</div> </div>
<small class="form-text"
>Enter an address, private key (DOGE/BTC/FLO/LTC) to check
balance</small
>
</div> </div>
<button <button
class="btn btn-primary btn-block" class="btn btn-primary btn-block"
@ -511,6 +508,27 @@
<!-- Transaction Details Results --> <!-- Transaction Details Results -->
<div id="txOutput" class="transaction-result"></div> <div id="txOutput" class="transaction-result"></div>
<!-- Searched Addresses History -->
<div
id="searchedAddressesContainer"
class="card searched-addresses-card"
style="display: none"
>
<div class="searched-addresses-header">
<h3><i class="fas fa-history"></i> Recent Addresses</h3>
<button
onclick="clearAllSearchedAddresses()"
class="btn-clear-all"
title="Clear all"
>
<i class="fas fa-trash"></i> Clear All
</button>
</div>
<div class="searched-addresses-list" id="searchedAddressesList">
<!-- Searched addresses will be displayed here -->
</div>
</div>
</div> </div>
<div id="historyPage" class="page hidden"></div> <div id="historyPage" class="page hidden"></div>
@ -662,6 +680,9 @@
<div id="notification_drawer" class="notification-drawer"></div> <div id="notification_drawer" class="notification-drawer"></div>
<script> <script>
// Initialize the searched address database
const searchedAddressDB = new SearchedAddressDB();
let currentTxOffset = 0; let currentTxOffset = 0;
let txPerPage = 10; let txPerPage = 10;
let totalTxCount = 0; let totalTxCount = 0;
@ -1378,33 +1399,93 @@
}); });
} }
// Transaction History & Balance (Combined) function getBlockchainType(address) {
if (address.startsWith("D")) return "DOGE";
if (address.startsWith("F")) return "FLO";
if (address.startsWith("L")) return "LTC";
if (
address.startsWith("bc1") ||
address.startsWith("1") ||
address.startsWith("3")
)
return "BTC";
return "UNKNOWN";
}
function loadTransactions() { function loadTransactions() {
const input = document.getElementById("transactionAddr").value.trim(); const input = document.getElementById("transactionAddr").value.trim();
if (!input) { if (!input) {
notify("Please enter a Dogecoin address or private key", "error"); notify("Please enter a blockchain address", "error");
return; return;
} }
// Determine if the input is an address or a private key // Determine if the input is a valid address
let address = input; let address = input;
let isDogeAddress = input.startsWith("D");
let isOtherSupportedAddress =
input.startsWith("F") ||
input.startsWith("L") ||
input.startsWith("bc1") ||
input.startsWith("1") ||
input.startsWith("3");
// Check if input might be a private key (basic validation) // Check if it's potentially a private key (which we don't want to accept)
if (input.length >= 40 && !input.startsWith("D")) { if (
try { input.length >= 40 &&
// Attempt to derive address from private key (input.startsWith("5") ||
address = dogeCrypto.generateMultiChain(input).DOGE.address; input.startsWith("6") ||
notify("Using address derived from private key", "success"); input.startsWith("K") ||
} catch (error) { input.startsWith("L") ||
console.error( input.startsWith("Q") ||
"[ERROR] Failed to derive address from input:", input.startsWith("R") ||
error input.startsWith("T"))
); ) {
} document.getElementById("txList").innerHTML = createErrorUI(
"Invalid Input Type",
"Private keys are not accepted in the balance & history search. Please enter a valid blockchain address (DOGE, FLO, BTC, or LTC)."
);
notify("Private keys not allowed in this search", "error");
return;
}
// Check if address is a valid format
if (!isDogeAddress && !isOtherSupportedAddress) {
document.getElementById("txList").innerHTML = createErrorUI(
"Invalid Address Format",
"Please enter a valid blockchain address (DOGE, FLO, BTC, or LTC)."
);
notify("Invalid address format", "error");
return;
} }
setButtonLoading("loadTransactions", true); setButtonLoading("loadTransactions", true);
if (!isDogeAddress && isOtherSupportedAddress) {
try {
notify("Translating address to DOGE equivalent...", "info");
const translatedAddresses = dogeCrypto.translateAddress(input);
address = translatedAddresses.DOGE;
notify("Address translated: " + address, "success");
} catch (error) {
console.error("Translation error:", error);
document.getElementById("balanceSection").style.display = "none";
document.getElementById("transactionSection").style.display =
"none";
document.getElementById("txList").innerHTML = createErrorUI(
"Address Translation Failed",
`Error: ${
error.message ||
"Unable to translate address to DOGE equivalent"
}`,
"loadTransactions()"
);
notify("Failed to translate address: " + error.message, "error");
setButtonLoading("loadTransactions", false);
return;
}
}
// Reset pagination // Reset pagination
currentTxOffset = 0; currentTxOffset = 0;
currentTxAddress = address; currentTxAddress = address;
@ -1424,10 +1505,36 @@
// Set balance values // Set balance values
document.getElementById("balanceValue").textContent = document.getElementById("balanceValue").textContent =
balance.toFixed(8); balance.toFixed(8);
document.getElementById("displayedAddress").textContent = address;
const input = document
.getElementById("transactionAddr")
.value.trim();
if (input !== address) {
document.getElementById(
"displayedAddress"
).innerHTML = `${address}<br><small class="translated-from">translated from ${input}</small>`;
// Save to search history with source info (translated address)
searchedAddressDB.saveSearchedAddress(
address,
balance,
Date.now(),
{
originalAddress: input,
blockchain: getBlockchainType(input),
}
);
} else {
document.getElementById("displayedAddress").textContent = address;
// Save to search history
searchedAddressDB.saveSearchedAddress(address, balance);
}
document.getElementById("balanceSection").style.display = "block"; document.getElementById("balanceSection").style.display = "block";
updateSearchedAddressesList();
fetchTransactionsWithPagination(); fetchTransactionsWithPagination();
document document
@ -1476,8 +1583,6 @@
// Make sure the transaction section is visible // Make sure the transaction section is visible
document.getElementById("transactionSection").style.display = "block"; document.getElementById("transactionSection").style.display = "block";
document.getElementById("paginationInfo").innerText = ""; document.getElementById("paginationInfo").innerText = "";
dogeBlockchainAPI dogeBlockchainAPI
@ -1703,6 +1808,232 @@
document.body.style.overflow = "auto"; document.body.style.overflow = "auto";
} }
async function updateSearchedAddressesList() {
try {
const searchedAddresses =
await searchedAddressDB.getSearchedAddresses();
displaySearchedAddresses(searchedAddresses);
} catch (error) {
console.error("Error loading searched addresses:", error);
}
}
function displaySearchedAddresses(addresses) {
const container = document.getElementById("searchedAddressesContainer");
const list = document.getElementById("searchedAddressesList");
if (!container || !list) return;
if (addresses.length === 0) {
container.style.display = "none";
return;
}
container.style.display = "block";
let html = "";
addresses.forEach((addr, index) => {
// Check if this was translated from another blockchain
const hasSourceInfo =
addr.sourceInfo && addr.sourceInfo.originalAddress !== addr.address;
html += `
<div class="searched-address-item ${
hasSourceInfo ? "has-source-info" : ""
}" data-index="${index}" data-current-type="${
hasSourceInfo ? addr.sourceInfo.blockchain.toLowerCase() : "doge"
}">
${
hasSourceInfo
? `
<div class="address-toggle-section">
<div class="address-toggle-group">
<button onclick="toggleAddressType(${index}, '${addr.sourceInfo.blockchain.toLowerCase()}')"
class="btn-toggle-address active"
data-type="${addr.sourceInfo.blockchain.toLowerCase()}"
title="Show ${addr.sourceInfo.blockchain} Address">
${addr.sourceInfo.blockchain}
</button>
<button onclick="toggleAddressType(${index}, 'doge')"
class="btn-toggle-address"
data-type="doge"
title="Show DOGE Address">
DOGE
</button>
</div>
</div>
<div class="address-content-wrapper">
<div class="address-info">
<div class="address-display">
<div class="address-text" id="address-display-${index}" title="${
addr.sourceInfo.originalAddress
}">
${addr.sourceInfo.originalAddress}
</div>
</div>
</div>
<div class="address-actions">
<button onclick="copyCurrentAddress(${index})" class="btn-copy-current" title="Copy Selected Address">
<i class="fas fa-copy"></i>
</button>
<button onclick="deleteSearchedAddress('${
addr.address
}')" class="btn-delete" title="Delete">
<i class="fas fa-trash"></i>
</button>
<button onclick="recheckBalance('${
addr.address
}')" class="btn-check" title="Check balance">
<i class="fas fa-search"></i>
</button>
</div>
</div>
`
: `
<div class="address-info">
<div class="address-display">
<div class="address-text" id="address-display-${index}" title="${addr.address}">
${addr.address}
</div>
</div>
</div>
<div class="address-actions">
<button onclick="copyAddressToClipboard('${addr.address}')" class="btn-copy" title="Copy DOGE Address">
<i class="fas fa-copy"></i>
</button>
<button onclick="deleteSearchedAddress('${addr.address}')" class="btn-delete" title="Delete">
<i class="fas fa-trash"></i>
</button>
<button onclick="recheckBalance('${addr.address}')" class="btn-check" title="Check balance">
<i class="fas fa-search"></i>
</button>
</div>
`
}
</div>
`;
});
list.innerHTML = html;
}
// toggle between address types in searched addresses
async function toggleAddressType(addressIndex, type) {
try {
const addresses = await searchedAddressDB.getSearchedAddresses();
if (!addresses[addressIndex]) return;
const addressItem = addresses[addressIndex];
const container = document.querySelector(
`[data-index="${addressIndex}"]`
);
if (!container) return;
const toggleButtons = container.querySelectorAll(
".btn-toggle-address"
);
toggleButtons.forEach((btn) => btn.classList.remove("active"));
const activeButton = container.querySelector(`[data-type="${type}"]`);
if (activeButton) {
activeButton.classList.add("active");
}
container.setAttribute("data-current-type", type);
const addressDisplay = container.querySelector(
`#address-display-${addressIndex}`
);
if (addressDisplay) {
if (type === "doge") {
// Show DOGE address
addressDisplay.textContent = addressItem.address;
addressDisplay.title = addressItem.address;
} else {
// Show original blockchain address (FLO/BTC/LTC)
const originalAddress =
addressItem.sourceInfo?.originalAddress || addressItem.address;
addressDisplay.textContent = originalAddress;
addressDisplay.title = originalAddress;
}
}
} catch (error) {
console.error("Error toggling address type:", error);
}
}
async function copyCurrentAddress(addressIndex) {
try {
const addresses = await searchedAddressDB.getSearchedAddresses();
if (!addresses[addressIndex]) return;
const addressItem = addresses[addressIndex];
const container = document.querySelector(
`[data-index="${addressIndex}"]`
);
if (!container) return;
const currentType =
container.getAttribute("data-current-type") || "doge";
let addressToCopy;
let addressLabel;
if (currentType === "doge") {
addressToCopy = addressItem.address;
addressLabel = "DOGE address";
} else {
addressToCopy =
addressItem.sourceInfo?.originalAddress || addressItem.address;
addressLabel = `${
addressItem.sourceInfo?.blockchain || "Original"
} address`;
}
await copyAddressToClipboard(addressToCopy, addressLabel);
} catch (error) {
console.error("Error copying current address:", error);
notify("Failed to copy address", "error");
}
}
async function deleteSearchedAddress(address) {
try {
await searchedAddressDB.deleteSearchedAddress(address);
await updateSearchedAddressesList();
notify("Address removed from history", "success");
} catch (error) {
console.error("Error deleting searched address:", error);
notify("Failed to remove address", "error");
}
}
async function clearAllSearchedAddresses() {
try {
await searchedAddressDB.clearAllSearchedAddresses();
await updateSearchedAddressesList();
notify("All searched addresses cleared", "success");
} catch (error) {
console.error("Error clearing searched addresses:", error);
notify("Failed to clear addresses", "error");
}
}
async function copyAddressToClipboard(address, label = "Address") {
try {
await navigator.clipboard.writeText(address);
notify(`${label} copied to clipboard`, "success");
} catch (error) {
console.error("Error copying to clipboard:", error);
notify("Failed to copy address", "error");
}
}
async function recheckBalance(address) {
document.getElementById("transactionAddr").value = address;
loadTransactions();
}
// Send Dogecoin using RPC method // Send Dogecoin using RPC method
function sendDogeRPC() { function sendDogeRPC() {
let privateKey = document.getElementById("privateKey").value.trim(); let privateKey = document.getElementById("privateKey").value.trim();
@ -1742,7 +2073,6 @@
function () { function () {
setButtonLoading("sendBtn", true); setButtonLoading("sendBtn", true);
dogeBlockchainAPI dogeBlockchainAPI
.sendDogecoinRPC( .sendDogecoinRPC(
senderAddress, senderAddress,
@ -1937,7 +2267,19 @@
} }
function shareAddress() { function shareAddress() {
const address = document.getElementById("displayedAddress").textContent; const addressElement = document.getElementById("displayedAddress");
let address;
if (
addressElement.firstChild &&
addressElement.firstChild.nodeType === Node.TEXT_NODE
) {
address = addressElement.firstChild.textContent.trim();
} else {
const fullText = addressElement.textContent;
address = fullText.split("translated from")[0].trim();
}
if (!address) { if (!address) {
notify("No address to share", "error"); notify("No address to share", "error");
return; return;
@ -2412,6 +2754,8 @@
.appendChild(txOutputElement); .appendChild(txOutputElement);
} }
updateSearchedAddressesList();
initializeTheme(); initializeTheme();
// Show loading screen // Show loading screen
@ -2439,8 +2783,6 @@
loadingScreen.style.opacity = "0"; loadingScreen.style.opacity = "0";
setTimeout(() => { setTimeout(() => {
loadingScreen.remove(); loadingScreen.remove();
notify("Welcome to RanchiMall Dogecoin Wallet", "success");
handleSharedLinks(); handleSharedLinks();
if (!window.location.hash) { if (!window.location.hash) {

740
style.css
View File

@ -1712,6 +1712,16 @@ h4 {
width: calc(100% - 2rem); width: calc(100% - 2rem);
} }
.translated-from {
font-size: 0.8em;
opacity: 0.7;
font-style: italic;
display: block;
margin-top: 0.25rem;
text-align: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
@media (max-width: 480px) { @media (max-width: 480px) {
.address-text { .address-text {
font-size: 0.75rem; font-size: 0.75rem;
@ -2194,7 +2204,6 @@ h4 {
} }
} }
@media (max-width: 380px) { @media (max-width: 380px) {
.filter-btn { .filter-btn {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
@ -2552,17 +2561,14 @@ h4 {
margin-left: 0.5rem; margin-left: 0.5rem;
} }
#txOutput { #txOutput {
overflow: visible !important; overflow: visible !important;
} }
.tx-detail-row { .tx-detail-row {
padding: 0.75rem 0; padding: 0.75rem 0;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.tx-detail-value { .tx-detail-value {
overflow-x: auto; overflow-x: auto;
@ -2581,7 +2587,6 @@ h4 {
} }
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.tx-detail-row { .tx-detail-row {
flex-direction: column; flex-direction: column;
@ -2606,7 +2611,6 @@ h4 {
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3); box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
} }
[data-theme="dark"] .notification.success { [data-theme="dark"] .notification.success {
background-color: var(--success-color, #10b981); background-color: var(--success-color, #10b981);
color: white; color: white;
@ -2625,7 +2629,6 @@ h4 {
border-radius: 0 0 0.5rem 0.5rem; border-radius: 0 0 0.5rem 0.5rem;
} }
.tx-io-address { .tx-io-address {
display: flex; display: flex;
align-items: center; align-items: center;
@ -2654,7 +2657,6 @@ h4 {
transform: translateY(0); transform: translateY(0);
} }
.tx-detail-value { .tx-detail-value {
display: flex; display: flex;
align-items: center; align-items: center;
@ -2664,7 +2666,6 @@ h4 {
position: relative; position: relative;
} }
.transaction-result { .transaction-result {
margin-top: 1rem; margin-top: 1rem;
overflow: visible; overflow: visible;
@ -2684,7 +2685,6 @@ h4 {
overflow: visible; overflow: visible;
} }
#balanceHistoryResults, #balanceHistoryResults,
#txOutput { #txOutput {
transition: opacity 0.3s ease, display 0.3s ease; transition: opacity 0.3s ease, display 0.3s ease;
@ -3068,10 +3068,10 @@ h4 {
font-weight: 600; font-weight: 600;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.tx-detail-value { .tx-detail-value {
margin-left: 0; margin-left: 0;
} }
} }
.transaction-error h3 { .transaction-error h3 {
@ -3156,245 +3156,533 @@ h4 {
} }
} }
/* Transaction Details Card */ /* Transaction Details Card */
.tx-details-card { .tx-details-card {
background: rgba(255, 255, 255, 0.05); background: rgba(255, 255, 255, 0.05);
border-radius: 12px; border-radius: 12px;
padding: 0; padding: 0;
margin: 1rem 0 1.5rem; margin: 1rem 0 1.5rem;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
} }
.tx-detail-item {
display: flex;
flex-direction: column;
padding: 0.75rem 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
align-items: flex-start;
text-align: left;
}
.tx-detail-item:last-child {
border-bottom: none;
}
.tx-detail-label {
font-weight: 500;
color: #4794ff;
font-size: 0.75rem;
text-transform: uppercase;
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
width: 100%;
letter-spacing: 0.02em;
}
.tx-detail-label i {
color: #4794ff;
font-size: 0.875rem;
width: 18px;
text-align: center;
}
.tx-detail-value {
color: var(--text-color);
font-weight: 400;
word-break: break-all;
font-family: "Monaco", "Menlo", "Ubuntu Mono", "Courier New", monospace;
padding: 0;
font-size: 0.875rem;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-left: 0;
}
.tx-detail-value span {
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
display: block;
}
.tx-detail-item:last-child .tx-detail-value span {
white-space: nowrap;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.copy-small {
background: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: 0.25rem;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: auto;
transition: all 0.2s ease;
flex-shrink: 0;
width: 24px;
height: 24px;
}
.copy-small:hover {
color: var(--primary-color);
background-color: rgba(var(--primary-color-rgb), 0.1);
}
.copy-small:active {
transform: scale(0.95);
}
.view-on-chain {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.tx-actions {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 0.75rem;
width: 100%;
}
.tx-actions .btn {
width: 100%;
transition: all 0.2s ease;
padding: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
border-radius: 0.25rem;
}
.tx-actions .btn:hover {
opacity: 0.9;
}
.tx-actions .btn-primary {
background: #4794ff;
border: none;
color: white;
font-weight: 500;
}
.tx-actions .btn-secondary {
background: transparent;
color: var(--text-color);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.view-on-chain {
margin-bottom: 0.75rem;
}
.transaction-error .tx-actions .btn-primary {
background: var(--danger-color);
border-color: var(--danger-color);
}
.transaction-error .tx-actions .btn-primary:hover {
background: rgba(var(--error-color-rgb), 0.9);
border-color: var(--danger-color);
}
.transaction-error .error-details {
background: rgba(var(--error-color-rgb), 0.05);
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid rgba(var(--error-color-rgb), 0.2);
margin: 1rem 0;
color: var(--danger-color);
font-family: monospace;
font-size: 0.875rem;
word-break: break-all;
text-align: left;
max-height: 200px;
overflow-y: auto;
}
@media (max-width: 768px) {
.tx-detail-item { .tx-detail-item {
display: flex;
flex-direction: column; flex-direction: column;
padding: 0.75rem 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
align-items: flex-start; align-items: flex-start;
text-align: left;
}
.tx-detail-item:last-child {
border-bottom: none;
} }
.tx-detail-label { .tx-detail-label {
font-weight: 500;
color: #4794ff;
font-size: 0.75rem;
text-transform: uppercase;
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
width: 100%; min-width: auto;
letter-spacing: 0.02em;
}
.tx-detail-label i {
color: #4794ff;
font-size: 0.875rem;
width: 18px;
text-align: center;
} }
.tx-detail-value { .tx-detail-value {
color: var(--text-color);
font-weight: 400;
word-break: break-all;
font-family: "Monaco", "Menlo", "Ubuntu Mono", "Courier New", monospace;
padding: 0;
font-size: 0.875rem;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-left: 0; margin-left: 0;
} }
.tx-detail-value span { .transaction-success h3,
overflow: hidden; .transaction-error h3 {
text-overflow: ellipsis; font-size: 1.25rem;
flex: 1; }
display: block; }
@media (max-width: 480px) {
.transaction-success,
.transaction-error {
padding: 1rem;
} }
.tx-detail-item:last-child .tx-detail-value span { .tx-details-card {
white-space: nowrap; padding: 0;
max-width: 100%; margin: 0.75rem 0;
overflow: hidden;
text-overflow: ellipsis;
}
.copy-small {
background: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: 0.25rem;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: auto;
transition: all 0.2s ease;
flex-shrink: 0;
width: 24px;
height: 24px;
}
.copy-small:hover {
color: var(--primary-color);
background-color: rgba(var(--primary-color-rgb), 0.1);
}
.copy-small:active {
transform: scale(0.95);
}
.view-on-chain {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 0.875rem;
} }
.tx-actions { .tx-actions {
display: flex; gap: 0.5rem;
flex-direction: column; }
gap: 0.75rem;
margin-top: 0.75rem; .checkmark-circle,
width: 100%; .error-circle {
width: 3.5rem;
height: 3.5rem;
}
.checkmark,
.error-icon {
font-size: 1.25rem;
}
.tx-detail-value {
font-size: 0.75rem;
}
.tx-detail-item {
padding: 0.5rem 0.75rem;
} }
.tx-actions .btn { .tx-actions .btn {
padding: 0.625rem;
}
.searched-addresses-header h3 {
font-size: 0.9rem;
}
.btn-clear-all {
font-size: 0.75rem;
padding: 0.2rem 0.4rem;
}
.address-actions {
flex-direction: row;
flex-wrap: wrap;
gap: 0.25rem;
}
.searched-address-item {
padding: 0.4rem;
}
.btn-copy,
.btn-copy-current,
.btn-delete,
.btn-check {
padding: 0.15rem 0.3rem;
font-size: 0.65rem;
}
.address-display {
font-size: 0.7rem;
}
.address-toggle-group {
gap: 0.25rem;
}
.btn-toggle-address {
font-size: 0.65rem;
padding: 0.1rem 0.25rem;
}
} /* Transaction list */
.transaction-list {
gap: 0.5rem;
}
.hash-value,
.address-value {
font-size: 0.8rem;
word-break: break-all;
}
/* Searched Addresses Styles */
.searched-addresses-card {
margin-top: 2rem;
border-radius: 12px;
overflow: hidden;
}
.searched-addresses-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
.searched-addresses-header h3 {
margin: 0;
font-size: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.searched-addresses-header h3 i {
color: var(--primary-color);
}
.btn-clear-all {
background: none;
border: none;
cursor: pointer;
color: var(--danger-color);
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.875rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
.btn-clear-all:hover {
background-color: rgba(var(--danger-color-rgb), 0.1);
}
.searched-addresses-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem;
max-height: 300px;
overflow-y: auto;
width: 100%;
}
.searched-address-item {
display: flex;
flex-direction: column;
background: var(--card-bg);
border-radius: 8px;
border: 1px solid var(--border-color);
padding: 0.75rem;
position: relative;
transition: all 0.2s ease;
width: 100%;
box-sizing: border-box;
}
.searched-address-item:hover {
border-color: var(--primary-color);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.searched-address-item.has-source-info {
padding-bottom: 1rem;
}
.address-toggle-section {
margin-bottom: 0.5rem;
width: 100%;
}
.address-toggle-group {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.btn-toggle-address {
background: var(--background-secondary);
border: 1px solid var(--border-color);
color: var(--text-secondary);
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-toggle-address.active {
background: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.address-content-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
}
.address-info {
flex-grow: 1;
min-width: 0;
width: 100%;
}
.address-display {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
font-family: monospace;
}
.address-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
@media (max-width: 768px) {
.address-text {
white-space: normal;
word-break: break-all;
font-size: 0.75rem;
}
}
.address-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.btn-copy,
.btn-copy-current,
.btn-delete,
.btn-check {
background: none;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.25rem;
transition: all 0.2s ease;
}
.btn-copy i,
.btn-copy-current i {
color: var(--primary-color);
}
.btn-delete i {
color: var(--danger-color);
}
.btn-check i {
color: var(--success-color);
}
.btn-copy:hover,
.btn-copy-current:hover {
background-color: rgba(var(--primary-color-rgb), 0.1);
}
.btn-delete:hover {
background-color: rgba(var(--danger-color-rgb), 0.1);
}
.btn-check:hover {
background-color: rgba(var(--success-color-rgb), 0.1);
}
@media (max-width: 768px) {
.address-content-wrapper {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.address-actions {
width: 100%; width: 100%;
transition: all 0.2s ease; justify-content: flex-start;
gap: 0.35rem;
}
.searched-addresses-card {
margin-top: 1.5rem;
}
.searched-addresses-header {
padding: 0.75rem; padding: 0.75rem;
flex-wrap: wrap;
gap: 0.5rem;
}
.searched-addresses-list {
padding: 0.5rem;
max-height: 250px;
}
.searched-address-item {
padding: 0.75rem 0.5rem;
}
.address-toggle-group {
flex-wrap: wrap;
margin-bottom: 0.25rem;
width: 100%;
justify-content: flex-start;
}
.btn-toggle-address {
font-size: 0.7rem;
padding: 0.15rem 0.35rem;
flex-grow: 1;
text-align: center;
}
.btn-copy,
.btn-copy-current,
.btn-delete,
.btn-check {
padding: 0.3rem 0.5rem;
font-size: 0.7rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 0.5rem;
border-radius: 0.25rem;
} }
.tx-actions .btn:hover { .address-info {
opacity: 0.9; margin-bottom: 0.25rem;
} }
}
.tx-actions .btn-primary {
background: #4794ff;
border: none;
color: white;
font-weight: 500;
}
.tx-actions .btn-secondary {
background: transparent;
color: var(--text-color);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.view-on-chain {
margin-bottom: 0.75rem;
}
.transaction-error .tx-actions .btn-primary {
background: var(--danger-color);
border-color: var(--danger-color);
}
.transaction-error .tx-actions .btn-primary:hover {
background: rgba(var(--error-color-rgb), 0.9);
border-color: var(--danger-color);
}
.transaction-error .error-details {
background: rgba(var(--error-color-rgb), 0.05);
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid rgba(var(--error-color-rgb), 0.2);
margin: 1rem 0;
color: var(--danger-color);
font-family: monospace;
font-size: 0.875rem;
word-break: break-all;
text-align: left;
max-height: 200px;
overflow-y: auto;
}
@media (max-width: 768px) {
.tx-detail-item {
flex-direction: column;
align-items: flex-start;
}
.tx-detail-label {
margin-bottom: 0.5rem;
min-width: auto;
}
.tx-detail-value {
margin-left: 0;
}
.transaction-success h3,
.transaction-error h3 {
font-size: 1.25rem;
}
}
@media (max-width: 480px) {
.transaction-success,
.transaction-error {
padding: 1rem;
}
.tx-details-card {
padding: 0;
margin: 0.75rem 0;
}
.tx-actions {
gap: 0.5rem;
}
.checkmark-circle,
.error-circle {
width: 3.5rem;
height: 3.5rem;
}
.checkmark,
.error-icon {
font-size: 1.25rem;
}
.tx-detail-value {
font-size: 0.75rem;
}
.tx-detail-item {
padding: 0.5rem 0.75rem;
}
.tx-actions .btn {
padding: 0.625rem;
}
}
/* Transaction list */
.transaction-list {
gap: 0.5rem;
}
.hash-value,
.address-value {
font-size: 0.8rem;
word-break: break-all;
}