mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-22 13:11:55 +00:00
list all names owned
This commit is contained in:
1 parent
2f92c6b7ae
commit
6de3e28949
8 files changed
+57
-8
No files matched your search
@@ -370,6 +370,7 @@ undocumentedCommands =
|
||||
"APICreateWallet",
|
||||
"APIDeleteChatTag",
|
||||
"APIDeleteWallet",
|
||||
"APIGetWalletNames",
|
||||
"APIScanWallet",
|
||||
"APIDeleteMemberSupportChat",
|
||||
"APIDeleteReceivedReports",
|
||||
|
||||
@@ -219,5 +219,6 @@ undocumentedResponses =
|
||||
"CRWalletAccountSecret",
|
||||
"CRWalletAddress",
|
||||
"CRWalletMnemonic",
|
||||
"CRWalletNames",
|
||||
"CRWelcome"
|
||||
]
|
||||
@@ -68,7 +68,7 @@ import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Chat.Wallet (AccountIndex, WalletAddress, WalletError)
|
||||
import Simplex.Chat.Wallet (AccountIndex, WalletAddress, WalletError, WalletName)
|
||||
import Simplex.Chat.Util (liftIOEither)
|
||||
import Simplex.FileTransfer.Description (FileDescriptionURI)
|
||||
import Simplex.Messaging.Server.Information (ServerPublicInfo)
|
||||
@@ -444,6 +444,7 @@ data ChatCommand
|
||||
| APIExportWalletMnemonic
|
||||
| APIExportWalletAccount {accountIndex :: AccountIndex}
|
||||
| APIScanWallet
|
||||
| APIGetWalletNames
|
||||
| APIDeleteWallet
|
||||
| APISendCallInvitation ContactId CallType
|
||||
| SendCallInvitation ContactName CallType
|
||||
@@ -883,6 +884,7 @@ data ChatResponse
|
||||
| CRWallet {user :: User, accountIndexes_ :: Maybe [AccountIndex]}
|
||||
| CRWalletMnemonic {user :: User, mnemonic :: Text}
|
||||
| CRWalletAddress {user :: User, walletAddress :: WalletAddress}
|
||||
| CRWalletNames {user :: User, walletNames :: [WalletName]}
|
||||
| CRWalletAccountSecret {user :: User, walletAddress :: WalletAddress, secret :: Text}
|
||||
| CRUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact}
|
||||
| CRUserDeletedMembers {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], withMessages :: Bool, msgSigned :: Bool}
|
||||
|
||||
@@ -65,7 +65,7 @@ import Simplex.Chat.Badges.Types (BadgeAlert (..), BadgeAlertKind (..), BadgeSta
|
||||
import Simplex.Chat.Badges.Code (badgeCodeText, parseBadgeCode)
|
||||
import Simplex.Chat.Badges.Service (BadgeBalance (..), BadgeServiceCommand (..), BadgeServiceErrorCode (..), BadgeServiceRequest (..), BadgeServiceResponse (..), BadgeStatement (..), StatementDebitType (..), StatementEntry (..), StatementEntryType (..), currentBadgeServiceVersion)
|
||||
import Simplex.Chat.Names (SimplexDomainProof (..), SimplexDomainClaim (..), claimDomain, mkDomainClaim)
|
||||
import Simplex.Chat.Store.Wallets (WalletSeed (..), accountHeldByOther, bindAccount, createWalletSeed, deleteWalletSeed, getUserAccounts, getWalletSeed, recordScan, resolveAccount)
|
||||
import Simplex.Chat.Store.Wallets (WalletSeed (..), accountHeldByOther, bindAccount, createWalletSeed, deleteWalletSeed, getUserAccounts, getUserNames, getWalletSeed, recordScan, resolveAccount)
|
||||
import Simplex.Chat.Wallet (AccountIndex, AccountKey, WalletAddress (..), WalletError (..), accountSecret, deriveAccountKey, entropyFromMnemonic, newSeedEntropy, renderAccountPath, scanGapLimit, scanMaxAccounts, seedMaster, seedMnemonic)
|
||||
import Simplex.Messaging.Eth.Address (addressFromPrivateKey)
|
||||
import Simplex.Messaging.Names.Record (NameResponse, OwnedNames (..))
|
||||
@@ -1527,6 +1527,8 @@ processChatCommand cxt nm = \case
|
||||
when heldByOther $ throwWalletError WEAccountBound
|
||||
k <- accountKey seed n
|
||||
pure $ CRWalletAccountSecret user (accountAddress n k) (accountSecret k)
|
||||
APIGetWalletNames -> withUser $ \user@User {userId} ->
|
||||
CRWalletNames user <$> withFastStore' (\db -> getWalletSeed db >>= maybe (pure []) (\WalletSeed {wsId} -> getUserNames db wsId userId))
|
||||
APIScanWallet -> withUser $ \user@User {userId, viewPwdHash} -> do
|
||||
when (isJust viewPwdHash) $ throwWalletError WEHiddenProfile
|
||||
seed <- walletSeed
|
||||
@@ -6119,6 +6121,7 @@ chatCommandP =
|
||||
"/_wallet address" $> APIGetWalletAddress Nothing,
|
||||
"/_wallet export master" $> APIExportWalletMnemonic,
|
||||
"/_wallet export account " *> (APIExportWalletAccount <$> accountIndexP),
|
||||
"/_wallet names" $> APIGetWalletNames,
|
||||
"/_wallet scan" $> APIScanWallet,
|
||||
"/_wallet delete" $> APIDeleteWallet,
|
||||
"/_wallet" $> APIGetWallet,
|
||||
|
||||
@@ -14,6 +14,7 @@ module Simplex.Chat.Store.Wallets
|
||||
deleteWalletSeed,
|
||||
resolveAccount,
|
||||
getUserAccounts,
|
||||
getUserNames,
|
||||
accountHeldByOther,
|
||||
bindAccount,
|
||||
recordScan,
|
||||
@@ -26,7 +27,7 @@ import Control.Monad.IO.Class (liftIO)
|
||||
import qualified Data.ByteArray as BA
|
||||
import Data.ByteString (ByteString)
|
||||
import Data.Int (Int64)
|
||||
import Simplex.Chat.Wallet (AccountIndex, WalletError (..), checkAccountIndex)
|
||||
import Simplex.Chat.Wallet (AccountIndex, WalletError (..), WalletName (..), checkAccountIndex)
|
||||
import Simplex.Messaging.Agent.Protocol (UserId)
|
||||
import Simplex.Messaging.Names.Record (NameRecord (..), NameRegistration (..), NameResponse (..))
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow)
|
||||
@@ -107,6 +108,22 @@ getUserAccounts db sId userId =
|
||||
|]
|
||||
(sId, userId)
|
||||
|
||||
-- | The names the profile's accounts hold, as the last scan saw them.
|
||||
getUserNames :: DB.Connection -> SeedId -> UserId -> IO [WalletName]
|
||||
getUserNames db sId userId =
|
||||
map toName
|
||||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT n.account_index, n.name FROM wallet_owned_names n
|
||||
JOIN wallet_accounts a ON a.wallet_seed_id = n.wallet_seed_id AND a.account_index = n.account_index
|
||||
WHERE n.wallet_seed_id = ? AND a.user_id = ?
|
||||
ORDER BY n.name
|
||||
|]
|
||||
(sId, userId)
|
||||
where
|
||||
toName (n, name) = WalletName {accountIndex = fromIntegral @Int64 n, name}
|
||||
|
||||
-- | Which profile holds an account: 'Nothing' when it is unknown, @Just Nothing@ when no profile holds it.
|
||||
accountUser :: DB.Connection -> SeedId -> AccountIndex -> IO (Maybe (Maybe Int64))
|
||||
accountUser db sId n =
|
||||
|
||||
@@ -60,7 +60,7 @@ import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Chat.Wallet (WalletAddress (..), WalletError (..))
|
||||
import Simplex.Chat.Wallet (WalletAddress (..), WalletError (..), WalletName (..))
|
||||
import qualified Simplex.FileTransfer.Transport as XFTP
|
||||
import Simplex.Messaging.Agent (DatabaseDiff (..))
|
||||
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..))
|
||||
@@ -202,6 +202,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, showFullLinks, te
|
||||
Just accounts -> [plain $ "accounts: " <> T.intercalate ", " (map tshow accounts)]
|
||||
CRWalletMnemonic u mnemonic -> ttyUser u [plain mnemonic]
|
||||
CRWalletAddress u a -> ttyUser u [walletAddressRow a]
|
||||
CRWalletNames u names -> ttyUser u $ if null names then ["wallet, no names for this profile"] else map walletNameRow names
|
||||
CRWalletAccountSecret u a secret -> ttyUser u [walletAddressRow a <> " " <> plain secret]
|
||||
CRGroupCreated u g -> ttyUser u $ viewGroupCreated g testView
|
||||
CRPublicGroupCreated u g _groupLink _relays -> ttyUser u $ viewGroupCreated g testView
|
||||
@@ -1114,6 +1115,9 @@ walletAddressRow :: WalletAddress -> StyledString
|
||||
walletAddressRow WalletAddress {accountIndex, keyPath, address} =
|
||||
plain $ tshow accountIndex <> " " <> keyPath <> " " <> address
|
||||
|
||||
walletNameRow :: WalletName -> StyledString
|
||||
walletNameRow WalletName {accountIndex, name} = plain $ tshow accountIndex <> " " <> name
|
||||
|
||||
walletErrorText :: WalletError -> Text
|
||||
walletErrorText = \case
|
||||
WENoMaster -> "this device has no wallet"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
|
||||
@@ -6,6 +7,7 @@ module Simplex.Chat.Wallet
|
||||
( AccountIndex,
|
||||
AccountKey,
|
||||
WalletAddress (..),
|
||||
WalletName (..),
|
||||
WalletError (..),
|
||||
newSeedEntropy,
|
||||
entropyFromMnemonic,
|
||||
@@ -48,6 +50,13 @@ data WalletAddress = WalletAddress
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
-- | A name one of the seed's accounts holds, as the last scan saw it.
|
||||
data WalletName = WalletName
|
||||
{ accountIndex :: AccountIndex,
|
||||
name :: Text
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data WalletError
|
||||
= WENoMaster -- the device has no master entropy
|
||||
| WEMasterExists -- create, when it already has one
|
||||
@@ -117,4 +126,6 @@ bipError = either (Left . WEDerivation) Right
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''WalletAddress)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''WalletName)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "WE") ''WalletError)
|
||||
+14
-4
@@ -7,13 +7,13 @@ module WalletTests where
|
||||
import ChatClient
|
||||
import ChatTests.DBUtils
|
||||
import ChatTests.Utils
|
||||
import Database.SQLite.Simple (Only (..))
|
||||
import qualified Data.ByteArray as BA
|
||||
import qualified Data.ByteArray.Encoding as BAE
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Char (toUpper)
|
||||
import Data.Either (isRight)
|
||||
import Data.Int (Int64)
|
||||
import Data.List (nub)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
@@ -111,6 +111,7 @@ walletScanTests = do
|
||||
it "a scan of a recovered phrase finds the accounts in use" testWalletScan
|
||||
it "the counter a scan sets clears the accounts already bound" testWalletScanCounterPastBound
|
||||
it "a hidden profile cannot scan" testWalletScanHiddenProfile
|
||||
it "a profile with no names says so" testWalletNamesEmpty
|
||||
|
||||
testWalletScan :: HasCallStack => TestParams -> IO ()
|
||||
testWalletScan ps = withSmpServerAndNames $ \reg -> withNewTestChat ps "alice" aliceProfile $ \alice -> do
|
||||
@@ -121,9 +122,10 @@ testWalletScan ps = withSmpServerAndNames $ \reg -> withNewTestChat ps "alice" a
|
||||
alice ##> "/_wallet scan"
|
||||
alice <## "accounts: 1"
|
||||
-- the names the scan saw are recorded against the account that holds them
|
||||
names <- withCCTransaction alice $ \db -> DB.query_ db "SELECT account_index, name, name_response FROM wallet_owned_names"
|
||||
map (\(n, nm, _) -> (n, nm)) names `shouldBe` [(1 :: Int64, "alice.simplex" :: Text)]
|
||||
map (\(_, _, r) -> registeredName <$> decodeJSON r) names `shouldBe` [Just (Just "alice.simplex")]
|
||||
alice ##> "/_wallet names"
|
||||
alice <## "1 alice.simplex"
|
||||
names <- withCCTransaction alice $ \db -> DB.query_ db "SELECT name_response FROM wallet_owned_names"
|
||||
map (\(Only r) -> registeredName <$> decodeJSON r) names `shouldBe` [Just (Just "alice.simplex")]
|
||||
-- the scan gives the imported phrase the counter it had none of
|
||||
alice ##> "/_wallet bind"
|
||||
alice <## "accounts: 1, 2"
|
||||
@@ -294,6 +296,14 @@ testWalletScanCounterPastBound ps = withSmpServerAndNames $ \_reg -> withNewTest
|
||||
alice ##> "/_wallet bind"
|
||||
alice <## "accounts: 5, 6"
|
||||
|
||||
-- | Names come from a scan, so a profile that has not scanned has none to list.
|
||||
testWalletNamesEmpty :: HasCallStack => TestParams -> IO ()
|
||||
testWalletNamesEmpty ps = withNewTestChat ps "alice" aliceProfile $ \alice -> do
|
||||
alice ##> "/_wallet create new"
|
||||
alice <## "wallet, no accounts for this profile"
|
||||
alice ##> "/_wallet names"
|
||||
alice <## "wallet, no names for this profile"
|
||||
|
||||
-- | A scan binds what it finds, so it is refused where a bind is.
|
||||
testWalletScanHiddenProfile :: HasCallStack => TestParams -> IO ()
|
||||
testWalletScanHiddenProfile ps = withSmpServerAndNames $ \_reg -> withNewTestChat ps "alice" aliceProfile $ \alice -> do
|
||||
|
||||
Reference in new issue
Block a user