Compare commits

...

10 commits

Author SHA1 Message Date
bd7a72f3c0 Improve balance verification in LamassuTransactionProcessor: Added rounding to two decimal places for balance and fiat amounts to ensure precision in comparisons. Enhanced logging for insufficient balance scenarios, improving clarity in transaction processing and error reporting.
Some checks failed
CI / lint (push) Has been cancelled
CI / tests (3.10) (push) Has been cancelled
CI / tests (3.9) (push) Has been cancelled
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
2025-07-19 00:21:13 +02:00
d1242e5cd2 Refactor transaction data handling in LamassuTransactionProcessor: Improved consistency in processing None and empty values during data ingestion. Defaulted numeric fields to 0 and percentage fields to 0.0 for better error handling. Ensured clean extraction of transaction details, enhancing reliability in transaction processing. 2025-07-08 06:14:52 +02:00
4843b43147 FIX: exclude flow_clients remaining_balance values less than 0.01 2025-07-06 02:04:11 +02:00
5d41e0c50e Refine distribution logic in transaction processing: Updated the LamassuTransactionProcessor to only create distributions for clients with positive allocated sats. Enhanced logging to indicate when clients are skipped due to zero amounts, improving clarity and accuracy in transaction reporting. 2025-07-06 01:15:26 +02:00
bca39b91cd Enhance commission memo generation in transaction processing: Added discount handling to the commission memo in LamassuTransactionProcessor, allowing for the calculation of effective commission percentages when discounts are applied. This improves clarity in transaction details by providing a more accurate representation of commissions after discounts. 2025-07-06 00:54:19 +02:00
a864f285e4 Refactor m004_convert_to_gtq_storage migration: Streamlined the conversion of centavo amounts to GTQ by detecting the database type (PostgreSQL or SQLite) and applying appropriate data type changes and updates. This enhances clarity and ensures proper handling of data conversions across relevant tables. 2025-07-06 00:24:49 +02:00
c83ebf43ab 01 Refactor currency handling to store amounts in GTQ: Removed currency conversion utilities, updated models and API endpoints to directly handle GTQ amounts, and modified transaction processing logic for consistency. Enhanced frontend to reflect these changes, ensuring accurate display and submission of GTQ values across the application.
Refactor GTQ storage migration: Moved the conversion logic for centavo amounts to GTQ into a new migration function, m004_convert_to_gtq_storage, ensuring proper data type changes and updates across relevant tables. This enhances clarity and maintains the integrity of the migration process.
2025-07-06 00:13:03 +02:00
aa71321c84 00 Add currency conversion utilities and update models for GTQ handling: Introduced currency conversion functions for GTQ and centavos, updated API and database models to handle GTQ amounts directly, and modified API endpoints to streamline deposit and balance summary responses. Adjusted frontend to reflect changes in currency representation, ensuring consistency across the application. 2025-07-05 23:38:23 +02:00
7b40fcef21 Change order of transaction confirmation sorting: Updated the SQL query in the LamassuTransactionProcessor to sort confirmed transactions in ascending order by confirmation date, improving the retrieval of older transactions. 2025-07-05 18:31:59 +02:00
d701b7c770 Implement final balance verification in distribution process: Added checks to ensure clients have positive balances before finalizing distributions and making payments. Enhanced logging for rejected clients and balance sufficiency, improving transaction reliability and transparency. 2025-07-05 18:31:20 +02:00
5 changed files with 170 additions and 57 deletions

View file

@ -267,7 +267,7 @@ async def get_client_balance_summary(client_id: str, as_of_time: Optional[dateti
from loguru import logger
# Verify timezone consistency for temporal filtering
tz_info = "UTC" if as_of_time.tzinfo == timezone.utc else f"TZ: {as_of_time.tzinfo}"
logger.info(f"Client {client_id[:8]}... balance as of {as_of_time} ({tz_info}): deposits.confirmed_at <= cutoff, payments.transaction_time <= cutoff → {total_deposits - total_payments} centavos remaining")
logger.info(f"Client {client_id[:8]}... balance as of {as_of_time} ({tz_info}): deposits.confirmed_at <= cutoff, payments.transaction_time <= cutoff → {total_deposits - total_payments:.2f} GTQ remaining")
return ClientBalanceSummary(
client_id=client_id,

View file

@ -134,4 +134,39 @@ async def m003_add_max_daily_limit_config(db):
ALTER TABLE satoshimachine.lamassu_config
ADD COLUMN max_daily_limit_gtq INTEGER NOT NULL DEFAULT 2000
"""
)
)
async def m004_convert_to_gtq_storage(db):
"""
Convert centavo storage to GTQ storage by changing data types and converting existing data.
Handles both SQLite (data conversion only) and PostgreSQL (data + schema changes).
"""
# Detect database type
db_type = str(type(db)).lower()
is_postgres = 'postgres' in db_type or 'asyncpg' in db_type
if is_postgres:
# PostgreSQL: Need to change column types first, then convert data
# Change column types to DECIMAL(10,2)
await db.execute("ALTER TABLE satoshimachine.dca_deposits ALTER COLUMN amount TYPE DECIMAL(10,2)")
await db.execute("ALTER TABLE satoshimachine.dca_payments ALTER COLUMN amount_fiat TYPE DECIMAL(10,2)")
await db.execute("ALTER TABLE satoshimachine.lamassu_transactions ALTER COLUMN fiat_amount TYPE DECIMAL(10,2)")
await db.execute("ALTER TABLE satoshimachine.dca_clients ALTER COLUMN fixed_mode_daily_limit TYPE DECIMAL(10,2)")
await db.execute("ALTER TABLE satoshimachine.lamassu_config ALTER COLUMN max_daily_limit_gtq TYPE DECIMAL(10,2)")
# Convert data from centavos to GTQ
await db.execute("UPDATE satoshimachine.dca_deposits SET amount = amount / 100.0 WHERE currency = 'GTQ'")
await db.execute("UPDATE satoshimachine.dca_payments SET amount_fiat = amount_fiat / 100.0")
await db.execute("UPDATE satoshimachine.lamassu_transactions SET fiat_amount = fiat_amount / 100.0")
await db.execute("UPDATE satoshimachine.dca_clients SET fixed_mode_daily_limit = fixed_mode_daily_limit / 100.0 WHERE fixed_mode_daily_limit IS NOT NULL")
await db.execute("UPDATE satoshimachine.lamassu_config SET max_daily_limit_gtq = max_daily_limit_gtq / 100.0 WHERE max_daily_limit_gtq > 1000")
else:
# SQLite: Data conversion only (dynamic typing handles the rest)
await db.execute("UPDATE satoshimachine.dca_deposits SET amount = CAST(amount AS REAL) / 100.0 WHERE currency = 'GTQ'")
await db.execute("UPDATE satoshimachine.dca_payments SET amount_fiat = CAST(amount_fiat AS REAL) / 100.0")
await db.execute("UPDATE satoshimachine.lamassu_transactions SET fiat_amount = CAST(fiat_amount AS REAL) / 100.0")
await db.execute("UPDATE satoshimachine.dca_clients SET fixed_mode_daily_limit = CAST(fixed_mode_daily_limit AS REAL) / 100.0 WHERE fixed_mode_daily_limit IS NOT NULL")
await db.execute("UPDATE satoshimachine.lamassu_config SET max_daily_limit_gtq = CAST(max_daily_limit_gtq AS REAL) / 100.0 WHERE max_daily_limit_gtq > 1000")

View file

@ -3,7 +3,7 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
from pydantic import BaseModel, validator
# DCA Client Models
@ -12,7 +12,7 @@ class CreateDcaClientData(BaseModel):
wallet_id: str
username: str
dca_mode: str = "flow" # 'flow' or 'fixed'
fixed_mode_daily_limit: Optional[int] = None
fixed_mode_daily_limit: Optional[float] = None
class DcaClient(BaseModel):
@ -30,22 +30,29 @@ class DcaClient(BaseModel):
class UpdateDcaClientData(BaseModel):
username: Optional[str] = None
dca_mode: Optional[str] = None
fixed_mode_daily_limit: Optional[int] = None
fixed_mode_daily_limit: Optional[float] = None
status: Optional[str] = None
# Deposit Models
# Deposit Models (Now storing GTQ directly)
class CreateDepositData(BaseModel):
client_id: str
amount: int # Amount in smallest currency unit (centavos for GTQ)
amount: float # Amount in GTQ (e.g., 150.75)
currency: str = "GTQ"
notes: Optional[str] = None
@validator('amount')
def round_amount_to_cents(cls, v):
"""Ensure amount is rounded to 2 decimal places for DECIMAL(10,2) storage"""
if v is not None:
return round(float(v), 2)
return v
class DcaDeposit(BaseModel):
id: str
client_id: str
amount: int
amount: float # Amount in GTQ (e.g., 150.75)
currency: str
status: str # 'pending' or 'confirmed'
notes: Optional[str]
@ -62,7 +69,7 @@ class UpdateDepositStatusData(BaseModel):
class CreateDcaPaymentData(BaseModel):
client_id: str
amount_sats: int
amount_fiat: int # Stored in centavos (GTQ * 100) for precision
amount_fiat: float # Amount in GTQ (e.g., 150.75)
exchange_rate: float
transaction_type: str # 'flow', 'fixed', 'manual', 'commission'
lamassu_transaction_id: Optional[str] = None
@ -74,7 +81,7 @@ class DcaPayment(BaseModel):
id: str
client_id: str
amount_sats: int
amount_fiat: int # Stored in centavos (GTQ * 100) for precision
amount_fiat: float # Amount in GTQ (e.g., 150.75)
exchange_rate: float
transaction_type: str
lamassu_transaction_id: Optional[str]
@ -84,19 +91,19 @@ class DcaPayment(BaseModel):
transaction_time: Optional[datetime] = None # Original ATM transaction time
# Client Balance Summary
# Client Balance Summary (Now storing GTQ directly)
class ClientBalanceSummary(BaseModel):
client_id: str
total_deposits: int # Total confirmed deposits
total_payments: int # Total payments made
remaining_balance: int # Available balance for DCA
total_deposits: float # Total confirmed deposits in GTQ
total_payments: float # Total payments made in GTQ
remaining_balance: float # Available balance for DCA in GTQ
currency: str
# Transaction Processing Models
class LamassuTransaction(BaseModel):
transaction_id: str
amount_fiat: int # Stored in centavos (GTQ * 100) for precision
amount_fiat: float # Amount in GTQ (e.g., 150.75)
amount_crypto: int
exchange_rate: float
transaction_type: str # 'cash_in' or 'cash_out'
@ -107,7 +114,7 @@ class LamassuTransaction(BaseModel):
# Lamassu Transaction Storage Models
class CreateLamassuTransactionData(BaseModel):
lamassu_transaction_id: str
fiat_amount: int # Stored in centavos (GTQ * 100) for precision
fiat_amount: float # Amount in GTQ (e.g., 150.75)
crypto_amount: int
commission_percentage: float
discount: float = 0.0
@ -124,7 +131,7 @@ class CreateLamassuTransactionData(BaseModel):
class StoredLamassuTransaction(BaseModel):
id: str
lamassu_transaction_id: str
fiat_amount: int
fiat_amount: float # Amount in GTQ (e.g., 150.75)
crypto_amount: int
commission_percentage: float
discount: float
@ -160,7 +167,14 @@ class CreateLamassuConfigData(BaseModel):
ssh_password: Optional[str] = None
ssh_private_key: Optional[str] = None # Path to private key file or key content
# DCA Client Limits
max_daily_limit_gtq: int = 2000 # Maximum daily limit for Fixed mode clients
max_daily_limit_gtq: float = 2000.0 # Maximum daily limit for Fixed mode clients
@validator('max_daily_limit_gtq')
def round_max_daily_limit(cls, v):
"""Ensure max daily limit is rounded to 2 decimal places"""
if v is not None:
return round(float(v), 2)
return v
class LamassuConfig(BaseModel):
@ -190,7 +204,7 @@ class LamassuConfig(BaseModel):
last_poll_time: Optional[datetime] = None
last_successful_poll: Optional[datetime] = None
# DCA Client Limits
max_daily_limit_gtq: int = 2000 # Maximum daily limit for Fixed mode clients
max_daily_limit_gtq: float = 2000.0 # Maximum daily limit for Fixed mode clients
class UpdateLamassuConfigData(BaseModel):

View file

@ -134,13 +134,11 @@ window.app = Vue.createApp({
formatCurrency(amount) {
if (!amount) return 'Q 0.00';
// Convert centavos to GTQ for display
const gtqAmount = amount / 100;
// Amount is now stored as GTQ directly in database
return new Intl.NumberFormat('es-GT', {
style: 'currency',
currency: 'GTQ',
}).format(gtqAmount);
}).format(amount);
},
formatDate(dateString) {
@ -305,7 +303,7 @@ window.app = Vue.createApp({
try {
const data = {
client_id: this.quickDepositForm.selectedClient?.value,
amount: Math.round(this.quickDepositForm.amount * 100), // Convert GTQ to centavos
amount: this.quickDepositForm.amount, // Send GTQ directly - now stored as GTQ
currency: 'GTQ',
notes: this.quickDepositForm.notes
}
@ -378,7 +376,7 @@ window.app = Vue.createApp({
try {
const data = {
client_id: this.depositFormDialog.data.client_id,
amount: Math.round(this.depositFormDialog.data.amount * 100), // Convert GTQ to centavos
amount: this.depositFormDialog.data.amount, // Send GTQ directly - now stored as GTQ
currency: this.depositFormDialog.data.currency,
notes: this.depositFormDialog.data.notes
}

View file

@ -493,14 +493,26 @@ class LamassuTransactionProcessor:
# Convert string values to appropriate types
processed_row = {}
for key, value in row.items():
if value == '':
processed_row[key] = None
# Handle None/empty values consistently at data ingestion boundary
if value == '' or value is None:
if key in ['fiat_amount', 'crypto_amount']:
processed_row[key] = 0 # Default numeric fields to 0
elif key in ['commission_percentage', 'discount']:
processed_row[key] = 0.0 # Default percentage fields to 0.0
else:
processed_row[key] = None # Keep None for non-numeric fields
elif key in ['transaction_id', 'device_id', 'crypto_code', 'fiat_code']:
processed_row[key] = str(value)
elif key in ['fiat_amount', 'crypto_amount']:
processed_row[key] = int(float(value)) if value else 0
try:
processed_row[key] = int(float(value))
except (ValueError, TypeError):
processed_row[key] = 0 # Fallback to 0 for invalid values
elif key in ['commission_percentage', 'discount']:
processed_row[key] = float(value) if value else 0.0
try:
processed_row[key] = float(value)
except (ValueError, TypeError):
processed_row[key] = 0.0 # Fallback to 0.0 for invalid values
elif key == 'transaction_time':
from datetime import datetime
# Parse PostgreSQL timestamp format and ensure it's in UTC for consistency
@ -591,7 +603,7 @@ class LamassuTransactionProcessor:
AND co.status IN ('confirmed', 'authorized')
AND co.dispense = 't'
AND co.dispense_confirmed = 't'
ORDER BY co.confirmed_at DESC
ORDER BY co.confirmed_at ASC
"""
all_transactions = await self.execute_ssh_query(db_config, lamassu_query)
@ -628,11 +640,11 @@ class LamassuTransactionProcessor:
logger.info("No Flow Mode clients found - skipping distribution")
return {}
# Extract transaction details with None-safe defaults
crypto_atoms = transaction.get("crypto_amount") # Total sats with commission baked in
fiat_amount = transaction.get("fiat_amount") # Actual fiat dispensed (principal only)
commission_percentage = transaction.get("commission_percentage") # Already stored as decimal (e.g., 0.045)
discount = transaction.get("discount") # Discount percentage
# Extract transaction details - guaranteed clean from data ingestion
crypto_atoms = transaction.get("crypto_amount", 0) # Total sats with commission baked in
fiat_amount = transaction.get("fiat_amount", 0) # Actual fiat dispensed (principal only)
commission_percentage = transaction.get("commission_percentage", 0.0) # Already stored as decimal (e.g., 0.045)
discount = transaction.get("discount", 0.0) # Discount percentage
transaction_time = transaction.get("transaction_time") # ATM transaction timestamp for temporal accuracy
# Normalize transaction_time to UTC if present
@ -671,7 +683,7 @@ class LamassuTransactionProcessor:
# Since crypto_atoms already includes commission, we need to extract the base amount
# Formula: crypto_atoms = base_amount * (1 + effective_commission)
# Therefore: base_amount = crypto_atoms / (1 + effective_commission)
base_crypto_atoms = int(crypto_atoms / (1 + effective_commission))
base_crypto_atoms = round(crypto_atoms / (1 + effective_commission))
commission_amount_sats = crypto_atoms - base_crypto_atoms
else:
effective_commission = 0.0
@ -696,9 +708,14 @@ class LamassuTransactionProcessor:
for client in flow_clients:
# Get balance as of the transaction time for temporal accuracy
balance = await get_client_balance_summary(client.id, as_of_time=transaction_time)
if balance.remaining_balance > 0: # Only include clients with remaining balance
# Only include clients with positive remaining balance
# NOTE: This works for fiat amounts that use cents
if balance.remaining_balance >= 0.01:
client_balances[client.id] = balance.remaining_balance
total_confirmed_deposits += balance.remaining_balance
logger.debug(f"Client {client.id[:8]}... included with balance: {balance.remaining_balance:.2f} GTQ")
else:
logger.info(f"Client {client.id[:8]}... excluded - zero/negative balance: {balance.remaining_balance:.2f} GTQ")
if total_confirmed_deposits == 0:
logger.info("No clients with remaining DCA balance - skipping distribution")
@ -755,9 +772,8 @@ class LamassuTransactionProcessor:
client_sats_amount = calc['allocated_sats']
proportion = calc['proportion']
# Calculate equivalent fiat value in centavos for tracking purposes (industry standard)
# Store as centavos to maintain precision and avoid floating-point errors
client_fiat_amount = round(client_sats_amount * 100 / exchange_rate) if exchange_rate > 0 else 0
# Calculate equivalent fiat value in GTQ for tracking purposes
client_fiat_amount = round(client_sats_amount / exchange_rate, 2) if exchange_rate > 0 else 0.0
distributions[client_id] = {
"fiat_amount": client_fiat_amount,
@ -765,7 +781,7 @@ class LamassuTransactionProcessor:
"exchange_rate": exchange_rate
}
logger.info(f"Client {client_id[:8]}... gets {client_sats_amount} sats (≈{client_fiat_amount/100:.2f} GTQ, {proportion:.2%} share)")
logger.info(f"Client {client_id[:8]}... gets {client_sats_amount} sats (≈{client_fiat_amount:.2f} GTQ, {proportion:.2%} share)")
# Verification: ensure total distribution equals base amount
total_distributed = sum(dist["sats_amount"] for dist in distributions.values())
@ -773,7 +789,31 @@ class LamassuTransactionProcessor:
logger.error(f"Distribution mismatch! Expected: {base_crypto_atoms} sats, Distributed: {total_distributed} sats")
raise ValueError(f"Satoshi distribution calculation error: {base_crypto_atoms} != {total_distributed}")
logger.info(f"Distribution verified: {total_distributed} sats distributed across {len(distributions)} clients")
# Safety check: Re-verify all clients still have positive balances before finalizing distributions
# This prevents race conditions where balances changed during calculation
final_distributions = {}
for client_id, distribution in distributions.items():
# Re-check current balance (without temporal filtering to get most recent state)
current_balance = await get_client_balance_summary(client_id)
if current_balance.remaining_balance > 0:
final_distributions[client_id] = distribution
logger.info(f"Client {client_id[:8]}... final balance check: {current_balance.remaining_balance:.2f} GTQ - APPROVED for {distribution['sats_amount']} sats")
else:
logger.warning(f"Client {client_id[:8]}... final balance check: {current_balance.remaining_balance:.2f} GTQ - REJECTED (negative balance)")
if len(final_distributions) != len(distributions):
logger.warning(f"Rejected {len(distributions) - len(final_distributions)} clients due to negative balances during final check")
# Recalculate proportions if some clients were rejected
if len(final_distributions) == 0:
logger.info("All clients rejected due to negative balances - no distributions")
return {}
# For simplicity, we'll still return the original distributions but log the warning
# In a production system, you might want to recalculate the entire distribution
logger.warning("Proceeding with original distribution despite balance warnings - manual review recommended")
logger.info(f"Distribution verified: {total_distributed} sats distributed across {len(distributions)} clients (clients with positive allocations only)")
return distributions
except Exception as e:
@ -803,11 +843,28 @@ class LamassuTransactionProcessor:
logger.error(f"Client {client_id} not found")
continue
# Final safety check: Verify client still has positive balance before payment
current_balance = await get_client_balance_summary(client_id)
if current_balance.remaining_balance <= 0:
logger.error(f"CRITICAL: Client {client_id[:8]}... has negative balance ({current_balance.remaining_balance:.2f} GTQ) - REFUSING payment of {distribution['sats_amount']} sats")
continue
# Verify balance is sufficient for this distribution (round to 2 decimal places to match DECIMAL(10,2) precision)
fiat_equivalent = distribution["fiat_amount"] # Amount in GTQ
# Round both values to 2 decimal places to match database precision and avoid floating point comparison issues
balance_rounded = round(current_balance.remaining_balance, 2)
amount_rounded = round(fiat_equivalent, 2)
if balance_rounded < amount_rounded:
logger.error(f"CRITICAL: Client {client_id[:8]}... insufficient balance ({balance_rounded:.2f} < {amount_rounded:.2f} GTQ) - REFUSING payment")
continue
logger.info(f"Client {client_id[:8]}... pre-payment balance check: {current_balance.remaining_balance:.2f} GTQ - SUFFICIENT for {fiat_equivalent:.2f} GTQ payment")
# Create DCA payment record
payment_data = CreateDcaPaymentData(
client_id=client_id,
amount_sats=distribution["sats_amount"],
amount_fiat=distribution["fiat_amount"], # Still store centavos in DB
amount_fiat=distribution["fiat_amount"], # Amount in GTQ
exchange_rate=distribution["exchange_rate"],
transaction_type="flow",
lamassu_transaction_id=transaction_id,
@ -850,12 +907,9 @@ class LamassuTransactionProcessor:
return False
# Create descriptive memo with DCA metrics
fiat_amount_centavos = distribution.get("fiat_amount", 0)
fiat_amount_gtq = distribution.get("fiat_amount", 0.0)
exchange_rate = distribution.get("exchange_rate", 0)
# Convert centavos to GTQ for display
fiat_amount_gtq = fiat_amount_centavos / 100
# Calculate cost basis (fiat per BTC)
if exchange_rate > 0:
# exchange_rate is sats per fiat unit, so convert to fiat per BTC
@ -956,11 +1010,11 @@ class LamassuTransactionProcessor:
async def store_lamassu_transaction(self, transaction: Dict[str, Any]) -> Optional[str]:
"""Store the Lamassu transaction in our database for audit and UI"""
try:
# Extract and validate transaction data
# Extract transaction data - guaranteed clean from data ingestion boundary
crypto_atoms = transaction.get("crypto_amount", 0)
fiat_amount = transaction.get("fiat_amount", 0)
commission_percentage = transaction.get("commission_percentage") or 0.0
discount = transaction.get("discount") or 0.0
commission_percentage = transaction.get("commission_percentage", 0.0)
discount = transaction.get("discount", 0.0)
transaction_time = transaction.get("transaction_time")
# Normalize transaction_time to UTC if present
@ -973,7 +1027,7 @@ class LamassuTransactionProcessor:
# Calculate commission metrics
if commission_percentage > 0:
effective_commission = commission_percentage * (100 - discount) / 100
base_crypto_atoms = int(crypto_atoms / (1 + effective_commission))
base_crypto_atoms = round(crypto_atoms / (1 + effective_commission))
commission_amount_sats = crypto_atoms - base_crypto_atoms
else:
effective_commission = 0.0
@ -983,10 +1037,10 @@ class LamassuTransactionProcessor:
# Calculate exchange rate
exchange_rate = base_crypto_atoms / fiat_amount if fiat_amount > 0 else 0
# Create transaction data (store fiat_amount in centavos for consistency)
# Create transaction data with GTQ amounts
transaction_data = CreateLamassuTransactionData(
lamassu_transaction_id=transaction["transaction_id"],
fiat_amount=int(fiat_amount * 100), # Convert GTQ to centavos
fiat_amount=round(fiat_amount, 2), # Store GTQ with 2 decimal places
crypto_amount=crypto_atoms,
commission_percentage=commission_percentage,
discount=discount,
@ -1027,7 +1081,19 @@ class LamassuTransactionProcessor:
# Create invoice in commission wallet with DCA metrics
fiat_amount = transaction.get("fiat_amount", 0)
commission_percentage = transaction.get("commission_percentage", 0) * 100 # Convert to percentage
commission_memo = f"DCA Commission: {commission_amount_sats:,} sats • {commission_percentage:.1f}% • {fiat_amount:,} GTQ transaction"
discount = transaction.get("discount", 0.0) # Discount percentage
# Calculate effective commission for display
if commission_percentage > 0:
effective_commission_percentage = commission_percentage * (100 - discount) / 100
else:
effective_commission_percentage = 0.0
# Create detailed memo showing discount if applied
if discount > 0:
commission_memo = f"DCA Commission: {commission_amount_sats:,} sats • {commission_percentage:.1f}% - {discount:.1f}% discount = {effective_commission_percentage:.1f}% effective • {fiat_amount:,} GTQ transaction"
else:
commission_memo = f"DCA Commission: {commission_amount_sats:,} sats • {commission_percentage:.1f}% • {fiat_amount:,} GTQ transaction"
commission_payment = await create_invoice(
wallet_id=admin_config.commission_wallet_id,
@ -1094,12 +1160,12 @@ class LamassuTransactionProcessor:
# Calculate commission amount for sending to commission wallet
crypto_atoms = transaction.get("crypto_amount", 0)
commission_percentage = transaction.get("commission_percentage") or 0.0
discount = transaction.get("discount") or 0.0
commission_percentage = transaction.get("commission_percentage", 0.0)
discount = transaction.get("discount", 0.0)
if commission_percentage and commission_percentage > 0:
effective_commission = commission_percentage * (100 - discount) / 100
base_crypto_atoms = int(crypto_atoms / (1 + effective_commission))
base_crypto_atoms = round(crypto_atoms / (1 + effective_commission))
commission_amount_sats = crypto_atoms - base_crypto_atoms
else:
commission_amount_sats = 0