diff --git a/index.html b/index.html index 41a5889..54578d4 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,7 @@ - + - + \ No newline at end of file diff --git a/scripts/floCloudAPI.js b/scripts/floCloudAPI.js index 19ede97..d484695 100644 --- a/scripts/floCloudAPI.js +++ b/scripts/floCloudAPI.js @@ -392,7 +392,9 @@ if (!address) return; var bytes; - if (address.length == 33 || address.length == 34) { //legacy encoding + + // FLO/BTC legacy encoding (33-34 chars) + if (address.length == 33 || address.length == 34) { let decode = bitjs.Base58.decode(address); bytes = decode.slice(0, decode.length - 4); let checksum = decode.slice(decode.length - 4), @@ -403,7 +405,9 @@ }); hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3] ? bytes = undefined : bytes.shift(); - } else if (!address.startsWith("0x") && address.length == 42 || address.length == 62) { //bech encoding + } + // BTC/LTC Bech32 encoding (42 or 62 chars, not starting with 0x) + else if (!address.startsWith("0x") && (address.length == 42 || address.length == 62) && !address.startsWith("addr1")) { if (typeof coinjs !== 'function') throw "library missing (lib_btc.js)"; let decode = coinjs.bech32_decode(address); @@ -414,14 +418,154 @@ if (address.length == 62) //for long bech, aggregate once more to get 160 bit bytes = coinjs.bech32_convert(bytes, 5, 8, false); } - } else if (address.length == 66) { //public key hex + } + // Public key hex (66 chars starting with 02, 03, or 04) + else if (address.length == 66 && /^0[234][a-fA-F0-9]{64}$/.test(address)) { bytes = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(address), { asBytes: true })); - } else if ((address.length == 42 && address.startsWith("0x")) || (address.length == 40 && !address.startsWith("0x"))) { //Ethereum Address + } + // Ethereum/EVM Address (40 or 42 chars with 0x prefix) + else if ((address.length == 42 && address.startsWith("0x")) || (address.length == 40 && /^[a-fA-F0-9]{40}$/.test(address))) { if (address.startsWith("0x")) { address = address.substring(2); } bytes = Crypto.util.hexToBytes(address); } + // SUI Address (66 chars with 0x prefix, 64 hex chars) + else if (address.length == 66 && address.startsWith("0x") && /^0x[a-fA-F0-9]{64}$/.test(address)) { + // SUI uses 32-byte (256-bit) addresses, hash to get 20 bytes for compatibility + let fullBytes = Crypto.util.hexToBytes(address.substring(2)); + bytes = ripemd160(Crypto.SHA256(fullBytes, { asBytes: true })); + } + // Solana Address (Base58, 43-44 chars) + else if ((address.length == 43 || address.length == 44) && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address)) { + try { + // Solana address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } + // XRP Address (Base58, starts with 'r', 25-35 chars) + else if (address.length >= 25 && address.length <= 35 && address.startsWith('r')) { + try { + // XRP address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } + // TRON Address (Base58, starts with 'T', 34 chars) + else if (address.length == 34 && address.startsWith('T')) { + try { + // TRON address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } + // Cardano Address (Bech32, starts with 'addr1', typically 98-103 chars) + else if (address.startsWith('addr1') && address.length > 50) { + try { + // Cardano bech32 address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } + // Polkadot Address (SS58, starts with '1', 47-48 chars) + else if ((address.length == 47 || address.length == 48) && /^1[a-zA-Z0-9]+$/.test(address)) { + try { + // Polkadot address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } + // TON Address (Base64, 48 chars) + else if (address.length == 48 && /^[A-Za-z0-9_-]+$/.test(address)) { + try { + // TON address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } + // Algorand Address (Base32, 58 chars) + else if (address.length == 58 && /^[A-Z2-7]+$/.test(address)) { + try { + // Algorand address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } + // Stellar Address (Base32, starts with 'G', 56 chars) + else if (address.length == 56 && address.startsWith('G')) { + try { + // Stellar address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } + // Bitcoin Cash CashAddr format (without prefix, 42 chars of data) + else if (address.length >= 34 && address.length <= 45 && /^q[a-z0-9]+$/.test(address)) { + try { + // BCH address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } + // HBAR Address (account ID format: 0.0.xxxxx) + else if (/^0\.0\.\d+$/.test(address)) { + try { + // HBAR address - hash the raw address for unique proxy ID + let addrBytes = []; + for (let i = 0; i < address.length; i++) { + addrBytes.push(address.charCodeAt(i)); + } + bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true })); + } catch (e) { + bytes = undefined; + } + } if (!bytes) throw "Invalid address: " + address; @@ -535,9 +679,17 @@ //send any message to supernode cloud storage const sendApplicationData = floCloudAPI.sendApplicationData = function (message, type, options = {}) { return new Promise((resolve, reject) => { + let originalReceiverID = options.receiverID || DEFAULT.adminID; + let serverReceiverID = originalReceiverID; + try { + if (!floCrypto.validateAddr(originalReceiverID)) { + serverReceiverID = proxyID(originalReceiverID); + } + } catch (e) { } + console.log("[SEND] original:", originalReceiverID, "server:", serverReceiverID); var data = { senderID: user.id, - receiverID: options.receiverID || DEFAULT.adminID, + receiverID: serverReceiverID, pubKey: user.public, message: encodeMessage(message), time: Date.now(), @@ -548,7 +700,7 @@ let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"] .map(d => data[d]).join("|") data.sign = user.sign(hashcontent); - singleRequest(data.receiverID, data) + singleRequest(originalReceiverID, data) .then(result => resolve(result)) .catch(error => reject(error)) }) @@ -557,8 +709,16 @@ //request any data from supernode cloud const requestApplicationData = floCloudAPI.requestApplicationData = function (type, options = {}) { return new Promise((resolve, reject) => { + let originalReceiverID = options.receiverID || DEFAULT.adminID; + let serverReceiverID = originalReceiverID; + try { + if (!floCrypto.validateAddr(originalReceiverID)) { + serverReceiverID = proxyID(originalReceiverID); + } + } catch (e) { } + console.log("[RECV] original:", originalReceiverID, "server:", serverReceiverID); var request = { - receiverID: options.receiverID || DEFAULT.adminID, + receiverID: serverReceiverID, senderID: options.senderID || undefined, application: options.application || DEFAULT.application, type: type, @@ -571,7 +731,7 @@ } if (options.callback instanceof Function) { - liveRequest(request.receiverID, request, options.callback) + liveRequest(originalReceiverID, request, options.callback) .then(result => resolve(result)) .catch(error => reject(error)) } else { @@ -580,7 +740,7 @@ time: Date.now(), request }; - singleRequest(request.receiverID, request, options.method || "GET") + singleRequest(originalReceiverID, request, options.method || "GET") .then(data => resolve(data)).catch(error => reject(error)) } }) diff --git a/scripts/floCrypto.js b/scripts/floCrypto.js index db90758..97a0485 100644 --- a/scripts/floCrypto.js +++ b/scripts/floCrypto.js @@ -431,8 +431,8 @@ } else return null; } else if ((address.length == 42 && address.startsWith("0x")) || (address.length == 40 && !address.startsWith("0x"))) { //Ethereum Address - if (address.startsWith("0x")) { address = address.substring(2); } - let bytes = Crypto.util.hexToBytes(address); + if (address.startsWith("0x")) { address = address.substring(2); } + let bytes = Crypto.util.hexToBytes(address); return { version: 1, hex: address, diff --git a/scripts/floDapps.js b/scripts/floDapps.js index 480e4e4..96360e6 100644 --- a/scripts/floDapps.js +++ b/scripts/floDapps.js @@ -30,13 +30,18 @@ } } - var user_id, user_public, user_private; + var user_id, user_public, user_private, user_loginID; const user = floDapps.user = { get id() { if (!user_id) throw "User not logged in"; return user_id; }, + get loginID() { + if (!user_loginID) + return user_id; // Default to FLO ID if not set + return user_loginID; + }, get public() { if (!user_public) throw "User not logged in"; @@ -97,7 +102,7 @@ } }, clear() { - user_id = user_public = user_private = undefined; + user_id = user_public = user_private = user_loginID = undefined; user_priv_raw = aes_key = undefined; delete user.contacts; delete user.pubKeys; @@ -450,8 +455,80 @@ try { user_public = floCrypto.getPubKeyHex(privKey); user_id = floCrypto.getAddress(privKey); - if (startUpOptions.cloud) - floCloudAPI.user(user_id, privKey); //Set user for floCloudAPI + + // Derive Login ID based on Private Key Type + try { + if (privKey.length === 64) { // Hex Key -> Ethereum + if (typeof floEthereum !== 'undefined' && floEthereum.ethAddressFromPrivateKey) { + user_loginID = floEthereum.ethAddressFromPrivateKey(privKey); + } + } else { // WIF Key + let decode = bitjs.Base58.decode(privKey); + let version = decode[0]; + switch (version) { + case 128: // BTC (Mainnet WIF) + case 0: // BTC (Address Version - fallback) + if (typeof btcOperator !== 'undefined') + user_loginID = new Bitcoin.ECKey(privKey).getBitcoinAddress(); + break; + case 176: // LTC (Mainnet WIF) + case 48: // LTC (Address Version - fallback) + if (typeof convertWIFtoLitecoinAddress === 'function') + user_loginID = convertWIFtoLitecoinAddress(privKey); + break; + case 163: // FLO (Mainnet WIF 0xA3) + case 35: // FLO (Address Version) + user_loginID = user_id; + break; + case 158: // DOGE (Mainnet WIF 0x9E) + case 30: // DOGE (Address Version) + if (typeof convertWIFtoDogeAddress === 'function') + user_loginID = convertWIFtoDogeAddress(privKey); + break; + } + // Manual overrides/checks for keys that might not match standard WIF versions or rely on library detection + if (!user_loginID) { + // Try XRP + if (typeof convertWIFtoXrpAddress === 'function') { + let xrpAddr = convertWIFtoXrpAddress(privKey); + if (xrpAddr) user_loginID = xrpAddr; + } + } + } + } catch (e) { + console.error("Login ID derivation failed:", e); + } + if (!user_loginID) user_loginID = user_id; // Fallback + + if (startUpOptions.cloud) { + // Ensure we pass a format floCloudAPI/floCrypto accepts. + // For DOGE/LTC WIFs, Bitcoin.ECKey might reject the version byte if gloabls aren't set. + // safer to pass RAW HEX Private Key to floCloudAPI. + let cloudPrivKey = privKey; + try { + if (privKey.length > 50 && typeof bitjs !== 'undefined') { // valid WIF length check + let decode = bitjs.Base58.decode(privKey); + if (decode && decode.length) { + // Remove 4-byte checksum + let raw = decode.slice(0, decode.length - 4); + // Remove 1-byte Version + raw.shift(); + // Check compression flag (last byte 0x01) if length is 33 + if (raw.length === 33 && raw[32] === 1) { + raw.pop(); + } + // Convert to Hex + if (raw.length === 32) + cloudPrivKey = Crypto.util.bytesToHex(raw); + } + } + } catch (e) { + console.warn("WIF decode for cloud failed, using original:", e); + } + floCloudAPI.user(user_loginID, cloudPrivKey); + } + + user_priv_wrap = () => checkIfPinRequired(key); let n = floCrypto.randInt(12, 20); aes_key = floCrypto.randString(n); diff --git a/scripts/messenger.js b/scripts/messenger.js index 5e03c32..1441241 100644 --- a/scripts/messenger.js +++ b/scripts/messenger.js @@ -18,7 +18,8 @@ direct: (d, e) => console.log(d, e), chats: (c) => console.log(c), mails: (m) => console.log(m), - marked: (r) => console.log(r) + marked: (r) => console.log(r), + onChatMigrated: (oldID, newID) => console.log(`Migrated ${oldID} to ${newID}`) }; rmMessenger.renderUI = {}; Object.defineProperties(rmMessenger.renderUI, { @@ -39,6 +40,9 @@ }, marked: { set: ui_fn => UI.marked = ui_fn + }, + onChatMigrated: { + set: ui_fn => UI.onChatMigrated = ui_fn } }); @@ -74,13 +78,58 @@ } }); + // Validate any blockchain address (all 19 supported chains) + function isValidBlockchainAddress(address) { + if (!address || typeof address !== 'string') return false; + + // FLO/BTC/DOGE/LTC legacy (Base58, 33-34 chars) + if ((address.length === 33 || address.length === 34) && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address)) { + return floCrypto.validateAddr(address) || true; + } + // Ethereum/EVM addresses (0x prefix, 40 hex chars) - ETH, AVAX, BSC, MATIC + if (/^0x[a-fA-F0-9]{40}$/.test(address)) return true; + // SUI addresses (0x prefix, 64 hex chars) + if (/^0x[a-fA-F0-9]{64}$/.test(address)) return true; + // BTC/LTC Bech32 (bc1/ltc1 prefix) + if (/^(bc1|ltc1)[a-zA-HJ-NP-Z0-9]{25,62}$/.test(address)) return true; + // Solana (Base58, 43-44 chars) + if ((address.length === 43 || address.length === 44) && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address)) return true; + // XRP (r-prefix, 25-35 chars) + if (address.startsWith('r') && address.length >= 25 && address.length <= 35 && /^r[a-zA-Z0-9]+$/.test(address)) return true; + // TRON (T-prefix, 34 chars) + if (address.startsWith('T') && address.length === 34 && /^T[a-zA-Z0-9]+$/.test(address)) return true; + // Cardano (addr1 prefix, Bech32) + if (address.startsWith('addr1') && address.length > 50) return true; + // Polkadot (SS58, starts with 1, 47-48 chars) + if (address.startsWith('1') && (address.length === 47 || address.length === 48) && /^[a-zA-Z0-9]+$/.test(address)) return true; + // TON (Base64 URL-safe, 48 chars) + if (address.length === 48 && /^[A-Za-z0-9_-]+$/.test(address)) return true; + // Algorand (Base32, 58 chars, uppercase + 2-7) + if (address.length === 58 && /^[A-Z2-7]+$/.test(address)) return true; + // Stellar (G-prefix, Base32, 56 chars) + if (address.startsWith('G') && address.length === 56 && /^G[A-Z2-7]+$/.test(address)) return true; + // Bitcoin Cash CashAddr (q-prefix) + if (address.startsWith('q') && address.length >= 34 && address.length <= 45 && /^q[a-z0-9]+$/.test(address)) return true; + // HBAR (0.0.xxxxx format) + if (/^0\.0\.\d+$/.test(address)) return true; + + return false; + } + function sendRaw(message, recipient, type, encrypt = null, comment = undefined) { return new Promise((resolve, reject) => { - if (!floCrypto.validateAddr(recipient)) + if (!isValidBlockchainAddress(recipient)) return reject("Invalid Recipient"); if ([true, null].includes(encrypt)) { - let r_pubKey = floDapps.user.get_pubKey(recipient); + // Try to get pubKey safely (may fail for non-FLO addresses) + let r_pubKey = null; + try { + r_pubKey = floDapps.user.get_pubKey(recipient); + } catch (e) { + // Address format not supported by floCrypto.decodeAddr + r_pubKey = floGlobals.pubKeys ? floGlobals.pubKeys[recipient] : null; + } if (r_pubKey) message = floCrypto.encryptData(message, r_pubKey); else if (encrypt === true) @@ -89,8 +138,44 @@ let options = { receiverID: recipient, } - if (comment) - options.comment = comment + if (comment) { + options.comment = comment; + } + + // If we're logged in with an alt chain, embed it in the comment for migration mapping + try { + let activeChain = localStorage.getItem(`${floGlobals.application}#activeChain`); + if (activeChain && activeChain !== 'FLO') { + let proxyID = null; + switch (activeChain) { + case 'ETH': + case 'AVAX': + case 'BSC': + case 'MATIC': + case 'HBAR': + proxyID = floEthereum.ethAddressFromCompressedPublicKey(user.public); break; + case 'BTC': proxyID = floGlobals.myBtcID; break; + case 'BCH': proxyID = floGlobals.myBchID; break; + case 'XRP': proxyID = floGlobals.myXrpID; break; + case 'SUI': proxyID = floGlobals.mySuiID; break; + case 'TON': proxyID = floGlobals.myTonID; break; + case 'TRON': proxyID = floGlobals.myTronID; break; + case 'DOGE': proxyID = floGlobals.myDogeID; break; + case 'LTC': proxyID = floGlobals.myLtcID; break; + case 'DOT': proxyID = floGlobals.myDotID; break; + case 'ALGO': proxyID = floGlobals.myAlgoID; break; + case 'XLM': proxyID = floGlobals.myXlmID; break; + case 'SOL': proxyID = floGlobals.mySolID; break; + case 'ADA': proxyID = floGlobals.myAdaID; break; + } + if (proxyID && proxyID !== user.id) { + options.comment = (options.comment ? options.comment + "|" : "") + "FROM_ALT:" + proxyID; + } + } + } catch (e) { + console.warn("Could not append FROM_ALT tag", e); + } + floCloudAPI.sendApplicationData(message, type, options) .then(result => resolve(result)) .catch(error => reject(error)) @@ -316,7 +401,7 @@ const processData = {}; processData.direct = function () { - return (unparsed, newInbox) => { + return async (unparsed, newInbox) => { //store the pubKey if not stored already floDapps.storePubKey(unparsed.senderID, unparsed.pubKey); if (_loaded.blocked.has(unparsed.senderID) && unparsed.type !== "REVOKE_KEY") @@ -326,6 +411,51 @@ let vc = unparsed.vectorClock; switch (unparsed.type) { case "MESSAGE": { //process as message + let vc = unparsed.vectorClock; + + // Chat Migration Logic + if (unparsed.comment && unparsed.comment.includes("FROM_ALT:")) { + let altID = unparsed.comment.split("FROM_ALT:")[1].split("|")[0]; // Extract the alt ID + if (altID && altID !== unparsed.senderID) { + // Check if an existing chat with this alt ID exists + if (_loaded.chats[altID] !== undefined) { + console.log(`Migrating chat history from ${altID} to ${unparsed.senderID}`); + + // Run migration asynchronously but wait for it to complete + try { + // Migrate Messages + let _options = { lowerKey: `${altID}|`, upperKey: `${altID}||` }; + let result = await compactIDB.searchData("messages", _options); + for (let i in result) { + let messageData = result[i]; + messageData.floID = unparsed.senderID; + let oldVc = i.split("|")[1]; + await compactIDB.addData("messages", messageData, `${unparsed.senderID}|${oldVc}`).catch(e => console.warn(e)); + await compactIDB.removeData("messages", i); + } + + // Migrate Contacts + if (floGlobals.contacts[altID]) { + let contactName = floGlobals.contacts[altID]; + floDapps.storeContact(unparsed.senderID, contactName); + await compactIDB.removeData("contacts", altID, floDapps.user.db_name); + delete floGlobals.contacts[altID]; + } + + // Delete old chat reference + delete _loaded.chats[altID]; + await compactIDB.removeData("chats", altID); + + // Trigger UI update if available + if (UI.onChatMigrated) UI.onChatMigrated(altID, unparsed.senderID); + if (UI.chats) await UI.chats(getChatOrder()); + } catch (error) { + console.error("Migration failed:", error); + } + } + } + } + let dm = { time: unparsed.time, floID: unparsed.senderID, @@ -333,7 +463,12 @@ message: encrypt(unparsed.message) } console.debug(dm, `${dm.floID}|${vc}`); - compactIDB.addData("messages", Object.assign({}, dm), `${dm.floID}|${vc}`) + try { + await compactIDB.addData("messages", Object.assign({}, dm), `${dm.floID}|${vc}`); + } catch (e) { + console.warn("Message already exists (skipping UI push):", e); + break; // Deduplicate: don't push to UI if we already processed it (e.g. self-messaging) + } _loaded.chats[dm.floID] = parseInt(vc) compactIDB.writeData("chats", parseInt(vc), dm.floID) dm.message = unparsed.message; @@ -431,13 +566,13 @@ } } - function requestDirectInbox() { + const requestDirectInbox = rmMessenger.reconnectInbox = function () { if (directConnID.length) { //close existing request connection (if any) directConnID.forEach(id => floCloudAPI.closeRequest(id)); directConnID = []; } const parseData = processData.direct(); - let callbackFn = function (dataSet, error) { + let callbackFn = async function (dataSet, error) { if (error) return console.error(error) let newInbox = { @@ -449,9 +584,11 @@ keyrevoke: [], pipeline: {} } - for (let vc in dataSet) { + // Await processing in order according to vector clocks + let sortedVCs = Object.keys(dataSet).sort((a, b) => parseInt(a) - parseInt(b)); + for (let vc of sortedVCs) { try { - parseData(dataSet[vc], newInbox); + await parseData(dataSet[vc], newInbox); } catch (error) { //if (error !== "blocked-user") console.log(error); @@ -464,19 +601,80 @@ console.debug(newInbox); UI.direct(newInbox) } - return new Promise((resolve, reject) => { - const promises = [ + return new Promise(async (resolve, reject) => { + // All blockchain address IDs to listen on + let activeChain = localStorage.getItem(`${floGlobals.application}#activeChain`); + const blockchainAddressIDs = [user.id]; // Always listen to FLO address (primary) + + if (!activeChain) { + try { + let privKey = floDapps.user.private; + if (privKey instanceof Promise) privKey = await privKey; + if (typeof privKey === 'string' && privKey.length > 0) { + if (privKey.startsWith('suiprivkey1')) activeChain = 'SUI'; + else if (privKey.startsWith('s')) activeChain = 'XRP'; + else if (privKey.startsWith('Q') || privKey.startsWith('6')) activeChain = 'DOGE'; + else if (privKey.startsWith('T') && privKey.length === 51) activeChain = 'LTC'; + else if (privKey.startsWith('K') || privKey.startsWith('L') || privKey.startsWith('5')) activeChain = 'BTC'; + else if (privKey.startsWith('S') && privateKey.length === 56) activeChain = 'XLM'; + else if (privKey.startsWith('R') || privKey.startsWith('c') || privKey.startsWith('p')) activeChain = 'FLO'; + } + } catch (e) { + console.warn("Could not deduce fallback activeChain", e); + } + } + + if (activeChain) { + const addIfValid = (id) => { if (id && !blockchainAddressIDs.includes(id)) blockchainAddressIDs.push(id) }; + + switch (activeChain) { + case 'ETH': + case 'AVAX': + case 'BSC': + case 'MATIC': + case 'HBAR': + addIfValid(floEthereum.ethAddressFromCompressedPublicKey(user.public)); + break; + case 'BTC': addIfValid(floGlobals.myBtcID); break; + case 'BCH': addIfValid(floGlobals.myBchID); break; + case 'XRP': addIfValid(floGlobals.myXrpID); break; + case 'SUI': addIfValid(floGlobals.mySuiID); break; + case 'TON': addIfValid(floGlobals.myTonID); break; + case 'TRON': addIfValid(floGlobals.myTronID); break; + case 'DOGE': addIfValid(floGlobals.myDogeID); break; + case 'LTC': addIfValid(floGlobals.myLtcID); break; + case 'DOT': addIfValid(floGlobals.myDotID); break; + case 'ALGO': addIfValid(floGlobals.myAlgoID); break; + case 'XLM': addIfValid(floGlobals.myXlmID); break; + case 'SOL': addIfValid(floGlobals.mySolID); break; + case 'ADA': addIfValid(floGlobals.myAdaID); break; + case 'FLO': break; + } + } else { + // Fallback: listen to all derived addresses if no active chain is set + const allDerived = [ + floEthereum.ethAddressFromCompressedPublicKey(user.public), + floGlobals.myBtcID, floGlobals.myAvaxID, floGlobals.myBscID, + floGlobals.myMaticID, floGlobals.myHbarID, floGlobals.myXrpID, + floGlobals.mySuiID, floGlobals.myTonID, floGlobals.myTronID, + floGlobals.myDogeID, floGlobals.myLtcID, floGlobals.myBchID, + floGlobals.myDotID, floGlobals.myAlgoID, floGlobals.myXlmID, + floGlobals.mySolID, floGlobals.myAdaID + ]; + allDerived.forEach(id => { + if (id && !blockchainAddressIDs.includes(id)) { + blockchainAddressIDs.push(id); + } + }); + } + + const promises = blockchainAddressIDs.map(receiverID => floCloudAPI.requestApplicationData(null, { - receiverID: user.id, - lowerVectorClock: _loaded.appendix.lastReceived + 1, - callback: callbackFn - }), - floCloudAPI.requestApplicationData(null, { - receiverID: floEthereum.ethAddressFromCompressedPublicKey(user.public), + receiverID: receiverID, lowerVectorClock: _loaded.appendix.lastReceived + 1, callback: callbackFn }) - ] + ); Promise.all(promises).then(connectionIds => { directConnID = [...directConnID, ...connectionIds]; resolve("Direct Inbox connected"); @@ -513,7 +711,17 @@ } rmMessenger.storeContact = function (floID, name) { - return floDapps.storeContact(floID, name) + // For FLO/BTC addresses, use the standard validation + if (floCrypto.validateAddr(floID)) { + return floDapps.storeContact(floID, name); + } + // For other blockchain addresses (ETH, SOL, ADA, etc.), store directly + return new Promise((resolve, reject) => { + compactIDB.writeData("contacts", name, floID, floDapps.user.db_name).then(result => { + floGlobals.contacts[floID] = name; + resolve("Contact stored"); + }).catch(error => reject(error)); + }); } const loadDataFromIDB = function (defaultList = true) {