upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/main/lightning_payout.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/lightning_payout.c')
-rw-r--r--main/lightning_payout.c93
1 files changed, 93 insertions, 0 deletions
diff --git a/main/lightning_payout.c b/main/lightning_payout.c
new file mode 100644
index 0000000..42593a9
--- /dev/null
+++ b/main/lightning_payout.c
@@ -0,0 +1,93 @@
1#include "lightning_payout.h"
2#include "lnurl_pay.h"
3#include "nucula_wallet.h"
4#include "esp_log.h"
5#include "freertos/FreeRTOS.h"
6#include "freertos/task.h"
7#include <string.h>
8#include <math.h>
9
10static const char *TAG = "ln_payout";
11
12static payout_config_t s_config;
13static int64_t s_last_check_ms = 0;
14
15static int64_t get_time_ms(void) {
16 return (int64_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
17}
18
19esp_err_t lightning_payout_init(const payout_config_t *config)
20{
21 if (!config) return ESP_FAIL;
22 memcpy(&s_config, config, sizeof(s_config));
23 s_last_check_ms = get_time_ms();
24 ESP_LOGI(TAG, "Payout initialized: %d mints, %d recipients, interval=%ds",
25 s_config.mint_count, s_config.recipient_count, s_config.check_interval_s);
26 return ESP_OK;
27}
28
29static esp_err_t payout_one(const char *lightning_address, uint64_t amount_sats, uint64_t fee_tolerance_pct)
30{
31 if (amount_sats == 0) return ESP_OK;
32
33 char bolt11[LNURL_MAX_BOLT11_LEN];
34 esp_err_t err = lnurl_get_invoice(lightning_address, amount_sats, bolt11, sizeof(bolt11));
35 if (err != ESP_OK) {
36 ESP_LOGE(TAG, "Failed to get invoice for %s (%llu sats)", lightning_address, (unsigned long long)amount_sats);
37 return err;
38 }
39
40 uint64_t max_cost = amount_sats + (amount_sats * fee_tolerance_pct / 100);
41 err = nucula_wallet_melt(bolt11, max_cost);
42 if (err != ESP_OK) {
43 ESP_LOGE(TAG, "Melt failed for %s", lightning_address);
44 return err;
45 }
46
47 ESP_LOGI(TAG, "Payout: %llu sats -> %s", (unsigned long long)amount_sats, lightning_address);
48 return ESP_OK;
49}
50
51void lightning_payout_tick(void)
52{
53 if (!s_config.enabled) return;
54 if (s_config.recipient_count == 0) return;
55
56 int64_t now = get_time_ms();
57 int64_t elapsed = now - s_last_check_ms;
58 int64_t interval_ms = (int64_t)s_config.check_interval_s * 1000;
59 if (elapsed < interval_ms) return;
60
61 s_last_check_ms = now;
62
63 uint64_t balance = nucula_wallet_balance();
64
65 for (int m = 0; m < s_config.mint_count; m++) {
66 const payout_mint_config_t *mc = &s_config.mints[m];
67
68 if (balance < mc->min_payout_amount) {
69 ESP_LOGI(TAG, "Balance %llu < min_payout %llu for %s, skipping",
70 (unsigned long long)balance, (unsigned long long)mc->min_payout_amount, mc->url);
71 continue;
72 }
73
74 uint64_t payout_pool = balance - mc->min_balance;
75 if (payout_pool == 0) continue;
76
77 ESP_LOGI(TAG, "Payout pool: %llu sats (balance=%llu - reserve=%llu)",
78 (unsigned long long)payout_pool, (unsigned long long)balance,
79 (unsigned long long)mc->min_balance);
80
81 for (int r = 0; r < s_config.recipient_count; r++) {
82 const payout_recipient_t *recip = &s_config.recipients[r];
83 if (recip->factor <= 0.0 || recip->lightning_address[0] == '\0') continue;
84
85 uint64_t share = (uint64_t)round((double)payout_pool * recip->factor);
86 if (share == 0) continue;
87
88 payout_one(recip->lightning_address, share, s_config.fee_tolerance_pct);
89 }
90
91 balance = nucula_wallet_balance();
92 }
93}