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,367 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Bridges the macOS GUI to the AgentVault Node.js CLI.
|
|
4
|
+
/// Runs CLI commands as child processes and parses their output.
|
|
5
|
+
actor CLIBridge {
|
|
6
|
+
|
|
7
|
+
enum CLIError: LocalizedError {
|
|
8
|
+
case nodeNotFound
|
|
9
|
+
case cliNotFound
|
|
10
|
+
case commandFailed(String)
|
|
11
|
+
case parseError(String)
|
|
12
|
+
case timeout
|
|
13
|
+
|
|
14
|
+
var errorDescription: String? {
|
|
15
|
+
switch self {
|
|
16
|
+
case .nodeNotFound:
|
|
17
|
+
return "Node.js is not installed. Please install Node.js 18+ from nodejs.org or via Homebrew."
|
|
18
|
+
case .cliNotFound:
|
|
19
|
+
return "AgentVault CLI not found. Run 'npm install' in the AgentVault directory."
|
|
20
|
+
case .commandFailed(let msg):
|
|
21
|
+
return "CLI command failed: \(msg)"
|
|
22
|
+
case .parseError(let msg):
|
|
23
|
+
return "Failed to parse CLI output: \(msg)"
|
|
24
|
+
case .timeout:
|
|
25
|
+
return "Command timed out after 60 seconds."
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Cached path to the AgentVault project root
|
|
31
|
+
private var projectRoot: String?
|
|
32
|
+
|
|
33
|
+
/// Discover the AgentVault project root directory
|
|
34
|
+
func findProjectRoot() -> String? {
|
|
35
|
+
if let cached = projectRoot { return cached }
|
|
36
|
+
|
|
37
|
+
// Check common locations
|
|
38
|
+
let candidates = [
|
|
39
|
+
// Relative to the app bundle
|
|
40
|
+
Bundle.main.bundlePath + "/../../../../",
|
|
41
|
+
// Home directory
|
|
42
|
+
NSHomeDirectory() + "/AgentVault",
|
|
43
|
+
// Common dev paths
|
|
44
|
+
"/usr/local/src/AgentVault",
|
|
45
|
+
NSHomeDirectory() + "/Developer/AgentVault",
|
|
46
|
+
NSHomeDirectory() + "/Projects/AgentVault",
|
|
47
|
+
NSHomeDirectory() + "/Code/AgentVault",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
for path in candidates {
|
|
51
|
+
let packageJSON = (path as NSString).appendingPathComponent("package.json")
|
|
52
|
+
if FileManager.default.fileExists(atPath: packageJSON) {
|
|
53
|
+
// Verify it's actually AgentVault
|
|
54
|
+
if let data = FileManager.default.contents(atPath: packageJSON),
|
|
55
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
56
|
+
let name = json["name"] as? String,
|
|
57
|
+
name.contains("agentvault") {
|
|
58
|
+
let resolved = (path as NSString).standardizingPath
|
|
59
|
+
projectRoot = resolved
|
|
60
|
+
return resolved
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return nil
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Set the project root manually (from Settings)
|
|
68
|
+
func setProjectRoot(_ path: String) {
|
|
69
|
+
projectRoot = path
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// MARK: - Environment Checks
|
|
73
|
+
|
|
74
|
+
func checkEnvironment() async -> EnvironmentStatus {
|
|
75
|
+
var status = EnvironmentStatus()
|
|
76
|
+
|
|
77
|
+
// Check Node.js
|
|
78
|
+
if let result = try? await run("node", args: ["--version"]) {
|
|
79
|
+
status.nodeInstalled = true
|
|
80
|
+
status.nodeVersion = result.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check npm
|
|
84
|
+
if let _ = try? await run("npm", args: ["--version"]) {
|
|
85
|
+
status.npmInstalled = true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check AgentVault CLI
|
|
89
|
+
if let root = findProjectRoot() {
|
|
90
|
+
let cliEntry = (root as NSString).appendingPathComponent("cli/index.ts")
|
|
91
|
+
status.agentVaultInstalled = FileManager.default.fileExists(atPath: cliEntry)
|
|
92
|
+
if status.agentVaultInstalled {
|
|
93
|
+
status.agentVaultVersion = "local"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return status
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// MARK: - Wallet Operations
|
|
101
|
+
|
|
102
|
+
/// Generate a new wallet for the given chain
|
|
103
|
+
func generateWallet(chain: Chain, name: String) async throws -> CLIWalletResult {
|
|
104
|
+
let root = try requireProjectRoot()
|
|
105
|
+
|
|
106
|
+
let output = try await runCLI(
|
|
107
|
+
root: root,
|
|
108
|
+
args: ["wallet", "generate", "--chain", chain.rawValue.lowercased(), "--name", name, "--json"]
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return try parseCLIWalletResult(output, chain: chain)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Import a wallet from a mnemonic phrase
|
|
115
|
+
func importFromMnemonic(chain: Chain, mnemonic: String, name: String) async throws -> CLIWalletResult {
|
|
116
|
+
let root = try requireProjectRoot()
|
|
117
|
+
|
|
118
|
+
let output = try await runCLI(
|
|
119
|
+
root: root,
|
|
120
|
+
args: ["wallet", "import", "--chain", chain.rawValue.lowercased(),
|
|
121
|
+
"--mnemonic", mnemonic, "--name", name, "--json"]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return try parseCLIWalletResult(output, chain: chain)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Import a wallet from a private key
|
|
128
|
+
func importFromPrivateKey(chain: Chain, privateKey: String, name: String) async throws -> CLIWalletResult {
|
|
129
|
+
let root = try requireProjectRoot()
|
|
130
|
+
|
|
131
|
+
let output = try await runCLI(
|
|
132
|
+
root: root,
|
|
133
|
+
args: ["wallet", "import", "--chain", chain.rawValue.lowercased(),
|
|
134
|
+
"--private-key", privateKey, "--name", name, "--json"]
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return try parseCLIWalletResult(output, chain: chain)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Import a wallet from a JWK file (Arweave)
|
|
141
|
+
func importFromJWK(filePath: String, name: String) async throws -> CLIWalletResult {
|
|
142
|
+
let root = try requireProjectRoot()
|
|
143
|
+
|
|
144
|
+
let output = try await runCLI(
|
|
145
|
+
root: root,
|
|
146
|
+
args: ["wallet", "import", "--chain", "ar", "--jwk-file", filePath, "--name", name, "--json"]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return try parseCLIWalletResult(output, chain: .arweave)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Import from PEM file (ICP)
|
|
153
|
+
func importFromPEM(filePath: String, name: String) async throws -> CLIWalletResult {
|
|
154
|
+
let root = try requireProjectRoot()
|
|
155
|
+
|
|
156
|
+
let output = try await runCLI(
|
|
157
|
+
root: root,
|
|
158
|
+
args: ["wallet", "import", "--chain", "icp", "--pem-file", filePath, "--name", name, "--json"]
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return try parseCLIWalletResult(output, chain: .icp)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Import from keystore JSON (Ethereum)
|
|
165
|
+
func importFromKeystore(filePath: String, password: String, name: String) async throws -> CLIWalletResult {
|
|
166
|
+
let root = try requireProjectRoot()
|
|
167
|
+
|
|
168
|
+
let output = try await runCLI(
|
|
169
|
+
root: root,
|
|
170
|
+
args: ["wallet", "import", "--chain", "eth", "--keystore", filePath,
|
|
171
|
+
"--password", password, "--name", name, "--json"]
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return try parseCLIWalletResult(output, chain: .ethereum)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Get wallet balance
|
|
178
|
+
func getBalance(chain: Chain, address: String) async throws -> String {
|
|
179
|
+
let root = try requireProjectRoot()
|
|
180
|
+
|
|
181
|
+
let output = try await runCLI(
|
|
182
|
+
root: root,
|
|
183
|
+
args: ["wallet", "balance", "--chain", chain.rawValue.lowercased(), "--address", address, "--json"]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if let data = output.data(using: .utf8),
|
|
187
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
188
|
+
let balance = json["balance"] as? String {
|
|
189
|
+
return balance
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Fallback: try to extract balance from plain text
|
|
193
|
+
let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
194
|
+
if !trimmed.isEmpty { return trimmed }
|
|
195
|
+
return "0"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Export wallet data
|
|
199
|
+
func exportWallet(walletId: String, format: String, outputPath: String) async throws -> String {
|
|
200
|
+
let root = try requireProjectRoot()
|
|
201
|
+
|
|
202
|
+
return try await runCLI(
|
|
203
|
+
root: root,
|
|
204
|
+
args: ["wallet-export", "--id", walletId, "--format", format, "--output", outputPath, "--json"]
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/// Create a backup of all wallets
|
|
209
|
+
func createBackup(outputPath: String, password: String) async throws -> String {
|
|
210
|
+
let root = try requireProjectRoot()
|
|
211
|
+
|
|
212
|
+
return try await runCLI(
|
|
213
|
+
root: root,
|
|
214
|
+
args: ["backup", "create", "--output", outputPath, "--password", password, "--json"]
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Restore wallets from a backup
|
|
219
|
+
func restoreBackup(inputPath: String, password: String) async throws -> String {
|
|
220
|
+
let root = try requireProjectRoot()
|
|
221
|
+
|
|
222
|
+
return try await runCLI(
|
|
223
|
+
root: root,
|
|
224
|
+
args: ["backup", "restore", "--input", inputPath, "--password", password, "--json"]
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// MARK: - Process Execution
|
|
229
|
+
|
|
230
|
+
private func requireProjectRoot() throws -> String {
|
|
231
|
+
guard let root = findProjectRoot() else {
|
|
232
|
+
throw CLIError.cliNotFound
|
|
233
|
+
}
|
|
234
|
+
return root
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// Run the AgentVault CLI via tsx
|
|
238
|
+
private func runCLI(root: String, args: [String]) async throws -> String {
|
|
239
|
+
let tsxPath = (root as NSString).appendingPathComponent("node_modules/.bin/tsx")
|
|
240
|
+
let cliEntry = (root as NSString).appendingPathComponent("cli/index.ts")
|
|
241
|
+
|
|
242
|
+
let command: String
|
|
243
|
+
let fullArgs: [String]
|
|
244
|
+
|
|
245
|
+
if FileManager.default.fileExists(atPath: tsxPath) {
|
|
246
|
+
command = tsxPath
|
|
247
|
+
fullArgs = [cliEntry] + args
|
|
248
|
+
} else {
|
|
249
|
+
// Fallback: use npx tsx
|
|
250
|
+
command = "npx"
|
|
251
|
+
fullArgs = ["tsx", cliEntry] + args
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return try await run(command, args: fullArgs, cwd: root)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// Execute a process and capture its stdout
|
|
258
|
+
@discardableResult
|
|
259
|
+
private func run(_ command: String, args: [String] = [], cwd: String? = nil) async throws -> String {
|
|
260
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
261
|
+
let process = Process()
|
|
262
|
+
let pipe = Pipe()
|
|
263
|
+
|
|
264
|
+
// Resolve command path
|
|
265
|
+
if command.hasPrefix("/") || command.hasPrefix(".") {
|
|
266
|
+
process.executableURL = URL(fileURLWithPath: command)
|
|
267
|
+
} else {
|
|
268
|
+
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
|
|
269
|
+
process.arguments = [command] + args
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if process.executableURL?.lastPathComponent != "env" {
|
|
273
|
+
process.arguments = args
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if let cwd = cwd {
|
|
277
|
+
process.currentDirectoryURL = URL(fileURLWithPath: cwd)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Inherit PATH from user environment
|
|
281
|
+
var env = ProcessInfo.processInfo.environment
|
|
282
|
+
let additionalPaths = [
|
|
283
|
+
"/usr/local/bin",
|
|
284
|
+
"/opt/homebrew/bin",
|
|
285
|
+
NSHomeDirectory() + "/.nvm/versions/node/current/bin",
|
|
286
|
+
NSHomeDirectory() + "/.volta/bin",
|
|
287
|
+
NSHomeDirectory() + "/.fnm/current/bin",
|
|
288
|
+
]
|
|
289
|
+
let currentPath = env["PATH"] ?? "/usr/bin:/bin"
|
|
290
|
+
env["PATH"] = (additionalPaths + [currentPath]).joined(separator: ":")
|
|
291
|
+
process.environment = env
|
|
292
|
+
|
|
293
|
+
process.standardOutput = pipe
|
|
294
|
+
process.standardError = pipe
|
|
295
|
+
|
|
296
|
+
process.terminationHandler = { proc in
|
|
297
|
+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
298
|
+
let output = String(data: data, encoding: .utf8) ?? ""
|
|
299
|
+
|
|
300
|
+
if proc.terminationStatus == 0 {
|
|
301
|
+
continuation.resume(returning: output)
|
|
302
|
+
} else {
|
|
303
|
+
continuation.resume(throwing: CLIError.commandFailed(output))
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
do {
|
|
308
|
+
try process.run()
|
|
309
|
+
} catch {
|
|
310
|
+
continuation.resume(throwing: CLIError.commandFailed(error.localizedDescription))
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// MARK: - Parsing
|
|
316
|
+
|
|
317
|
+
private func parseCLIWalletResult(_ output: String, chain: Chain) throws -> CLIWalletResult {
|
|
318
|
+
// Try JSON parse first
|
|
319
|
+
if let data = output.data(using: .utf8),
|
|
320
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
321
|
+
return CLIWalletResult(
|
|
322
|
+
address: json["address"] as? String ?? "",
|
|
323
|
+
mnemonic: json["mnemonic"] as? String,
|
|
324
|
+
privateKey: json["privateKey"] as? String,
|
|
325
|
+
publicKey: json["publicKey"] as? String,
|
|
326
|
+
chain: chain
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Fallback: parse text output
|
|
331
|
+
var address = ""
|
|
332
|
+
var mnemonic: String?
|
|
333
|
+
var privateKey: String?
|
|
334
|
+
|
|
335
|
+
for line in output.components(separatedBy: .newlines) {
|
|
336
|
+
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
|
337
|
+
if trimmed.lowercased().contains("address:") || trimmed.lowercased().contains("principal:") {
|
|
338
|
+
address = trimmed.components(separatedBy: ":").dropFirst().joined(separator: ":").trimmingCharacters(in: .whitespaces)
|
|
339
|
+
} else if trimmed.lowercased().contains("mnemonic:") {
|
|
340
|
+
mnemonic = trimmed.components(separatedBy: ":").dropFirst().joined(separator: ":").trimmingCharacters(in: .whitespaces)
|
|
341
|
+
} else if trimmed.lowercased().contains("private") && trimmed.contains(":") {
|
|
342
|
+
privateKey = trimmed.components(separatedBy: ":").dropFirst().joined(separator: ":").trimmingCharacters(in: .whitespaces)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
guard !address.isEmpty else {
|
|
347
|
+
throw CLIError.parseError("Could not extract wallet address from CLI output")
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return CLIWalletResult(
|
|
351
|
+
address: address,
|
|
352
|
+
mnemonic: mnemonic,
|
|
353
|
+
privateKey: privateKey,
|
|
354
|
+
publicKey: nil,
|
|
355
|
+
chain: chain
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/// Parsed result from CLI wallet operations
|
|
361
|
+
struct CLIWalletResult {
|
|
362
|
+
let address: String
|
|
363
|
+
let mnemonic: String?
|
|
364
|
+
let privateKey: String?
|
|
365
|
+
let publicKey: String?
|
|
366
|
+
let chain: Chain
|
|
367
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import CryptoKit
|
|
3
|
+
|
|
4
|
+
/// Native cryptographic operations for wallet management.
|
|
5
|
+
/// Uses Apple CryptoKit for encryption, hashing, and key derivation.
|
|
6
|
+
final class CryptoService {
|
|
7
|
+
|
|
8
|
+
static let shared = CryptoService()
|
|
9
|
+
|
|
10
|
+
private init() {}
|
|
11
|
+
|
|
12
|
+
// MARK: - Encryption (AES-256-GCM)
|
|
13
|
+
|
|
14
|
+
struct EncryptedData: Codable {
|
|
15
|
+
let ciphertext: Data
|
|
16
|
+
let nonce: Data
|
|
17
|
+
let tag: Data
|
|
18
|
+
let salt: Data
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Encrypt data with a password using AES-256-GCM + PBKDF2 key derivation
|
|
22
|
+
func encrypt(data: Data, password: String) throws -> EncryptedData {
|
|
23
|
+
let salt = generateSalt()
|
|
24
|
+
let key = try deriveKey(from: password, salt: salt)
|
|
25
|
+
let nonce = AES.GCM.Nonce()
|
|
26
|
+
|
|
27
|
+
let sealed = try AES.GCM.seal(data, using: key, nonce: nonce)
|
|
28
|
+
|
|
29
|
+
guard let combined = sealed.combined else {
|
|
30
|
+
throw CryptoError.encryptionFailed
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract components from the combined representation
|
|
34
|
+
// Combined = nonce (12 bytes) + ciphertext + tag (16 bytes)
|
|
35
|
+
let nonceData = Data(nonce)
|
|
36
|
+
let ciphertext = combined.dropFirst(12).dropLast(16)
|
|
37
|
+
let tag = combined.suffix(16)
|
|
38
|
+
|
|
39
|
+
return EncryptedData(
|
|
40
|
+
ciphertext: Data(ciphertext),
|
|
41
|
+
nonce: nonceData,
|
|
42
|
+
tag: Data(tag),
|
|
43
|
+
salt: salt
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Decrypt data with a password
|
|
48
|
+
func decrypt(encrypted: EncryptedData, password: String) throws -> Data {
|
|
49
|
+
let key = try deriveKey(from: password, salt: encrypted.salt)
|
|
50
|
+
|
|
51
|
+
let nonce = try AES.GCM.Nonce(data: encrypted.nonce)
|
|
52
|
+
let sealedBox = try AES.GCM.SealedBox(
|
|
53
|
+
nonce: nonce,
|
|
54
|
+
ciphertext: encrypted.ciphertext,
|
|
55
|
+
tag: encrypted.tag
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return try AES.GCM.open(sealedBox, using: key)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// MARK: - Key Derivation
|
|
62
|
+
|
|
63
|
+
/// Derive a symmetric key from a password using HKDF (CryptoKit approach)
|
|
64
|
+
private func deriveKey(from password: String, salt: Data) throws -> SymmetricKey {
|
|
65
|
+
guard let passwordData = password.data(using: .utf8) else {
|
|
66
|
+
throw CryptoError.invalidPassword
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Use HKDF with SHA-256 for key derivation
|
|
70
|
+
let inputKey = SymmetricKey(data: passwordData)
|
|
71
|
+
let derivedKey = HKDF<SHA256>.deriveKey(
|
|
72
|
+
inputKeyMaterial: inputKey,
|
|
73
|
+
salt: salt,
|
|
74
|
+
info: "AgentVault Wallet Backup".data(using: .utf8)!,
|
|
75
|
+
outputByteCount: 32
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return derivedKey
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// MARK: - Hashing
|
|
82
|
+
|
|
83
|
+
/// SHA-256 hash of data
|
|
84
|
+
func sha256(_ data: Data) -> Data {
|
|
85
|
+
Data(SHA256.hash(data: data))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// SHA-256 hash of a string
|
|
89
|
+
func sha256(_ string: String) -> String {
|
|
90
|
+
let data = Data(string.utf8)
|
|
91
|
+
let hash = SHA256.hash(data: data)
|
|
92
|
+
return hash.compactMap { String(format: "%02x", $0) }.joined()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// MARK: - Random Generation
|
|
96
|
+
|
|
97
|
+
/// Generate a cryptographically secure random salt (32 bytes)
|
|
98
|
+
func generateSalt() -> Data {
|
|
99
|
+
var bytes = [UInt8](repeating: 0, count: 32)
|
|
100
|
+
_ = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
|
|
101
|
+
return Data(bytes)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Generate a random password/passphrase of given length
|
|
105
|
+
func generateRandomPassword(length: Int = 32) -> String {
|
|
106
|
+
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*"
|
|
107
|
+
var password = ""
|
|
108
|
+
var randomBytes = [UInt8](repeating: 0, count: length)
|
|
109
|
+
_ = SecRandomCopyBytes(kSecRandomDefault, length, &randomBytes)
|
|
110
|
+
|
|
111
|
+
for byte in randomBytes {
|
|
112
|
+
let index = Int(byte) % chars.count
|
|
113
|
+
password.append(chars[chars.index(chars.startIndex, offsetBy: index)])
|
|
114
|
+
}
|
|
115
|
+
return password
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// MARK: - Validation
|
|
119
|
+
|
|
120
|
+
/// Validate a BIP39 mnemonic phrase (basic word count check)
|
|
121
|
+
func isValidMnemonic(_ phrase: String) -> Bool {
|
|
122
|
+
let words = phrase.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
123
|
+
.components(separatedBy: .whitespaces)
|
|
124
|
+
.filter { !$0.isEmpty }
|
|
125
|
+
return words.count == 12 || words.count == 24
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Validate an Ethereum private key format
|
|
129
|
+
func isValidEthPrivateKey(_ key: String) -> Bool {
|
|
130
|
+
let cleaned = key.hasPrefix("0x") ? String(key.dropFirst(2)) : key
|
|
131
|
+
return cleaned.count == 64 && cleaned.allSatisfy { $0.isHexDigit }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Validate an Ethereum address format
|
|
135
|
+
func isValidEthAddress(_ address: String) -> Bool {
|
|
136
|
+
let cleaned = address.hasPrefix("0x") ? String(address.dropFirst(2)) : address
|
|
137
|
+
return cleaned.count == 40 && cleaned.allSatisfy { $0.isHexDigit }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// MARK: - Errors
|
|
141
|
+
|
|
142
|
+
enum CryptoError: LocalizedError {
|
|
143
|
+
case encryptionFailed
|
|
144
|
+
case decryptionFailed
|
|
145
|
+
case invalidPassword
|
|
146
|
+
case keyDerivationFailed
|
|
147
|
+
|
|
148
|
+
var errorDescription: String? {
|
|
149
|
+
switch self {
|
|
150
|
+
case .encryptionFailed: return "Encryption failed"
|
|
151
|
+
case .decryptionFailed: return "Decryption failed — wrong password?"
|
|
152
|
+
case .invalidPassword: return "Invalid password"
|
|
153
|
+
case .keyDerivationFailed: return "Key derivation failed"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Manages persistent storage of wallet metadata on the filesystem.
|
|
4
|
+
/// Secrets are NOT stored here — they live in Keychain only.
|
|
5
|
+
final class FileService {
|
|
6
|
+
|
|
7
|
+
static let shared = FileService()
|
|
8
|
+
|
|
9
|
+
private let fileManager = FileManager.default
|
|
10
|
+
|
|
11
|
+
private init() {}
|
|
12
|
+
|
|
13
|
+
// MARK: - App Support Directory
|
|
14
|
+
|
|
15
|
+
/// The application's data directory
|
|
16
|
+
var appDataDirectory: URL {
|
|
17
|
+
let appSupport = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
|
18
|
+
let appDir = appSupport.appendingPathComponent("AgentVaultWallet", isDirectory: true)
|
|
19
|
+
try? fileManager.createDirectory(at: appDir, withIntermediateDirectories: true)
|
|
20
|
+
return appDir
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Path to the wallet metadata file
|
|
24
|
+
private var walletsFilePath: URL {
|
|
25
|
+
appDataDirectory.appendingPathComponent("wallets.json")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// MARK: - Wallet Metadata Persistence
|
|
29
|
+
|
|
30
|
+
/// Save wallet list to disk (metadata only, no secrets)
|
|
31
|
+
func saveWallets(_ wallets: [Wallet]) throws {
|
|
32
|
+
let encoder = JSONEncoder()
|
|
33
|
+
encoder.dateEncodingStrategy = .iso8601
|
|
34
|
+
encoder.outputFormatting = [.prettyPrinted]
|
|
35
|
+
let data = try encoder.encode(wallets)
|
|
36
|
+
try data.write(to: walletsFilePath, options: [.atomic])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Load wallet list from disk
|
|
40
|
+
func loadWallets() throws -> [Wallet] {
|
|
41
|
+
guard fileManager.fileExists(atPath: walletsFilePath.path) else {
|
|
42
|
+
return []
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let data = try Data(contentsOf: walletsFilePath)
|
|
46
|
+
let decoder = JSONDecoder()
|
|
47
|
+
decoder.dateDecodingStrategy = .iso8601
|
|
48
|
+
return try decoder.decode([Wallet].self, from: data)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Delete a specific wallet's metadata from the stored list
|
|
52
|
+
func deleteWallet(withId id: UUID) throws {
|
|
53
|
+
var wallets = try loadWallets()
|
|
54
|
+
wallets.removeAll { $0.id == id }
|
|
55
|
+
try saveWallets(wallets)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// MARK: - File Dialogs
|
|
59
|
+
|
|
60
|
+
/// Suggest a save location for backup files
|
|
61
|
+
var suggestedBackupURL: URL {
|
|
62
|
+
let desktop = fileManager.urls(for: .desktopDirectory, in: .userDomainMask).first!
|
|
63
|
+
let formatter = DateFormatter()
|
|
64
|
+
formatter.dateFormat = "yyyy-MM-dd"
|
|
65
|
+
let filename = "AgentVault-Backup-\(formatter.string(from: Date())).avbackup"
|
|
66
|
+
return desktop.appendingPathComponent(filename)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// MARK: - Import File Validation
|
|
70
|
+
|
|
71
|
+
/// Read and validate a JWK file
|
|
72
|
+
func readJWKFile(at url: URL) throws -> Data {
|
|
73
|
+
let data = try Data(contentsOf: url)
|
|
74
|
+
|
|
75
|
+
// Basic validation: should be valid JSON with "n" and "d" fields (RSA key)
|
|
76
|
+
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
77
|
+
json["n"] != nil,
|
|
78
|
+
json["kty"] as? String == "RSA" else {
|
|
79
|
+
throw FileError.invalidJWKFile
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return data
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Read and validate a PEM file
|
|
86
|
+
func readPEMFile(at url: URL) throws -> String {
|
|
87
|
+
let content = try String(contentsOf: url, encoding: .utf8)
|
|
88
|
+
guard content.contains("BEGIN") && content.contains("KEY") else {
|
|
89
|
+
throw FileError.invalidPEMFile
|
|
90
|
+
}
|
|
91
|
+
return content
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Read and validate an Ethereum keystore JSON file
|
|
95
|
+
func readKeystoreFile(at url: URL) throws -> Data {
|
|
96
|
+
let data = try Data(contentsOf: url)
|
|
97
|
+
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
98
|
+
json["crypto"] != nil || json["Crypto"] != nil else {
|
|
99
|
+
throw FileError.invalidKeystoreFile
|
|
100
|
+
}
|
|
101
|
+
return data
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
enum FileError: LocalizedError {
|
|
105
|
+
case invalidJWKFile
|
|
106
|
+
case invalidPEMFile
|
|
107
|
+
case invalidKeystoreFile
|
|
108
|
+
|
|
109
|
+
var errorDescription: String? {
|
|
110
|
+
switch self {
|
|
111
|
+
case .invalidJWKFile:
|
|
112
|
+
return "The selected file is not a valid Arweave JWK wallet file"
|
|
113
|
+
case .invalidPEMFile:
|
|
114
|
+
return "The selected file is not a valid PEM identity file"
|
|
115
|
+
case .invalidKeystoreFile:
|
|
116
|
+
return "The selected file is not a valid Ethereum keystore file"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|