]> zoso.dev Git - nemo-wallet.git/commitdiff
Implement wallet import. Refactor account creation to use password unlock modal.
authorChris Duncan <chris@zoso.dev>
Wed, 6 Nov 2024 00:39:40 +0000 (16:39 -0800)
committerChris Duncan <chris@zoso.dev>
Wed, 6 Nov 2024 00:39:40 +0000 (16:39 -0800)
nemo-wallet.html

index cb549e9c6bef6c75f50fd8f105855bf6735f631e..f67eb4a350aac9c47b42ee7e2a84dfaca5d656c0 100644 (file)
                        const themeCheckbox = document.getElementById('theme-checkbox')
                        themeCheckbox.addEventListener('change', selectTheme)
                        selectTheme()
-                       const forms = document.querySelectorAll('.form')
-                       for (const form of forms) {
-                               const cancelButtons = form.querySelectorAll('button.cancel')
+                       const dialogs = document.querySelectorAll('dialog, form, .form')
+                       for (const dialog of dialogs) {
+                               const cancelButtons = dialog.querySelectorAll('button.cancel')
                                for (const cancelButton of cancelButtons) {
                                        cancelButton.addEventListener('click', (event) => {
-                                               cancelForm(form.id)
+                                               cancelDialog(dialog.id)
                                        })
                                }
                        }
                        const exportMnemonicButton = document.getElementById('export-mnemonic-button')
                        exportMnemonicButton.addEventListener('click', async (event) => {
                                if (event.detail === 1) {
-                                       requestPassword(exportMnemonic)
+                                       unlockThen(exportMnemonic)
                                }
                        })
                        const exportSeedButton = document.getElementById('export-seed-button')
                        exportSeedButton.addEventListener('click', async (event) => {
                                if (event.detail === 1) {
-                                       requestPassword(exportSeed)
+                                       unlockThen(exportSeed)
                                }
                        })
                        setTimeout(() => {
                                }
                                case '_new': {
                                        try {
-                                               document.querySelector(location.hash).classList.add('hide')
+                                               document.querySelectorAll('.page').forEach(n => n.classList.add('hide'))
                                                document.getElementById('wallet-new').classList.remove('hide')
                                        } catch (err) {
                                                walletSelect.value = ''
                                }
                                case '_import': {
                                        try {
-                                               document.querySelector(location.hash).classList.add('hide')
+                                               document.querySelectorAll('.page').forEach(n => n.classList.add('hide'))
                                                document.getElementById('wallet-import').classList.remove('hide')
                                        } catch (err) {
                                                walletSelect.value = ''
                                option?.remove()
                                notify.error(err.msg)
                        } finally {
-                               await cancelForm('wallet-new')
+                               await cancelDialog('wallet-new')
+                               return wallet
+                       }
+               }
+
+               async function importWallet () {
+                       const walletSelect = document.getElementById('wallet')
+                       const form = document.getElementById('wallet-import')
+                       const name = document.getElementById('wallet-import-name-input').value
+                       if (name.substring(0, 1) === '_') {
+                               notify.error(`Wallet name cannot start with an underscore`)
+                               return
+                       }
+                       const algorithm = document.getElementById('wallet-import-algorithm-select').value
+                       const walletClass = getWalletClass(algorithm)
+                       const type = document.getElementById(`wallet-import-type-select`).value
+                       const secret = document.getElementById(`wallet-import-secret-input`).value
+                       const salt = document.getElementById('wallet-import-salt-input')?.value
+                       const password = document.getElementById('wallet-import-password-input')?.value
+                       if (!password) {
+                               notify.error('Password is required')
+                               return
+                       }
+                       let wallet, option
+                       try {
+                               if (type === 'mnemonic') {
+                                       wallet = await walletClass.fromMnemonic(password, secret, salt)
+                               } else if (type === 'seed') {
+                                       wallet = await walletClass.fromSeed(password, secret)
+                               } else {
+                                       notify.error(`Error importing unknown wallet type`)
+                                       return
+                               }
+                               const walletData = {
+                                       id: wallet.id,
+                                       name: name || wallet.id,
+                                       algorithm
+                               }
+                               await addToStorage(`wallets`, JSON.stringify(walletData))
+
+                               const select = document.getElementById('wallet')
+                               option = new Option(walletData.name, walletData.id, false, true)
+                               select.add(option)
+                               await selectWallet()
+                       } catch (err) {
+                               option?.remove()
+                               console.error(err)
+                               notify.error(err.message)
+                       } finally {
+                               await cancelDialog('wallet-import')
                                return wallet
                        }
                }
 
+               async function unlockThen (callback) {
+                       const getPassword = async (event) => {
+                               if (event.detail === 1) {
+                                       try {
+                                               const passwordInput = document.getElementById('unlock-modal-password-input')
+                                               const password = passwordInput.value
+                                               passwordInput.value = ''
+                                               document.getElementById('unlock-modal').close()
+                                               unlockModalOkButton.removeEventListener('click', getPassword)
+                                               const wallet = await loadWallet()
+                                               await wallet.unlock(password)
+                                               await callback(wallet)
+                                               await wallet.lock(password)
+                                       } catch (err) {
+                                               notify.error(err)
+                                       }
+                               }
+                       }
+                       const unlockModalOkButton = document.getElementById('unlock-modal-ok-button')
+                       unlockModalOkButton.addEventListener('click', getPassword)
+                       const unlockModal = document.getElementById('unlock-modal')
+                       unlockModal.showModal()
+               }
+
+               async function changePassword (newPassword) {
+                       const getPassword = async (event) => {
+                               if (event.detail === 1) {
+                                       const oldPassword = document.getElementById('unlock-modal-password-input').value
+                                       await cancelDialog('unlock-modal')
+                                       unlockModalOkButton.removeEventListener('click', getPassword)
+                                       const wallet = await loadWallet()
+                                       await wallet.unlock(oldPassword)
+                                       await wallet.lock(newPassword)
+                               }
+                       }
+                       const unlockModalOkButton = document.getElementById('unlock-modal-ok-button')
+                       unlockModalOkButton.addEventListener('click', getPassword)
+                       const unlockModal = document.getElementById('unlock-modal')
+                       unlockModal.showModal()
+               }
+
                function updateWalletForm (id) {
                        const form = document.getElementById(id)
                        if (!form) {
                        const algorithm = document.getElementById(`${id}-algorithm-select`)
                        const typeContainer = document.getElementById(`${id}-type`)
                        const type = document.getElementById(`${id}-type-select`)
-                       const importValueContainer = document.getElementById(`${id}-value`)
-                       const importValue = document.getElementById(`${id}-value-input`)
-                       const importValueLabel = document.getElementById(`${id}-value-label`)
+                       const importSecretContainer = document.getElementById(`${id}-secret`)
+                       const importSecret = document.getElementById(`${id}-secret-input`)
+                       const importSecretLabel = document.getElementById(`${id}-secret-label`)
                        const saltContainer = document.getElementById(`${id}-salt`)
                        const salt = document.getElementById(`${id}-salt-input`)
-                       if (algorithm.value === 'bip44' && type?.value === 'mnemonic') {
+                       if (algorithm.value === 'bip44' && type?.value !== 'seed') {
                                saltContainer.classList.remove('hide')
                        } else {
                                saltContainer.classList.add('hide')
                        }
                        if (type?.value != null && type?.value !== '') {
-                               importValueContainer.classList.remove('hide')
+                               importSecretContainer.classList.remove('hide')
                        }
-                       if (importValueLabel) {
-                               importValueLabel.textContent = type.value
+                       if (importSecretLabel) {
+                               importSecretLabel.textContent = type.value
                        }
                }
 
                                default: {
                                        try {
                                                const account = await loadAccount()
-                                               createQrCode(account.address)
+                                               await loadBalance(account)
+                                               await createQrCode(account.address)
                                        } catch (err) {
                                                accountSelect.value = ''
                                                console.error(err)
                        return account
                }
 
-               async function createAccount () {
-                       let account, option, wallet
-                       const accountSelect = document.getElementById('account')
+               async function loadBalance (a) {
+                       const balanceContainer = document.getElementById('account-balance')
+                       const account = new libnemo.Account(a.address)
                        try {
-                               wallet = await loadWallet()
+                               await account.refresh('https://node.somenano.com/proxy')
                        } catch (err) {
                                console.error(err)
-                               notify.error(`Error creating account`)
-                               return
                        }
+                       balanceContainer.textContent = account.balance ?? 0
+               }
+
+               async function createAccount () {
+                       await unlockThen(async (wallet) => {
+                               const indexInput = document.getElementById('account-new-index-input')
+                               const index = indexInput?.value
+                               const accounts = await wallet.accounts(+index)
+                               await storeAccount(wallet.id, accounts[0])
+                       })
+               }
+
+               async function storeAccount (walletId, account) {
+                       let option
+                       const accountSelect = document.getElementById('account')
                        const form = document.getElementById('account-new')
-                       const name = document.getElementById('account-new-name-input')?.value
+                       const nameInput = document.getElementById('account-new-name-input')
+                       const name = nameInput?.value
                        if (name?.substring(0, 1) === '_') {
                                notify.error(`Account name cannot start with an underscore`)
                                return
                        }
-                       const index = document.getElementById('account-new-index-input')?.value
-                       const password = document.getElementById('account-new-password-input')?.value
                        try {
-                               await wallet.unlock(password)
-                               const accounts = await wallet.accounts(+index)
-                               await wallet.lock(password)
-                               const account = accounts[0]
                                const accountData = {
-                                       name: `${index}${name ? ': ' : ''}${name}`,
-                                       walletId: wallet.id,
+                                       name: `${account.index}${name ? ': ' : ''}${name}`,
+                                       walletId: walletId,
                                        index: await account.index,
                                        address: await account.address
                                }
                                console.error(err)
                                notify.error(`Error creating account`)
                        } finally {
-                               await cancelForm('account-new')
+                               await cancelDialog('account-new')
                                return account
                        }
                }
 
-               async function cancelForm (id) {
-                       await resetFormInputs()
-                       document.getElementById(id).classList.add('hide')
-                       document.querySelector(location.hash).classList.remove('hide')
+               async function cancelDialog (id) {
+                       const form = document.getElementById(id)
+                       await resetInputs()
+                       try {
+                               form.close()
+                       } catch (err) {
+                               form.classList.add('hide')
+                               document.querySelector(location.hash).classList.remove('hide')
+                       }
                }
 
-               async function resetFormInputs () {
-                       const fields = document.querySelectorAll('main form input, main form select')
+               async function resetInputs () {
+                       const fields = document.querySelectorAll('main input, main select')
                        for (const field of fields) {
                                field.value = ''
                        }
                        qrCode.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABDgAAAQ4AQMAAADW3v7MAAAABlBMVEUAAAD///+l2Z/dAAAC0klEQVR42u3cPQ6CMBiAYYWFjStwE6+mN+Mq3oCVxKQO4k8N6ABNWvK8G1+HPuuXNBzDIYuOHBwcHBwcHBwcHBwcHMU6rt3cyanf/raxmZu2AwcHBwcHBwcHBwcHR3GOdoiPLudUjvoWj6bLOTg4ODg4ODg4ODg4ynO8d62Q2BGqj52Rg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4Oj986Rg4ODg4ODg4ODg4NjlWMm///g4ODg4ODg4ODg4OBYcuQQBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHB8e+Hdcu/g4cHBwcHBwcHBwcHBz5Otrhaag4ODg4ODg4ODg4ODjKdnzvWo9O/faOsTnUNw4ODg4ODg4ODg4Ojj04XrvW1OWcxPF3n+Pg4ODg4ODg4ODg4CjIMTbPWUjjiOLg4ODg4ODg4ODg4OBYFwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHB8feHVEcHBwcHBwcHBwcHBwcvx0zcXBwcHBwcHBwcHBwcCw5coiDg4ODg4ODg4ODg4ODY013wW3C00//4ZIAAAAASUVORK5CYII='
                }
 
-               async function requestPassword (callback) {
-                       const dialog = document.createElement('dialog')
-                       dialog.classList.add('flex-y', 'center', 'mid')
-                       document.body.appendChild(dialog)
-
-                       const label = document.createElement('label')
-                       label.textContent = 'Unlock wallet to continue'
-                       label.id = 'unlock-modal-password-label'
-                       label.for = 'unlock-modal-password-input'
-                       dialog.appendChild(label)
-
-                       const input = document.createElement('input')
-                       input.type = 'password'
-                       input.placeholder = 'Password'
-                       input.id = 'unlock-modal-password-input'
-                       dialog.appendChild(input)
-
-                       const buttons = document.createElement('div')
-                       buttons.classList.add('flex-x', 'center')
-                       dialog.appendChild(buttons)
-
-                       const cancel = document.createElement('button')
-                       cancel.textContent = 'Cancel'
-                       cancel.addEventListener('click', (event) => {
-                               input.value = ''
-                               dialog.close()
-                               dialog.remove()
-                       })
-                       buttons.appendChild(cancel)
-
-                       const ok = document.createElement('button')
-                       ok.textContent = 'OK'
-                       ok.addEventListener('click', (event) => {
-                               if (event.detail === 1) {
-                                       const password = input.value
-                                       cancel.click()
-                                       callback(password)
-                               }
-                       })
-                       buttons.appendChild(ok)
-
-                       dialog.showModal()
-               }
-
-               async function exportMnemonic (password) {
+               async function exportMnemonic (wallet) {
                        try {
-                               const wallet = await loadWallet()
-                               await wallet.unlock(password)
-                               const mnemonic = wallet.mnemonic
-                               await wallet.lock(password)
-                               notify.info(mnemonic)
+                               notify.info(wallet.mnemonic)
                        } catch (err) {
                                console.error(err)
                                notify.error(`Error exporting mnemonic`)
                        }
                }
 
-               async function exportSeed (password) {
+               async function exportSeed (wallet) {
                        try {
-                               const wallet = await loadWallet()
-                               await wallet.unlock(password)
-                               const seed = wallet.seed
-                               await wallet.lock(password)
-                               notify.info(seed)
+                               notify.info(wallet.seed)
                        } catch (err) {
                                console.error(err)
                                notify.error(`Error exporting seed`)
                .page {
                        height: 100%;
                }
-               .page.form {
-                       z-index: 10;
-               }
                .hide {
                        display: none;
                }
 <body onhashchange="switchPage(event)" style="visibility:hidden;">
        <div id="notifications"></div>
        <header>
-               <form>
-
-                       <!-- Wallet Select -->
-
-                       <div id="wallets" class="grid">
-                               <label for="wallet" class="col-3">Wallet</label>
-                               <select id="wallet" class="col-9" onchange="selectWallet()">
-                                       <option disabled selected value>Choose...</option>
-                                       <hr />
-                                       <option value="_new">New</option>
-                                       <option value="_import">Import</option>
-                                       <hr />
-                                       <option disabled value="_ledger">Ledger</option>
-                                       <hr />
-                               </select>
-                       </div>
 
-                       <!-- Account Select -->
+               <!-- Wallet Select -->
 
-                       <div id="accounts" class="grid">
-                               <label for="account" class="col-3">Account</label>
-                               <select id="account" class="col-9" disabled onchange="selectAccount()">
-                                       <option disabled selected value>Choose...</option>
-                                       <hr />
-                                       <option value="_new">New</option>
-                                       <hr />
-                               </select>
-                       </div>
+               <div id="wallets" class="grid">
+                       <label for="wallet" class="col-3">Wallet</label>
+                       <select id="wallet" class="col-9" onchange="selectWallet()">
+                               <option disabled selected value>Choose...</option>
+                               <hr />
+                               <option value="_new">New</option>
+                               <option value="_import">Import</option>
+                               <hr />
+                               <option disabled value="_ledger">Ledger</option>
+                               <hr />
+                       </select>
+               </div>
+
+               <!-- Account Select -->
+
+               <div id="accounts" class="grid">
+                       <label for="account" class="col-3">Account</label>
+                       <select id="account" class="col-9" disabled onchange="selectAccount()">
+                               <option disabled selected value>Choose...</option>
+                               <hr />
+                               <option value="_new">New</option>
+                               <hr />
+                       </select>
+               </div>
+
+               <!-- Account Balance -->
+
+               <div id="account-balance">
+                       X0
+               </div>
 
-               </form>
        </header>
 
        <!-- Main Content Pages -->
                                                <option value="seed">Import seed</option>
                                        </select>
                                </div>
-                               <div id="wallet-import-value" class="flex-x left mid hide">
-                                       <label id="wallet-import-value-label" for="wallet-import-value-input"
+                               <div id="wallet-import-secret" class="flex-x left mid hide">
+                                       <label id="wallet-import-secret-label" for="wallet-import-secret-input"
                                                style="text-transform:capitalize"></label>
-                                       <input id="wallet-import-value-input" name="wallet-import-value" type="password" autocomplete="off"
+                                       <input id="wallet-import-secret-input" name="wallet-import-secret" type="password" autocomplete="off"
                                                placeholder="" required />
                                </div>
                                <div id="wallet-import-salt" class="flex-x left mid hide">
                                        <input id="account-new-index-input" name="account-new-index" type="number" autocomplete="off" min="0"
                                                max="2147483647" required value="0" />
                                </div>
-                               <div id="account-new-password" class="flex-x left mid">
-                                       <label id="account-new-password-label" for="account-new-password-input">Unlock</label>
-                                       <input id="account-new-password-input" name="account-new-password" autocomplete="off" type="password"
-                                               placeholder="Password" required />
-                               </div>
                                <div class="grid">
                                        <div class="col-3"></div>
                                        <button id="account-new-cancel" class="cancel col-3" type="reset">Cancel</button>
-                                       <button id="account-new-ok" class="col-3" onclick="createAccount().then(a => { console.log(a) })">OK</button>
+                                       <button id="account-new-ok" class="col-3" onclick="createAccount()">OK</button>
                                </div>
                        </div>
                </div>
 
+               <!-- Unlock Form -->
+
+               <dialog id="unlock-modal" class="">
+                       <div class="flex-y center mid">
+                               <label id="unlock-modal-password-label" for="unlock-modal-password-input">Unlock wallet to continue</label>
+                               <input id="unlock-modal-password-input" name="unlock-modal-password" autocomplete="off" type="password"
+                                       placeholder="Password" required />
+                               <div class="grid">
+                                       <div class="col-3"></div>
+                                       <button id="unlock-modal-cancel-button" class="cancel col-3" type="reset">Cancel</button>
+                                       <button id="unlock-modal-ok-button" class="col-3">OK</button>
+                               </div>
+                       </div>
+               </dialog>
+
                <!-- Exchange Page -->
 
                <div id="exchange" class="page hide">