From df8b36fc0fdda326cebbc1f5959cd2ef9d12fa65 Mon Sep 17 00:00:00 2001 From: padreug Date: Thu, 19 Jun 2025 17:08:07 +0200 Subject: [PATCH] Add commission wallet support in Lamassu configuration: update database schema to include commission_wallet_id, modify related models and CRUD operations, and implement commission payment functionality in transaction processing. Enhance UI components to allow selection of the commission wallet for improved user experience. --- crud.py | 5 ++- migrations.py | 12 ++++++ models.py | 6 +++ static/js/index.js | 17 ++++++++ templates/myextension/index.html | 10 +++++ transaction_processor.py | 68 ++++++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 2 deletions(-) diff --git a/crud.py b/crud.py index bf85351..43e4030 100644 --- a/crud.py +++ b/crud.py @@ -309,9 +309,9 @@ async def create_lamassu_config(data: CreateLamassuConfigData) -> LamassuConfig: await db.execute( """ INSERT INTO myextension.lamassu_config - (id, host, port, database_name, username, password, source_wallet_id, is_active, created_at, updated_at, + (id, host, port, database_name, username, password, source_wallet_id, commission_wallet_id, is_active, created_at, updated_at, use_ssh_tunnel, ssh_host, ssh_port, ssh_username, ssh_password, ssh_private_key) - VALUES (:id, :host, :port, :database_name, :username, :password, :source_wallet_id, :is_active, :created_at, :updated_at, + VALUES (:id, :host, :port, :database_name, :username, :password, :source_wallet_id, :commission_wallet_id, :is_active, :created_at, :updated_at, :use_ssh_tunnel, :ssh_host, :ssh_port, :ssh_username, :ssh_password, :ssh_private_key) """, { @@ -322,6 +322,7 @@ async def create_lamassu_config(data: CreateLamassuConfigData) -> LamassuConfig: "username": data.username, "password": data.password, "source_wallet_id": data.source_wallet_id, + "commission_wallet_id": data.commission_wallet_id, "is_active": True, "created_at": datetime.now(), "updated_at": datetime.now(), diff --git a/migrations.py b/migrations.py index 3d3435d..39379a3 100644 --- a/migrations.py +++ b/migrations.py @@ -200,3 +200,15 @@ async def m010_add_source_wallet_to_lamassu_config(db): ADD COLUMN source_wallet_id TEXT; """ ) + + +async def m011_add_commission_wallet_to_lamassu_config(db): + """ + Add commission wallet ID to Lamassu configuration table for commission earnings. + """ + await db.execute( + """ + ALTER TABLE myextension.lamassu_config + ADD COLUMN commission_wallet_id TEXT; + """ + ) diff --git a/models.py b/models.py index 928e9a6..77dac12 100644 --- a/models.py +++ b/models.py @@ -111,6 +111,8 @@ class CreateLamassuConfigData(BaseModel): password: str # Source wallet for DCA distributions source_wallet_id: Optional[str] = None + # Commission wallet for storing commission earnings + commission_wallet_id: Optional[str] = None # SSH Tunnel settings use_ssh_tunnel: bool = False ssh_host: Optional[str] = None @@ -134,6 +136,8 @@ class LamassuConfig(BaseModel): updated_at: datetime # Source wallet for DCA distributions source_wallet_id: Optional[str] = None + # Commission wallet for storing commission earnings + commission_wallet_id: Optional[str] = None # SSH Tunnel settings use_ssh_tunnel: bool = False ssh_host: Optional[str] = None @@ -155,6 +159,8 @@ class UpdateLamassuConfigData(BaseModel): is_active: Optional[bool] = None # Source wallet for DCA distributions source_wallet_id: Optional[str] = None + # Commission wallet for storing commission earnings + commission_wallet_id: Optional[str] = None # SSH Tunnel settings use_ssh_tunnel: Optional[bool] = None ssh_host: Optional[str] = None diff --git a/static/js/index.js b/static/js/index.js index f7065a2..2620c7c 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -72,6 +72,7 @@ window.app = Vue.createApp({ username: '', password: '', selectedWallet: null, + selectedCommissionWallet: null, // SSH Tunnel settings use_ssh_tunnel: false, ssh_host: '', @@ -156,6 +157,20 @@ window.app = Vue.createApp({ this.g.user.wallets[0].inkey ) this.lamassuConfig = data + + // When opening config dialog, populate the selected wallets if they exist + if (data && data.source_wallet_id) { + const wallet = this.g.user.wallets.find(w => w.id === data.source_wallet_id) + if (wallet) { + this.configDialog.data.selectedWallet = wallet + } + } + if (data && data.commission_wallet_id) { + const commissionWallet = this.g.user.wallets.find(w => w.id === data.commission_wallet_id) + if (commissionWallet) { + this.configDialog.data.selectedCommissionWallet = commissionWallet + } + } } catch (error) { // It's OK if no config exists yet this.lamassuConfig = null @@ -171,6 +186,7 @@ window.app = Vue.createApp({ username: this.configDialog.data.username, password: this.configDialog.data.password, source_wallet_id: this.configDialog.data.selectedWallet?.id, + commission_wallet_id: this.configDialog.data.selectedCommissionWallet?.id, // SSH Tunnel settings use_ssh_tunnel: this.configDialog.data.use_ssh_tunnel, ssh_host: this.configDialog.data.ssh_host, @@ -209,6 +225,7 @@ window.app = Vue.createApp({ username: '', password: '', selectedWallet: null, + selectedCommissionWallet: null, // SSH Tunnel settings use_ssh_tunnel: false, ssh_host: '', diff --git a/templates/myextension/index.html b/templates/myextension/index.html index 1e22471..a7ad3b8 100644 --- a/templates/myextension/index.html +++ b/templates/myextension/index.html @@ -580,6 +580,16 @@ hint="Wallet that holds Bitcoin for distribution to DCA clients" > + +
SSH Tunnel (Recommended)
diff --git a/transaction_processor.py b/transaction_processor.py index 1d7053d..6c071e8 100644 --- a/transaction_processor.py +++ b/transaction_processor.py @@ -706,6 +706,58 @@ class LamassuTransactionProcessor: logger.error(f"Error crediting source wallet for transaction {transaction.get('transaction_id', 'unknown')}: {e}") return False + async def send_commission_payment(self, transaction: Dict[str, Any], commission_amount_sats: int) -> bool: + """Send commission to the configured commission wallet""" + try: + # Get the configuration to find commission wallet + admin_config = await get_active_lamassu_config() + if not admin_config or not admin_config.commission_wallet_id: + logger.info("No commission wallet configured - commission remains in source wallet") + return True # Not an error, just no transfer needed + + if not admin_config.source_wallet_id: + logger.error("No source wallet configured - cannot send commission") + return False + + transaction_id = transaction["transaction_id"] + + # Create invoice in commission wallet + commission_memo = f"Commission: {commission_amount_sats} sats from Lamassu transaction {transaction_id[:8]}..." + + commission_payment = await create_invoice( + wallet_id=admin_config.commission_wallet_id, + amount=commission_amount_sats, + internal=True, + memo=commission_memo, + extra={ + "tag": "dca_commission", + "lamassu_transaction_id": transaction_id, + "commission_amount": commission_amount_sats + } + ) + + if not commission_payment: + logger.error(f"Failed to create commission invoice for transaction {transaction_id}") + return False + + # Pay the commission invoice from source wallet + await pay_invoice( + payment_request=commission_payment.bolt11, + wallet_id=admin_config.source_wallet_id, + description=commission_memo, + extra={ + "tag": "dca_commission_payment", + "lamassu_transaction_id": transaction_id + } + ) + + logger.info(f"Commission payment completed: {commission_amount_sats} sats sent to commission wallet for transaction {transaction_id}") + return True + + except Exception as e: + logger.error(f"Error sending commission payment for transaction {transaction.get('transaction_id', 'unknown')}: {e}") + return False + async def process_transaction(self, transaction: Dict[str, Any]) -> None: """Process a single transaction - calculate and distribute DCA payments""" try: @@ -732,9 +784,25 @@ class LamassuTransactionProcessor: logger.info(f"No distributions calculated for transaction {transaction_id}") return + # Calculate commission amount for sending to commission wallet + crypto_atoms = transaction["crypto_amount"] + commission_percentage = transaction["commission_percentage"] + discount = transaction.get("discount", 0.0) + + if commission_percentage > 0: + effective_commission = commission_percentage * (100 - discount) / 100 + base_crypto_atoms = int(crypto_atoms / (1 + effective_commission)) + commission_amount_sats = crypto_atoms - base_crypto_atoms + else: + commission_amount_sats = 0 + # Distribute to clients await self.distribute_to_clients(transaction, distributions) + # Send commission to commission wallet (if configured) + if commission_amount_sats > 0: + await self.send_commission_payment(transaction, commission_amount_sats) + logger.info(f"Successfully processed transaction {transaction_id}") except Exception as e: