use atp_caip::curve::Curve;
use candid::Principal;
use ethers_core::types::transaction::eip1559::Eip1559TransactionRequest;
use crate::application::dtos::account_messages::*;
use crate::application::dtos::account_reply::AccountReply;
use crate::domain::models::account::{Account, AccountState};
use crate::domain::models::signer::SignatureAlgorithm;
use crate::domain::repositories::account_repository::IAccountRepository;
use crate::domain::repositories::signer_repository::ISignerRepository;
use crate::infrastructure::repositories::account_repository_impl::AccountRepositoryImpl;
use crate::infrastructure::repositories::signer_repository_impl::SignerRepositoryImpl;
use crate::utils::config::get_chain_registry;
use crate::utils::eth_utils::sha256;
pub struct AccountService {
account_repository: AccountRepositoryImpl,
signer_repository: SignerRepositoryImpl,
}
impl AccountService {
pub fn new(
account_repository: AccountRepositoryImpl,
signer_repository: SignerRepositoryImpl,
) -> Self {
Self {
account_repository,
signer_repository,
}
}
pub fn to_account_reply(&self, account: &Account) -> AccountReply {
AccountReply {
id: account.id().clone(),
owner: account.owner().to_string(),
public_key_hex: hex::encode(account.public_key()),
algorithm: account.algorithm().clone(),
curve: account.curve().clone(),
account_state: account.account_state().clone(),
approved_address: match account.approved_address() {
Some(address) => address.to_string(),
None => "".to_string(),
},
}
}
pub async fn create_account(
&self,
request: CreateAccountRequest,
owner: Principal,
) -> Result<CreateAccountResponse, String> {
let principal = ic_cdk::api::caller().to_string();
let timestamp = ic_cdk::api::time();
let id_string = format!("{}{}", principal, timestamp);
let id = hex::encode(sha256(&id_string));
let public_key = self
.signer_repository
.generate_public_key(request.algorithm.clone(), request.curve.clone(), id.clone())
.await?;
let account = Account::new(
id,
owner,
public_key.public_key,
request.algorithm.clone(),
request.curve.clone(),
request.approved_address,
);
let created_account = self.account_repository.insert(account.clone())?;
Ok(CreateAccountResponse {
account: self.to_account_reply(&created_account),
})
}
pub fn unlock_account(
&self,
request: UnlockAccountRequest,
) -> Result<UnlockAccountResponse, String> {
let mut account = self.account_repository.get(&request.account_id)?;
account.unlock()?;
let updated_account = self.account_repository.insert(account.clone())?;
Ok(UnlockAccountResponse {
account: self.to_account_reply(&updated_account),
})
}
pub fn transfer_account(
&self,
request: TransferAccountRequest,
) -> Result<TransferAccountResponse, String> {
let mut account = self.account_repository.get(&request.account_id)?;
account.transfer_account(request.to)?;
let updated_account = self.account_repository.insert(account.clone())?;
Ok(TransferAccountResponse {
account: self.to_account_reply(&updated_account),
})
}
pub fn activate_account(
&self,
request: ActivateAccountRequest,
) -> Result<ActivateAccountResponse, String> {
let mut account = self.account_repository.get(&request.account_id)?;
account.activate()?;
let updated_account = self.account_repository.insert(account.clone())?;
Ok(ActivateAccountResponse {
account: self.to_account_reply(&updated_account),
})
}
pub fn get_account(&self, request: GetAccountRequest) -> Result<GetAccountResponse, String> {
let account = self.account_repository.get(&request.account_id)?;
Ok(GetAccountResponse {
account: self.to_account_reply(&account),
})
}
pub async fn sign(&self, request: SignRequest) -> Result<SignResponse, String> {
let account = self.account_repository.get(&request.account_id)?;
if account.account_state().clone() != AccountState::Active {
return Err("Account is not activated".to_string());
}
let message_bytes = match hex::decode(&request.message_hex) {
Ok(bytes) => bytes,
Err(_) => return Err("Invalid hex string".to_string()),
};
if account.is_owner(ic_cdk::api::caller()) {
let signature = self
.signer_repository
.sign(
account.algorithm().clone(),
account.curve().clone(),
message_bytes,
account.id().clone(),
)
.await?;
Ok(SignResponse {
signature: hex::encode(signature.signature),
})
} else {
Err("Caller is not the owner of the account".to_string())
}
}
pub async fn sign_eip1559_transaction(
&self,
request: SignEip1559TransactionRequest,
) -> Result<SignEip1559TransactionResponse, String> {
let account = self.account_repository.get(&request.account_id)?;
if account.algorithm().clone() != SignatureAlgorithm::Ecdsa {
return Err("Signature algorithm is not ECDSA".to_string());
}
if account.curve().clone() != Curve::Secp256k1 {
return Err("Curve is not secp256k1".to_string());
}
if account.account_state().clone() != AccountState::Active {
return Err("Account is not activated".to_string());
}
if account.is_owner(ic_cdk::api::caller()) {
let tx = Eip1559TransactionRequest::try_from(request.tx_request)?;
let signature = self
.signer_repository
.sign_eip1559_transaction(tx, account.id().clone())
.await?;
Ok(SignEip1559TransactionResponse { signature })
} else {
Err("Caller is not the owner of the account".to_string())
}
}
pub fn generate_address(
&self,
request: GenerateAddressRequest,
) -> Result<GenerateAddressResponse, String> {
let account = self.account_repository.get(&request.account_id)?;
let chain_id_wildcard = request
.chain_id
.to_wildcard()
.map_err(|e| format!("Invalid chain ID {}: {}", request.chain_id, e))?;
let registry = get_chain_registry()?;
let chain_config = registry
.get_chain(&chain_id_wildcard)
.map_err(|e| format!("Chain {} not found: {}", request.chain_id, e))?;
if !chain_config.is_supported_curve(account.curve()) {
return Err(format!(
"Curve {} is not supported for chain {}",
account.curve(),
request.chain_id
));
}
let pub_key_hex = hex::encode(account.public_key());
let address = atp_chain_utils::address::generate_address(pub_key_hex, request.chain_id)
.map_err(|e| format!("Failed to generate address: {}", e))?;
Ok(GenerateAddressResponse { address })
}
}