From afed0812a32e2d0be1ea3e345a47aed5d5573e82 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 19 Mar 2026 20:43:13 -0400 Subject: [PATCH] v0.1.4 - Rename cashu wallet module API functions to remove _json suffix and keep tool-facing names consistent --- README.md | 4 +- src/cashu_wallet.c | 302 +++++++++++++++++++++++++++++++++- src/cashu_wallet.h | 26 ++- src/main.h | 4 +- src/tools/tool_cashu_wallet.c | 109 +++++++++++- src/tools/tools_dispatch.c | 6 + src/tools/tools_internal.h | 2 + src/tools/tools_schema.c | 55 +++++++ 8 files changed, 477 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index b5d2648..8ec6fe0 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.3 +## Current Status — v0.1.4 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.1.3 — Make cashu wallet initialization unconditional and add lazy wallet readiness for first-run DM tool usage +> Last release update: v0.1.4 — Rename cashu wallet module API functions to remove _json suffix and keep tool-facing names consistent - 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 ce17ea7..7c5cb05 100644 --- a/src/cashu_wallet.c +++ b/src/cashu_wallet.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "debug.h" #include "nostr_handler.h" @@ -226,6 +227,156 @@ static int token_proofs_total_for_mint_locked(const char* mint_url, uint64_t* ou return 0; } +static int dup_string_array(const char** src, int count, char*** out_arr, int* out_count) { + if (!out_arr || !out_count) return -1; + *out_arr = NULL; + *out_count = 0; + if (!src || count <= 0) return 0; + + char** arr = (char**)calloc((size_t)count, sizeof(char*)); + if (!arr) return -1; + + int n = 0; + for (int i = 0; i < count; i++) { + if (!src[i] || src[i][0] == '\0') continue; + arr[n] = strdup(src[i]); + if (!arr[n]) { + for (int j = 0; j < n; j++) free(arr[j]); + free(arr); + return -1; + } + n++; + } + + *out_arr = arr; + *out_count = n; + return 0; +} + +static int build_nutzap_info_from_mints_locked(const char** mint_urls, + int mint_count, + nostr_nip61_nutzap_info_t* out_info) { + if (!g_wallet.cfg || !out_info) return -1; + if (!g_wallet.wallet_loaded || g_wallet.wallet.privkey[0] == '\0') return -1; + + memset(out_info, 0, sizeof(*out_info)); + + if (g_wallet.cfg->relay_count > 0 && g_wallet.cfg->relays) { + out_info->relay_urls = (char**)calloc((size_t)g_wallet.cfg->relay_count, sizeof(char*)); + if (!out_info->relay_urls) return -1; + + for (int i = 0; i < g_wallet.cfg->relay_count; i++) { + out_info->relay_urls[i] = strdup(g_wallet.cfg->relays[i] ? g_wallet.cfg->relays[i] : ""); + if (!out_info->relay_urls[i]) { + nostr_nip61_free_nutzap_info(out_info); + return -1; + } + out_info->relay_count++; + } + } + + out_info->mints = (nostr_nip61_mint_entry_t*)calloc((size_t)mint_count, sizeof(nostr_nip61_mint_entry_t)); + if (!out_info->mints) { + nostr_nip61_free_nutzap_info(out_info); + return -1; + } + + for (int i = 0; i < mint_count; i++) { + out_info->mints[i].url = strdup(mint_urls[i]); + if (!out_info->mints[i].url) { + nostr_nip61_free_nutzap_info(out_info); + return -1; + } + + out_info->mints[i].units = (char**)calloc(1, sizeof(char*)); + if (!out_info->mints[i].units) { + nostr_nip61_free_nutzap_info(out_info); + return -1; + } + out_info->mints[i].units[0] = strdup(g_wallet.cfg->cashu_wallet.unit[0] ? g_wallet.cfg->cashu_wallet.unit : "sat"); + if (!out_info->mints[i].units[0]) { + nostr_nip61_free_nutzap_info(out_info); + return -1; + } + out_info->mints[i].unit_count = 1; + out_info->mint_count++; + } + + unsigned char wallet_priv[32] = {0}; + unsigned char wallet_pub[32] = {0}; + if (nostr_hex_to_bytes(g_wallet.wallet.privkey, wallet_priv, 32) != 0 || + nostr_ec_public_key_from_private_key(wallet_priv, wallet_pub) != 0) { + nostr_nip61_free_nutzap_info(out_info); + return -1; + } + + nostr_bytes_to_hex(wallet_pub, 32, out_info->pubkey); + return 0; +} + +static int load_latest_public_nutzap_info(nostr_nip61_nutzap_info_t* out_info) { + if (!out_info || !g_wallet.cfg) return -1; + memset(out_info, 0, sizeof(*out_info)); + + cJSON* filter = cJSON_CreateObject(); + cJSON* kinds = cJSON_CreateArray(); + cJSON* authors = cJSON_CreateArray(); + if (!filter || !kinds || !authors) { + cJSON_Delete(filter); + cJSON_Delete(kinds); + cJSON_Delete(authors); + return -1; + } + + cJSON_AddItemToArray(kinds, cJSON_CreateNumber((double)NOSTR_NIP61_NUTZAP_INFO_KIND)); + cJSON_AddItemToArray(authors, cJSON_CreateString(g_wallet.cfg->keys.public_key_hex)); + cJSON_AddItemToObject(filter, "kinds", kinds); + cJSON_AddItemToObject(filter, "authors", authors); + cJSON_AddNumberToObject(filter, "limit", 20); + + char* events_json = nostr_handler_query_json(filter, CASHU_WALLET_QUERY_TIMEOUT_MS); + cJSON_Delete(filter); + if (!events_json) return -1; + + cJSON* arr = cJSON_Parse(events_json); + free(events_json); + if (!arr || !cJSON_IsArray(arr)) { + cJSON_Delete(arr); + return -1; + } + + int found = 0; + double newest_created_at = -1; + nostr_nip61_nutzap_info_t newest; + memset(&newest, 0, sizeof(newest)); + + int n = cJSON_GetArraySize(arr); + for (int i = 0; i < n; i++) { + cJSON* ev = cJSON_GetArrayItem(arr, i); + cJSON* created_at = ev ? cJSON_GetObjectItemCaseSensitive(ev, "created_at") : NULL; + if (!created_at || !cJSON_IsNumber(created_at)) continue; + + nostr_nip61_nutzap_info_t parsed; + memset(&parsed, 0, sizeof(parsed)); + if (nostr_nip61_parse_nutzap_info_event(ev, &parsed) != 0) continue; + + if (!found || created_at->valuedouble > newest_created_at) { + nostr_nip61_free_nutzap_info(&newest); + newest = parsed; + newest_created_at = created_at->valuedouble; + found = 1; + } else { + nostr_nip61_free_nutzap_info(&parsed); + } + } + + cJSON_Delete(arr); + if (!found) return -1; + + *out_info = newest; + return 0; +} + int cashu_wallet_init(didactyl_config_t* cfg) { if (!cfg) return -1; @@ -415,7 +566,7 @@ int cashu_wallet_load_from_relays(void) { return 0; } -int cashu_wallet_balance_json(cJSON** out_json) { +int cashu_wallet_balance(cJSON** out_json) { if (!out_json || !g_wallet.initialized || !g_wallet.cfg) return -1; (void)ensure_wallet_ready(); @@ -455,7 +606,7 @@ int cashu_wallet_balance_json(cJSON** out_json) { return 0; } -int cashu_wallet_info_json(const char* mint_url, cJSON** out_json) { +int cashu_wallet_info(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; @@ -503,7 +654,7 @@ int cashu_wallet_info_json(const char* mint_url, cJSON** out_json) { return 0; } -int cashu_wallet_mint_quote_json(const char* mint_url, uint64_t amount, const char* unit, cJSON** out_json) { +int cashu_wallet_mint_quote(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; @@ -537,7 +688,7 @@ int cashu_wallet_mint_quote_json(const char* mint_url, uint64_t amount, const ch return 0; } -int cashu_wallet_mint_check_json(const char* mint_url, const char* quote_id, cJSON** out_json) { +int cashu_wallet_mint_check(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; @@ -568,7 +719,7 @@ int cashu_wallet_mint_check_json(const char* mint_url, const char* quote_id, cJS return 0; } -int cashu_wallet_mint_claim_json(const char* mint_url, const char* quote_id, uint64_t amount, cJSON** out_json) { +int cashu_wallet_mint_claim(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; @@ -702,7 +853,7 @@ int cashu_wallet_mint_claim_json(const char* mint_url, const char* quote_id, uin return 0; } -int cashu_wallet_melt_quote_json(const char* mint_url, const char* payment_request, const char* unit, cJSON** out_json) { +int cashu_wallet_melt_quote(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; @@ -735,7 +886,7 @@ int cashu_wallet_melt_quote_json(const char* mint_url, const char* payment_reque return 0; } -int cashu_wallet_melt_pay_json(const char* mint_url, const char* quote_id, cJSON** out_json) { +int cashu_wallet_melt_pay(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; @@ -916,7 +1067,7 @@ int cashu_wallet_melt_pay_json(const char* mint_url, const char* quote_id, cJSON return 0; } -int cashu_wallet_check_proofs_json(const char* mint_url, cJSON** out_json) { +int cashu_wallet_check_proofs(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; @@ -1016,7 +1167,7 @@ int cashu_wallet_check_proofs_json(const char* mint_url, cJSON** out_json) { return 0; } -int cashu_wallet_receive_token_json(const char* token_string, const char* mint_url, cJSON** out_json) { +int cashu_wallet_receive_token(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; @@ -1254,3 +1405,136 @@ int cashu_wallet_receive_token_json(const char* token_string, const char* mint_u cashu_free_decoded_token(&decoded); return 0; } + +int cashu_wallet_mints_get(cJSON** out_json) { + if (!out_json || !g_wallet.initialized || !g_wallet.cfg) return -1; + + (void)ensure_wallet_ready(); + + cJSON* root = cJSON_CreateObject(); + cJSON* wallet_mints = cJSON_CreateArray(); + cJSON* public_mints = cJSON_CreateArray(); + if (!root || !wallet_mints || !public_mints) { + cJSON_Delete(root); + cJSON_Delete(wallet_mints); + cJSON_Delete(public_mints); + return -1; + } + + cJSON_AddBoolToObject(root, "success", 1); + + pthread_mutex_lock(&g_wallet.mutex); + int wallet_loaded = g_wallet.wallet_loaded; + if (g_wallet.wallet_loaded && g_wallet.wallet.mint_urls) { + for (int i = 0; i < g_wallet.wallet.mint_count; i++) { + if (g_wallet.wallet.mint_urls[i] && g_wallet.wallet.mint_urls[i][0] != '\0') { + cJSON_AddItemToArray(wallet_mints, cJSON_CreateString(g_wallet.wallet.mint_urls[i])); + } + } + } + pthread_mutex_unlock(&g_wallet.mutex); + + nostr_nip61_nutzap_info_t info; + memset(&info, 0, sizeof(info)); + int have_public = (load_latest_public_nutzap_info(&info) == 0); + if (have_public) { + for (int i = 0; i < info.mint_count; i++) { + if (info.mints[i].url && info.mints[i].url[0] != '\0') { + cJSON_AddItemToArray(public_mints, cJSON_CreateString(info.mints[i].url)); + } + } + } + + cJSON_AddBoolToObject(root, "wallet_loaded", wallet_loaded ? 1 : 0); + cJSON_AddBoolToObject(root, "public_info_found", have_public ? 1 : 0); + cJSON_AddItemToObject(root, "wallet_event_mints", wallet_mints); + cJSON_AddItemToObject(root, "public_nutzap_mints", public_mints); + + if (have_public) { + cJSON_AddStringToObject(root, "wallet_pubkey", info.pubkey); + nostr_nip61_free_nutzap_info(&info); + } + + *out_json = root; + return 0; +} + +int cashu_wallet_mints_set(const char** wallet_mints, + int wallet_mint_count, + const char** public_mints, + int public_mint_count, + int set_wallet, + int set_public, + cJSON** out_json) { + if (!out_json || !g_wallet.initialized || !g_wallet.cfg) return -1; + if (!set_wallet && !set_public) return NOSTR_ERROR_INVALID_INPUT; + + int wallet_updated = 0; + int public_updated = 0; + + if (set_wallet) { + if (!wallet_mints || wallet_mint_count <= 0) return NOSTR_ERROR_INVALID_INPUT; + + pthread_mutex_lock(&g_wallet.mutex); + + nostr_nip60_wallet_data_t next_wallet; + memset(&next_wallet, 0, sizeof(next_wallet)); + if (g_wallet.wallet_loaded && g_wallet.wallet.privkey[0] != '\0') { + snprintf(next_wallet.privkey, sizeof(next_wallet.privkey), "%s", g_wallet.wallet.privkey); + } else { + unsigned char priv[32] = {0}; + unsigned char pub[32] = {0}; + if (nostr_generate_keypair(priv, pub) != NOSTR_SUCCESS) { + pthread_mutex_unlock(&g_wallet.mutex); + return -1; + } + nostr_bytes_to_hex(priv, 32, next_wallet.privkey); + } + + if (dup_string_array(wallet_mints, wallet_mint_count, &next_wallet.mint_urls, &next_wallet.mint_count) != 0 || + next_wallet.mint_count <= 0) { + nostr_nip60_free_wallet_data(&next_wallet); + pthread_mutex_unlock(&g_wallet.mutex); + return -1; + } + + int rc = publish_wallet_event_and_store_locked(&next_wallet, NULL); + nostr_nip60_free_wallet_data(&next_wallet); + pthread_mutex_unlock(&g_wallet.mutex); + if (rc != 0) return rc; + + wallet_updated = 1; + } + + if (set_public) { + if (!public_mints || public_mint_count <= 0) return NOSTR_ERROR_INVALID_INPUT; + + if (ensure_wallet_ready() != 0) return -1; + + pthread_mutex_lock(&g_wallet.mutex); + nostr_nip61_nutzap_info_t info; + int rc = build_nutzap_info_from_mints_locked(public_mints, public_mint_count, &info); + pthread_mutex_unlock(&g_wallet.mutex); + if (rc != 0) return -1; + + cJSON* ev = nostr_nip61_create_nutzap_info_event(&info, g_wallet.cfg->keys.private_key, time(NULL)); + nostr_nip61_free_nutzap_info(&info); + if (!ev) return -1; + + rc = publish_from_signed_event_template(ev, NULL); + cJSON_Delete(ev); + if (rc != 0) return rc; + + public_updated = 1; + } + + cJSON* root = cJSON_CreateObject(); + if (!root) return -1; + + cJSON_AddBoolToObject(root, "success", 1); + cJSON_AddBoolToObject(root, "wallet_updated", wallet_updated ? 1 : 0); + cJSON_AddBoolToObject(root, "public_updated", public_updated ? 1 : 0); + + *out_json = root; + return 0; +} diff --git a/src/cashu_wallet.h b/src/cashu_wallet.h index cce8166..8c87240 100644 --- a/src/cashu_wallet.h +++ b/src/cashu_wallet.h @@ -12,14 +12,22 @@ void cashu_wallet_cleanup(void); int cashu_wallet_load_from_relays(void); int cashu_wallet_create_new_from_config(void); -int cashu_wallet_balance_json(cJSON** out_json); -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); -int cashu_wallet_mint_check_json(const char* mint_url, const char* quote_id, cJSON** out_json); -int cashu_wallet_mint_claim_json(const char* mint_url, const char* quote_id, uint64_t amount, cJSON** out_json); -int cashu_wallet_melt_quote_json(const char* mint_url, const char* payment_request, const char* unit, cJSON** out_json); -int cashu_wallet_melt_pay_json(const char* mint_url, const char* quote_id, cJSON** out_json); -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); +int cashu_wallet_balance(cJSON** out_json); +int cashu_wallet_info(const char* mint_url, cJSON** out_json); +int cashu_wallet_mint_quote(const char* mint_url, uint64_t amount, const char* unit, cJSON** out_json); +int cashu_wallet_mint_check(const char* mint_url, const char* quote_id, cJSON** out_json); +int cashu_wallet_mint_claim(const char* mint_url, const char* quote_id, uint64_t amount, cJSON** out_json); +int cashu_wallet_melt_quote(const char* mint_url, const char* payment_request, const char* unit, cJSON** out_json); +int cashu_wallet_melt_pay(const char* mint_url, const char* quote_id, cJSON** out_json); +int cashu_wallet_check_proofs(const char* mint_url, cJSON** out_json); +int cashu_wallet_receive_token(const char* token_string, const char* mint_url, cJSON** out_json); +int cashu_wallet_mints_get(cJSON** out_json); +int cashu_wallet_mints_set(const char** wallet_mints, + int wallet_mint_count, + const char** public_mints, + int public_mint_count, + int set_wallet, + int set_public, + cJSON** out_json); #endif diff --git a/src/main.h b/src/main.h index fec6062..62946cc 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 3 -#define DIDACTYL_VERSION "v0.1.3" +#define DIDACTYL_VERSION_PATCH 4 +#define DIDACTYL_VERSION "v0.1.4" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/tools/tool_cashu_wallet.c b/src/tools/tool_cashu_wallet.c index 6ee0439..9fc6625 100644 --- a/src/tools/tool_cashu_wallet.c +++ b/src/tools/tool_cashu_wallet.c @@ -46,7 +46,7 @@ char* execute_cashu_wallet_balance(tools_context_t* ctx, const char* args_json) if (!args) return json_error_local("invalid arguments JSON"); cJSON* out = NULL; - int rc = cashu_wallet_balance_json(&out); + int rc = cashu_wallet_balance(&out); cJSON_Delete(args); return wrap_wallet_json_result(rc, out, "cashu_wallet_balance failed"); } @@ -65,7 +65,7 @@ char* execute_cashu_wallet_info(tools_context_t* ctx, const char* args_json) { } cJSON* out = NULL; - int rc = cashu_wallet_info_json(mint_url, &out); + int rc = cashu_wallet_info(mint_url, &out); cJSON_Delete(args); return wrap_wallet_json_result(rc, out, "cashu_wallet_info failed (check mint_url and mint connectivity)"); } @@ -92,7 +92,7 @@ char* execute_cashu_wallet_mint_quote(tools_context_t* ctx, const char* args_jso } cJSON* out = NULL; - int rc = cashu_wallet_mint_quote_json(mint_url, (uint64_t)amount->valuedouble, unit_s, &out); + int rc = cashu_wallet_mint_quote(mint_url, (uint64_t)amount->valuedouble, unit_s, &out); cJSON_Delete(args); return wrap_wallet_json_result(rc, out, "cashu_wallet_mint_quote failed"); } @@ -117,7 +117,7 @@ char* execute_cashu_wallet_mint_check(tools_context_t* ctx, const char* args_jso } cJSON* out = NULL; - int rc = cashu_wallet_mint_check_json(mint_url, quote_id->valuestring, &out); + int rc = cashu_wallet_mint_check(mint_url, quote_id->valuestring, &out); cJSON_Delete(args); return wrap_wallet_json_result(rc, out, "cashu_wallet_mint_check failed"); } @@ -152,7 +152,7 @@ char* execute_cashu_wallet_mint_claim(tools_context_t* ctx, const char* args_jso } cJSON* out = NULL; - int rc = cashu_wallet_mint_claim_json(mint_url, quote_id->valuestring, amount, &out); + int rc = cashu_wallet_mint_claim(mint_url, quote_id->valuestring, amount, &out); cJSON_Delete(args); return wrap_wallet_json_result(rc, out, "cashu_wallet_mint_claim failed"); } @@ -179,7 +179,7 @@ char* execute_cashu_wallet_melt_quote(tools_context_t* ctx, const char* args_jso } cJSON* out = NULL; - int rc = cashu_wallet_melt_quote_json(mint_url, payment_request->valuestring, unit_s, &out); + int rc = cashu_wallet_melt_quote(mint_url, payment_request->valuestring, unit_s, &out); cJSON_Delete(args); return wrap_wallet_json_result(rc, out, "cashu_wallet_melt_quote failed"); } @@ -204,7 +204,7 @@ char* execute_cashu_wallet_melt_pay(tools_context_t* ctx, const char* args_json) } cJSON* out = NULL; - int rc = cashu_wallet_melt_pay_json(mint_url, quote_id->valuestring, &out); + int rc = cashu_wallet_melt_pay(mint_url, quote_id->valuestring, &out); cJSON_Delete(args); return wrap_wallet_json_result(rc, out, "cashu_wallet_melt_pay failed"); } @@ -223,7 +223,7 @@ char* execute_cashu_wallet_check_proofs(tools_context_t* ctx, const char* args_j } cJSON* out = NULL; - int rc = cashu_wallet_check_proofs_json(mint_url, &out); + int rc = cashu_wallet_check_proofs(mint_url, &out); cJSON_Delete(args); return wrap_wallet_json_result(rc, out, "cashu_wallet_check_proofs failed"); } @@ -244,7 +244,98 @@ char* execute_cashu_wallet_receive_token(tools_context_t* ctx, const char* args_ const char* mint_url = (mint && cJSON_IsString(mint) && mint->valuestring) ? mint->valuestring : NULL; cJSON* out = NULL; - int rc = cashu_wallet_receive_token_json(token->valuestring, mint_url, &out); + int rc = cashu_wallet_receive_token(token->valuestring, mint_url, &out); cJSON_Delete(args); return wrap_wallet_json_result(rc, out, "cashu_wallet_receive_token failed"); } + +char* execute_cashu_wallet_mints_get(tools_context_t* ctx, const char* args_json) { + if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); + + cJSON* args = parse_args_local(args_json); + if (!args) return json_error_local("invalid arguments JSON"); + cJSON_Delete(args); + + cJSON* out = NULL; + int rc = cashu_wallet_mints_get(&out); + return wrap_wallet_json_result(rc, out, "cashu_wallet_mints_get failed"); +} + +char* execute_cashu_wallet_mints_set(tools_context_t* ctx, const char* args_json) { + if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); + + cJSON* args = parse_args_local(args_json); + if (!args) return json_error_local("invalid arguments JSON"); + + cJSON* target = cJSON_GetObjectItemCaseSensitive(args, "target"); + const char* target_s = (target && cJSON_IsString(target) && target->valuestring) ? target->valuestring : "both"; + + int set_wallet = 0; + int set_public = 0; + if (strcmp(target_s, "wallet") == 0) { + set_wallet = 1; + } else if (strcmp(target_s, "public") == 0) { + set_public = 1; + } else if (strcmp(target_s, "both") == 0) { + set_wallet = 1; + set_public = 1; + } else { + cJSON_Delete(args); + return json_error_local("cashu_wallet_mints_set target must be wallet, public, or both"); + } + + cJSON* wallet_arr = cJSON_GetObjectItemCaseSensitive(args, "wallet_mints"); + cJSON* public_arr = cJSON_GetObjectItemCaseSensitive(args, "public_mints"); + + const char* wallet_buf[32] = {0}; + int wallet_count = 0; + if (wallet_arr) { + if (!cJSON_IsArray(wallet_arr)) { + cJSON_Delete(args); + return json_error_local("wallet_mints must be an array of strings"); + } + int n = cJSON_GetArraySize(wallet_arr); + for (int i = 0; i < n && wallet_count < 32; i++) { + cJSON* it = cJSON_GetArrayItem(wallet_arr, i); + if (it && cJSON_IsString(it) && it->valuestring && it->valuestring[0] != '\0') { + wallet_buf[wallet_count++] = it->valuestring; + } + } + } + + const char* public_buf[32] = {0}; + int public_count = 0; + if (public_arr) { + if (!cJSON_IsArray(public_arr)) { + cJSON_Delete(args); + return json_error_local("public_mints must be an array of strings"); + } + int n = cJSON_GetArraySize(public_arr); + for (int i = 0; i < n && public_count < 32; i++) { + cJSON* it = cJSON_GetArrayItem(public_arr, i); + if (it && cJSON_IsString(it) && it->valuestring && it->valuestring[0] != '\0') { + public_buf[public_count++] = it->valuestring; + } + } + } + + if (set_wallet && wallet_count <= 0) { + cJSON_Delete(args); + return json_error_local("cashu_wallet_mints_set requires wallet_mints when target includes wallet"); + } + if (set_public && public_count <= 0) { + cJSON_Delete(args); + return json_error_local("cashu_wallet_mints_set requires public_mints when target includes public"); + } + + cJSON* out = NULL; + int rc = cashu_wallet_mints_set(wallet_buf, + wallet_count, + public_buf, + public_count, + set_wallet, + set_public, + &out); + cJSON_Delete(args); + return wrap_wallet_json_result(rc, out, "cashu_wallet_mints_set failed"); +} diff --git a/src/tools/tools_dispatch.c b/src/tools/tools_dispatch.c index c4b599e..3bc86fc 100644 --- a/src/tools/tools_dispatch.c +++ b/src/tools/tools_dispatch.c @@ -259,6 +259,12 @@ char* tools_execute_legacy(tools_context_t* ctx, const char* tool_name, const ch if (strcmp(tool_name, "cashu_wallet_receive_token") == 0) { return execute_cashu_wallet_receive_token(ctx, args_json); } + if (strcmp(tool_name, "cashu_wallet_mints_get") == 0) { + return execute_cashu_wallet_mints_get(ctx, args_json); + } + if (strcmp(tool_name, "cashu_wallet_mints_set") == 0) { + return execute_cashu_wallet_mints_set(ctx, args_json); + } return json_error("unknown tool"); } diff --git a/src/tools/tools_internal.h b/src/tools/tools_internal.h index 827cec7..6ada877 100644 --- a/src/tools/tools_internal.h +++ b/src/tools/tools_internal.h @@ -78,6 +78,8 @@ char* execute_cashu_wallet_melt_quote(tools_context_t* ctx, const char* args_jso char* execute_cashu_wallet_melt_pay(tools_context_t* ctx, const char* args_json); char* execute_cashu_wallet_check_proofs(tools_context_t* ctx, const char* args_json); char* execute_cashu_wallet_receive_token(tools_context_t* ctx, const char* args_json); +char* execute_cashu_wallet_mints_get(tools_context_t* ctx, const char* args_json); +char* execute_cashu_wallet_mints_set(tools_context_t* ctx, const char* args_json); int memory_init(tools_context_t* ctx); void memory_cleanup(void); diff --git a/src/tools/tools_schema.c b/src/tools/tools_schema.c index d92f617..df5e067 100644 --- a/src/tools/tools_schema.c +++ b/src/tools/tools_schema.c @@ -1711,6 +1711,61 @@ char* tools_build_openai_schema_json_legacy(const tools_context_t* ctx) { cJSON_AddItemToObject(t59, "function", t59_fn); cJSON_AddItemToArray(tools, t59); + cJSON* t60 = cJSON_CreateObject(); + cJSON* t60_fn = cJSON_CreateObject(); + cJSON* t60_params = cJSON_CreateObject(); + cJSON* t60_props = cJSON_CreateObject(); + + cJSON_AddStringToObject(t60, "type", "function"); + cJSON_AddStringToObject(t60_fn, "name", "cashu_wallet_mints_get"); + cJSON_AddStringToObject(t60_fn, "description", "Get wallet mints from NIP-60 wallet event and public mints from NIP-61 nutzap info"); + cJSON_AddStringToObject(t60_params, "type", "object"); + cJSON_AddItemToObject(t60_params, "properties", t60_props); + + cJSON_AddItemToObject(t60_fn, "parameters", t60_params); + cJSON_AddItemToObject(t60, "function", t60_fn); + cJSON_AddItemToArray(tools, t60); + + cJSON* t61 = cJSON_CreateObject(); + cJSON* t61_fn = cJSON_CreateObject(); + cJSON* t61_params = cJSON_CreateObject(); + cJSON* t61_props = cJSON_CreateObject(); + cJSON* t61_required = cJSON_CreateArray(); + + cJSON_AddStringToObject(t61, "type", "function"); + cJSON_AddStringToObject(t61_fn, "name", "cashu_wallet_mints_set"); + cJSON_AddStringToObject(t61_fn, "description", "Set wallet mints (NIP-60), public mints (NIP-61), or both"); + cJSON_AddStringToObject(t61_params, "type", "object"); + cJSON_AddItemToObject(t61_params, "properties", t61_props); + cJSON_AddItemToObject(t61_params, "required", t61_required); + + cJSON* p_wallet_ms_target = cJSON_CreateObject(); + cJSON_AddStringToObject(p_wallet_ms_target, "type", "string"); + cJSON_AddStringToObject(p_wallet_ms_target, "description", "Where to apply mint updates: wallet, public, or both"); + cJSON_AddItemToObject(t61_props, "target", p_wallet_ms_target); + + cJSON* p_wallet_ms_wallet = cJSON_CreateObject(); + cJSON_AddStringToObject(p_wallet_ms_wallet, "type", "array"); + cJSON* p_wallet_ms_wallet_items = cJSON_CreateObject(); + cJSON_AddStringToObject(p_wallet_ms_wallet_items, "type", "string"); + cJSON_AddItemToObject(p_wallet_ms_wallet, "items", p_wallet_ms_wallet_items); + cJSON_AddStringToObject(p_wallet_ms_wallet, "description", "Mint URLs for wallet event update"); + cJSON_AddItemToObject(t61_props, "wallet_mints", p_wallet_ms_wallet); + + cJSON* p_wallet_ms_public = cJSON_CreateObject(); + cJSON_AddStringToObject(p_wallet_ms_public, "type", "array"); + cJSON* p_wallet_ms_public_items = cJSON_CreateObject(); + cJSON_AddStringToObject(p_wallet_ms_public_items, "type", "string"); + cJSON_AddItemToObject(p_wallet_ms_public, "items", p_wallet_ms_public_items); + cJSON_AddStringToObject(p_wallet_ms_public, "description", "Mint URLs for public nutzap info update"); + cJSON_AddItemToObject(t61_props, "public_mints", p_wallet_ms_public); + + cJSON_AddItemToArray(t61_required, cJSON_CreateString("target")); + + cJSON_AddItemToObject(t61_fn, "parameters", t61_params); + cJSON_AddItemToObject(t61, "function", t61_fn); + cJSON_AddItemToArray(tools, t61); + char* out = cJSON_PrintUnformatted(tools); cJSON_Delete(tools); return out;