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