Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libudev-dev
sudo apt-get install -y libudev-dev libdbus-1-dev pkg-config

- uses: actions-rs/toolchain@v1
with:
Expand Down
86 changes: 83 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jup-ag = "0.9.1"
#kraken_sdk_rest = { path = "../kraken_sdk_rust/kraken_sdk_rest" }
kraken_sdk_rest = { git = "https://github.com/mvines/kraken_sdk_rust", rev = "80c634b3a8527f653db989689b298496dad30d4e" }
#kraken_sdk_rest = "0.18.0"
keyring = { version = "3", features = ["apple-native", "sync-secret-service"] }
lazy_static = "1.4.0"
log = "0.4.17"
num-derive = "0.4"
Expand Down
4 changes: 4 additions & 0 deletions src/coinbase_exchange.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ impl ExchangeClient for CoinbaseExchangeClient {
pin_mut!(accounts);

while let Some(account_result) = accounts.next().await {
if let Err(e) = account_result {
return Err(format!("Failed to get accounts: {e}").into());
}
for account in account_result.unwrap() {
if let Ok(id) = coinbase_rs::Uuid::from_str(&account.id) {
if token.name() == account.currency.code
Expand Down Expand Up @@ -157,6 +160,7 @@ pub fn new(
}: ExchangeCredentials,
) -> Result<CoinbaseExchangeClient, Box<dyn std::error::Error>> {
assert!(subaccount.is_none());
let secret = secret.replace("\\n", "\n");
Ok(CoinbaseExchangeClient {
client: coinbase_rs::Private::new(coinbase_rs::MAIN_URL, &api_key, &secret),
})
Expand Down
133 changes: 109 additions & 24 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ pub enum DbError {

#[error("Import failed: {0}")]
ImportFailed(String),

#[error("Keyring failed: {0}")]
KeyringFailed(String),
}

pub type DbResult<T> = std::result::Result<T, DbError>;
Expand Down Expand Up @@ -631,6 +634,7 @@ pub struct DbData {
sweep_stake_account: Option<SweepStakeAccount>,
transitory_sweep_stake_accounts: Vec<TransitorySweepStake>,
tax_rate: Option<TaxRate>,
exchanges: Option<HashSet<Exchange>>,
}

impl DbData {
Expand Down Expand Up @@ -684,6 +688,7 @@ impl DbData {
.get("transitory-sweep-stake-accounts")
.unwrap_or_default(),
tax_rate: None,
exchanges: None,
}
}

Expand Down Expand Up @@ -720,25 +725,56 @@ impl Db {
exchange_account: &str,
exchange_credentials: ExchangeCredentials,
) -> DbResult<()> {
self.clear_exchange_credentials(exchange, exchange_account)?;

self.credentials_db
.set(
&format!("{exchange:?}{exchange_account}"),
&exchange_credentials,
)
.unwrap();

Ok(self.credentials_db.dump()?)
let ec = serde_json::to_string(&exchange_credentials).unwrap();
// user can't be an empty string
let user = format!("{exchange}_{exchange_account}");
let keyring_entry = match keyring::Entry::new(&exchange.to_string(), &user) {
Ok(entry) => entry,
Err(e) => {
return Err(DbError::KeyringFailed(format!(
"Failed to create keyring entry: {e}"
)))
}
};
if let Err(e) = keyring_entry.set_secret(ec.as_bytes()) {
return Err(DbError::KeyringFailed(format!("Failed to set: {e}")));
}
self.add_exchange(exchange)
}

pub fn get_exchange_credentials(
&self,
exchange: Exchange,
exchange_account: &str,
) -> Option<ExchangeCredentials> {
self.credentials_db
if let Some(cred) = self
.credentials_db
.get(&format!("{exchange:?}{exchange_account}"))
{
return Some(cred);
}
let user = format!("{exchange}_{exchange_account}");
let keyring_entry = match keyring::Entry::new(&exchange.to_string(), &user) {
Ok(entry) => entry,
Err(e) => {
eprintln!("Failed to create keyring entry: {e}");
return None;
}
};
let value = match keyring_entry.get_secret() {
Ok(secret) => String::from_utf8(secret).unwrap(),
Err(e) => {
eprintln!("Failed to get the secret: {e}");
return None;
}
};
match serde_json::from_str(&value) {
Ok(v) => Some(v),
Err(e) => {
eprintln!("Failed to deserialize credentials: {e}");
None
}
}
}

pub fn clear_exchange_credentials(
Expand All @@ -754,25 +790,50 @@ impl Db {
.rem(&format!("{exchange:?}{exchange_account}"))
.ok();
self.credentials_db.dump()?;
return Ok(());
}
Ok(())
let user = format!("{exchange}_{exchange_account}");
let keyring_entry = match keyring::Entry::new(&exchange.to_string(), &user) {
Ok(entry) => entry,
Err(e) => {
return Err(DbError::KeyringFailed(format!(
"Failed to create keyring entry: {e}"
)))
}
};
if let Err(e) = keyring_entry.delete_credential() {
return Err(DbError::KeyringFailed(format!(
"Failed to delete credential: {e}"
)));
}
self.remove_exchange(exchange)
}

pub fn get_default_accounts_from_configured_exchanges(
&self,
) -> Vec<(Exchange, ExchangeCredentials, String)> {
self.credentials_db
.get_all()
.into_iter()
.filter_map(|key| {
if let Ok(exchange) = key.parse() {
self.get_exchange_credentials(exchange, "")
.map(|exchange_credentials| (exchange, exchange_credentials, "".into()))
} else {
None
}
})
.collect()
if let Some(exchanges) = self.get_exchanges() {
exchanges
.iter()
.filter_map(|exchange| {
self.get_exchange_credentials(*exchange, "")
.map(|exchange_credentials| (*exchange, exchange_credentials, "".into()))
})
.collect()
} else {
self.credentials_db
.get_all()
.into_iter()
.filter_map(|key| {
if let Ok(exchange) = key.parse() {
self.get_exchange_credentials(exchange, "")
.map(|exchange_credentials| (exchange, exchange_credentials, "".into()))
} else {
None
}
})
.collect()
}
}

pub fn set_metrics_config(&mut self, metrics_config: MetricsConfig) -> DbResult<()> {
Expand Down Expand Up @@ -1586,6 +1647,30 @@ impl Db {
self.save()
}

fn get_exchanges(&self) -> Option<HashSet<Exchange>> {
self.data.exchanges.clone()
}

fn add_exchange(&mut self, exchange: Exchange) -> DbResult<()> {
if self.data.exchanges.is_none() {
self.data.exchanges = Some(HashSet::<_>::new());
}
if self.data.exchanges.as_mut().unwrap().insert(exchange) {
self.save()
} else {
Ok(())
}
}

fn remove_exchange(&mut self, exchange: Exchange) -> DbResult<()> {
if self.data.exchanges.is_some() && self.data.exchanges.as_mut().unwrap().remove(&exchange)
{
self.save()
} else {
Ok(())
}
}

#[allow(clippy::too_many_arguments)]
pub fn record_transfer(
&mut self,
Expand Down
Loading