1use anyhow::Result;
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5use url::Url;
6use uuid::Uuid;
7
8use penumbra_sdk_keys::FullViewingKey;
9use penumbra_sdk_num::Amount;
10
11#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct FvkEntry {
13 pub fvk: FullViewingKey,
14 pub wallet_id: Uuid,
15}
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
18pub struct AccountConfig {
20 original: FvkEntry,
24 genesis_balance: Amount,
26 migrations: Vec<FvkEntry>,
28}
29
30impl AccountConfig {
31 pub fn new(original: FvkEntry, genesis_balance: Amount) -> Self {
32 Self {
33 original,
34 genesis_balance,
35 migrations: vec![],
36 }
37 }
38
39 pub fn original_fvk(&self) -> FullViewingKey {
41 self.original.fvk.clone()
42 }
43
44 pub fn genesis_balance(&self) -> Amount {
46 self.genesis_balance
47 }
48
49 pub fn add_migration(&mut self, fvk_entry: FvkEntry) {
51 self.migrations.push(fvk_entry);
52 }
53
54 pub fn active_wallet(&self) -> FvkEntry {
56 if self.migrations.is_empty() {
57 self.original.clone()
58 } else {
59 self.migrations
60 .last()
61 .expect("migrations must not be empty")
62 .clone()
63 }
64 }
65
66 pub fn active_fvk(&self) -> FullViewingKey {
67 self.active_wallet().fvk
68 }
69
70 pub fn active_uuid(&self) -> Uuid {
71 self.active_wallet().wallet_id
72 }
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize)]
76pub struct PmonitorConfig {
82 grpc_url: Url,
84 accounts: Vec<AccountConfig>,
86}
87
88impl PmonitorConfig {
89 pub fn new(grpc_url: Url, accounts: Vec<AccountConfig>) -> Self {
90 Self { grpc_url, accounts }
91 }
92
93 pub fn grpc_url(&self) -> Url {
94 self.grpc_url.clone()
95 }
96
97 pub fn accounts(&self) -> &Vec<AccountConfig> {
98 &self.accounts
99 }
100
101 pub fn set_account(&mut self, index: usize, account: AccountConfig) {
102 self.accounts[index] = account;
103 }
104}
105
106pub fn parse_dest_fvk_from_memo(memo: &str) -> Result<FullViewingKey> {
108 let re = Regex::new(r"Migrating balance from .+ to (.+)")?;
109 if let Some(captures) = re.captures(memo) {
110 if let Some(dest_fvk_str) = captures.get(1) {
111 return dest_fvk_str
112 .as_str()
113 .parse::<FullViewingKey>()
114 .map_err(|_| anyhow::anyhow!("Invalid destination FVK in memo"));
115 }
116 }
117 Err(anyhow::anyhow!("Could not parse destination FVK from memo"))
118}