| … | … | @@ -1040,6 +1040,67 @@ async function addContact({ address, chain, name, notes = '' }) { |
| 1040 | 1040 | * @param {string} [chain] - Chain name to filter by (optional) |
| 1041 | 1041 | * @returns {Promise<Array>} - Array of contacts sorted by name |
| 1042 | 1042 | */ |
| 1043 | /** |
| 1044 | * Migrate a contact to new multi-address format with IDs |
| 1045 | * @param {Object} contact - Contact to migrate |
| 1046 | * @returns {Object} - Migrated contact |
| 1047 | */ |
| 1048 | function migrateContactFormat(contact) { |
| 1049 | // Already in new format with proper IDs - check if ALL addresses have IDs |
| 1050 | if (contact.addresses && Array.isArray(contact.addresses)) { |
| 1051 | // Check if any address is missing an ID |
| 1052 | const needsMigration = contact.addresses.some(addr => !addr.id) |
| 1053 | |
| 1054 | if (!needsMigration) { |
| 1055 | // All addresses have IDs, no migration needed |
| 1056 | return contact |
| 1057 | } |
| 1058 | |
| 1059 | // Some addresses missing IDs - add them |
| 1060 | const migratedAddresses = contact.addresses.map(addr => { |
| 1061 | if (!addr.id) { |
| 1062 | return { |
| 1063 | ...addr, |
| 1064 | id: crypto.randomBytes(8).toString('hex'), |
| 1065 | network: addr.network || getConfig().network || 'testnet', |
| 1066 | label: addr.label || '', |
| 1067 | addedAt: addr.addedAt || Date.now() |
| 1068 | } |
| 1069 | } |
| 1070 | return addr |
| 1071 | }) |
| 1072 | |
| 1073 | return { |
| 1074 | ...contact, |
| 1075 | addresses: migratedAddresses |
| 1076 | } |
| 1077 | } |
| 1078 | |
| 1079 | // Old single-address format - migrate to multi-address |
| 1080 | if (contact.address && contact.chain) { |
| 1081 | const config = getConfig() |
| 1082 | const defaultNetwork = config.network || 'testnet' |
| 1083 | |
| 1084 | return { |
| 1085 | ...contact, |
| 1086 | addresses: [{ |
| 1087 | id: crypto.randomBytes(8).toString('hex'), |
| 1088 | chain: contact.chain, |
| 1089 | address: contact.address, |
| 1090 | network: contact.network || defaultNetwork, |
| 1091 | label: '', |
| 1092 | addedAt: contact.addedAt || Date.now() |
| 1093 | }], |
| 1094 | // Remove old fields |
| 1095 | address: undefined, |
| 1096 | chain: undefined |
| 1097 | } |
| 1098 | } |
| 1099 | |
| 1100 | // Contact with no addresses - return as is |
| 1101 | return contact |
| 1102 | } |
| 1103 | |
| 1043 | 1104 | async function getContacts(chain = null) { |
| 1044 | 1105 | if (!txHistoryDb) { |
| 1045 | 1106 | throw new Error('Storage not initialized') |
| … | … | @@ -1050,11 +1111,21 @@ async function getContacts(chain = null) { |
| 1050 | 1111 | |
| 1051 | 1112 | for await (const data of txHistoryDb.createReadStream({ gte: prefix, lt: prefix + '\xff' })) { |
| 1052 | 1113 | try { |
| 1053 | | const contact = JSON.parse(data.value) |
| 1054 | | |
| 1055 | | // Filter by chain if specified |
| 1056 | | if (chain && contact.chain !== chain) { |
| 1057 | | continue |
| 1114 | let contact = JSON.parse(data.value) |
| 1115 | |
| 1116 | // Migrate contact format if needed |
| 1117 | contact = migrateContactFormat(contact) |
| 1118 | |
| 1119 | // Filter by chain if specified (check both old and new format) |
| 1120 | if (chain) { |
| 1121 | // New format: check if any address matches chain |
| 1122 | if (contact.addresses && Array.isArray(contact.addresses)) { |
| 1123 | const hasChain = contact.addresses.some(addr => addr.chain === chain) |
| 1124 | if (!hasChain) continue |
| 1125 | } else if (contact.chain && contact.chain !== chain) { |
| 1126 | // Old format fallback |
| 1127 | continue |
| 1128 | } |
| 1058 | 1129 | } |
| 1059 | 1130 | |
| 1060 | 1131 | contacts.push(contact) |
| … | … | @@ -1084,10 +1155,15 @@ async function findContact(nameOrAddress, chain = null) { |
| 1084 | 1155 | ) |
| 1085 | 1156 | if (byName) return byName |
| 1086 | 1157 | |
| 1087 | | // Try exact address match |
| 1088 | | const byAddress = contacts.find(c => |
| 1089 | | c.address.toLowerCase() === nameOrAddress.toLowerCase() |
| 1090 | | ) |
| 1158 | // Try exact address match (check in addresses array) |
| 1159 | const byAddress = contacts.find(c => { |
| 1160 | if (c.addresses && Array.isArray(c.addresses)) { |
| 1161 | return c.addresses.some(addr => |
| 1162 | addr.address.toLowerCase() === nameOrAddress.toLowerCase() |
| 1163 | ) |
| 1164 | } |
| 1165 | return false |
| 1166 | }) |
| 1091 | 1167 | if (byAddress) return byAddress |
| 1092 | 1168 | |
| 1093 | 1169 | // Try partial name match |
| … | … | @@ -1111,7 +1187,13 @@ async function getContactById(id) { |
| 1111 | 1187 | try { |
| 1112 | 1188 | const data = await txHistoryDb.get(`contact:${id}`) |
| 1113 | 1189 | if (!data) return null |
| 1114 | | return JSON.parse(data.value) |
| 1190 | |
| 1191 | let contact = JSON.parse(data.value) |
| 1192 | |
| 1193 | // Migrate contact format if needed |
| 1194 | contact = migrateContactFormat(contact) |
| 1195 | |
| 1196 | return contact |
| 1115 | 1197 | } catch (err) { |
| 1116 | 1198 | if (err.code === 'LEVEL_NOT_FOUND') { |
| 1117 | 1199 | return null |