use thiserror::Error;
pub type Result<T> = std::result::Result<T, TallyError>;
#[derive(Error, Debug)]
pub enum TallyError {
#[error("Anchor error: {0}")]
Anchor(anchor_lang::error::Error),
#[error("Anchor client error: {0}")]
AnchorClient(Box<anchor_client::ClientError>),
#[error("Solana SDK error: {0}")]
Solana(#[from] anchor_client::solana_sdk::pubkey::ParsePubkeyError),
#[error("SPL Token error: {0}")]
SplToken(#[from] spl_token::error::TokenError),
#[error("Program error: {0}")]
Program(#[from] solana_program::program_error::ProgramError),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Tally SDK error: {0}")]
Generic(String),
#[error("Event parsing error: {0}")]
ParseError(String),
#[error("Invalid PDA: {0}")]
InvalidPda(String),
#[error("Invalid token program: expected {expected}, found {found}")]
InvalidTokenProgram { expected: String, found: String },
#[error("Account not found: {0}")]
AccountNotFound(String),
#[error("Insufficient funds: required {required}, available {available}")]
InsufficientFunds { required: u64, available: u64 },
#[error("Invalid subscription state: {0}")]
InvalidSubscriptionState(String),
#[error("Failed to detect token program for mint: {mint}")]
TokenProgramDetectionFailed { mint: String },
#[error("RPC error: {0}")]
RpcError(String),
#[error("Invalid subscriber token account. Ensure the account is a valid USDC token account owned by the subscriber.")]
InvalidSubscriberTokenAccount,
#[error("Invalid merchant treasury token account. Ensure the account is a valid USDC token account.")]
InvalidMerchantTreasuryAccount,
#[error("Invalid platform treasury token account. Ensure the account is a valid USDC token account.")]
InvalidPlatformTreasuryAccount,
#[error("Invalid USDC mint account. Ensure the account is a valid token mint account.")]
InvalidUsdcMint,
#[error(
"Merchant account not found or invalid. Ensure the merchant has been properly initialized."
)]
MerchantNotFound,
#[error("Subscription plan not found or invalid. Ensure the plan exists and belongs to the specified merchant.")]
PlanNotFound,
#[error("Subscription not found or invalid. Ensure the subscription exists for this plan and subscriber.")]
SubscriptionNotFound,
#[error("Global configuration account not found or invalid. Ensure the program has been properly initialized.")]
ConfigNotFound,
}
impl From<anchor_client::ClientError> for TallyError {
fn from(error: anchor_client::ClientError) -> Self {
Self::from_anchor_client_error(error)
}
}
impl From<anchor_lang::error::Error> for TallyError {
fn from(error: anchor_lang::error::Error) -> Self {
Self::from_anchor_error(error)
}
}
impl From<String> for TallyError {
fn from(msg: String) -> Self {
Self::Generic(msg)
}
}
impl From<&str> for TallyError {
fn from(msg: &str) -> Self {
Self::Generic(msg.to_string())
}
}
impl From<anchor_lang::prelude::ProgramError> for TallyError {
fn from(error: anchor_lang::prelude::ProgramError) -> Self {
Self::Generic(format!("Program error: {error:?}"))
}
}
impl From<anyhow::Error> for TallyError {
fn from(error: anyhow::Error) -> Self {
Self::Generic(error.to_string())
}
}
impl TallyError {
#[must_use]
pub fn from_anchor_error(anchor_error: anchor_lang::error::Error) -> Self {
use anchor_lang::error::Error;
match &anchor_error {
Error::AnchorError(anchor_err) => {
match anchor_err.error_code_number {
6012 => Self::InvalidSubscriberTokenAccount,
6013 => Self::InvalidMerchantTreasuryAccount,
6014 => Self::InvalidPlatformTreasuryAccount,
6015 => Self::InvalidUsdcMint,
6016 => Self::MerchantNotFound,
6017 => Self::PlanNotFound,
6018 => Self::SubscriptionNotFound,
6019 => Self::ConfigNotFound,
_ => Self::Anchor(anchor_error),
}
}
Error::ProgramError(_) => Self::Anchor(anchor_error),
}
}
pub fn from_anchor_client_error(client_error: anchor_client::ClientError) -> Self {
if let anchor_client::ClientError::SolanaClientError(solana_err) = &client_error {
if let Some(
anchor_client::solana_sdk::transaction::TransactionError::InstructionError(
_,
anchor_client::solana_sdk::instruction::InstructionError::Custom(error_code),
),
) = solana_err.get_transaction_error()
{
match error_code {
6012 => return Self::InvalidSubscriberTokenAccount,
6013 => return Self::InvalidMerchantTreasuryAccount,
6014 => return Self::InvalidPlatformTreasuryAccount,
6015 => return Self::InvalidUsdcMint,
6016 => return Self::MerchantNotFound,
6017 => return Self::PlanNotFound,
6018 => return Self::SubscriptionNotFound,
6019 => return Self::ConfigNotFound,
_ => {} }
}
}
Self::AnchorClient(Box::new(client_error))
}
}