v0.1.7 - Improve Cashu receive diagnostics: preserve mint error JSON on non-2xx, add step-level DEBUG_WARN logging in cashu_wallet_receive_token, and return structured error details in wallet tool responses
This commit is contained in:
@@ -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.6
|
||||
## Current Status — v0.1.7
|
||||
|
||||
**Active build — this project is barely working. Experiment at your own risk.**
|
||||
|
||||
> Last release update: v0.1.6 — Security: ignore genesis.jsonc, add safe genesis.jsonc.example, purge genesis history, and restore local ignored genesis file
|
||||
> Last release update: v0.1.7 — Improve Cashu receive diagnostics: preserve mint error JSON on non-2xx, add step-level DEBUG_WARN logging in cashu_wallet_receive_token, and return structured error details in wallet tool responses
|
||||
|
||||
- Connects to configured relays with auto-reconnect and relay state transition logging
|
||||
- Publishes configured startup events per relay as each relay becomes connected
|
||||
|
||||
+176
-26
@@ -1167,14 +1167,69 @@ int cashu_wallet_check_proofs(const char* mint_url, cJSON** out_json) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int receive_token_error(cJSON** out_json,
|
||||
const char* step,
|
||||
int rc,
|
||||
const char* error,
|
||||
cJSON* mint_error_json) {
|
||||
if (out_json) {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
if (root) {
|
||||
cJSON_AddBoolToObject(root, "success", 0);
|
||||
cJSON_AddStringToObject(root,
|
||||
"error",
|
||||
(error && error[0]) ? error : "cashu_wallet_receive_token failed");
|
||||
if (step && step[0]) {
|
||||
cJSON_AddStringToObject(root, "step", step);
|
||||
}
|
||||
cJSON_AddNumberToObject(root, "code", (double)rc);
|
||||
if (mint_error_json) {
|
||||
cJSON* dup = cJSON_Duplicate(mint_error_json, 1);
|
||||
if (dup) {
|
||||
cJSON_AddItemToObject(root, "mint_error", dup);
|
||||
}
|
||||
cJSON* detail = cJSON_GetObjectItemCaseSensitive(mint_error_json, "detail");
|
||||
if (detail && cJSON_IsString(detail) && detail->valuestring) {
|
||||
cJSON_AddStringToObject(root, "mint_error_detail", detail->valuestring);
|
||||
}
|
||||
}
|
||||
*out_json = root;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
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;
|
||||
if (!out_json || !token_string || token_string[0] == '\0' || !g_wallet.initialized || !g_wallet.cfg) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: invalid input or wallet not initialized");
|
||||
return receive_token_error(out_json,
|
||||
"preconditions",
|
||||
NOSTR_ERROR_INVALID_INPUT,
|
||||
"cashu_wallet_receive_token preconditions failed",
|
||||
NULL);
|
||||
}
|
||||
*out_json = NULL;
|
||||
|
||||
int rc = ensure_wallet_ready();
|
||||
if (rc != 0) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: ensure_wallet_ready failed rc=%d", rc);
|
||||
return receive_token_error(out_json,
|
||||
"ensure_wallet_ready",
|
||||
rc,
|
||||
"wallet is not ready",
|
||||
NULL);
|
||||
}
|
||||
|
||||
cashu_decoded_token_t decoded;
|
||||
memset(&decoded, 0, sizeof(decoded));
|
||||
if (cashu_decode_token(token_string, &decoded) != 0) {
|
||||
return -1;
|
||||
rc = cashu_decode_token(token_string, &decoded);
|
||||
if (rc != 0) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: cashu_decode_token failed rc=%d", rc);
|
||||
return receive_token_error(out_json,
|
||||
"decode_token",
|
||||
rc,
|
||||
"failed to decode cashu token",
|
||||
NULL);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_wallet.mutex);
|
||||
@@ -1185,68 +1240,112 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
|
||||
const char* resolved = cfg_resolved && cfg_resolved[0] ? cfg_resolved : decoded.mint_url;
|
||||
if (!resolved || resolved[0] == '\0') {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: no resolved mint URL");
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"resolve_mint_url",
|
||||
NOSTR_ERROR_INVALID_INPUT,
|
||||
"no mint URL available for token receive",
|
||||
NULL);
|
||||
}
|
||||
|
||||
if (decoded.mint_url && decoded.mint_url[0] && strcmp(decoded.mint_url, resolved) != 0) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: token mint mismatch token_mint=%s resolved_mint=%s",
|
||||
decoded.mint_url,
|
||||
resolved);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
return receive_token_error(out_json,
|
||||
"validate_mint_match",
|
||||
NOSTR_ERROR_INVALID_INPUT,
|
||||
"token mint does not match selected wallet mint",
|
||||
NULL);
|
||||
}
|
||||
|
||||
uint64_t incoming_total = nostr_nip60_sum_proofs(decoded.proofs, decoded.proof_count);
|
||||
if (incoming_total == 0) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: incoming token proof total is zero");
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
return receive_token_error(out_json,
|
||||
"sum_proofs",
|
||||
NOSTR_ERROR_INVALID_INPUT,
|
||||
"token proofs total to zero",
|
||||
NULL);
|
||||
}
|
||||
|
||||
cashu_mint_info_t info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
if (cashu_mint_get_info(resolved, &info, timeout) != 0) {
|
||||
rc = cashu_mint_get_info(resolved, &info, timeout);
|
||||
if (rc != 0) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: cashu_mint_get_info failed rc=%d mint=%s", rc, resolved);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"mint_get_info",
|
||||
rc,
|
||||
"failed to query mint info",
|
||||
NULL);
|
||||
}
|
||||
|
||||
cashu_keyset_t keyset;
|
||||
memset(&keyset, 0, sizeof(keyset));
|
||||
if (cashu_mint_get_active_keyset(&info, unit, &keyset) != 0 || !info.pubkey || info.pubkey[0] == '\0') {
|
||||
rc = cashu_mint_get_active_keyset(&info, unit, &keyset);
|
||||
if (rc != 0 || !info.pubkey || info.pubkey[0] == '\0') {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: active keyset/pubkey unavailable rc=%d unit=%s", rc, unit);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"get_active_keyset",
|
||||
rc != 0 ? rc : NOSTR_ERROR_CASHU_INVALID_KEYSET,
|
||||
"mint has no usable active keyset/pubkey for requested unit",
|
||||
NULL);
|
||||
}
|
||||
|
||||
uint64_t parts[CASHU_WALLET_MAX_SPLIT_PARTS] = {0};
|
||||
int part_count = 0;
|
||||
if (cashu_mint_plan_split_amounts(incoming_total, parts, CASHU_WALLET_MAX_SPLIT_PARTS, &part_count) != 0 || part_count <= 0) {
|
||||
rc = cashu_mint_plan_split_amounts(incoming_total, parts, CASHU_WALLET_MAX_SPLIT_PARTS, &part_count);
|
||||
if (rc != 0 || part_count <= 0) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: cashu_mint_plan_split_amounts failed rc=%d incoming_total=%llu",
|
||||
rc,
|
||||
(unsigned long long)incoming_total);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"plan_split_amounts",
|
||||
rc != 0 ? rc : -1,
|
||||
"failed to split receive amount into blinded output parts",
|
||||
NULL);
|
||||
}
|
||||
|
||||
cashu_blinded_output_t* outputs = (cashu_blinded_output_t*)calloc((size_t)part_count, sizeof(cashu_blinded_output_t));
|
||||
unsigned char* blinds = (unsigned char*)calloc((size_t)part_count, 32);
|
||||
char** secrets = (char**)calloc((size_t)part_count, sizeof(char*));
|
||||
if (!outputs || !blinds || !secrets) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: allocation failed for outputs/blinds/secrets part_count=%d", part_count);
|
||||
free(outputs);
|
||||
free(blinds);
|
||||
free(secrets);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"allocate_receive_buffers",
|
||||
NOSTR_ERROR_MEMORY_FAILED,
|
||||
"failed to allocate receive buffers",
|
||||
NULL);
|
||||
}
|
||||
|
||||
int rc = 0;
|
||||
rc = 0;
|
||||
for (int i = 0; i < part_count; i++) {
|
||||
unsigned char rnd_priv[32] = {0};
|
||||
unsigned char rnd_pub[32] = {0};
|
||||
if (nostr_generate_keypair(rnd_priv, rnd_pub) != NOSTR_SUCCESS) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: nostr_generate_keypair failed at part=%d", i);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
char* secret = (char*)malloc(65);
|
||||
if (!secret) {
|
||||
rc = -1;
|
||||
DEBUG_WARN("cashu_wallet_receive_token: secret allocation failed at part=%d", i);
|
||||
rc = NOSTR_ERROR_MEMORY_FAILED;
|
||||
break;
|
||||
}
|
||||
nostr_bytes_to_hex(rnd_priv, 32, secret);
|
||||
@@ -1255,6 +1354,7 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
outputs[i].amount = parts[i];
|
||||
|
||||
if (cashu_blind_message(secret, info.pubkey, outputs[i].B_, &blinds[(size_t)i * 32]) != 0) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: cashu_blind_message failed at part=%d", i);
|
||||
free(secret);
|
||||
rc = -1;
|
||||
break;
|
||||
@@ -1270,7 +1370,11 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
free(outputs);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"prepare_blinded_outputs",
|
||||
rc,
|
||||
"failed to prepare blinded outputs for swap",
|
||||
NULL);
|
||||
}
|
||||
|
||||
cashu_swap_request_t swap_req;
|
||||
@@ -1284,41 +1388,62 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
rc = cashu_build_swap_request(&swap_req, &request_body);
|
||||
free(outputs);
|
||||
if (rc != 0 || !request_body) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: cashu_build_swap_request failed rc=%d", rc);
|
||||
for (int i = 0; i < part_count; i++) free(secrets[i]);
|
||||
free(secrets);
|
||||
free(blinds);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
cJSON_Delete(request_body);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"build_swap_request",
|
||||
rc != 0 ? rc : -1,
|
||||
"failed to build swap request",
|
||||
NULL);
|
||||
}
|
||||
|
||||
cJSON* response_body = NULL;
|
||||
rc = cashu_mint_swap(resolved, request_body, &response_body, timeout);
|
||||
cJSON_Delete(request_body);
|
||||
if (rc != 0 || !response_body) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: cashu_mint_swap failed rc=%d mint=%s", rc, resolved);
|
||||
for (int i = 0; i < part_count; i++) free(secrets[i]);
|
||||
free(secrets);
|
||||
free(blinds);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
int err_rc = receive_token_error(out_json,
|
||||
"mint_swap",
|
||||
rc != 0 ? rc : -1,
|
||||
"mint rejected swap request",
|
||||
response_body);
|
||||
cJSON_Delete(response_body);
|
||||
return -1;
|
||||
return err_rc;
|
||||
}
|
||||
|
||||
cashu_swap_response_t parsed;
|
||||
memset(&parsed, 0, sizeof(parsed));
|
||||
rc = cashu_parse_swap_response(response_body, &parsed);
|
||||
cJSON_Delete(response_body);
|
||||
if (rc != 0 || parsed.signature_count <= 0 || parsed.signature_count != part_count) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: cashu_parse_swap_response failed rc=%d signature_count=%d expected=%d",
|
||||
rc,
|
||||
parsed.signature_count,
|
||||
part_count);
|
||||
cashu_mint_free_swap_response(&parsed);
|
||||
for (int i = 0; i < part_count; i++) free(secrets[i]);
|
||||
free(secrets);
|
||||
free(blinds);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
int err_rc = receive_token_error(out_json,
|
||||
"parse_swap_response",
|
||||
rc != 0 ? rc : -1,
|
||||
"invalid swap response from mint",
|
||||
response_body);
|
||||
cJSON_Delete(response_body);
|
||||
return err_rc;
|
||||
}
|
||||
cJSON_Delete(response_body);
|
||||
|
||||
nostr_nip60_token_data_t token;
|
||||
memset(&token, 0, sizeof(token));
|
||||
@@ -1326,6 +1451,7 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
token.proof_count = parsed.signature_count;
|
||||
token.proofs = (nostr_cashu_proof_t*)calloc((size_t)token.proof_count, sizeof(nostr_cashu_proof_t));
|
||||
if (!token.mint_url || !token.proofs) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: token allocation failed proof_count=%d", token.proof_count);
|
||||
nostr_nip60_free_token_data(&token);
|
||||
cashu_mint_free_swap_response(&parsed);
|
||||
for (int i = 0; i < part_count; i++) free(secrets[i]);
|
||||
@@ -1333,7 +1459,11 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
free(blinds);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"allocate_token",
|
||||
NOSTR_ERROR_MEMORY_FAILED,
|
||||
"failed to allocate received token storage",
|
||||
NULL);
|
||||
}
|
||||
|
||||
for (int i = 0; i < parsed.signature_count; i++) {
|
||||
@@ -1342,6 +1472,7 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
|
||||
char unblinded_C[67] = {0};
|
||||
if (cashu_unblind_signature(parsed.signatures[i].C_, info.pubkey, &blinds[(size_t)i * 32], unblinded_C) != 0) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: cashu_unblind_signature failed at index=%d", i);
|
||||
nostr_nip60_free_token_data(&token);
|
||||
cashu_mint_free_swap_response(&parsed);
|
||||
for (int j = 0; j < part_count; j++) free(secrets[j]);
|
||||
@@ -1349,12 +1480,17 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
free(blinds);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"unblind_signature",
|
||||
-1,
|
||||
"failed to unblind mint signature",
|
||||
NULL);
|
||||
}
|
||||
|
||||
token.proofs[i].secret = secrets[i] ? strdup(secrets[i]) : NULL;
|
||||
token.proofs[i].C = strdup(unblinded_C);
|
||||
if (!token.proofs[i].secret || !token.proofs[i].C) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: proof string allocation failed at index=%d", i);
|
||||
nostr_nip60_free_token_data(&token);
|
||||
cashu_mint_free_swap_response(&parsed);
|
||||
for (int j = 0; j < part_count; j++) free(secrets[j]);
|
||||
@@ -1362,7 +1498,11 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
free(blinds);
|
||||
cashu_mint_free_info(&info);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"store_unblinded_proofs",
|
||||
NOSTR_ERROR_MEMORY_FAILED,
|
||||
"failed to store unblinded proofs",
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1377,9 +1517,14 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
rc = publish_token_event_and_store_locked(&token, token_event_id);
|
||||
pthread_mutex_unlock(&g_wallet.mutex);
|
||||
if (rc != 0) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: publish_token_event_and_store_locked failed rc=%d", rc);
|
||||
nostr_nip60_free_token_data(&token);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"publish_token_event",
|
||||
rc,
|
||||
"failed to persist received token event",
|
||||
NULL);
|
||||
}
|
||||
|
||||
(void)publish_history_event(NOSTR_NIP60_DIRECTION_IN,
|
||||
@@ -1389,8 +1534,13 @@ int cashu_wallet_receive_token(const char* token_string, const char* mint_url, c
|
||||
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
if (!root) {
|
||||
DEBUG_WARN("cashu_wallet_receive_token: failed to create success JSON root");
|
||||
cashu_free_decoded_token(&decoded);
|
||||
return -1;
|
||||
return receive_token_error(out_json,
|
||||
"build_success_json",
|
||||
NOSTR_ERROR_MEMORY_FAILED,
|
||||
"failed to build success response",
|
||||
NULL);
|
||||
}
|
||||
|
||||
cJSON_AddBoolToObject(root, "success", 1);
|
||||
|
||||
+2
-2
@@ -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 6
|
||||
#define DIDACTYL_VERSION "v0.1.6"
|
||||
#define DIDACTYL_VERSION_PATCH 7
|
||||
#define DIDACTYL_VERSION "v0.1.7"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
@@ -29,13 +29,27 @@ static cJSON* parse_args_local(const char* args_json) {
|
||||
}
|
||||
|
||||
static char* wrap_wallet_json_result(int rc, cJSON* obj, const char* default_error) {
|
||||
if (rc != 0 || !obj) {
|
||||
cJSON_Delete(obj);
|
||||
if (!obj) {
|
||||
return json_error_local(default_error ? default_error : "cashu wallet operation failed");
|
||||
}
|
||||
|
||||
if (rc != 0) {
|
||||
cJSON* success = cJSON_GetObjectItemCaseSensitive(obj, "success");
|
||||
if (!success) {
|
||||
cJSON_AddBoolToObject(obj, "success", 0);
|
||||
}
|
||||
if (!cJSON_GetObjectItemCaseSensitive(obj, "error")) {
|
||||
cJSON_AddStringToObject(obj,
|
||||
"error",
|
||||
default_error ? default_error : "cashu wallet operation failed");
|
||||
}
|
||||
}
|
||||
|
||||
char* out = cJSON_PrintUnformatted(obj);
|
||||
cJSON_Delete(obj);
|
||||
if (!out) {
|
||||
return json_error_local(default_error ? default_error : "cashu wallet operation failed");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user