From 0c3c07b5d2525d7de0f20d1c51b39e2d204ecfea Mon Sep 17 00:00:00 2001 From: void-57 Date: Wed, 4 Feb 2026 11:12:22 +0530 Subject: [PATCH 01/10] Add blockchain address derivation utilities for XRP, SUI, TON, TRON, and DOGE --- index.html | 211 ++++++++++++++++- scripts/blockchainAddresses.js | 407 +++++++++++++++++++++++++++++++++ 2 files changed, 613 insertions(+), 5 deletions(-) create mode 100644 scripts/blockchainAddresses.js diff --git a/index.html b/index.html index ac0e35a..e6682e5 100644 --- a/index.html +++ b/index.html @@ -18,6 +18,10 @@ } + + + + @@ -28,7 +32,9 @@ + + + + + + @@ -78,13 +83,27 @@ floGlobals.myEthID = floEthereum.ethAddressFromCompressedPublicKey(floDapps.user.public) // AVAX C-Chain uses same address format as Ethereum floGlobals.myAvaxID = floGlobals.myEthID; + // BSC (Binance Smart Chain) uses same address format as Ethereum + floGlobals.myBscID = floGlobals.myEthID; + // MATIC (Polygon) uses same address format as Ethereum + floGlobals.myMaticID = floGlobals.myEthID; + // HBAR (Hedera) uses same address format as Ethereum + floGlobals.myHbarID = floGlobals.myEthID; // Initialize private-key-dependent addresses to null (will be derived after messenger init) floGlobals.myXrpID = null; floGlobals.mySuiID = null; + floGlobals.myAdaID = null; floGlobals.myTonID = null; floGlobals.myTronID = null; floGlobals.myDogeID = null; + floGlobals.myLtcID = null; + floGlobals.myBchID = null; + floGlobals.myDotID = null; + floGlobals.myAlgoID = null; + floGlobals.myXlmID = null; + floGlobals.mySolID = null; + // Note: Cardano (ADA) address will be derived from private key later document.querySelectorAll('.user-profile-id').forEach(el => el.textContent = floGlobals.myFloID) //load messages from IDB and render them @@ -128,6 +147,13 @@ try { floGlobals.myTonID = await convertWIFtoTonAddress(privKey); } catch (e) { console.warn('TON derivation failed:', e); } try { floGlobals.myTronID = convertWIFtoTronAddress(privKey); } catch (e) { console.warn('TRON derivation failed:', e); } try { floGlobals.myDogeID = convertWIFtoDogeAddress(privKey); } catch (e) { console.warn('DOGE derivation failed:', e); } + try { floGlobals.myLtcID = convertWIFtoLitecoinAddress(privKey); } catch (e) { console.warn('LTC derivation failed:', e); } + try { floGlobals.myBchID = convertWIFtoBitcoinCashAddress(privKey); } catch (e) { console.warn('BCH derivation failed:', e); } + try { floGlobals.myDotID = await convertWIFtoPolkadotAddress(privKey); } catch (e) { console.warn('DOT derivation failed:', e); } + try { floGlobals.myAlgoID = convertWIFtoAlgorandAddress(privKey); } catch (e) { console.warn('ALGO derivation failed:', e); } + try { floGlobals.myXlmID = convertWIFtoStellarAddress(privKey); } catch (e) { console.warn('XLM derivation failed:', e); } + try { floGlobals.mySolID = convertWIFtoSolanaAddress(privKey); } catch (e) { console.warn('SOL derivation failed:', e); } + try { floGlobals.myAdaID = await convertWIFtoCardanoAddress(privKey); } catch (e) { console.warn('ADA derivation failed:', e); } } } catch (e) { console.warn('Failed to derive addresses with stored password:', e); @@ -147,6 +173,13 @@ try { floGlobals.myTonID = await convertWIFtoTonAddress(privKey); } catch (e) { console.warn('TON derivation failed:', e); } try { floGlobals.myTronID = convertWIFtoTronAddress(privKey); } catch (e) { console.warn('TRON derivation failed:', e); } try { floGlobals.myDogeID = convertWIFtoDogeAddress(privKey); } catch (e) { console.warn('DOGE derivation failed:', e); } + try { floGlobals.myLtcID = convertWIFtoLitecoinAddress(privKey); } catch (e) { console.warn('LTC derivation failed:', e); } + try { floGlobals.myBchID = convertWIFtoBitcoinCashAddress(privKey); } catch (e) { console.warn('BCH derivation failed:', e); } + try { floGlobals.myDotID = await convertWIFtoPolkadotAddress(privKey); } catch (e) { console.warn('DOT derivation failed:', e); } + try { floGlobals.myAlgoID = convertWIFtoAlgorandAddress(privKey); } catch (e) { console.warn('ALGO derivation failed:', e); } + try { floGlobals.myXlmID = convertWIFtoStellarAddress(privKey); } catch (e) { console.warn('XLM derivation failed:', e); } + try { floGlobals.mySolID = convertWIFtoSolanaAddress(privKey); } catch (e) { console.warn('SOL derivation failed:', e); } + try { floGlobals.myAdaID = await convertWIFtoCardanoAddress(privKey); } catch (e) { console.warn('ADA derivation failed:', e); } } } catch (e) { // Private key is password-secured but we don't have the password @@ -1930,18 +1963,28 @@ removeNotificationBadge('#chat_page_button') if (floGlobals.idInterval) clearInterval(floGlobals.idInterval) - // Cycle through all blockchain addresses: FLO -> BTC -> ETH -> AVAX -> XRP -> SUI -> TON -> TRON -> DOGE + // Cycle through all blockchain addresses: FLO -> BTC -> ETH -> AVAX -> BSC -> MATIC -> HBAR -> XRP -> SUI -> TON -> TRON -> DOGE -> LTC -> BCH -> DOT -> ALGO -> XLM -> SOL let currentIdIndex = 0 const idList = [ { id: floGlobals.myFloID, label: 'FLO' }, { id: floGlobals.myBtcID, label: 'BTC' }, { id: floGlobals.myEthID, label: 'ETH' }, { id: floGlobals.myAvaxID, label: 'AVAX' }, + { id: floGlobals.myBscID, label: 'BSC' }, + { id: floGlobals.myMaticID, label: 'MATIC' }, + { id: floGlobals.myHbarID, label: 'HBAR' }, { id: floGlobals.myXrpID, label: 'XRP' }, { id: floGlobals.mySuiID, label: 'SUI' }, { id: floGlobals.myTonID, label: 'TON' }, { id: floGlobals.myTronID, label: 'TRON' }, - { id: floGlobals.myDogeID, label: 'DOGE' } + { id: floGlobals.myDogeID, label: 'DOGE' }, + { id: floGlobals.myLtcID, label: 'LTC' }, + { id: floGlobals.myBchID, label: 'BCH' }, + { id: floGlobals.myDotID, label: 'DOT' }, + { id: floGlobals.myAlgoID, label: 'ALGO' }, + { id: floGlobals.myXlmID, label: 'XLM' }, + { id: floGlobals.mySolID, label: 'SOL' }, + { id: floGlobals.myAdaID, label: 'ADA' } ].filter(item => item.id) floGlobals.idInterval = setInterval(() => { currentIdIndex = (currentIdIndex + 1) % idList.length @@ -2468,7 +2511,7 @@ }); } - // Derive private-key-dependent blockchain addresses (XRP, SUI, TON, TRON, DOGE) with password + // Derive private-key-dependent blockchain addresses (XRP, SUI, TON, TRON, DOGE, DOT, ALGO) with password async function derivePrivKeyAddresses() { try { const password = await getPromptInput('Enter password to unlock addresses', '', { isPassword: true }); @@ -2505,6 +2548,41 @@ } catch (e) { console.warn('DOGE derivation failed:', e); } + try { + floGlobals.myLtcID = convertWIFtoLitecoinAddress(privKey); + } catch (e) { + console.warn('LTC derivation failed:', e); + } + try { + floGlobals.myBchID = convertWIFtoBitcoinCashAddress(privKey); + } catch (e) { + console.warn('BCH derivation failed:', e); + } + try { + floGlobals.myDotID = await convertWIFtoPolkadotAddress(privKey); + } catch (e) { + console.warn('DOT derivation failed:', e); + } + try { + floGlobals.myAlgoID = convertWIFtoAlgorandAddress(privKey); + } catch (e) { + console.warn('ALGO derivation failed:', e); + } + try { + floGlobals.myXlmID = convertWIFtoStellarAddress(privKey); + } catch (e) { + console.warn('XLM derivation failed:', e); + } + try { + floGlobals.mySolID = convertWIFtoSolanaAddress(privKey); + } catch (e) { + console.warn('SOL derivation failed:', e); + } + try { + floGlobals.myAdaID = await convertWIFtoCardanoAddress(privKey); + } catch (e) { + console.warn('ADA derivation failed:', e); + } // Re-render profile to show new addresses routeTo(window.location.hash); notify('Addresses unlocked successfully!', 'success'); @@ -3010,23 +3088,35 @@
My FLO address - +
My Bitcoin address - +
My Ethereum address - +
My AVAX (Avalanche C-Chain) address - + +
+
+ My BSC (Binance Smart Chain) address + +
+
+ My MATIC (Polygon) address + +
+
+ My HBAR (Hedera) address +
My XRP (Ripple) address - +
My SUI address @@ -3044,6 +3134,34 @@ My DOGE address
+
+ My LTC (Litecoin) address + +
+
+ My BCH (Bitcoin Cash) address + +
+
+ My DOT (Polkadot) address + +
+
+ My ALGO (Algorand) address + +
+
+ My XLM (Stellar) address + +
+
+ My SOL (Solana) address + +
+
+ My ADA (Cardano) address + +
${(!floGlobals.myXrpID && floGlobals.isPrivKeySecured) ? html` @@ -2723,9 +2724,16 @@ getRef('private_key_field').setAttribute('placeholder', 'Enter Blockchain Private Key'); getRef('private_key_field').customValidation = (value) => { if (!value) return { isValid: false, errorText: 'Please enter a private key' } + let isValid = false; + let checkVal = value.startsWith('0x') ? value.substring(2) : value; + if (/^[0-9a-fA-F]{64}$/.test(checkVal) || /^[0-9a-fA-F]{128}$/.test(checkVal)) { + isValid = true; + } else { + try { isValid = !!floCrypto.getPubKeyHex(value); } catch (e) { } + } return { - isValid: floCrypto.getPubKeyHex(value), - errorText: `Invalid private key.
It's a long string of random characters usually starting with 'R'.` + isValid, + errorText: `Invalid private key.
Please enter a valid WIF or Hex string.` } }; } @@ -2734,9 +2742,12 @@ } getRef('sign_in_button').onclick = async () => { let privateKey = getRef('private_key_field').value.trim(); + // For users who prefix their hex keys with 0x + if (privateKey.startsWith('0x')) privateKey = privateKey.substring(2); + let activeChain = null; - if (/^[0-9a-fA-F]{64}$/.test(privateKey)) { //if hex private key might be ethereum, ADA, SOL, TRON, DOT + if (/^[0-9a-fA-F]{64}$/.test(privateKey)) { //if hex private key might be ethereum, ADA, SOL, TRON, DOT, HBAR activeChain = await new Promise(resolve => { _blockchainResolve = resolve; openPopup('blockchain_select_popup'); @@ -2748,12 +2759,18 @@ let key = new Bitcoin.ECKey(privateKey); key.setCompressed(true); privateKey = key.getBitcoinWalletImportFormat(); + } else if (/^[0-9a-fA-F]{128}$/.test(privateKey)) { + activeChain = 'TON'; + // Convert 128-char TON ed25519 key to workable FLO WIF using the first 32-bytes (seed) + let key = new Bitcoin.ECKey(privateKey.substring(0, 64)); + key.setCompressed(true); + privateKey = key.getBitcoinWalletImportFormat(); } else { if (privateKey.startsWith('suiprivkey1')) activeChain = 'SUI'; else if (privateKey.startsWith('s')) activeChain = 'XRP'; else if (privateKey.startsWith('Q')) activeChain = 'DOGE'; else if (privateKey.startsWith('T') && privateKey.length === 51) activeChain = 'LTC'; - else if (privateKey.startsWith('K') || privateKey.startsWith('L') ) activeChain = 'BTC'; + else if (privateKey.startsWith('K') || privateKey.startsWith('L')) activeChain = 'BTC'; else if (privateKey.startsWith('S') && privateKey.length === 56) activeChain = 'XLM'; else if (privateKey.startsWith('R')) activeChain = 'FLO'; else activeChain = 'UNKNOWN'; From 1ff93355f662f159d2bea925458b081cff761bed Mon Sep 17 00:00:00 2001 From: void-57 Date: Mon, 23 Feb 2026 02:57:20 +0530 Subject: [PATCH 06/10] feat: Add validation and conversion for SUI private keys to Bitcoin WIF format using their 32-byte seed. --- index.html | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 5d86372..b4ba309 100644 --- a/index.html +++ b/index.html @@ -2726,8 +2726,15 @@ if (!value) return { isValid: false, errorText: 'Please enter a private key' } let isValid = false; let checkVal = value.startsWith('0x') ? value.substring(2) : value; + + // Hex lengths if (/^[0-9a-fA-F]{64}$/.test(checkVal) || /^[0-9a-fA-F]{128}$/.test(checkVal)) { isValid = true; + } + else if ( + value.startsWith('suiprivkey1') + ) { + isValid = true; } else { try { isValid = !!floCrypto.getPubKeyHex(value); } catch (e) { } } @@ -2766,7 +2773,23 @@ key.setCompressed(true); privateKey = key.getBitcoinWalletImportFormat(); } else { - if (privateKey.startsWith('suiprivkey1')) activeChain = 'SUI'; + if (privateKey.startsWith('suiprivkey1')) { + activeChain = 'SUI'; + // Convert Bech32 SUI key to workable FLO WIF using the 32-byte seed + try { + let decoded = coinjs.bech32_decode(privateKey); + if (!decoded) throw new Error("Invalid SUI private key checksum"); + let bytes = coinjs.bech32_convert(decoded.data, 5, 8, false); + // bytes[0] is the scheme flag (0x00 for Ed25519) + // bytes.slice(1) contains the 32-byte seed + let seedHex = Crypto.util.bytesToHex(bytes.slice(1)); + let key = new Bitcoin.ECKey(seedHex); + key.setCompressed(true); + privateKey = key.getBitcoinWalletImportFormat(); + } catch (e) { + console.error("Failed to decode SUI key", e); + } + } else if (privateKey.startsWith('s')) activeChain = 'XRP'; else if (privateKey.startsWith('Q')) activeChain = 'DOGE'; else if (privateKey.startsWith('T') && privateKey.length === 51) activeChain = 'LTC'; From ec5c8495dd01055fdffb423e10b2b943135325a1 Mon Sep 17 00:00:00 2001 From: void-57 Date: Fri, 27 Feb 2026 16:16:25 +0530 Subject: [PATCH 07/10] feat(wallet): add support for Arbitrum and Optimism networks - Address Derivation: Added ARB and OP to blockchainAddresses.js and messenger.js EVM checks, utilizing myArbID and myOpID where myEthID is used for validation and proxy ID generation. - Login Selection: Added Arbitrum and Optimism buttons to the blockchain_select_popup in index.html to allow users to specifically choose these networks when logging in with indistinguishable EVM hex keys. - Profile UI Data: Added ARB and OP copy boxes to the expanded list of blockchain network identifiers in the user profile page. - Profile Ticker Simplification: Refactored the animated profile ID ticker in index.html to only cycle between the base FLO address and the user's currently signed-in activeChain address --- index.html | 31 ++++++++++++++++++++++++++++--- scripts/blockchainAddresses.js | 10 ++++++++-- scripts/messenger.js | 9 +++++++-- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index b4ba309..7ae713e 100644 --- a/index.html +++ b/index.html @@ -87,6 +87,10 @@ floGlobals.myBscID = floGlobals.myEthID; // MATIC (Polygon) uses same address format as Ethereum floGlobals.myMaticID = floGlobals.myEthID; + // Arbitrum uses same address format as Ethereum + floGlobals.myArbID = floGlobals.myEthID; + // Optimism uses same address format as Ethereum + floGlobals.myOpID = floGlobals.myEthID; // HBAR (Hedera) uses same address format as Ethereum floGlobals.myHbarID = floGlobals.myEthID; @@ -264,6 +268,8 @@ + +
@@ -2062,15 +2068,17 @@ removeNotificationBadge('#chat_page_button') if (floGlobals.idInterval) clearInterval(floGlobals.idInterval) - // Cycle through all blockchain addresses: FLO -> BTC -> ETH -> AVAX -> BSC -> MATIC -> HBAR -> XRP -> SUI -> TON -> TRON -> DOGE -> LTC -> BCH -> DOT -> ALGO -> XLM -> SOL + let activeChain = localStorage.getItem(`${floGlobals.application}#activeChain`) || 'FLO'; let currentIdIndex = 0 - const idList = [ + const allIds = [ { id: floGlobals.myFloID, label: 'FLO' }, { id: floGlobals.myBtcID, label: 'BTC' }, { id: floGlobals.myEthID, label: 'ETH' }, { id: floGlobals.myAvaxID, label: 'AVAX' }, { id: floGlobals.myBscID, label: 'BSC' }, { id: floGlobals.myMaticID, label: 'MATIC' }, + { id: floGlobals.myArbID, label: 'ARB' }, + { id: floGlobals.myOpID, label: 'OP' }, { id: floGlobals.myHbarID, label: 'HBAR' }, { id: floGlobals.myXrpID, label: 'XRP' }, { id: floGlobals.mySuiID, label: 'SUI' }, @@ -2084,7 +2092,16 @@ { id: floGlobals.myXlmID, label: 'XLM' }, { id: floGlobals.mySolID, label: 'SOL' }, { id: floGlobals.myAdaID, label: 'ADA' } - ].filter(item => item.id) + ]; + + const idList = allIds.filter(item => { + if (!item.id) return false; + if (item.label === 'FLO') return true; // Always include FLO + if (item.label === activeChain) return true; // Include active chain + // For EVM compatible chains which share the ETH ID: + if (['ETH', 'AVAX', 'BSC', 'MATIC', 'ARB', 'OP', 'HBAR'].includes(activeChain) && item.label === activeChain) return true; + return false; + }); floGlobals.idInterval = setInterval(() => { currentIdIndex = (currentIdIndex + 1) % idList.length document.querySelectorAll('.user-profile-id').forEach(el => el.textContent = idList[currentIdIndex].id) @@ -3284,6 +3301,14 @@ My MATIC (Polygon) address
+
+ My ARB (Arbitrum) address + +
+
+ My OP (Optimism) address + +
My HBAR (Hedera) address diff --git a/scripts/blockchainAddresses.js b/scripts/blockchainAddresses.js index eb48a4f..d708a91 100644 --- a/scripts/blockchainAddresses.js +++ b/scripts/blockchainAddresses.js @@ -9,6 +9,8 @@ * - AVAX (Avalanche C-Chain) - same as ETH (EVM-compatible) * - BSC (Binance Smart Chain) - same as ETH (EVM-compatible) * - MATIC (Polygon) - same as ETH (EVM-compatible) + * - ARB (Arbitrum) - same as ETH (EVM-compatible) + * - OP (Optimism) - same as ETH (EVM-compatible) * - HBAR (Hedera) - same as ETH (EVM-compatible) * - XRP (Ripple) - via xrpl library * - SUI - via nacl + BLAKE2b @@ -423,6 +425,8 @@ async function deriveAllBlockchainAddresses(wif) { bsc: null, matic: null, hbar: null, + arb: null, + op: null, xrp: null, sui: null, ton: null, @@ -438,13 +442,15 @@ async function deriveAllBlockchainAddresses(wif) { }; try { - // BSC, MATIC, and HBAR use same address as ETH (requires public key, not WIF) + // BSC, MATIC, HBAR, ARB, and OP use same address as ETH (requires public key, not WIF) // These will be set from floGlobals.myEthID in the main code addresses.bsc = null; // Set in main code as same as ETH addresses.matic = null; // Set in main code as same as ETH addresses.hbar = null; // Set in main code as same as ETH + addresses.arb = null; // Set in main code as same as ETH + addresses.op = null; // Set in main code as same as ETH } catch (e) { - console.warn("BSC/MATIC/HBAR derivation failed:", e); + console.warn("BSC/MATIC/HBAR/ARB/OP derivation failed:", e); } try { addresses.xrp = convertWIFtoXrpAddress(wif); diff --git a/scripts/messenger.js b/scripts/messenger.js index 1441241..262fb48 100644 --- a/scripts/messenger.js +++ b/scripts/messenger.js @@ -86,7 +86,7 @@ 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 + // Ethereum/EVM addresses (0x prefix, 40 hex chars) - ETH, AVAX, BSC, MATIC, ARB, OP 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; @@ -152,6 +152,8 @@ case 'AVAX': case 'BSC': case 'MATIC': + case 'ARB': + case 'OP': case 'HBAR': proxyID = floEthereum.ethAddressFromCompressedPublicKey(user.public); break; case 'BTC': proxyID = floGlobals.myBtcID; break; @@ -632,6 +634,8 @@ case 'AVAX': case 'BSC': case 'MATIC': + case 'ARB': + case 'OP': case 'HBAR': addIfValid(floEthereum.ethAddressFromCompressedPublicKey(user.public)); break; @@ -655,7 +659,8 @@ const allDerived = [ floEthereum.ethAddressFromCompressedPublicKey(user.public), floGlobals.myBtcID, floGlobals.myAvaxID, floGlobals.myBscID, - floGlobals.myMaticID, floGlobals.myHbarID, floGlobals.myXrpID, + floGlobals.myMaticID, floGlobals.myArbID, floGlobals.myOpID, + floGlobals.myHbarID, floGlobals.myXrpID, floGlobals.mySuiID, floGlobals.myTonID, floGlobals.myTronID, floGlobals.myDogeID, floGlobals.myLtcID, floGlobals.myBchID, floGlobals.myDotID, floGlobals.myAlgoID, floGlobals.myXlmID, From 6dc2c8f92282f665ac4604a0b096d6b6ad5f4000 Mon Sep 17 00:00:00 2001 From: void-57 Date: Sat, 28 Feb 2026 04:35:04 +0530 Subject: [PATCH 08/10] Fix proxy ID listener derivation and add TON/ALGO blockchain selection popup - Handle CashAddr format validating natively for BCH in floCrypto.js - Require explicit TON vs ALGO blockchain selection for 128-char hex private keys during login - Fix fallback logic in messenger.js to cleanly default to myFloID listener instead of duplicate listener addresses --- index.html | 40 +++++++++++++++++++++++++++++++++++++--- scripts/floCloudAPI.js | 4 ++-- scripts/floCrypto.js | 18 +++++++++++++++++- scripts/messenger.js | 2 +- 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index 7ae713e..6291802 100644 --- a/index.html +++ b/index.html @@ -275,6 +275,29 @@
+ +

Select Blockchain

+

Your private key format is used by Bitcoin and Bitcoin Cash. Please select the correct blockchain for this + key:

+
+ + +
+
+ +
+
+ +

Select Blockchain

+

Your private key format is used by TON and Algorand. Please select the correct blockchain for this key:

+
+ + +
+
+ +
+