| … | … | @@ -914,55 +914,74 @@ async function loadContactsList() { |
| 914 | 914 | return |
| 915 | 915 | } |
| 916 | 916 | |
| 917 | | // Group contacts by chain |
| 918 | | const byChain = {} |
| 919 | | for (const contact of currentContacts) { |
| 920 | | if (!byChain[contact.chain]) byChain[contact.chain] = [] |
| 921 | | byChain[contact.chain].push(contact) |
| 922 | | } |
| 917 | // Sort contacts alphabetically |
| 918 | currentContacts.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())) |
| 923 | 919 | |
| 924 | | let html = '' |
| 920 | let html = '<div class="contacts-grid">' |
| 921 | |
| 922 | for (const contact of currentContacts) { |
| 923 | const favIcon = contact.metadata?.favorite ? '⭐' : '' |
| 924 | |
| 925 | // Support both old format (single address) and new format (multiple addresses) |
| 926 | let addresses = [] |
| 927 | if (contact.addresses && Array.isArray(contact.addresses)) { |
| 928 | // NEW FORMAT: Multiple addresses |
| 929 | addresses = contact.addresses |
| 930 | } else if (contact.chain && contact.address) { |
| 931 | // OLD FORMAT: Single address - convert to array |
| 932 | addresses = [{ |
| 933 | chain: contact.chain, |
| 934 | address: contact.address, |
| 935 | network: contact.network || selectedNetwork |
| 936 | }] |
| 937 | } |
| 925 | 938 | |
| 926 | | for (const [chain, contacts] of Object.entries(byChain)) { |
| 927 | 939 | html += ` |
| 928 | | <div class="contacts-chain-section"> |
| 929 | | <h4 class="chain-header">${chain.toUpperCase()}</h4> |
| 930 | | <div class="contacts-grid"> |
| 940 | <div class="contact-card"> |
| 941 | <div class="contact-header"> |
| 942 | <span class="contact-name">${favIcon} ${escapeHtml(contact.name)}</span> |
| 943 | <div class="contact-actions-inline"> |
| 944 | <button class="btn-icon-small" onclick="editContact('${contact.id}')" title="Edit">✏️</button> |
| 945 | <button class="btn-icon-small" onclick="deleteContactConfirm('${contact.id}', '${escapeHtml(contact.name)}')" title="Delete">🗑️</button> |
| 946 | </div> |
| 947 | </div> |
| 931 | 948 | ` |
| 932 | 949 | |
| 933 | | for (const contact of contacts) { |
| 934 | | const shortAddr = contact.address.substring(0, 16) + '...' |
| 935 | | const favIcon = contact.metadata?.favorite ? '⭐' : '' |
| 936 | | |
| 937 | | html += ` |
| 938 | | <div class="contact-card"> |
| 939 | | <div class="contact-header"> |
| 940 | | <span class="contact-name">${favIcon} ${escapeHtml(contact.name)}</span> |
| 941 | | <div class="contact-actions-inline"> |
| 942 | | <button class="btn-icon-small" onclick="editContact('${contact.id}')" title="Edit">✏️</button> |
| 943 | | <button class="btn-icon-small" onclick="deleteContactConfirm('${contact.id}', '${escapeHtml(contact.name)}')" title="Delete">🗑️</button> |
| 950 | // Show all addresses |
| 951 | if (addresses.length > 0) { |
| 952 | html += '<div class="contact-addresses">' |
| 953 | for (const addr of addresses) { |
| 954 | const shortAddr = addr.address.substring(0, 16) + '...' |
| 955 | const chainIcon = addr.chain === 'bitcoin' ? '₿' : addr.chain === 'ethereum' ? 'Ξ' : '🔗' |
| 956 | |
| 957 | html += ` |
| 958 | <div class="contact-address-row"> |
| 959 | <div class="contact-address-info"> |
| 960 | <span class="chain-icon" title="${addr.chain}">${chainIcon}</span> |
| 961 | <code class="contact-address" onclick="copyToClipboard('${addr.address}', 'Address copied!')" title="Click to copy">${shortAddr}</code> |
| 962 | <span class="chain-badge chain-badge-${addr.chain}">${addr.chain}</span> |
| 944 | 963 | </div> |
| 964 | <button class="btn-icon-small" onclick="sendToContactAddress('${contact.id}', '${addr.chain}')" title="Send ${addr.chain}">💸</button> |
| 945 | 965 | </div> |
| 946 | | <div class="contact-details"> |
| 947 | | <code class="contact-address" onclick="copyToClipboard('${contact.address}', 'Address copied!')" title="Click to copy">${shortAddr}</code> |
| 948 | | <span class="chain-badge chain-badge-${chain}">${chain}</span> |
| 949 | | </div> |
| 950 | | ${contact.notes ? `<p class="contact-notes">${escapeHtml(contact.notes)}</p>` : ''} |
| 951 | | <div class="contact-footer"> |
| 952 | | <button class="btn-secondary btn-small btn-full-width" onclick="sendToContact('${contact.id}')">💸 Send to ${escapeHtml(contact.name)}</button> |
| 953 | | </div> |
| 954 | | </div> |
| 955 | | ` |
| 966 | ` |
| 967 | } |
| 968 | html += '</div>' |
| 969 | } else { |
| 970 | html += '<p class="contact-error">⚠️ No addresses configured</p>' |
| 956 | 971 | } |
| 957 | 972 | |
| 958 | | html += ` |
| 959 | | </div> |
| 960 | | </div> |
| 961 | | ` |
| 973 | if (contact.notes) { |
| 974 | html += `<p class="contact-notes">${escapeHtml(contact.notes)}</p>` |
| 975 | } |
| 976 | |
| 977 | html += '</div>' |
| 962 | 978 | } |
| 963 | 979 | |
| 980 | html += '</div>' |
| 981 | |
| 964 | 982 | contactsList.innerHTML = html |
| 965 | 983 | } catch (err) { |
| 984 | console.error('Load contacts error:', err) |
| 966 | 985 | contactsList.innerHTML = `<div class="error-message">Error loading contacts: ${err.message}</div>` |
| 967 | 986 | } |
| 968 | 987 | } |
| … | … | @@ -1104,47 +1123,79 @@ async function deleteContactConfirm(contactId, contactName) { |
| 1104 | 1123 | } |
| 1105 | 1124 | } |
| 1106 | 1125 | |
| 1107 | | async function sendToContact(contactId) { |
| 1126 | // Send to specific address of a contact |
| 1127 | async function sendToContactAddress(contactId, chain) { |
| 1108 | 1128 | const contact = currentContacts.find(c => c.id === contactId) |
| 1109 | 1129 | if (!contact) { |
| 1110 | 1130 | showError('Contact not found') |
| 1111 | 1131 | return |
| 1112 | 1132 | } |
| 1113 | 1133 | |
| 1114 | | // Close contacts modal |
| 1115 | | closeContacts() |
| 1134 | // Find the address for this chain |
| 1135 | let address = null |
| 1116 | 1136 | |
| 1117 | | // Set currency to match contact's chain |
| 1118 | | const chainMap = { |
| 1119 | | 'bitcoin': 'testnet-bitcoin', |
| 1120 | | 'ethereum': 'testnet-ethereum' |
| 1137 | if (contact.addresses && Array.isArray(contact.addresses)) { |
| 1138 | // NEW FORMAT: Multiple addresses |
| 1139 | const addrInfo = contact.addresses.find(a => a.chain === chain) |
| 1140 | if (addrInfo) { |
| 1141 | address = addrInfo.address |
| 1142 | } |
| 1143 | } else if (contact.chain === chain && contact.address) { |
| 1144 | // OLD FORMAT: Single address |
| 1145 | address = contact.address |
| 1121 | 1146 | } |
| 1122 | 1147 | |
| 1123 | | const currencyKey = chainMap[contact.chain] |
| 1124 | | if (currencyKey) { |
| 1125 | | document.getElementById('currency-selector').value = currencyKey |
| 1126 | | switchCurrency() |
| 1148 | if (!address) { |
| 1149 | showError(`No ${chain} address for ${contact.name}`) |
| 1150 | return |
| 1127 | 1151 | } |
| 1128 | 1152 | |
| 1153 | // Close contacts modal |
| 1154 | closeContacts() |
| 1155 | |
| 1156 | // Set currency to match chain |
| 1157 | selectedCurrency = chain |
| 1158 | localStorage.setItem('currency', chain) |
| 1159 | document.getElementById('currency-selector').value = chain |
| 1160 | |
| 1161 | // Update wallet view |
| 1162 | updateWalletView() |
| 1163 | |
| 1129 | 1164 | // Show send form |
| 1130 | | await showSendForCurrent() |
| 1165 | await showSend(chain) |
| 1131 | 1166 | |
| 1132 | 1167 | // Pre-fill address |
| 1133 | 1168 | setTimeout(() => { |
| 1134 | | const addressInput = document.getElementById('send-address') |
| 1169 | const addressInput = document.getElementById('send-to') |
| 1135 | 1170 | if (addressInput) { |
| 1136 | | addressInput.value = contact.address |
| 1137 | | |
| 1138 | | // Also update the recipient display if there is one |
| 1139 | | const recipientDisplay = document.getElementById('send-recipient-name') |
| 1140 | | if (recipientDisplay) { |
| 1141 | | recipientDisplay.textContent = `Sending to: ${contact.name}` |
| 1142 | | recipientDisplay.style.display = 'block' |
| 1143 | | } |
| 1171 | addressInput.value = address |
| 1144 | 1172 | } |
| 1145 | 1173 | }, 100) |
| 1146 | 1174 | } |
| 1147 | 1175 | |
| 1176 | // Legacy function - send to first available address |
| 1177 | async function sendToContact(contactId) { |
| 1178 | const contact = currentContacts.find(c => c.id === contactId) |
| 1179 | if (!contact) { |
| 1180 | showError('Contact not found') |
| 1181 | return |
| 1182 | } |
| 1183 | |
| 1184 | // Get first available address |
| 1185 | let chain = null |
| 1186 | if (contact.addresses && Array.isArray(contact.addresses) && contact.addresses.length > 0) { |
| 1187 | chain = contact.addresses[0].chain |
| 1188 | } else if (contact.chain) { |
| 1189 | chain = contact.chain |
| 1190 | } else { |
| 1191 | showError('Contact has no addresses') |
| 1192 | return |
| 1193 | } |
| 1194 | |
| 1195 | // Use the chain-specific function |
| 1196 | await sendToContactAddress(contactId, chain) |
| 1197 | } |
| 1198 | |
| 1148 | 1199 | function escapeHtml(text) { |
| 1149 | 1200 | const div = document.createElement('div') |
| 1150 | 1201 | div.textContent = text |
| … | … | @@ -1928,14 +1979,33 @@ function closeSend() { |
| 1928 | 1979 | |
| 1929 | 1980 | // Set amount to maximum balance |
| 1930 | 1981 | function setMaxAmount() { |
| 1931 | | if (currentBalance > 0) { |
| 1932 | | // Reserve some for fees (from chain config) |
| 1933 | | const chain = SUPPORTED_CHAINS[currentSendChain] |
| 1934 | | const reserved = chain ? chain.feeReserve : 0 |
| 1935 | | const maxAmount = Math.max(0, currentBalance - reserved) |
| 1936 | | document.getElementById('send-amount').value = maxAmount |
| 1937 | | estimateFee() |
| 1982 | console.log('setMaxAmount called', { currentBalance, currentSendChain }) |
| 1983 | |
| 1984 | if (!currentSendChain) { |
| 1985 | showError('Chain not selected') |
| 1986 | return |
| 1987 | } |
| 1988 | |
| 1989 | if (!currentBalance || currentBalance <= 0) { |
| 1990 | showError('No balance available') |
| 1991 | return |
| 1992 | } |
| 1993 | |
| 1994 | // Reserve some for fees (from chain config) |
| 1995 | const chain = SUPPORTED_CHAINS[currentSendChain] |
| 1996 | if (!chain) { |
| 1997 | console.error('Chain config not found:', currentSendChain) |
| 1998 | showError('Invalid chain configuration') |
| 1999 | return |
| 1938 | 2000 | } |
| 2001 | |
| 2002 | const reserved = chain.feeReserve || 0 |
| 2003 | const maxAmount = Math.max(0, currentBalance - reserved) |
| 2004 | |
| 2005 | console.log('Setting max amount:', { maxAmount, reserved, currentBalance }) |
| 2006 | |
| 2007 | document.getElementById('send-amount').value = maxAmount |
| 2008 | estimateFee() |
| 1939 | 2009 | } |
| 1940 | 2010 | |
| 1941 | 2011 | // Estimate transaction fee |