From 8790c4ae4d78fa11f94a84f9dc230d2082d1592b Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 19 Mar 2026 19:26:12 -0400 Subject: [PATCH] v0.1.3 - Make cashu wallet initialization unconditional and add lazy wallet readiness for first-run DM tool usage --- README.md | 4 +-- src/cashu_wallet.c | 61 ++++++++++++++++++++++++++++++++++------------ src/main.c | 34 ++++++++++++-------------- src/main.h | 4 +-- 4 files changed, 65 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 942ac59..b5d2648 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers. -## Current Status — v0.1.2 +## Current Status — v0.1.3 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.1.2 — Implement cashu_wallet_receive_token tool flow and integrate Cashu receive/swap handling +> Last release update: v0.1.3 — Make cashu wallet initialization unconditional and add lazy wallet readiness for first-run DM tool usage - Connects to configured relays with auto-reconnect and relay state transition logging - Publishes configured startup events per relay as each relay becomes connected diff --git a/src/cashu_wallet.c b/src/cashu_wallet.c index 15be494..ce17ea7 100644 --- a/src/cashu_wallet.c +++ b/src/cashu_wallet.c @@ -256,17 +256,35 @@ void cashu_wallet_cleanup(void) { memset(&g_wallet, 0, sizeof(g_wallet)); } +static int ensure_wallet_ready(void) { + if (!g_wallet.initialized || !g_wallet.cfg) return -1; + + int wallet_loaded = 0; + int auto_load = 0; + pthread_mutex_lock(&g_wallet.mutex); + wallet_loaded = g_wallet.wallet_loaded; + auto_load = g_wallet.cfg->cashu_wallet.auto_load ? 1 : 0; + pthread_mutex_unlock(&g_wallet.mutex); + + if (wallet_loaded) return 0; + + int rc = 0; + if (auto_load) { + rc = cashu_wallet_load_from_relays(); + if (rc == 0) return 0; + } + + rc = cashu_wallet_create_new_from_config(); + if (rc == 0) return 0; + + return rc; +} + int cashu_wallet_create_new_from_config(void) { if (!g_wallet.initialized || !g_wallet.cfg) return -1; - if (!g_wallet.cfg->cashu_wallet.enabled) return -1; pthread_mutex_lock(&g_wallet.mutex); - if (!g_wallet.cfg->cashu_wallet.mint_urls || g_wallet.cfg->cashu_wallet.mint_count <= 0) { - pthread_mutex_unlock(&g_wallet.mutex); - return -1; - } - unsigned char priv[32] = {0}; unsigned char pub[32] = {0}; if (nostr_generate_keypair(priv, pub) != NOSTR_SUCCESS) { @@ -279,19 +297,21 @@ int cashu_wallet_create_new_from_config(void) { nostr_bytes_to_hex(priv, 32, wallet_data.privkey); wallet_data.mint_count = g_wallet.cfg->cashu_wallet.mint_count; - wallet_data.mint_urls = (char**)calloc((size_t)wallet_data.mint_count, sizeof(char*)); - if (!wallet_data.mint_urls) { - pthread_mutex_unlock(&g_wallet.mutex); - return -1; - } - - for (int i = 0; i < wallet_data.mint_count; i++) { - wallet_data.mint_urls[i] = strdup(g_wallet.cfg->cashu_wallet.mint_urls[i]); - if (!wallet_data.mint_urls[i]) { - nostr_nip60_free_wallet_data(&wallet_data); + if (wallet_data.mint_count > 0) { + wallet_data.mint_urls = (char**)calloc((size_t)wallet_data.mint_count, sizeof(char*)); + if (!wallet_data.mint_urls) { pthread_mutex_unlock(&g_wallet.mutex); return -1; } + + for (int i = 0; i < wallet_data.mint_count; i++) { + wallet_data.mint_urls[i] = strdup(g_wallet.cfg->cashu_wallet.mint_urls[i]); + if (!wallet_data.mint_urls[i]) { + nostr_nip60_free_wallet_data(&wallet_data); + pthread_mutex_unlock(&g_wallet.mutex); + return -1; + } + } } int rc = publish_wallet_event_and_store_locked(&wallet_data, NULL); @@ -397,6 +417,7 @@ int cashu_wallet_load_from_relays(void) { int cashu_wallet_balance_json(cJSON** out_json) { if (!out_json || !g_wallet.initialized || !g_wallet.cfg) return -1; + (void)ensure_wallet_ready(); pthread_mutex_lock(&g_wallet.mutex); @@ -436,6 +457,7 @@ int cashu_wallet_balance_json(cJSON** out_json) { int cashu_wallet_info_json(const char* mint_url, cJSON** out_json) { if (!out_json || !g_wallet.initialized || !g_wallet.cfg) return -1; + if (ensure_wallet_ready() != 0) return -1; pthread_mutex_lock(&g_wallet.mutex); const char* resolved = resolve_mint_url_locked(mint_url); @@ -483,6 +505,7 @@ int cashu_wallet_info_json(const char* mint_url, cJSON** out_json) { int cashu_wallet_mint_quote_json(const char* mint_url, uint64_t amount, const char* unit, cJSON** out_json) { if (!out_json || amount == 0 || !g_wallet.initialized || !g_wallet.cfg) return -1; + if (ensure_wallet_ready() != 0) return -1; pthread_mutex_lock(&g_wallet.mutex); const char* resolved = resolve_mint_url_locked(mint_url); @@ -516,6 +539,7 @@ int cashu_wallet_mint_quote_json(const char* mint_url, uint64_t amount, const ch int cashu_wallet_mint_check_json(const char* mint_url, const char* quote_id, cJSON** out_json) { if (!out_json || !quote_id || quote_id[0] == '\0' || !g_wallet.initialized || !g_wallet.cfg) return -1; + if (ensure_wallet_ready() != 0) return -1; pthread_mutex_lock(&g_wallet.mutex); const char* resolved = resolve_mint_url_locked(mint_url); @@ -546,6 +570,7 @@ int cashu_wallet_mint_check_json(const char* mint_url, const char* quote_id, cJS int cashu_wallet_mint_claim_json(const char* mint_url, const char* quote_id, uint64_t amount, cJSON** out_json) { if (!out_json || !quote_id || quote_id[0] == '\0' || !g_wallet.initialized || !g_wallet.cfg) return -1; + if (ensure_wallet_ready() != 0) return -1; pthread_mutex_lock(&g_wallet.mutex); const char* resolved = resolve_mint_url_locked(mint_url); @@ -679,6 +704,7 @@ int cashu_wallet_mint_claim_json(const char* mint_url, const char* quote_id, uin int cashu_wallet_melt_quote_json(const char* mint_url, const char* payment_request, const char* unit, cJSON** out_json) { if (!out_json || !payment_request || payment_request[0] == '\0' || !g_wallet.initialized || !g_wallet.cfg) return -1; + if (ensure_wallet_ready() != 0) return -1; pthread_mutex_lock(&g_wallet.mutex); const char* resolved = resolve_mint_url_locked(mint_url); @@ -711,6 +737,7 @@ int cashu_wallet_melt_quote_json(const char* mint_url, const char* payment_reque int cashu_wallet_melt_pay_json(const char* mint_url, const char* quote_id, cJSON** out_json) { if (!out_json || !quote_id || quote_id[0] == '\0' || !g_wallet.initialized || !g_wallet.cfg) return -1; + if (ensure_wallet_ready() != 0) return -1; pthread_mutex_lock(&g_wallet.mutex); const char* resolved = resolve_mint_url_locked(mint_url); @@ -891,6 +918,7 @@ int cashu_wallet_melt_pay_json(const char* mint_url, const char* quote_id, cJSON int cashu_wallet_check_proofs_json(const char* mint_url, cJSON** out_json) { if (!out_json || !g_wallet.initialized || !g_wallet.cfg) return -1; + if (ensure_wallet_ready() != 0) return -1; pthread_mutex_lock(&g_wallet.mutex); const char* resolved = resolve_mint_url_locked(mint_url); @@ -990,6 +1018,7 @@ int cashu_wallet_check_proofs_json(const char* mint_url, cJSON** out_json) { int cashu_wallet_receive_token_json(const char* token_string, const char* mint_url, cJSON** out_json) { if (!out_json || !token_string || token_string[0] == '\0' || !g_wallet.initialized || !g_wallet.cfg) return -1; + if (ensure_wallet_ready() != 0) return -1; cashu_decoded_token_t decoded; memset(&decoded, 0, sizeof(decoded)); diff --git a/src/main.c b/src/main.c index 14e19e8..cf352e3 100644 --- a/src/main.c +++ b/src/main.c @@ -1385,25 +1385,23 @@ int main(int argc, char** argv) { DEBUG_INFO("[didactyl] startup phase: subscribe DMs end"); startup_step_ok(15, "Subscribe DMs", NULL); - if (cfg.cashu_wallet.enabled) { - startup_step_begin(16, "Initialize cashu wallet"); - if (cashu_wallet_init(&cfg) != 0) { - DEBUG_WARN("[didactyl] startup phase: cashu_wallet_init failed (continuing without wallet)"); - startup_step_fail(16, "Initialize cashu wallet", "cashu_wallet_init failed; wallet features disabled for this run"); + startup_step_begin(16, "Initialize cashu wallet"); + if (cashu_wallet_init(&cfg) != 0) { + DEBUG_WARN("[didactyl] startup phase: cashu_wallet_init failed (continuing without wallet)"); + startup_step_fail(16, "Initialize cashu wallet", "cashu_wallet_init failed; wallet tools may return errors"); + } else { + int wallet_rc = 0; + if (cfg.cashu_wallet.auto_load) { + wallet_rc = cashu_wallet_load_from_relays(); + } + if (wallet_rc != 0 && cfg.cashu_wallet.mint_count > 0) { + wallet_rc = cashu_wallet_create_new_from_config(); + } + if (wallet_rc != 0) { + DEBUG_WARN("[didactyl] startup phase: cashu wallet load/create deferred to first tool call"); + startup_step_ok(16, "Initialize cashu wallet", "initialized; load/create deferred"); } else { - int wallet_rc = 0; - if (cfg.cashu_wallet.auto_load) { - wallet_rc = cashu_wallet_load_from_relays(); - } - if (wallet_rc != 0 && cfg.cashu_wallet.mint_count > 0) { - wallet_rc = cashu_wallet_create_new_from_config(); - } - if (wallet_rc != 0) { - DEBUG_WARN("[didactyl] startup phase: cashu wallet load/create failed (continuing)"); - startup_step_fail(16, "Initialize cashu wallet", "wallet load/create failed; tools may return errors until wallet is configured"); - } else { - startup_step_ok(16, "Initialize cashu wallet", cfg.cashu_wallet.auto_load ? "loaded or created" : "initialized"); - } + startup_step_ok(16, "Initialize cashu wallet", cfg.cashu_wallet.auto_load ? "loaded or created" : "initialized"); } } diff --git a/src/main.h b/src/main.h index 48204d3..fec6062 100644 --- a/src/main.h +++ b/src/main.h @@ -12,8 +12,8 @@ // Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros #define DIDACTYL_VERSION_MAJOR 0 #define DIDACTYL_VERSION_MINOR 1 -#define DIDACTYL_VERSION_PATCH 2 -#define DIDACTYL_VERSION "v0.1.2" +#define DIDACTYL_VERSION_PATCH 3 +#define DIDACTYL_VERSION "v0.1.3" // Agent metadata #define DIDACTYL_NAME "Didactyl"