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.
Files changed (293) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/README.md +1 -1
  3. package/dist/cli/commands/approve.js +5 -5
  4. package/dist/cli/commands/archive.js +5 -5
  5. package/dist/cli/commands/backup.js +5 -5
  6. package/dist/cli/commands/cloud-backup.js +12 -12
  7. package/dist/cli/commands/decrypt.js +2 -2
  8. package/dist/cli/commands/deploy.js +1 -1
  9. package/dist/cli/commands/exec.js +2 -2
  10. package/dist/cli/commands/fetch.js +4 -4
  11. package/dist/cli/commands/inference.js +5 -5
  12. package/dist/cli/commands/init.d.ts +1 -1
  13. package/dist/cli/commands/init.js +16 -16
  14. package/dist/cli/commands/list.js +4 -4
  15. package/dist/cli/commands/package.js +2 -2
  16. package/dist/cli/commands/profile.js +1 -1
  17. package/dist/cli/commands/rebuild.js +2 -2
  18. package/dist/cli/commands/show.js +1 -1
  19. package/dist/cli/commands/status.d.ts +1 -1
  20. package/dist/cli/commands/status.js +8 -8
  21. package/dist/cli/commands/trace.js +1 -1
  22. package/dist/cli/commands/wallet-export.js +1 -1
  23. package/dist/cli/commands/wallet-sign.js +1 -1
  24. package/dist/cli/commands/wallet.d.ts +1 -1
  25. package/dist/cli/commands/wallet.js +1 -1
  26. package/dist/cli/index.d.ts +2 -2
  27. package/dist/cli/index.js +3 -3
  28. package/dist/src/archival/archive-manager.d.ts +85 -0
  29. package/dist/src/archival/archive-manager.js +294 -0
  30. package/dist/src/archival/arweave-client.d.ts +88 -0
  31. package/dist/src/archival/arweave-client.js +223 -0
  32. package/dist/src/archival/index.d.ts +8 -0
  33. package/{src/archival/index.ts → dist/src/archival/index.js} +1 -1
  34. package/dist/src/backup/backup.d.ts +67 -0
  35. package/dist/src/backup/backup.js +231 -0
  36. package/dist/src/backup/index.d.ts +7 -0
  37. package/{src/backup/index.ts → dist/src/backup/index.js} +1 -1
  38. package/dist/src/cloud-storage/cloud-sync.d.ts +49 -0
  39. package/dist/src/cloud-storage/cloud-sync.js +372 -0
  40. package/dist/src/cloud-storage/index.d.ts +11 -0
  41. package/{src/cloud-storage/index.ts → dist/src/cloud-storage/index.js} +1 -1
  42. package/dist/src/cloud-storage/provider-detector.d.ts +34 -0
  43. package/dist/src/cloud-storage/provider-detector.js +158 -0
  44. package/{src/cloud-storage/types.ts → dist/src/cloud-storage/types.d.ts} +40 -53
  45. package/dist/src/cloud-storage/types.js +10 -0
  46. package/dist/src/debugging/index.d.ts +6 -0
  47. package/{src/debugging/index.ts → dist/src/debugging/index.js} +1 -1
  48. package/dist/src/debugging/logs.d.ts +32 -0
  49. package/dist/src/debugging/logs.js +158 -0
  50. package/dist/src/debugging/types.d.ts +91 -0
  51. package/dist/src/debugging/types.js +5 -0
  52. package/dist/src/deployment/deployer.d.ts +52 -0
  53. package/dist/src/deployment/deployer.js +211 -0
  54. package/dist/src/deployment/icpClient.d.ts +144 -0
  55. package/dist/src/deployment/icpClient.js +545 -0
  56. package/dist/src/deployment/index.d.ts +11 -0
  57. package/dist/src/deployment/index.js +14 -0
  58. package/dist/src/deployment/promotion.d.ts +32 -0
  59. package/dist/src/deployment/promotion.js +114 -0
  60. package/dist/src/deployment/types.d.ts +101 -0
  61. package/dist/src/deployment/types.js +5 -0
  62. package/dist/src/icp/batch.d.ts +112 -0
  63. package/dist/src/icp/batch.js +273 -0
  64. package/dist/src/icp/cycles.d.ts +29 -0
  65. package/{src/icp/cycles.ts → dist/src/icp/cycles.js} +8 -22
  66. package/dist/src/icp/environment.d.ts +60 -0
  67. package/dist/src/icp/environment.js +183 -0
  68. package/dist/src/icp/icpcli.d.ts +204 -0
  69. package/dist/src/icp/icpcli.js +374 -0
  70. package/dist/src/icp/icwasm.d.ts +94 -0
  71. package/dist/src/icp/icwasm.js +197 -0
  72. package/dist/src/icp/identity.d.ts +50 -0
  73. package/{src/icp/identity.ts → dist/src/icp/identity.js} +15 -28
  74. package/dist/src/icp/index.d.ts +16 -0
  75. package/dist/src/icp/index.js +20 -0
  76. package/dist/src/icp/optimization.d.ts +16 -0
  77. package/dist/src/icp/optimization.js +225 -0
  78. package/dist/src/icp/tokens.d.ts +24 -0
  79. package/{src/icp/tokens.ts → dist/src/icp/tokens.js} +5 -12
  80. package/dist/src/icp/tool-detector.d.ts +31 -0
  81. package/dist/src/icp/tool-detector.js +104 -0
  82. package/dist/src/icp/types.d.ts +493 -0
  83. package/dist/src/icp/types.js +7 -0
  84. package/dist/src/index.d.ts +12 -0
  85. package/dist/src/index.js +18 -0
  86. package/dist/src/inference/bittensor-client.d.ts +108 -0
  87. package/dist/src/inference/bittensor-client.js +224 -0
  88. package/dist/src/inference/index.d.ts +8 -0
  89. package/{src/inference/index.ts → dist/src/inference/index.js} +1 -1
  90. package/dist/src/inference/inference-manager.d.ts +76 -0
  91. package/dist/src/inference/inference-manager.js +228 -0
  92. package/dist/src/metrics/index.d.ts +7 -0
  93. package/{src/metrics/index.ts → dist/src/metrics/index.js} +1 -1
  94. package/dist/src/metrics/metrics.d.ts +39 -0
  95. package/dist/src/metrics/metrics.js +129 -0
  96. package/dist/src/monitoring/alerting.d.ts +51 -0
  97. package/dist/src/monitoring/alerting.js +169 -0
  98. package/dist/src/monitoring/health.d.ts +40 -0
  99. package/dist/src/monitoring/health.js +164 -0
  100. package/dist/src/monitoring/index.d.ts +10 -0
  101. package/dist/src/monitoring/index.js +12 -0
  102. package/dist/src/monitoring/info.d.ts +15 -0
  103. package/dist/src/monitoring/info.js +109 -0
  104. package/dist/src/monitoring/types.d.ts +93 -0
  105. package/dist/src/monitoring/types.js +7 -0
  106. package/dist/src/network/index.d.ts +5 -0
  107. package/{src/network/index.ts → dist/src/network/index.js} +1 -1
  108. package/dist/src/network/network-config.d.ts +31 -0
  109. package/dist/src/network/network-config.js +109 -0
  110. package/dist/src/packaging/compiler.d.ts +61 -0
  111. package/dist/src/packaging/compiler.js +562 -0
  112. package/dist/src/packaging/config-persistence.d.ts +46 -0
  113. package/dist/src/packaging/config-persistence.js +108 -0
  114. package/dist/src/packaging/config-schemas.d.ts +115 -0
  115. package/dist/src/packaging/config-schemas.js +43 -0
  116. package/dist/src/packaging/detector.d.ts +26 -0
  117. package/dist/src/packaging/detector.js +193 -0
  118. package/dist/src/packaging/index.d.ts +16 -0
  119. package/dist/src/packaging/index.js +22 -0
  120. package/dist/src/packaging/packager.d.ts +31 -0
  121. package/dist/src/packaging/packager.js +90 -0
  122. package/dist/src/packaging/parsers/clawdbot.d.ts +19 -0
  123. package/dist/src/packaging/parsers/clawdbot.js +231 -0
  124. package/dist/src/packaging/parsers/cline.d.ts +26 -0
  125. package/dist/src/packaging/parsers/cline.js +185 -0
  126. package/dist/src/packaging/parsers/generic.d.ts +27 -0
  127. package/dist/src/packaging/parsers/generic.js +228 -0
  128. package/dist/src/packaging/parsers/goose.d.ts +26 -0
  129. package/dist/src/packaging/parsers/goose.js +175 -0
  130. package/dist/src/packaging/parsers/index.d.ts +11 -0
  131. package/{src/packaging/parsers/index.ts → dist/src/packaging/parsers/index.js} +1 -1
  132. package/dist/src/packaging/serializer.d.ts +108 -0
  133. package/dist/src/packaging/serializer.js +153 -0
  134. package/dist/src/packaging/types.d.ts +131 -0
  135. package/dist/src/packaging/types.js +5 -0
  136. package/dist/src/packaging/wasmedge-compiler.d.ts +76 -0
  137. package/dist/src/packaging/wasmedge-compiler.js +349 -0
  138. package/dist/src/security/index.d.ts +11 -0
  139. package/{src/security/index.ts → dist/src/security/index.js} +1 -4
  140. package/dist/src/security/multisig.d.ts +102 -0
  141. package/dist/src/security/multisig.js +283 -0
  142. package/dist/src/security/types.d.ts +207 -0
  143. package/dist/src/security/types.js +217 -0
  144. package/dist/src/security/vetkeys.d.ts +179 -0
  145. package/dist/src/security/vetkeys.js +499 -0
  146. package/dist/src/testing/index.d.ts +6 -0
  147. package/{src/testing/index.ts → dist/src/testing/index.js} +1 -1
  148. package/dist/src/testing/local-runner.d.ts +23 -0
  149. package/dist/src/testing/local-runner.js +226 -0
  150. package/dist/src/testing/types.d.ts +98 -0
  151. package/dist/src/testing/types.js +5 -0
  152. package/dist/src/wallet/cbor-serializer.d.ts +82 -0
  153. package/dist/src/wallet/cbor-serializer.js +282 -0
  154. package/dist/src/wallet/chain-dispatcher.d.ts +112 -0
  155. package/dist/src/wallet/chain-dispatcher.js +241 -0
  156. package/dist/src/wallet/cross-chain-aggregator.d.ts +119 -0
  157. package/dist/src/wallet/cross-chain-aggregator.js +235 -0
  158. package/dist/src/wallet/index.d.ts +16 -0
  159. package/dist/src/wallet/index.js +22 -0
  160. package/dist/src/wallet/key-derivation.d.ts +117 -0
  161. package/dist/src/wallet/key-derivation.js +325 -0
  162. package/dist/src/wallet/providers/base-provider.d.ts +111 -0
  163. package/dist/src/wallet/providers/base-provider.js +58 -0
  164. package/dist/src/wallet/providers/cketh-provider.d.ts +104 -0
  165. package/dist/src/wallet/providers/cketh-provider.js +343 -0
  166. package/dist/src/wallet/providers/polkadot-provider.d.ts +115 -0
  167. package/dist/src/wallet/providers/polkadot-provider.js +407 -0
  168. package/dist/src/wallet/providers/solana-provider.d.ts +102 -0
  169. package/dist/src/wallet/providers/solana-provider.js +393 -0
  170. package/dist/src/wallet/transaction-queue.d.ts +133 -0
  171. package/dist/src/wallet/transaction-queue.js +195 -0
  172. package/dist/src/wallet/types.d.ts +167 -0
  173. package/dist/src/wallet/types.js +5 -0
  174. package/dist/src/wallet/vetkeys-adapter.d.ts +134 -0
  175. package/dist/src/wallet/vetkeys-adapter.js +313 -0
  176. package/dist/src/wallet/wallet-manager.d.ts +202 -0
  177. package/dist/src/wallet/wallet-manager.js +451 -0
  178. package/dist/src/wallet/wallet-storage.d.ts +131 -0
  179. package/dist/src/wallet/wallet-storage.js +274 -0
  180. package/macos-wallet-app/AgentVaultWallet/App/AgentVaultWalletApp.swift +54 -0
  181. package/macos-wallet-app/AgentVaultWallet/Models/AppState.swift +102 -0
  182. package/macos-wallet-app/AgentVaultWallet/Models/Chain.swift +121 -0
  183. package/macos-wallet-app/AgentVaultWallet/Models/Wallet.swift +98 -0
  184. package/macos-wallet-app/AgentVaultWallet/Resources/AgentVaultWallet.entitlements +27 -0
  185. package/macos-wallet-app/AgentVaultWallet/Resources/Info.plist +69 -0
  186. package/macos-wallet-app/AgentVaultWallet/Services/BackupService.swift +270 -0
  187. package/macos-wallet-app/AgentVaultWallet/Services/CLIBridge.swift +367 -0
  188. package/macos-wallet-app/AgentVaultWallet/Services/CryptoService.swift +157 -0
  189. package/macos-wallet-app/AgentVaultWallet/Services/FileService.swift +120 -0
  190. package/macos-wallet-app/AgentVaultWallet/Services/KeychainService.swift +219 -0
  191. package/macos-wallet-app/AgentVaultWallet/Utilities/Constants.swift +44 -0
  192. package/macos-wallet-app/AgentVaultWallet/Utilities/Extensions.swift +115 -0
  193. package/macos-wallet-app/AgentVaultWallet/ViewModels/BackupViewModel.swift +237 -0
  194. package/macos-wallet-app/AgentVaultWallet/ViewModels/CreateWalletViewModel.swift +137 -0
  195. package/macos-wallet-app/AgentVaultWallet/ViewModels/ImportWalletViewModel.swift +179 -0
  196. package/macos-wallet-app/AgentVaultWallet/ViewModels/WalletStore.swift +286 -0
  197. package/macos-wallet-app/AgentVaultWallet/Views/Backup/BackupView.swift +235 -0
  198. package/macos-wallet-app/AgentVaultWallet/Views/Backup/RestoreView.swift +316 -0
  199. package/macos-wallet-app/AgentVaultWallet/Views/Create/CreateWalletFlow.swift +438 -0
  200. package/macos-wallet-app/AgentVaultWallet/Views/Import/ImportWalletFlow.swift +399 -0
  201. package/macos-wallet-app/AgentVaultWallet/Views/MainView.swift +134 -0
  202. package/macos-wallet-app/AgentVaultWallet/Views/Settings/SettingsView.swift +276 -0
  203. package/macos-wallet-app/AgentVaultWallet/Views/Sidebar/SidebarView.swift +133 -0
  204. package/macos-wallet-app/AgentVaultWallet/Views/Wallet/DashboardView.swift +233 -0
  205. package/macos-wallet-app/AgentVaultWallet/Views/Wallet/WalletDetailView.swift +281 -0
  206. package/macos-wallet-app/AgentVaultWallet/Views/Wallet/WalletListView.swift +280 -0
  207. package/macos-wallet-app/AgentVaultWallet/Views/Welcome/WelcomeView.swift +176 -0
  208. package/macos-wallet-app/Makefile +47 -0
  209. package/macos-wallet-app/project.yml +40 -0
  210. package/macos-wallet-app/setup.sh +73 -0
  211. package/package.json +10 -2
  212. package/backups/agentvault-backup-test-agent-2026-02-12T17-54-28-967Z.json +0 -28
  213. package/backups/agentvault-backup-test-agent-2026-02-12T17-54-29-032Z.backup +0 -1
  214. package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-373Z.json +0 -28
  215. package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-428Z.backup +0 -1
  216. package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-132Z.json +0 -28
  217. package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-247Z.backup +0 -1
  218. package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-216Z.json +0 -28
  219. package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-283Z.backup +0 -1
  220. package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-772Z.backup +0 -1
  221. package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-793Z.json +0 -28
  222. package/backups/test-backup.json +0 -28
  223. package/scripts/dev-dashboard.mjs +0 -84
  224. package/site/README.md +0 -63
  225. package/site/docusaurus.config.ts +0 -148
  226. package/site/package-lock.json +0 -18383
  227. package/site/package.json +0 -47
  228. package/site/sidebars.ts +0 -86
  229. package/site/static/.gitkeep +0 -0
  230. package/site/static/img/logo.svg +0 -28
  231. package/site/static/img/og-image.svg +0 -35
  232. package/src/archival/archive-manager.ts +0 -372
  233. package/src/archival/arweave-client.ts +0 -289
  234. package/src/backup/backup.ts +0 -315
  235. package/src/cloud-storage/cloud-sync.ts +0 -461
  236. package/src/cloud-storage/provider-detector.ts +0 -198
  237. package/src/debugging/logs.ts +0 -193
  238. package/src/debugging/types.ts +0 -100
  239. package/src/deployment/deployer.ts +0 -274
  240. package/src/deployment/icpClient.ts +0 -620
  241. package/src/deployment/index.ts +0 -46
  242. package/src/deployment/promotion.ts +0 -161
  243. package/src/deployment/types.ts +0 -111
  244. package/src/icp/batch.ts +0 -374
  245. package/src/icp/environment.ts +0 -215
  246. package/src/icp/icpcli.ts +0 -438
  247. package/src/icp/icwasm.ts +0 -222
  248. package/src/icp/index.ts +0 -94
  249. package/src/icp/optimization.ts +0 -242
  250. package/src/icp/tool-detector.ts +0 -110
  251. package/src/icp/types.ts +0 -574
  252. package/src/index.ts +0 -25
  253. package/src/inference/bittensor-client.ts +0 -304
  254. package/src/inference/inference-manager.ts +0 -327
  255. package/src/metrics/metrics.ts +0 -186
  256. package/src/monitoring/alerting.ts +0 -190
  257. package/src/monitoring/health.ts +0 -197
  258. package/src/monitoring/index.ts +0 -38
  259. package/src/monitoring/info.ts +0 -114
  260. package/src/monitoring/types.ts +0 -99
  261. package/src/network/network-config.ts +0 -129
  262. package/src/packaging/compiler.ts +0 -647
  263. package/src/packaging/config-persistence.ts +0 -135
  264. package/src/packaging/config-schemas.ts +0 -156
  265. package/src/packaging/detector.ts +0 -220
  266. package/src/packaging/index.ts +0 -90
  267. package/src/packaging/packager.ts +0 -118
  268. package/src/packaging/parsers/clawdbot.ts +0 -278
  269. package/src/packaging/parsers/cline.ts +0 -223
  270. package/src/packaging/parsers/generic.ts +0 -266
  271. package/src/packaging/parsers/goose.ts +0 -214
  272. package/src/packaging/serializer.ts +0 -260
  273. package/src/packaging/types.ts +0 -144
  274. package/src/packaging/wasmedge-compiler.ts +0 -406
  275. package/src/security/multisig.ts +0 -415
  276. package/src/security/types.ts +0 -416
  277. package/src/security/vetkeys.ts +0 -655
  278. package/src/testing/local-runner.ts +0 -264
  279. package/src/testing/types.ts +0 -104
  280. package/src/wallet/cbor-serializer.ts +0 -323
  281. package/src/wallet/chain-dispatcher.ts +0 -313
  282. package/src/wallet/cross-chain-aggregator.ts +0 -346
  283. package/src/wallet/index.ts +0 -76
  284. package/src/wallet/key-derivation.ts +0 -425
  285. package/src/wallet/providers/base-provider.ts +0 -154
  286. package/src/wallet/providers/cketh-provider.ts +0 -434
  287. package/src/wallet/providers/polkadot-provider.ts +0 -503
  288. package/src/wallet/providers/solana-provider.ts +0 -490
  289. package/src/wallet/transaction-queue.ts +0 -284
  290. package/src/wallet/types.ts +0 -178
  291. package/src/wallet/vetkeys-adapter.ts +0 -431
  292. package/src/wallet/wallet-manager.ts +0 -597
  293. package/src/wallet/wallet-storage.ts +0 -380
@@ -0,0 +1,235 @@
1
+ import SwiftUI
2
+
3
+ /// Backup creation workflow
4
+ struct BackupView: View {
5
+ @EnvironmentObject var walletStore: WalletStore
6
+ @EnvironmentObject var appState: AppState
7
+ @StateObject private var vm = BackupViewModel()
8
+ @Environment(\.dismiss) private var dismiss
9
+
10
+ var body: some View {
11
+ VStack(spacing: 0) {
12
+ // Header
13
+ VStack(spacing: 8) {
14
+ Image(systemName: "arrow.down.doc.fill")
15
+ .font(.system(size: 32))
16
+ .foregroundStyle(.accentColor)
17
+ Text("Backup Wallets")
18
+ .font(.title2.bold())
19
+ Text("Create an encrypted backup of your wallet keys")
20
+ .font(.callout)
21
+ .foregroundStyle(.secondary)
22
+ }
23
+ .padding(.top, 20)
24
+ .padding(.bottom, 16)
25
+
26
+ Divider()
27
+
28
+ if vm.backupComplete {
29
+ backupCompleteView
30
+ } else {
31
+ ScrollView {
32
+ VStack(spacing: 24) {
33
+ // Wallet selection
34
+ walletSelectionSection
35
+
36
+ // Password
37
+ passwordSection
38
+ }
39
+ .padding(24)
40
+ }
41
+
42
+ Divider()
43
+
44
+ // Footer
45
+ HStack {
46
+ Button("Cancel") { dismiss() }
47
+ .buttonStyle(.bordered)
48
+ Spacer()
49
+ Button("Create Backup") {
50
+ Task {
51
+ await vm.createBackup(wallets: walletStore.wallets)
52
+ }
53
+ }
54
+ .buttonStyle(.borderedProminent)
55
+ .disabled(!vm.canCreateBackup)
56
+ }
57
+ .padding(16)
58
+ }
59
+ }
60
+ .frame(maxWidth: .infinity)
61
+ .background(.background)
62
+ .onAppear {
63
+ vm.selectAllWallets(from: walletStore.wallets)
64
+ }
65
+ .alert("Backup Error", isPresented: Binding(
66
+ get: { vm.errorMessage != nil },
67
+ set: { if !$0 { vm.errorMessage = nil } }
68
+ )) {
69
+ Button("OK") { vm.errorMessage = nil }
70
+ } message: {
71
+ Text(vm.errorMessage ?? "")
72
+ }
73
+ }
74
+
75
+ private var walletSelectionSection: some View {
76
+ VStack(alignment: .leading, spacing: 12) {
77
+ HStack {
78
+ Text("Select Wallets")
79
+ .font(.headline)
80
+ Spacer()
81
+ Button("Select All") { vm.selectAllWallets(from: walletStore.wallets) }
82
+ .buttonStyle(.plain)
83
+ .foregroundStyle(.accentColor)
84
+ .font(.caption)
85
+ Text("/")
86
+ .foregroundStyle(.tertiary)
87
+ .font(.caption)
88
+ Button("None") { vm.deselectAllWallets() }
89
+ .buttonStyle(.plain)
90
+ .foregroundStyle(.accentColor)
91
+ .font(.caption)
92
+ }
93
+
94
+ if walletStore.wallets.isEmpty {
95
+ HStack {
96
+ Image(systemName: "exclamationmark.triangle")
97
+ .foregroundStyle(.orange)
98
+ Text("No wallets to back up. Create or import a wallet first.")
99
+ .font(.callout)
100
+ .foregroundStyle(.secondary)
101
+ }
102
+ .padding()
103
+ .frame(maxWidth: .infinity)
104
+ .background(.orange.opacity(0.1), in: RoundedRectangle(cornerRadius: 8))
105
+ } else {
106
+ VStack(spacing: 2) {
107
+ ForEach(walletStore.wallets) { wallet in
108
+ HStack(spacing: 12) {
109
+ Toggle("", isOn: Binding(
110
+ get: { vm.selectedWalletsForBackup.contains(wallet.id) },
111
+ set: { selected in
112
+ if selected {
113
+ vm.selectedWalletsForBackup.insert(wallet.id)
114
+ } else {
115
+ vm.selectedWalletsForBackup.remove(wallet.id)
116
+ }
117
+ }
118
+ ))
119
+ .labelsHidden()
120
+
121
+ Image(systemName: wallet.chain.iconName)
122
+ .foregroundStyle(wallet.chain.color)
123
+
124
+ VStack(alignment: .leading) {
125
+ Text(wallet.name)
126
+ .font(.callout)
127
+ Text(wallet.shortAddress)
128
+ .font(.caption)
129
+ .foregroundStyle(.secondary)
130
+ }
131
+
132
+ Spacer()
133
+
134
+ Text(wallet.chain.symbol)
135
+ .font(.caption)
136
+ .foregroundStyle(.tertiary)
137
+ }
138
+ .padding(.horizontal, 12)
139
+ .padding(.vertical, 8)
140
+ }
141
+ }
142
+ .background(.quaternary.opacity(0.5), in: RoundedRectangle(cornerRadius: 10))
143
+ }
144
+
145
+ Text("\(vm.selectedWalletsForBackup.count) of \(walletStore.wallets.count) wallets selected")
146
+ .font(.caption)
147
+ .foregroundStyle(.secondary)
148
+ }
149
+ }
150
+
151
+ private var passwordSection: some View {
152
+ VStack(alignment: .leading, spacing: 12) {
153
+ Text("Encryption Password")
154
+ .font(.headline)
155
+
156
+ Text("Choose a strong password to encrypt your backup. You'll need this password to restore.")
157
+ .font(.caption)
158
+ .foregroundStyle(.secondary)
159
+
160
+ SecureField("Password (min 8 characters)", text: $vm.backupPassword)
161
+ .textFieldStyle(.roundedBorder)
162
+
163
+ // Password strength
164
+ if !vm.backupPassword.isEmpty {
165
+ HStack(spacing: 8) {
166
+ GeometryReader { geo in
167
+ ZStack(alignment: .leading) {
168
+ RoundedRectangle(cornerRadius: 2)
169
+ .fill(.quaternary)
170
+ RoundedRectangle(cornerRadius: 2)
171
+ .fill(vm.passwordStrength.color)
172
+ .frame(width: geo.size.width * vm.passwordStrength.progress)
173
+ }
174
+ }
175
+ .frame(height: 4)
176
+
177
+ Text(vm.passwordStrength.label)
178
+ .font(.caption2)
179
+ .foregroundStyle(vm.passwordStrength.color)
180
+ .frame(width: 50, alignment: .trailing)
181
+ }
182
+ }
183
+
184
+ SecureField("Confirm password", text: $vm.backupPasswordConfirm)
185
+ .textFieldStyle(.roundedBorder)
186
+
187
+ if !vm.backupPasswordConfirm.isEmpty && vm.backupPassword != vm.backupPasswordConfirm {
188
+ Text("Passwords do not match")
189
+ .font(.caption)
190
+ .foregroundStyle(.red)
191
+ }
192
+ }
193
+ }
194
+
195
+ private var backupCompleteView: some View {
196
+ VStack(spacing: 20) {
197
+ Spacer()
198
+
199
+ Image(systemName: "checkmark.circle.fill")
200
+ .font(.system(size: 56))
201
+ .foregroundStyle(.green)
202
+
203
+ Text("Backup Created!")
204
+ .font(.title2.bold())
205
+
206
+ if let path = vm.backupFilePath {
207
+ VStack(spacing: 8) {
208
+ Text("Saved to:")
209
+ .font(.callout)
210
+ .foregroundStyle(.secondary)
211
+ Text(path)
212
+ .font(.system(.caption, design: .monospaced))
213
+ .lineLimit(2)
214
+ .truncationMode(.middle)
215
+ .textSelection(.enabled)
216
+ .padding(8)
217
+ .background(.quaternary, in: RoundedRectangle(cornerRadius: 6))
218
+ }
219
+
220
+ Button("Reveal in Finder") {
221
+ NSWorkspace.shared.selectFile(path, inFileViewerRootedAtPath: "")
222
+ }
223
+ .buttonStyle(.bordered)
224
+ .controlSize(.small)
225
+ }
226
+
227
+ Spacer()
228
+
229
+ Button("Done") { dismiss() }
230
+ .buttonStyle(.borderedProminent)
231
+ .padding(.bottom, 20)
232
+ }
233
+ .padding(24)
234
+ }
235
+ }
@@ -0,0 +1,316 @@
1
+ import SwiftUI
2
+ import UniformTypeIdentifiers
3
+
4
+ /// Restore wallets from a backup file
5
+ struct RestoreView: View {
6
+ @EnvironmentObject var walletStore: WalletStore
7
+ @EnvironmentObject var appState: AppState
8
+ @StateObject private var vm = BackupViewModel()
9
+ @Environment(\.dismiss) private var dismiss
10
+ @State private var showFileImporter = false
11
+
12
+ var body: some View {
13
+ VStack(spacing: 0) {
14
+ // Header
15
+ VStack(spacing: 8) {
16
+ Image(systemName: "arrow.uturn.backward.circle.fill")
17
+ .font(.system(size: 32))
18
+ .foregroundStyle(.accentColor)
19
+ Text("Restore from Backup")
20
+ .font(.title2.bold())
21
+ Text("Recover your wallets from an encrypted backup file")
22
+ .font(.callout)
23
+ .foregroundStyle(.secondary)
24
+ }
25
+ .padding(.top, 20)
26
+ .padding(.bottom, 16)
27
+
28
+ Divider()
29
+
30
+ if vm.restoreComplete {
31
+ restoreCompleteView
32
+ } else {
33
+ ScrollView {
34
+ VStack(spacing: 24) {
35
+ // File selection
36
+ fileSelectionSection
37
+
38
+ // Backup info (if file selected)
39
+ if let metadata = vm.backupMetadata {
40
+ backupInfoSection(metadata)
41
+ }
42
+
43
+ // Password
44
+ if vm.restoreFileURL != nil {
45
+ passwordSection
46
+ }
47
+ }
48
+ .padding(24)
49
+ }
50
+
51
+ Divider()
52
+
53
+ // Footer
54
+ HStack {
55
+ Button("Cancel") { dismiss() }
56
+ .buttonStyle(.bordered)
57
+ Spacer()
58
+
59
+ if vm.isRestoring {
60
+ ProgressView()
61
+ .controlSize(.small)
62
+ }
63
+
64
+ Button("Restore") {
65
+ Task {
66
+ await vm.restoreFromBackup(store: walletStore)
67
+ }
68
+ }
69
+ .buttonStyle(.borderedProminent)
70
+ .disabled(!vm.canRestore)
71
+ }
72
+ .padding(16)
73
+ }
74
+ }
75
+ .frame(maxWidth: .infinity)
76
+ .background(.background)
77
+ .fileImporter(
78
+ isPresented: $showFileImporter,
79
+ allowedContentTypes: [
80
+ UTType(filenameExtension: "avbackup") ?? .data,
81
+ .json,
82
+ .data
83
+ ],
84
+ allowsMultipleSelection: false
85
+ ) { result in
86
+ switch result {
87
+ case .success(let urls):
88
+ vm.restoreFileURL = urls.first
89
+ vm.loadBackupMetadata()
90
+ case .failure(let error):
91
+ vm.errorMessage = error.localizedDescription
92
+ }
93
+ }
94
+ .onAppear {
95
+ vm.loadExistingBackups()
96
+ }
97
+ .alert("Restore Error", isPresented: Binding(
98
+ get: { vm.errorMessage != nil },
99
+ set: { if !$0 { vm.errorMessage = nil } }
100
+ )) {
101
+ Button("OK") { vm.errorMessage = nil }
102
+ } message: {
103
+ Text(vm.errorMessage ?? "")
104
+ }
105
+ }
106
+
107
+ private var fileSelectionSection: some View {
108
+ VStack(alignment: .leading, spacing: 12) {
109
+ Text("Select Backup File")
110
+ .font(.headline)
111
+
112
+ if let url = vm.restoreFileURL {
113
+ HStack {
114
+ Image(systemName: "doc.zipper")
115
+ .font(.title3)
116
+ .foregroundStyle(.accentColor)
117
+
118
+ VStack(alignment: .leading) {
119
+ Text(url.lastPathComponent)
120
+ .font(.callout)
121
+ Text(url.deletingLastPathComponent().path)
122
+ .font(.caption)
123
+ .foregroundStyle(.secondary)
124
+ .lineLimit(1)
125
+ }
126
+
127
+ Spacer()
128
+
129
+ Button("Change") {
130
+ showFileImporter = true
131
+ }
132
+ .buttonStyle(.bordered)
133
+ .controlSize(.small)
134
+ }
135
+ .padding(12)
136
+ .background(.quaternary.opacity(0.5), in: RoundedRectangle(cornerRadius: 10))
137
+ } else {
138
+ // File picker button
139
+ Button {
140
+ showFileImporter = true
141
+ } label: {
142
+ VStack(spacing: 12) {
143
+ Image(systemName: "doc.badge.plus")
144
+ .font(.system(size: 32))
145
+ .foregroundStyle(.secondary)
146
+ Text("Select a backup file (.avbackup)")
147
+ .font(.callout)
148
+ .foregroundStyle(.secondary)
149
+ }
150
+ .frame(maxWidth: .infinity, minHeight: 100)
151
+ .background(
152
+ RoundedRectangle(cornerRadius: 12, style: .continuous)
153
+ .strokeBorder(style: StrokeStyle(lineWidth: 2, dash: [8, 4]))
154
+ .foregroundStyle(.secondary.opacity(0.3))
155
+ )
156
+ }
157
+ .buttonStyle(.plain)
158
+
159
+ // Recent backups
160
+ if !vm.existingBackups.isEmpty {
161
+ VStack(alignment: .leading, spacing: 8) {
162
+ Text("Recent Backups")
163
+ .font(.subheadline.bold())
164
+ .foregroundStyle(.secondary)
165
+
166
+ ForEach(vm.existingBackups) { backup in
167
+ Button {
168
+ vm.restoreFileURL = backup.url
169
+ vm.loadBackupMetadata()
170
+ } label: {
171
+ HStack {
172
+ Image(systemName: "doc.zipper")
173
+ .foregroundStyle(.secondary)
174
+ VStack(alignment: .leading) {
175
+ Text(backup.filename)
176
+ .font(.callout)
177
+ HStack(spacing: 8) {
178
+ Text(backup.createdAt.formatted(date: .abbreviated, time: .shortened))
179
+ Text(backup.formattedSize)
180
+ if let count = backup.walletCount {
181
+ Text("\(count) wallets")
182
+ }
183
+ }
184
+ .font(.caption)
185
+ .foregroundStyle(.secondary)
186
+ }
187
+ Spacer()
188
+ }
189
+ }
190
+ .buttonStyle(.plain)
191
+ .padding(8)
192
+ .background(.quaternary.opacity(0.3), in: RoundedRectangle(cornerRadius: 6))
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ private func backupInfoSection(_ backup: WalletBackup) -> some View {
201
+ VStack(alignment: .leading, spacing: 12) {
202
+ Text("Backup Contents")
203
+ .font(.headline)
204
+
205
+ VStack(spacing: 0) {
206
+ InfoRow(label: "Created", value: backup.createdAt.formatted(date: .long, time: .shortened))
207
+ Divider().padding(.leading)
208
+ InfoRow(label: "App Version", value: backup.appVersion)
209
+ Divider().padding(.leading)
210
+ InfoRow(label: "Wallets", value: "\(backup.wallets.count)")
211
+ }
212
+ .background(.background, in: RoundedRectangle(cornerRadius: 10))
213
+ .overlay(RoundedRectangle(cornerRadius: 10).stroke(.secondary.opacity(0.15)))
214
+
215
+ // Wallet list preview
216
+ VStack(spacing: 4) {
217
+ ForEach(backup.wallets) { entry in
218
+ HStack(spacing: 10) {
219
+ Image(systemName: entry.chain.iconName)
220
+ .foregroundStyle(entry.chain.color)
221
+ .frame(width: 20)
222
+ Text(entry.name)
223
+ .font(.callout)
224
+ Spacer()
225
+ Text(entry.chain.symbol)
226
+ .font(.caption)
227
+ .foregroundStyle(.secondary)
228
+
229
+ // Show if wallet already exists
230
+ if walletStore.wallets.contains(where: { $0.address == entry.address && $0.chain == entry.chain }) {
231
+ Text("EXISTS")
232
+ .font(.caption2.bold())
233
+ .foregroundStyle(.orange)
234
+ .padding(.horizontal, 4)
235
+ .padding(.vertical, 1)
236
+ .background(.orange.opacity(0.15), in: Capsule())
237
+ }
238
+ }
239
+ .padding(.horizontal, 10)
240
+ .padding(.vertical, 6)
241
+ }
242
+ }
243
+ .background(.quaternary.opacity(0.3), in: RoundedRectangle(cornerRadius: 8))
244
+ }
245
+ }
246
+
247
+ private var passwordSection: some View {
248
+ VStack(alignment: .leading, spacing: 8) {
249
+ Text("Backup Password")
250
+ .font(.headline)
251
+
252
+ Text("Enter the password used when this backup was created.")
253
+ .font(.caption)
254
+ .foregroundStyle(.secondary)
255
+
256
+ SecureField("Backup password", text: $vm.restorePassword)
257
+ .textFieldStyle(.roundedBorder)
258
+ }
259
+ }
260
+
261
+ private var restoreCompleteView: some View {
262
+ VStack(spacing: 20) {
263
+ Spacer()
264
+
265
+ Image(systemName: "checkmark.circle.fill")
266
+ .font(.system(size: 56))
267
+ .foregroundStyle(.green)
268
+
269
+ Text("Restore Complete!")
270
+ .font(.title2.bold())
271
+
272
+ Text("\(vm.restoredCount) wallet(s) restored successfully.")
273
+ .font(.callout)
274
+ .foregroundStyle(.secondary)
275
+
276
+ if let metadata = vm.backupMetadata {
277
+ let skipped = metadata.wallets.count - vm.restoredCount
278
+ if skipped > 0 {
279
+ Text("\(skipped) wallet(s) were skipped because they already exist.")
280
+ .font(.caption)
281
+ .foregroundStyle(.tertiary)
282
+ }
283
+ }
284
+
285
+ Spacer()
286
+
287
+ Button("Done") { dismiss() }
288
+ .buttonStyle(.borderedProminent)
289
+ .padding(.bottom, 20)
290
+ }
291
+ .padding(24)
292
+ }
293
+ }
294
+
295
+ /// Combined backup and restore view for the sidebar navigation
296
+ struct BackupRestoreView: View {
297
+ @State private var selectedTab = 0
298
+
299
+ var body: some View {
300
+ VStack(spacing: 0) {
301
+ Picker("", selection: $selectedTab) {
302
+ Text("Backup").tag(0)
303
+ Text("Restore").tag(1)
304
+ }
305
+ .pickerStyle(.segmented)
306
+ .padding(16)
307
+
308
+ if selectedTab == 0 {
309
+ BackupView()
310
+ } else {
311
+ RestoreView()
312
+ }
313
+ }
314
+ .navigationTitle("Backup & Restore")
315
+ }
316
+ }