agentvault 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +9 -0
- package/README.md +1 -1
- package/dist/cli/commands/approve.js +5 -5
- package/dist/cli/commands/archive.js +5 -5
- package/dist/cli/commands/backup.js +5 -5
- package/dist/cli/commands/cloud-backup.js +12 -12
- package/dist/cli/commands/decrypt.js +2 -2
- package/dist/cli/commands/deploy.js +1 -1
- package/dist/cli/commands/exec.js +2 -2
- package/dist/cli/commands/fetch.js +4 -4
- package/dist/cli/commands/inference.js +5 -5
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.js +16 -16
- package/dist/cli/commands/list.js +4 -4
- package/dist/cli/commands/package.js +2 -2
- package/dist/cli/commands/profile.js +1 -1
- package/dist/cli/commands/rebuild.js +2 -2
- package/dist/cli/commands/show.js +1 -1
- package/dist/cli/commands/status.d.ts +1 -1
- package/dist/cli/commands/status.js +8 -8
- package/dist/cli/commands/trace.js +1 -1
- package/dist/cli/commands/wallet-export.js +1 -1
- package/dist/cli/commands/wallet-sign.js +1 -1
- package/dist/cli/commands/wallet.d.ts +1 -1
- package/dist/cli/commands/wallet.js +1 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +3 -3
- package/dist/src/archival/archive-manager.d.ts +85 -0
- package/dist/src/archival/archive-manager.js +294 -0
- package/dist/src/archival/arweave-client.d.ts +88 -0
- package/dist/src/archival/arweave-client.js +223 -0
- package/dist/src/archival/index.d.ts +8 -0
- package/{src/archival/index.ts → dist/src/archival/index.js} +1 -1
- package/dist/src/backup/backup.d.ts +67 -0
- package/dist/src/backup/backup.js +231 -0
- package/dist/src/backup/index.d.ts +7 -0
- package/{src/backup/index.ts → dist/src/backup/index.js} +1 -1
- package/dist/src/cloud-storage/cloud-sync.d.ts +49 -0
- package/dist/src/cloud-storage/cloud-sync.js +372 -0
- package/dist/src/cloud-storage/index.d.ts +11 -0
- package/{src/cloud-storage/index.ts → dist/src/cloud-storage/index.js} +1 -1
- package/dist/src/cloud-storage/provider-detector.d.ts +34 -0
- package/dist/src/cloud-storage/provider-detector.js +158 -0
- package/{src/cloud-storage/types.ts → dist/src/cloud-storage/types.d.ts} +40 -53
- package/dist/src/cloud-storage/types.js +10 -0
- package/dist/src/debugging/index.d.ts +6 -0
- package/{src/debugging/index.ts → dist/src/debugging/index.js} +1 -1
- package/dist/src/debugging/logs.d.ts +32 -0
- package/dist/src/debugging/logs.js +158 -0
- package/dist/src/debugging/types.d.ts +91 -0
- package/dist/src/debugging/types.js +5 -0
- package/dist/src/deployment/deployer.d.ts +52 -0
- package/dist/src/deployment/deployer.js +211 -0
- package/dist/src/deployment/icpClient.d.ts +144 -0
- package/dist/src/deployment/icpClient.js +545 -0
- package/dist/src/deployment/index.d.ts +11 -0
- package/dist/src/deployment/index.js +14 -0
- package/dist/src/deployment/promotion.d.ts +32 -0
- package/dist/src/deployment/promotion.js +114 -0
- package/dist/src/deployment/types.d.ts +101 -0
- package/dist/src/deployment/types.js +5 -0
- package/dist/src/icp/batch.d.ts +112 -0
- package/dist/src/icp/batch.js +273 -0
- package/dist/src/icp/cycles.d.ts +29 -0
- package/{src/icp/cycles.ts → dist/src/icp/cycles.js} +8 -22
- package/dist/src/icp/environment.d.ts +60 -0
- package/dist/src/icp/environment.js +183 -0
- package/dist/src/icp/icpcli.d.ts +204 -0
- package/dist/src/icp/icpcli.js +374 -0
- package/dist/src/icp/icwasm.d.ts +94 -0
- package/dist/src/icp/icwasm.js +197 -0
- package/dist/src/icp/identity.d.ts +50 -0
- package/{src/icp/identity.ts → dist/src/icp/identity.js} +15 -28
- package/dist/src/icp/index.d.ts +16 -0
- package/dist/src/icp/index.js +20 -0
- package/dist/src/icp/optimization.d.ts +16 -0
- package/dist/src/icp/optimization.js +225 -0
- package/dist/src/icp/tokens.d.ts +24 -0
- package/{src/icp/tokens.ts → dist/src/icp/tokens.js} +5 -12
- package/dist/src/icp/tool-detector.d.ts +31 -0
- package/dist/src/icp/tool-detector.js +104 -0
- package/dist/src/icp/types.d.ts +493 -0
- package/dist/src/icp/types.js +7 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +18 -0
- package/dist/src/inference/bittensor-client.d.ts +108 -0
- package/dist/src/inference/bittensor-client.js +224 -0
- package/dist/src/inference/index.d.ts +8 -0
- package/{src/inference/index.ts → dist/src/inference/index.js} +1 -1
- package/dist/src/inference/inference-manager.d.ts +76 -0
- package/dist/src/inference/inference-manager.js +228 -0
- package/dist/src/metrics/index.d.ts +7 -0
- package/{src/metrics/index.ts → dist/src/metrics/index.js} +1 -1
- package/dist/src/metrics/metrics.d.ts +39 -0
- package/dist/src/metrics/metrics.js +129 -0
- package/dist/src/monitoring/alerting.d.ts +51 -0
- package/dist/src/monitoring/alerting.js +169 -0
- package/dist/src/monitoring/health.d.ts +40 -0
- package/dist/src/monitoring/health.js +164 -0
- package/dist/src/monitoring/index.d.ts +10 -0
- package/dist/src/monitoring/index.js +12 -0
- package/dist/src/monitoring/info.d.ts +15 -0
- package/dist/src/monitoring/info.js +109 -0
- package/dist/src/monitoring/types.d.ts +93 -0
- package/dist/src/monitoring/types.js +7 -0
- package/dist/src/network/index.d.ts +5 -0
- package/{src/network/index.ts → dist/src/network/index.js} +1 -1
- package/dist/src/network/network-config.d.ts +31 -0
- package/dist/src/network/network-config.js +109 -0
- package/dist/src/packaging/compiler.d.ts +61 -0
- package/dist/src/packaging/compiler.js +562 -0
- package/dist/src/packaging/config-persistence.d.ts +46 -0
- package/dist/src/packaging/config-persistence.js +108 -0
- package/dist/src/packaging/config-schemas.d.ts +115 -0
- package/dist/src/packaging/config-schemas.js +43 -0
- package/dist/src/packaging/detector.d.ts +26 -0
- package/dist/src/packaging/detector.js +193 -0
- package/dist/src/packaging/index.d.ts +16 -0
- package/dist/src/packaging/index.js +22 -0
- package/dist/src/packaging/packager.d.ts +31 -0
- package/dist/src/packaging/packager.js +90 -0
- package/dist/src/packaging/parsers/clawdbot.d.ts +19 -0
- package/dist/src/packaging/parsers/clawdbot.js +231 -0
- package/dist/src/packaging/parsers/cline.d.ts +26 -0
- package/dist/src/packaging/parsers/cline.js +185 -0
- package/dist/src/packaging/parsers/generic.d.ts +27 -0
- package/dist/src/packaging/parsers/generic.js +228 -0
- package/dist/src/packaging/parsers/goose.d.ts +26 -0
- package/dist/src/packaging/parsers/goose.js +175 -0
- package/dist/src/packaging/parsers/index.d.ts +11 -0
- package/{src/packaging/parsers/index.ts → dist/src/packaging/parsers/index.js} +1 -1
- package/dist/src/packaging/serializer.d.ts +108 -0
- package/dist/src/packaging/serializer.js +153 -0
- package/dist/src/packaging/types.d.ts +131 -0
- package/dist/src/packaging/types.js +5 -0
- package/dist/src/packaging/wasmedge-compiler.d.ts +76 -0
- package/dist/src/packaging/wasmedge-compiler.js +349 -0
- package/dist/src/security/index.d.ts +11 -0
- package/{src/security/index.ts → dist/src/security/index.js} +1 -4
- package/dist/src/security/multisig.d.ts +102 -0
- package/dist/src/security/multisig.js +283 -0
- package/dist/src/security/types.d.ts +207 -0
- package/dist/src/security/types.js +217 -0
- package/dist/src/security/vetkeys.d.ts +179 -0
- package/dist/src/security/vetkeys.js +499 -0
- package/dist/src/testing/index.d.ts +6 -0
- package/{src/testing/index.ts → dist/src/testing/index.js} +1 -1
- package/dist/src/testing/local-runner.d.ts +23 -0
- package/dist/src/testing/local-runner.js +226 -0
- package/dist/src/testing/types.d.ts +98 -0
- package/dist/src/testing/types.js +5 -0
- package/dist/src/wallet/cbor-serializer.d.ts +82 -0
- package/dist/src/wallet/cbor-serializer.js +282 -0
- package/dist/src/wallet/chain-dispatcher.d.ts +112 -0
- package/dist/src/wallet/chain-dispatcher.js +241 -0
- package/dist/src/wallet/cross-chain-aggregator.d.ts +119 -0
- package/dist/src/wallet/cross-chain-aggregator.js +235 -0
- package/dist/src/wallet/index.d.ts +16 -0
- package/dist/src/wallet/index.js +22 -0
- package/dist/src/wallet/key-derivation.d.ts +117 -0
- package/dist/src/wallet/key-derivation.js +325 -0
- package/dist/src/wallet/providers/base-provider.d.ts +111 -0
- package/dist/src/wallet/providers/base-provider.js +58 -0
- package/dist/src/wallet/providers/cketh-provider.d.ts +104 -0
- package/dist/src/wallet/providers/cketh-provider.js +343 -0
- package/dist/src/wallet/providers/polkadot-provider.d.ts +115 -0
- package/dist/src/wallet/providers/polkadot-provider.js +407 -0
- package/dist/src/wallet/providers/solana-provider.d.ts +102 -0
- package/dist/src/wallet/providers/solana-provider.js +393 -0
- package/dist/src/wallet/transaction-queue.d.ts +133 -0
- package/dist/src/wallet/transaction-queue.js +195 -0
- package/dist/src/wallet/types.d.ts +167 -0
- package/dist/src/wallet/types.js +5 -0
- package/dist/src/wallet/vetkeys-adapter.d.ts +134 -0
- package/dist/src/wallet/vetkeys-adapter.js +313 -0
- package/dist/src/wallet/wallet-manager.d.ts +202 -0
- package/dist/src/wallet/wallet-manager.js +451 -0
- package/dist/src/wallet/wallet-storage.d.ts +131 -0
- package/dist/src/wallet/wallet-storage.js +274 -0
- package/macos-wallet-app/AgentVaultWallet/App/AgentVaultWalletApp.swift +54 -0
- package/macos-wallet-app/AgentVaultWallet/Models/AppState.swift +102 -0
- package/macos-wallet-app/AgentVaultWallet/Models/Chain.swift +121 -0
- package/macos-wallet-app/AgentVaultWallet/Models/Wallet.swift +98 -0
- package/macos-wallet-app/AgentVaultWallet/Resources/AgentVaultWallet.entitlements +27 -0
- package/macos-wallet-app/AgentVaultWallet/Resources/Info.plist +69 -0
- package/macos-wallet-app/AgentVaultWallet/Services/BackupService.swift +270 -0
- package/macos-wallet-app/AgentVaultWallet/Services/CLIBridge.swift +367 -0
- package/macos-wallet-app/AgentVaultWallet/Services/CryptoService.swift +157 -0
- package/macos-wallet-app/AgentVaultWallet/Services/FileService.swift +120 -0
- package/macos-wallet-app/AgentVaultWallet/Services/KeychainService.swift +219 -0
- package/macos-wallet-app/AgentVaultWallet/Utilities/Constants.swift +44 -0
- package/macos-wallet-app/AgentVaultWallet/Utilities/Extensions.swift +115 -0
- package/macos-wallet-app/AgentVaultWallet/ViewModels/BackupViewModel.swift +237 -0
- package/macos-wallet-app/AgentVaultWallet/ViewModels/CreateWalletViewModel.swift +137 -0
- package/macos-wallet-app/AgentVaultWallet/ViewModels/ImportWalletViewModel.swift +179 -0
- package/macos-wallet-app/AgentVaultWallet/ViewModels/WalletStore.swift +286 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Backup/BackupView.swift +235 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Backup/RestoreView.swift +316 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Create/CreateWalletFlow.swift +438 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Import/ImportWalletFlow.swift +399 -0
- package/macos-wallet-app/AgentVaultWallet/Views/MainView.swift +134 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Settings/SettingsView.swift +276 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Sidebar/SidebarView.swift +133 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Wallet/DashboardView.swift +233 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Wallet/WalletDetailView.swift +281 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Wallet/WalletListView.swift +280 -0
- package/macos-wallet-app/AgentVaultWallet/Views/Welcome/WelcomeView.swift +176 -0
- package/macos-wallet-app/Makefile +47 -0
- package/macos-wallet-app/project.yml +40 -0
- package/macos-wallet-app/setup.sh +73 -0
- package/package.json +10 -2
- package/backups/agentvault-backup-test-agent-2026-02-12T17-54-28-967Z.json +0 -28
- package/backups/agentvault-backup-test-agent-2026-02-12T17-54-29-032Z.backup +0 -1
- package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-373Z.json +0 -28
- package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-428Z.backup +0 -1
- package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-132Z.json +0 -28
- package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-247Z.backup +0 -1
- package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-216Z.json +0 -28
- package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-283Z.backup +0 -1
- package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-772Z.backup +0 -1
- package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-793Z.json +0 -28
- package/backups/test-backup.json +0 -28
- package/scripts/dev-dashboard.mjs +0 -84
- package/site/README.md +0 -63
- package/site/docusaurus.config.ts +0 -148
- package/site/package-lock.json +0 -18383
- package/site/package.json +0 -47
- package/site/sidebars.ts +0 -86
- package/site/static/.gitkeep +0 -0
- package/site/static/img/logo.svg +0 -28
- package/site/static/img/og-image.svg +0 -35
- package/src/archival/archive-manager.ts +0 -372
- package/src/archival/arweave-client.ts +0 -289
- package/src/backup/backup.ts +0 -315
- package/src/cloud-storage/cloud-sync.ts +0 -461
- package/src/cloud-storage/provider-detector.ts +0 -198
- package/src/debugging/logs.ts +0 -193
- package/src/debugging/types.ts +0 -100
- package/src/deployment/deployer.ts +0 -274
- package/src/deployment/icpClient.ts +0 -620
- package/src/deployment/index.ts +0 -46
- package/src/deployment/promotion.ts +0 -161
- package/src/deployment/types.ts +0 -111
- package/src/icp/batch.ts +0 -374
- package/src/icp/environment.ts +0 -215
- package/src/icp/icpcli.ts +0 -438
- package/src/icp/icwasm.ts +0 -222
- package/src/icp/index.ts +0 -94
- package/src/icp/optimization.ts +0 -242
- package/src/icp/tool-detector.ts +0 -110
- package/src/icp/types.ts +0 -574
- package/src/index.ts +0 -25
- package/src/inference/bittensor-client.ts +0 -304
- package/src/inference/inference-manager.ts +0 -327
- package/src/metrics/metrics.ts +0 -186
- package/src/monitoring/alerting.ts +0 -190
- package/src/monitoring/health.ts +0 -197
- package/src/monitoring/index.ts +0 -38
- package/src/monitoring/info.ts +0 -114
- package/src/monitoring/types.ts +0 -99
- package/src/network/network-config.ts +0 -129
- package/src/packaging/compiler.ts +0 -647
- package/src/packaging/config-persistence.ts +0 -135
- package/src/packaging/config-schemas.ts +0 -156
- package/src/packaging/detector.ts +0 -220
- package/src/packaging/index.ts +0 -90
- package/src/packaging/packager.ts +0 -118
- package/src/packaging/parsers/clawdbot.ts +0 -278
- package/src/packaging/parsers/cline.ts +0 -223
- package/src/packaging/parsers/generic.ts +0 -266
- package/src/packaging/parsers/goose.ts +0 -214
- package/src/packaging/serializer.ts +0 -260
- package/src/packaging/types.ts +0 -144
- package/src/packaging/wasmedge-compiler.ts +0 -406
- package/src/security/multisig.ts +0 -415
- package/src/security/types.ts +0 -416
- package/src/security/vetkeys.ts +0 -655
- package/src/testing/local-runner.ts +0 -264
- package/src/testing/types.ts +0 -104
- package/src/wallet/cbor-serializer.ts +0 -323
- package/src/wallet/chain-dispatcher.ts +0 -313
- package/src/wallet/cross-chain-aggregator.ts +0 -346
- package/src/wallet/index.ts +0 -76
- package/src/wallet/key-derivation.ts +0 -425
- package/src/wallet/providers/base-provider.ts +0 -154
- package/src/wallet/providers/cketh-provider.ts +0 -434
- package/src/wallet/providers/polkadot-provider.ts +0 -503
- package/src/wallet/providers/solana-provider.ts +0 -490
- package/src/wallet/transaction-queue.ts +0 -284
- package/src/wallet/types.ts +0 -178
- package/src/wallet/vetkeys-adapter.ts +0 -431
- package/src/wallet/wallet-manager.ts +0 -597
- package/src/wallet/wallet-storage.ts +0 -380
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
/// Manages secure storage of wallet secrets in the macOS Keychain.
|
|
5
|
+
/// Private keys and mnemonics are stored here, never on disk in plaintext.
|
|
6
|
+
final class KeychainService {
|
|
7
|
+
|
|
8
|
+
static let shared = KeychainService()
|
|
9
|
+
|
|
10
|
+
private let servicePrefix = "com.agentvault.wallet"
|
|
11
|
+
|
|
12
|
+
enum KeychainError: LocalizedError {
|
|
13
|
+
case saveFailed(OSStatus)
|
|
14
|
+
case readFailed(OSStatus)
|
|
15
|
+
case deleteFailed(OSStatus)
|
|
16
|
+
case dataConversionFailed
|
|
17
|
+
case itemNotFound
|
|
18
|
+
|
|
19
|
+
var errorDescription: String? {
|
|
20
|
+
switch self {
|
|
21
|
+
case .saveFailed(let status):
|
|
22
|
+
return "Keychain save failed (status: \(status))"
|
|
23
|
+
case .readFailed(let status):
|
|
24
|
+
return "Keychain read failed (status: \(status))"
|
|
25
|
+
case .deleteFailed(let status):
|
|
26
|
+
return "Keychain delete failed (status: \(status))"
|
|
27
|
+
case .dataConversionFailed:
|
|
28
|
+
return "Failed to convert data for Keychain storage"
|
|
29
|
+
case .itemNotFound:
|
|
30
|
+
return "Item not found in Keychain"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private init() {}
|
|
36
|
+
|
|
37
|
+
// MARK: - Mnemonic Storage
|
|
38
|
+
|
|
39
|
+
/// Save a mnemonic phrase for a wallet
|
|
40
|
+
func saveMnemonic(_ mnemonic: String, forWalletId id: UUID) throws {
|
|
41
|
+
let key = mnemonicKey(for: id)
|
|
42
|
+
try saveString(mnemonic, forKey: key)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Retrieve a mnemonic phrase for a wallet
|
|
46
|
+
func getMnemonic(forWalletId id: UUID) throws -> String {
|
|
47
|
+
let key = mnemonicKey(for: id)
|
|
48
|
+
return try getString(forKey: key)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Delete a stored mnemonic
|
|
52
|
+
func deleteMnemonic(forWalletId id: UUID) throws {
|
|
53
|
+
let key = mnemonicKey(for: id)
|
|
54
|
+
try deleteItem(forKey: key)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Check if a mnemonic exists for a wallet
|
|
58
|
+
func hasMnemonic(forWalletId id: UUID) -> Bool {
|
|
59
|
+
let key = mnemonicKey(for: id)
|
|
60
|
+
return (try? getString(forKey: key)) != nil
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// MARK: - Private Key Storage
|
|
64
|
+
|
|
65
|
+
/// Save a private key for a wallet
|
|
66
|
+
func savePrivateKey(_ key: String, forWalletId id: UUID) throws {
|
|
67
|
+
let storageKey = privateKeyKey(for: id)
|
|
68
|
+
try saveString(key, forKey: storageKey)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Retrieve a private key for a wallet
|
|
72
|
+
func getPrivateKey(forWalletId id: UUID) throws -> String {
|
|
73
|
+
let key = privateKeyKey(for: id)
|
|
74
|
+
return try getString(forKey: key)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Delete a stored private key
|
|
78
|
+
func deletePrivateKey(forWalletId id: UUID) throws {
|
|
79
|
+
let key = privateKeyKey(for: id)
|
|
80
|
+
try deleteItem(forKey: key)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// MARK: - JWK Storage (Arweave)
|
|
84
|
+
|
|
85
|
+
/// Save JWK data for an Arweave wallet
|
|
86
|
+
func saveJWK(_ jwkData: Data, forWalletId id: UUID) throws {
|
|
87
|
+
let key = jwkKey(for: id)
|
|
88
|
+
try saveData(jwkData, forKey: key)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Retrieve JWK data for an Arweave wallet
|
|
92
|
+
func getJWK(forWalletId id: UUID) throws -> Data {
|
|
93
|
+
let key = jwkKey(for: id)
|
|
94
|
+
return try getData(forKey: key)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Delete stored JWK data
|
|
98
|
+
func deleteJWK(forWalletId id: UUID) throws {
|
|
99
|
+
let key = jwkKey(for: id)
|
|
100
|
+
try deleteItem(forKey: key)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// MARK: - Backup Password
|
|
104
|
+
|
|
105
|
+
/// Save the backup encryption password hint (NOT the actual password)
|
|
106
|
+
func saveBackupPasswordHint(_ hint: String) throws {
|
|
107
|
+
try saveString(hint, forKey: "\(servicePrefix).backup-hint")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func getBackupPasswordHint() -> String? {
|
|
111
|
+
try? getString(forKey: "\(servicePrefix).backup-hint")
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MARK: - Bulk Operations
|
|
115
|
+
|
|
116
|
+
/// Delete all secrets for a wallet
|
|
117
|
+
func deleteAllSecrets(forWalletId id: UUID) {
|
|
118
|
+
try? deleteMnemonic(forWalletId: id)
|
|
119
|
+
try? deletePrivateKey(forWalletId: id)
|
|
120
|
+
try? deleteJWK(forWalletId: id)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Delete everything from our Keychain namespace
|
|
124
|
+
func purgeAll() {
|
|
125
|
+
let query: [String: Any] = [
|
|
126
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
127
|
+
kSecAttrService as String: servicePrefix,
|
|
128
|
+
]
|
|
129
|
+
SecItemDelete(query as CFDictionary)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// MARK: - Private Helpers
|
|
133
|
+
|
|
134
|
+
private func mnemonicKey(for id: UUID) -> String {
|
|
135
|
+
"\(servicePrefix).mnemonic.\(id.uuidString)"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private func privateKeyKey(for id: UUID) -> String {
|
|
139
|
+
"\(servicePrefix).privatekey.\(id.uuidString)"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private func jwkKey(for id: UUID) -> String {
|
|
143
|
+
"\(servicePrefix).jwk.\(id.uuidString)"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private func saveString(_ value: String, forKey key: String) throws {
|
|
147
|
+
guard let data = value.data(using: .utf8) else {
|
|
148
|
+
throw KeychainError.dataConversionFailed
|
|
149
|
+
}
|
|
150
|
+
try saveData(data, forKey: key)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private func getString(forKey key: String) throws -> String {
|
|
154
|
+
let data = try getData(forKey: key)
|
|
155
|
+
guard let string = String(data: data, encoding: .utf8) else {
|
|
156
|
+
throw KeychainError.dataConversionFailed
|
|
157
|
+
}
|
|
158
|
+
return string
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private func saveData(_ data: Data, forKey key: String) throws {
|
|
162
|
+
// Delete existing item first
|
|
163
|
+
try? deleteItem(forKey: key)
|
|
164
|
+
|
|
165
|
+
let query: [String: Any] = [
|
|
166
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
167
|
+
kSecAttrService as String: servicePrefix,
|
|
168
|
+
kSecAttrAccount as String: key,
|
|
169
|
+
kSecValueData as String: data,
|
|
170
|
+
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
171
|
+
kSecAttrLabel as String: "AgentVault Wallet Secret",
|
|
172
|
+
kSecAttrDescription as String: "Encrypted wallet key material",
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
let status = SecItemAdd(query as CFDictionary, nil)
|
|
176
|
+
guard status == errSecSuccess else {
|
|
177
|
+
throw KeychainError.saveFailed(status)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private func getData(forKey key: String) throws -> Data {
|
|
182
|
+
let query: [String: Any] = [
|
|
183
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
184
|
+
kSecAttrService as String: servicePrefix,
|
|
185
|
+
kSecAttrAccount as String: key,
|
|
186
|
+
kSecReturnData as String: true,
|
|
187
|
+
kSecMatchLimit as String: kSecMatchLimitOne,
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
var result: AnyObject?
|
|
191
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
192
|
+
|
|
193
|
+
guard status == errSecSuccess else {
|
|
194
|
+
if status == errSecItemNotFound {
|
|
195
|
+
throw KeychainError.itemNotFound
|
|
196
|
+
}
|
|
197
|
+
throw KeychainError.readFailed(status)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
guard let data = result as? Data else {
|
|
201
|
+
throw KeychainError.dataConversionFailed
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return data
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private func deleteItem(forKey key: String) throws {
|
|
208
|
+
let query: [String: Any] = [
|
|
209
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
210
|
+
kSecAttrService as String: servicePrefix,
|
|
211
|
+
kSecAttrAccount as String: key,
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
let status = SecItemDelete(query as CFDictionary)
|
|
215
|
+
guard status == errSecSuccess || status == errSecItemNotFound else {
|
|
216
|
+
throw KeychainError.deleteFailed(status)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Application-wide constants
|
|
4
|
+
enum AppConstants {
|
|
5
|
+
static let appName = "AgentVault Wallet"
|
|
6
|
+
static let appVersion = "1.0.0"
|
|
7
|
+
static let buildNumber = "1"
|
|
8
|
+
|
|
9
|
+
/// Keychain service identifier
|
|
10
|
+
static let keychainService = "com.agentvault.wallet"
|
|
11
|
+
|
|
12
|
+
/// Backup file extension
|
|
13
|
+
static let backupFileExtension = "avbackup"
|
|
14
|
+
|
|
15
|
+
/// Minimum password length for backups
|
|
16
|
+
static let minimumBackupPasswordLength = 8
|
|
17
|
+
|
|
18
|
+
/// How long to keep clipboard data before auto-clearing (seconds)
|
|
19
|
+
static let clipboardClearDelay: TimeInterval = 60
|
|
20
|
+
|
|
21
|
+
/// Maximum wallets per chain (soft limit)
|
|
22
|
+
static let maxWalletsPerChain = 50
|
|
23
|
+
|
|
24
|
+
/// Supported mnemonic word counts
|
|
25
|
+
static let supportedMnemonicLengths: Set<Int> = [12, 24]
|
|
26
|
+
|
|
27
|
+
/// Network endpoints (for reference; actual connections go through CLI)
|
|
28
|
+
enum Networks {
|
|
29
|
+
static let icpMainnet = "https://ic0.app"
|
|
30
|
+
static let icpLocal = "http://127.0.0.1:4943"
|
|
31
|
+
static let ethMainnet = "https://eth.llamarpc.com"
|
|
32
|
+
static let ethSepolia = "https://rpc.sepolia.org"
|
|
33
|
+
static let arweaveGateway = "https://arweave.net"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// UserDefaults keys
|
|
37
|
+
enum Defaults {
|
|
38
|
+
static let hasCompletedOnboarding = "hasCompletedOnboarding"
|
|
39
|
+
static let agentVaultCLIPath = "agentVaultCLIPath"
|
|
40
|
+
static let autoRefreshBalances = "autoRefreshBalances"
|
|
41
|
+
static let defaultChain = "defaultChain"
|
|
42
|
+
static let lastBackupDate = "lastBackupDate"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
// MARK: - Date Extensions
|
|
4
|
+
|
|
5
|
+
extension Date {
|
|
6
|
+
/// Format as a relative time string ("2 hours ago", "just now")
|
|
7
|
+
var relativeString: String {
|
|
8
|
+
let formatter = RelativeDateTimeFormatter()
|
|
9
|
+
formatter.unitsStyle = .abbreviated
|
|
10
|
+
return formatter.localizedString(for: self, relativeTo: Date())
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Format as ISO 8601 string for backup files
|
|
14
|
+
var iso8601String: String {
|
|
15
|
+
ISO8601DateFormatter().string(from: self)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// MARK: - String Extensions
|
|
20
|
+
|
|
21
|
+
extension String {
|
|
22
|
+
/// Truncate the middle of a string for display
|
|
23
|
+
func truncatedMiddle(maxLength: Int = 20) -> String {
|
|
24
|
+
guard count > maxLength else { return self }
|
|
25
|
+
let halfLength = (maxLength - 3) / 2
|
|
26
|
+
return "\(prefix(halfLength))...\(suffix(halfLength))"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Check if string is a valid hexadecimal string
|
|
30
|
+
var isValidHex: Bool {
|
|
31
|
+
let cleaned = hasPrefix("0x") ? String(dropFirst(2)) : self
|
|
32
|
+
return !cleaned.isEmpty && cleaned.allSatisfy { $0.isHexDigit }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// MARK: - Data Extensions
|
|
37
|
+
|
|
38
|
+
extension Data {
|
|
39
|
+
/// Hex string representation
|
|
40
|
+
var hexString: String {
|
|
41
|
+
map { String(format: "%02x", $0) }.joined()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Initialize from hex string
|
|
45
|
+
init?(hexString: String) {
|
|
46
|
+
let cleaned = hexString.hasPrefix("0x") ? String(hexString.dropFirst(2)) : hexString
|
|
47
|
+
let length = cleaned.count / 2
|
|
48
|
+
var data = Data(capacity: length)
|
|
49
|
+
var index = cleaned.startIndex
|
|
50
|
+
|
|
51
|
+
for _ in 0..<length {
|
|
52
|
+
let nextIndex = cleaned.index(index, offsetBy: 2)
|
|
53
|
+
guard let byte = UInt8(cleaned[index..<nextIndex], radix: 16) else {
|
|
54
|
+
return nil
|
|
55
|
+
}
|
|
56
|
+
data.append(byte)
|
|
57
|
+
index = nextIndex
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
self = data
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// MARK: - View Extensions
|
|
65
|
+
|
|
66
|
+
extension View {
|
|
67
|
+
/// Apply a card-style background
|
|
68
|
+
func cardStyle() -> some View {
|
|
69
|
+
self
|
|
70
|
+
.padding(16)
|
|
71
|
+
.background(.background, in: RoundedRectangle(cornerRadius: 12))
|
|
72
|
+
.overlay(
|
|
73
|
+
RoundedRectangle(cornerRadius: 12)
|
|
74
|
+
.stroke(Color.secondary.opacity(0.15))
|
|
75
|
+
)
|
|
76
|
+
.shadow(color: .black.opacity(0.03), radius: 4, y: 2)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Conditional modifier
|
|
80
|
+
@ViewBuilder
|
|
81
|
+
func `if`<Transform: View>(_ condition: Bool, transform: (Self) -> Transform) -> some View {
|
|
82
|
+
if condition {
|
|
83
|
+
transform(self)
|
|
84
|
+
} else {
|
|
85
|
+
self
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// MARK: - Color Extensions
|
|
91
|
+
|
|
92
|
+
extension Color {
|
|
93
|
+
/// Create a color from a hex string
|
|
94
|
+
init(hex: String) {
|
|
95
|
+
let cleaned = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
|
96
|
+
var int: UInt64 = 0
|
|
97
|
+
Scanner(string: cleaned).scanHexInt64(&int)
|
|
98
|
+
let r, g, b, a: UInt64
|
|
99
|
+
switch cleaned.count {
|
|
100
|
+
case 6:
|
|
101
|
+
(r, g, b, a) = (int >> 16, int >> 8 & 0xFF, int & 0xFF, 255)
|
|
102
|
+
case 8:
|
|
103
|
+
(r, g, b, a) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
|
|
104
|
+
default:
|
|
105
|
+
(r, g, b, a) = (0, 0, 0, 255)
|
|
106
|
+
}
|
|
107
|
+
self.init(
|
|
108
|
+
.sRGB,
|
|
109
|
+
red: Double(r) / 255,
|
|
110
|
+
green: Double(g) / 255,
|
|
111
|
+
blue: Double(b) / 255,
|
|
112
|
+
opacity: Double(a) / 255
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
/// Manages backup and restore workflows
|
|
4
|
+
@MainActor
|
|
5
|
+
final class BackupViewModel: ObservableObject {
|
|
6
|
+
|
|
7
|
+
// MARK: - Backup State
|
|
8
|
+
|
|
9
|
+
@Published var backupPassword: String = ""
|
|
10
|
+
@Published var backupPasswordConfirm: String = ""
|
|
11
|
+
@Published var selectedWalletsForBackup: Set<UUID> = []
|
|
12
|
+
@Published var isCreatingBackup: Bool = false
|
|
13
|
+
@Published var backupComplete: Bool = false
|
|
14
|
+
@Published var backupFilePath: String?
|
|
15
|
+
|
|
16
|
+
// MARK: - Restore State
|
|
17
|
+
|
|
18
|
+
@Published var restoreFileURL: URL?
|
|
19
|
+
@Published var restorePassword: String = ""
|
|
20
|
+
@Published var backupMetadata: WalletBackup?
|
|
21
|
+
@Published var isRestoring: Bool = false
|
|
22
|
+
@Published var restoreComplete: Bool = false
|
|
23
|
+
@Published var restoredCount: Int = 0
|
|
24
|
+
|
|
25
|
+
// MARK: - Common
|
|
26
|
+
|
|
27
|
+
@Published var errorMessage: String?
|
|
28
|
+
@Published var showFileImporter: Bool = false
|
|
29
|
+
@Published var showFileSaver: Bool = false
|
|
30
|
+
@Published var existingBackups: [BackupFileInfo] = []
|
|
31
|
+
|
|
32
|
+
private let backupService = BackupService.shared
|
|
33
|
+
|
|
34
|
+
// MARK: - Backup Validation
|
|
35
|
+
|
|
36
|
+
var isBackupPasswordValid: Bool {
|
|
37
|
+
backupPassword.count >= 8 && backupPassword == backupPasswordConfirm
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var hasSelectedWallets: Bool {
|
|
41
|
+
!selectedWalletsForBackup.isEmpty
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var canCreateBackup: Bool {
|
|
45
|
+
isBackupPasswordValid && hasSelectedWallets && !isCreatingBackup
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var passwordStrength: PasswordStrength {
|
|
49
|
+
PasswordStrength.evaluate(backupPassword)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// MARK: - Restore Validation
|
|
53
|
+
|
|
54
|
+
var canRestore: Bool {
|
|
55
|
+
restoreFileURL != nil && !restorePassword.isEmpty && !isRestoring
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
var hasBackupMetadata: Bool {
|
|
59
|
+
backupMetadata != nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// MARK: - Backup Operations
|
|
63
|
+
|
|
64
|
+
func selectAllWallets(from wallets: [Wallet]) {
|
|
65
|
+
selectedWalletsForBackup = Set(wallets.map(\.id))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func deselectAllWallets() {
|
|
69
|
+
selectedWalletsForBackup.removeAll()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func createBackup(wallets: [Wallet]) async {
|
|
73
|
+
isCreatingBackup = true
|
|
74
|
+
errorMessage = nil
|
|
75
|
+
|
|
76
|
+
do {
|
|
77
|
+
let walletsToBackup = wallets.filter { selectedWalletsForBackup.contains($0.id) }
|
|
78
|
+
let data = try backupService.createBackup(wallets: walletsToBackup, password: backupPassword)
|
|
79
|
+
|
|
80
|
+
let filename = backupService.defaultBackupFilename()
|
|
81
|
+
let fileURL = backupService.defaultBackupDirectory.appendingPathComponent(filename)
|
|
82
|
+
|
|
83
|
+
try backupService.writeBackup(data, to: fileURL)
|
|
84
|
+
|
|
85
|
+
backupFilePath = fileURL.path
|
|
86
|
+
backupComplete = true
|
|
87
|
+
isCreatingBackup = false
|
|
88
|
+
} catch {
|
|
89
|
+
errorMessage = error.localizedDescription
|
|
90
|
+
isCreatingBackup = false
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Save backup to a user-chosen location
|
|
95
|
+
func saveBackupToLocation(wallets: [Wallet], url: URL) async {
|
|
96
|
+
isCreatingBackup = true
|
|
97
|
+
errorMessage = nil
|
|
98
|
+
|
|
99
|
+
do {
|
|
100
|
+
let walletsToBackup = wallets.filter { selectedWalletsForBackup.contains($0.id) }
|
|
101
|
+
let data = try backupService.createBackup(wallets: walletsToBackup, password: backupPassword)
|
|
102
|
+
try backupService.writeBackup(data, to: url)
|
|
103
|
+
|
|
104
|
+
backupFilePath = url.path
|
|
105
|
+
backupComplete = true
|
|
106
|
+
isCreatingBackup = false
|
|
107
|
+
} catch {
|
|
108
|
+
errorMessage = error.localizedDescription
|
|
109
|
+
isCreatingBackup = false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// MARK: - Restore Operations
|
|
114
|
+
|
|
115
|
+
func loadBackupMetadata() {
|
|
116
|
+
guard let url = restoreFileURL else { return }
|
|
117
|
+
errorMessage = nil
|
|
118
|
+
|
|
119
|
+
do {
|
|
120
|
+
backupMetadata = try backupService.readBackupMetadata(from: url)
|
|
121
|
+
} catch {
|
|
122
|
+
errorMessage = error.localizedDescription
|
|
123
|
+
backupMetadata = nil
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func restoreFromBackup(store: WalletStore) async {
|
|
128
|
+
guard let backup = backupMetadata else { return }
|
|
129
|
+
isRestoring = true
|
|
130
|
+
errorMessage = nil
|
|
131
|
+
|
|
132
|
+
do {
|
|
133
|
+
let secrets = try backupService.restoreSecrets(from: backup, password: restorePassword)
|
|
134
|
+
try backupService.saveRestoredSecrets(secrets)
|
|
135
|
+
|
|
136
|
+
// Re-create wallet entries
|
|
137
|
+
var restored = 0
|
|
138
|
+
for entry in backup.wallets {
|
|
139
|
+
// Skip if wallet already exists
|
|
140
|
+
if store.wallets.contains(where: { $0.address == entry.address && $0.chain == entry.chain }) {
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let wallet = Wallet(
|
|
145
|
+
id: entry.id,
|
|
146
|
+
name: entry.name,
|
|
147
|
+
chain: entry.chain,
|
|
148
|
+
address: entry.address,
|
|
149
|
+
isImported: true,
|
|
150
|
+
derivationPath: entry.derivationPath,
|
|
151
|
+
hasMnemonicBackup: entry.hasMnemonicBackup
|
|
152
|
+
)
|
|
153
|
+
store.wallets.append(wallet)
|
|
154
|
+
restored += 1
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
store.saveWallets()
|
|
158
|
+
restoredCount = restored
|
|
159
|
+
restoreComplete = true
|
|
160
|
+
isRestoring = false
|
|
161
|
+
} catch {
|
|
162
|
+
errorMessage = error.localizedDescription
|
|
163
|
+
isRestoring = false
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
func loadExistingBackups() {
|
|
168
|
+
existingBackups = backupService.listBackups()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func reset() {
|
|
172
|
+
backupPassword = ""
|
|
173
|
+
backupPasswordConfirm = ""
|
|
174
|
+
selectedWalletsForBackup.removeAll()
|
|
175
|
+
isCreatingBackup = false
|
|
176
|
+
backupComplete = false
|
|
177
|
+
backupFilePath = nil
|
|
178
|
+
restoreFileURL = nil
|
|
179
|
+
restorePassword = ""
|
|
180
|
+
backupMetadata = nil
|
|
181
|
+
isRestoring = false
|
|
182
|
+
restoreComplete = false
|
|
183
|
+
restoredCount = 0
|
|
184
|
+
errorMessage = nil
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// Password strength indicator
|
|
189
|
+
enum PasswordStrength {
|
|
190
|
+
case weak, fair, good, strong
|
|
191
|
+
|
|
192
|
+
var label: String {
|
|
193
|
+
switch self {
|
|
194
|
+
case .weak: return "Weak"
|
|
195
|
+
case .fair: return "Fair"
|
|
196
|
+
case .good: return "Good"
|
|
197
|
+
case .strong: return "Strong"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
var color: Color {
|
|
202
|
+
switch self {
|
|
203
|
+
case .weak: return .red
|
|
204
|
+
case .fair: return .orange
|
|
205
|
+
case .good: return .yellow
|
|
206
|
+
case .strong: return .green
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
var progress: Double {
|
|
211
|
+
switch self {
|
|
212
|
+
case .weak: return 0.25
|
|
213
|
+
case .fair: return 0.5
|
|
214
|
+
case .good: return 0.75
|
|
215
|
+
case .strong: return 1.0
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static func evaluate(_ password: String) -> PasswordStrength {
|
|
220
|
+
guard !password.isEmpty else { return .weak }
|
|
221
|
+
|
|
222
|
+
var score = 0
|
|
223
|
+
if password.count >= 8 { score += 1 }
|
|
224
|
+
if password.count >= 12 { score += 1 }
|
|
225
|
+
if password.rangeOfCharacter(from: .uppercaseLetters) != nil { score += 1 }
|
|
226
|
+
if password.rangeOfCharacter(from: .lowercaseLetters) != nil { score += 1 }
|
|
227
|
+
if password.rangeOfCharacter(from: .decimalDigits) != nil { score += 1 }
|
|
228
|
+
if password.rangeOfCharacter(from: CharacterSet(charactersIn: "!@#$%^&*()_+-=[]{}|;:,.<>?")) != nil { score += 1 }
|
|
229
|
+
|
|
230
|
+
switch score {
|
|
231
|
+
case 0...2: return .weak
|
|
232
|
+
case 3: return .fair
|
|
233
|
+
case 4...5: return .good
|
|
234
|
+
default: return .strong
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|