Skip to content

Commit

Permalink
Merge pull request #55 from starknet-id/feat/add_altcoins_support
Browse files Browse the repository at this point in the history
fix: panic if no response from api & detect the right erc20 contract
  • Loading branch information
Th0rgal authored Mar 27, 2024
2 parents 5299e41 + d097316 commit 56e99d9
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 35 deletions.
55 changes: 38 additions & 17 deletions bot/src/bot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::models::{AggregateResult, AggregateResults, DomainAggregateResult, Me
use crate::pipelines::{get_auto_renewal_altcoins_data, get_auto_renewal_data};
use crate::starknet_utils::check_pending_transactions;
use crate::starknetid_utils::{get_altcoin_quote, get_balances, get_renewal_price_eth};
use crate::utils::to_hex;
use crate::utils::{from_uint256, hex_to_bigdecimal, to_uint256};
use crate::{config::Config, models::AppState};

Expand Down Expand Up @@ -66,7 +67,18 @@ pub async fn get_domains_ready_for_renewal(
// Fetch balances for all renewers
let renewer_and_erc20: Vec<(String, String)> = results
.iter()
.map(|result| (result.renewer_address.clone(), result.erc20_addr.clone()))
.map(|result| {
(
result.renewer_address.clone(),
// get the erc20 address for the given auto_renew_contract
to_hex(
*config
.altcoins_mapping
.get(&result.auto_renew_contract)
.unwrap(),
),
)
})
.collect();

let balances = get_balances(config, renewer_and_erc20.clone()).await;
Expand All @@ -85,9 +97,7 @@ pub async fn get_domains_ready_for_renewal(
balances_iter.next().expect("Expected high not found"),
);
let mut outer_map = dynamic_balances.lock().unwrap();
let inner_map = outer_map
.entry(address.clone())
.or_default();
let inner_map = outer_map.entry(address.clone()).or_default();
inner_map.insert(erc20.clone(), balance);
}

Expand All @@ -111,16 +121,18 @@ pub async fn get_domains_ready_for_renewal(
if FieldElement::from_hex_be(erc20).unwrap() == config.contract.erc20 {
renewal_price_eth
} else {
match get_altcoin_quote(config, result.erc20_addr.clone()).await {
match get_altcoin_quote(config, erc20.to_string()).await {
Ok(quote) => {
(quote * renewal_price_eth)
/ BigInt::from_str("1000000000000000000").unwrap()
}
Err(e) => {
println!("Error while fetching quote: {:?}", e);
// this case can happen if the quote is not in the right range
// we return 0 and won't renew this domain
BigInt::from(0)
// in case get_altcoin_quote endpoint returns an error we panic with the error
logger.severe(format!(
"Error while fetching quote on starknetid server: {:?}",
e
));
panic!("Error while fetching quote on starknetid server: {:?}", e)
}
}
};
Expand All @@ -130,6 +142,7 @@ pub async fn get_domains_ready_for_renewal(
logger,
BigDecimal::from(balance.to_owned()),
BigDecimal::from(renewal_price.to_owned()),
erc20.clone(),
)
.await;

Expand All @@ -138,7 +151,7 @@ pub async fn get_domains_ready_for_renewal(
dynamic_balances
.lock()
.unwrap()
.entry(result.erc20_addr)
.entry(erc20.to_string())
.or_default()
.insert(address.to_owned(), new_balance);
};
Expand Down Expand Up @@ -186,25 +199,27 @@ async fn process_aggregate_result(
_logger: &Logger,
balance: BigDecimal,
renewal_price: BigDecimal,
erc20_addr: String,
) -> Option<AggregateResult> {
// Skip the rest if auto-renewal is not enabled
if !result.enabled || result.allowance.is_none() {
return None;
}

let renewer_addr = FieldElement::from_hex_be(&result.renewer_address).unwrap();
let erc20_allowance = if let Some(approval_value) = result.approval_value {
// map the vec of approval_values to get tha approval_value for the erc20_addr selected
let erc20_allowance = if let Some(approval_value) = result
.approval_values
.iter()
.find(|&data| data.erc20_addr == erc20_addr)
.map(|data| data.approval_value.clone())
{
hex_to_bigdecimal(&approval_value).unwrap()
} else {
BigDecimal::from(0)
};
let allowance = hex_to_bigdecimal(&result.allowance.unwrap()).unwrap();

// if renewal_price is 0, we don't renew the domain
if renewal_price == BigDecimal::from(0) {
return None;
}

// Check user meta hash
let mut tax_price = BigDecimal::from(0);
let mut meta_hash = FieldElement::ZERO;
Expand Down Expand Up @@ -466,7 +481,13 @@ pub async fn send_transaction(
.fee_estimate_multiplier(5.0f64);

match execution.estimate_fee().await {
Ok(fee) => match execution.nonce(nonce).max_fee(fee.overall_fee).send().await {
Ok(_) => match execution
.nonce(nonce)
// harcode max fee to 10$ = 0.0028 ETH
.max_fee(FieldElement::from(2800000000000000_u64))
.send()
.await
{
Ok(tx_result) => Ok(tx_result.transaction_hash),
Err(e) => {
let error_message = format!("An error occurred while renewing domains: {}", e);
Expand Down
65 changes: 63 additions & 2 deletions bot/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use serde::de::MapAccess;
use serde::de::Visitor;
use serde::Deserialize;
use serde::Deserializer;
use starknet::core::types::FieldElement;
use std::collections::HashMap;
use std::env;
use std::fmt;
use std::fs;

macro_rules! pub_struct {
Expand Down Expand Up @@ -65,17 +70,73 @@ pub_struct!(Clone, Deserialize; Server {
starknetid_api: String,
});

pub_struct!(Clone, Deserialize; Config {
pub_struct!(Clone, Deserialize; Altcoin {
address: FieldElement,
renewal_contract: FieldElement,
});

pub_struct!(Clone; Config {
contract: Contract,
database: Database,
account: MyAccount,
renewals : Renewals,
renewals: Renewals,
indexer_server: IndexerServer,
rpc: Rpc,
watchtower: Watchtower,
server: Server,
altcoins_mapping: HashMap<FieldElement, FieldElement>,
});

impl<'de> Deserialize<'de> for Config {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct OuterConfig {
contract: Contract,
database: Database,
account: MyAccount,
renewals: Renewals,
indexer_server: IndexerServer,
rpc: Rpc,
watchtower: Watchtower,
server: Server,
altcoins: HashMap<String, Altcoin>,
}

let OuterConfig {
contract,
database,
account,
renewals,
indexer_server,
rpc,
watchtower,
server,
altcoins,
} = OuterConfig::deserialize(deserializer)?;

// Build atcoins mapping
let altcoins_mapping = altcoins
.into_values()
.map(|val| (val.renewal_contract, val.address))
.collect();

Ok(Config {
contract,
database,
account,
renewals,
indexer_server,
rpc,
watchtower,
server,
altcoins_mapping,
})
}
}

pub fn load() -> Config {
let args: Vec<String> = env::args().collect();
let config_path = if args.len() <= 1 {
Expand Down
9 changes: 7 additions & 2 deletions bot/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,24 @@ pub struct Cursor {
pub from: Option<i64>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AutoRenewAllowance {
pub approval_value: String,
pub erc20_addr: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DomainAggregateResult {
pub domain: String,
pub expiry: Option<i32>,
pub renewer_address: String,
pub enabled: bool,
pub approval_value: Option<String>,
pub allowance: Option<String>,
pub last_renewal: Option<i64>,
pub meta_hash: Option<String>,
pub _cursor: Cursor,
pub erc20_addr: String,
pub auto_renew_contract: FieldElement,
pub approval_values: Vec<AutoRenewAllowance>,
}

pub struct AggregateResult {
Expand Down
25 changes: 16 additions & 9 deletions bot/src/pipelines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,35 +58,37 @@ pub async fn get_auto_renewal_data(
}},
doc! { "$unwind": { "path": "$approval_info", "preserveNullAndEmptyArrays": true } },
doc! { "$addFields": {
"erc20_addr": erc20_addr,
"auto_renew_contract": auto_renew_contract,
}},
doc! { "$group": {
"_id": "$domain_info.domain",
"expiry": { "$first": "$domain_info.expiry" },
"renewer_address": { "$first": "$renewer_address" },
"enabled": { "$first": "$enabled" },
"approval_value": { "$first": { "$ifNull": [ "$approval_info.allowance", "0x0" ] } },
"allowance": { "$first": "$allowance" },
"last_renewal": { "$first": "$last_renewal" },
"meta_hash": { "$first": "$meta_hash" },
"_cursor": { "$first": "$_cursor" },
"erc20_addr": { "$first": "$erc20_addr" },
"auto_renew_contract": { "$first": "$auto_renew_contract" },
"approval_values": {
"$push": {
"approval_value": "$approval_info.allowance",
"erc20_addr": erc20_addr
}
},
}},
doc! { "$project": {
"_id": 0,
"domain": "$_id",
"expiry": 1,
"renewer_address": 1,
"enabled": 1,
"approval_value": 1,
"allowance": 1,
"last_renewal": 1,
"meta_hash": 1,
"_cursor": 1,
"erc20_addr": 1,
"auto_renew_contract": 1,
"approval_values": 1,
}},
];

Expand Down Expand Up @@ -154,27 +156,30 @@ pub async fn get_auto_renewal_altcoins_data(
"expiry": { "$first": "$domain_info.expiry" },
"renewer_address": { "$first": "$renewer_address" },
"enabled": { "$first": "$enabled" },
"approval_value": { "$first": { "$ifNull": [ "$approval_info.allowance", "0x0" ] } },
"allowance": { "$first": "$allowance" },
"last_renewal": { "$first": "$last_renewal" },
"meta_hash": { "$first": "$meta_hash" },
"_cursor": { "$first": "$_cursor" },
"erc20_addr": { "$first": "$approval_info.erc20_addr" },
"auto_renew_contract": { "$first": "$auto_renew_contract" },
"approval_values": {
"$push": {
"approval_value": "$approval_info.allowance",
"erc20_addr": "$approval_info.erc20_addr"
}
},
}},
doc! { "$project": {
"_id": 0,
"domain": "$_id",
"expiry": 1,
"renewer_address": 1,
"enabled": 1,
"approval_value": 1,
"allowance": 1,
"last_renewal": 1,
"meta_hash": 1,
"_cursor": 1,
"erc20_addr": 1,
"auto_renew_contract": 1,
"approval_values": 1,
}},
];

Expand All @@ -190,5 +195,7 @@ pub async fn get_auto_renewal_altcoins_data(
// Check if the conversion was successful
let results = results?;

println!("results: {:?}", results);

Ok(results)
}
8 changes: 3 additions & 5 deletions bot/src/starknetid_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use crate::{config::Config, starknet_utils::create_jsonrpc_client};
lazy_static::lazy_static! {
static ref PRICE_DOMAIN_LEN_1: BigInt = BigInt::from_u128(1068493150684932 * 365).unwrap();
static ref PRICE_DOMAIN_LEN_2: BigInt = BigInt::from_u128(657534246575343 * 365).unwrap();
static ref PRICE_DOMAIN_LEN_3: BigInt = BigInt::from_u128(410958904109590 * 365).unwrap();
static ref PRICE_DOMAIN_LEN_4: BigInt = BigInt::from_u128(232876712328767 * 365).unwrap();
static ref PRICE_DOMAIN_LEN_3: BigInt = BigInt::from_u128(200000000000000 * 365).unwrap();
static ref PRICE_DOMAIN_LEN_4: BigInt = BigInt::from_u128(73972602739726 * 365).unwrap();
static ref PRICE_DOMAIN: BigInt = BigInt::from_u128(24657534246575 * 365).unwrap();
}

Expand Down Expand Up @@ -49,9 +49,7 @@ pub async fn get_altcoin_quote(config: &Config, erc20: String) -> Result<BigInt>
match client.get(&url).send().await {
Ok(response) => match response.text().await {
Ok(text) => match serde_json::from_str::<QuoteQueryResult>(&text) {
Ok(results) => {
Ok(BigInt::from_str(&results.quote).unwrap())
}
Ok(results) => Ok(BigInt::from_str(&results.quote).unwrap()),
Err(err) => Err(anyhow!("Error parsing response: {:?}", err)),
},
Err(err) => Err(anyhow!("Error fetching response: {:?}", err)),
Expand Down
9 changes: 9 additions & 0 deletions config.template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,12 @@ rpc_url = "https://starknet-goerli.g.alchemy.com/v2/xxxxxxx"

[server]
starknetid_api = "https://api.starknet.id"

[altcoins]
[altcoins.ETH]
address = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"
renewal_contract = "0x020379Ba14750ECEE8dde204D0649808BCf6D32E9fDe81Ca952Ab0360cdC0937"

[altcoins.STRK]
address = "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"
renewal_contract = "0x078F63fcD145Ddc6ca932E562b466AFbfD7c9E882C9aa70f3e5b2ce05cD892eA"

0 comments on commit 56e99d9

Please sign in to comment.