Add Cashu token decode support for cashuA and cashuB
This commit is contained in:
@@ -197,7 +197,7 @@ print_info "Auto-detecting needed NIPs from your source code..."
|
||||
NEEDED_NIPS=""
|
||||
if [ -n "$FORCE_NIPS" ]; then
|
||||
if [ "$FORCE_NIPS" = "all" ]; then
|
||||
NEEDED_NIPS="001 004 005 006 011 013 017 019 042 044 046 059 060 061"
|
||||
NEEDED_NIPS="001 004 005 006 011 013 017 019 021 042 044 046 059 060 061"
|
||||
print_info "Forced: Building all available NIPs"
|
||||
else
|
||||
# Convert comma-separated list to space-separated with 3-digit format
|
||||
@@ -216,7 +216,7 @@ else
|
||||
# Check for nostr_core.h (includes everything)
|
||||
if grep -q '#include[[:space:]]*["\<]nostr_core\.h["\>]' *.c 2>/dev/null; then
|
||||
print_info "Found #include \"nostr_core.h\" - building all NIPs"
|
||||
NEEDED_NIPS="001 004 005 006 011 013 019 042 044 046 059 060 061"
|
||||
NEEDED_NIPS="001 004 005 006 011 013 019 021 042 044 046 059 060 061"
|
||||
elif [ -n "$DETECTED" ]; then
|
||||
NEEDED_NIPS="$DETECTED"
|
||||
print_success "Auto-detected NIPs: $(echo $NEEDED_NIPS | tr ' ' ',')"
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
#include "cashu_mint.h"
|
||||
#include "../cjson/cJSON.h"
|
||||
#include "utils.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
@@ -819,3 +821,452 @@ void cashu_mint_free_checkstate_response(cashu_checkstate_response_t* response)
|
||||
free(response->states);
|
||||
memset(response, 0, sizeof(*response));
|
||||
}
|
||||
|
||||
static char* cashu_trim_copy(const char* s) {
|
||||
if (!s) return NULL;
|
||||
const char* start = s;
|
||||
while (*start && isspace((unsigned char)*start)) start++;
|
||||
const char* end = start + strlen(start);
|
||||
while (end > start && isspace((unsigned char)*(end - 1))) end--;
|
||||
size_t len = (size_t)(end - start);
|
||||
char* out = (char*)malloc(len + 1);
|
||||
if (!out) return NULL;
|
||||
memcpy(out, start, len);
|
||||
out[len] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
static int cashu_base64url_decode_alloc(const char* input, unsigned char** out, size_t* out_len) {
|
||||
if (!input || !out || !out_len) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
*out = NULL;
|
||||
*out_len = 0;
|
||||
|
||||
size_t in_len = strlen(input);
|
||||
if (in_len == 0) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
size_t pad = (4 - (in_len % 4)) % 4;
|
||||
size_t b64_len = in_len + pad;
|
||||
|
||||
char* b64 = (char*)malloc(b64_len + 1);
|
||||
if (!b64) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
|
||||
for (size_t i = 0; i < in_len; i++) {
|
||||
char c = input[i];
|
||||
if (c == '-') c = '+';
|
||||
else if (c == '_') c = '/';
|
||||
b64[i] = c;
|
||||
}
|
||||
for (size_t i = 0; i < pad; i++) b64[in_len + i] = '=';
|
||||
b64[b64_len] = '\0';
|
||||
|
||||
size_t max_out = (b64_len / 4) * 3;
|
||||
unsigned char* decoded = (unsigned char*)malloc(max_out + 1);
|
||||
if (!decoded) {
|
||||
free(b64);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
size_t n = base64_decode(b64, decoded);
|
||||
free(b64);
|
||||
if (n == 0) {
|
||||
free(decoded);
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*out = decoded;
|
||||
*out_len = n;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const unsigned char* data;
|
||||
size_t len;
|
||||
size_t pos;
|
||||
int depth;
|
||||
} cashu_cbor_cursor_t;
|
||||
|
||||
static int cashu_cbor_read_uint(cashu_cbor_cursor_t* c, uint8_t ai, uint64_t* out) {
|
||||
if (!c || !out) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
if (ai < 24) {
|
||||
*out = ai;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
if (ai == 24) {
|
||||
if (c->pos + 1 > c->len) return NOSTR_ERROR_INVALID_INPUT;
|
||||
*out = c->data[c->pos++];
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
if (ai == 25) {
|
||||
if (c->pos + 2 > c->len) return NOSTR_ERROR_INVALID_INPUT;
|
||||
*out = ((uint64_t)c->data[c->pos] << 8) | (uint64_t)c->data[c->pos + 1];
|
||||
c->pos += 2;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
if (ai == 26) {
|
||||
if (c->pos + 4 > c->len) return NOSTR_ERROR_INVALID_INPUT;
|
||||
*out = ((uint64_t)c->data[c->pos] << 24) |
|
||||
((uint64_t)c->data[c->pos + 1] << 16) |
|
||||
((uint64_t)c->data[c->pos + 2] << 8) |
|
||||
(uint64_t)c->data[c->pos + 3];
|
||||
c->pos += 4;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
if (ai == 27) {
|
||||
if (c->pos + 8 > c->len) return NOSTR_ERROR_INVALID_INPUT;
|
||||
*out = ((uint64_t)c->data[c->pos] << 56) |
|
||||
((uint64_t)c->data[c->pos + 1] << 48) |
|
||||
((uint64_t)c->data[c->pos + 2] << 40) |
|
||||
((uint64_t)c->data[c->pos + 3] << 32) |
|
||||
((uint64_t)c->data[c->pos + 4] << 24) |
|
||||
((uint64_t)c->data[c->pos + 5] << 16) |
|
||||
((uint64_t)c->data[c->pos + 6] << 8) |
|
||||
(uint64_t)c->data[c->pos + 7];
|
||||
c->pos += 8;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
static int cashu_cbor_parse_item(cashu_cbor_cursor_t* c, cJSON** out);
|
||||
|
||||
static int cashu_cbor_parse_text(cashu_cbor_cursor_t* c, uint8_t ai, cJSON** out) {
|
||||
uint64_t n = 0;
|
||||
int rc = cashu_cbor_read_uint(c, ai, &n);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
if (c->pos + (size_t)n > c->len) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
char* s = (char*)malloc((size_t)n + 1);
|
||||
if (!s) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
memcpy(s, c->data + c->pos, (size_t)n);
|
||||
s[n] = '\0';
|
||||
c->pos += (size_t)n;
|
||||
|
||||
*out = cJSON_CreateString(s);
|
||||
free(s);
|
||||
return *out ? NOSTR_SUCCESS : NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
static int cashu_cbor_parse_bytes(cashu_cbor_cursor_t* c, uint8_t ai, cJSON** out) {
|
||||
uint64_t n = 0;
|
||||
int rc = cashu_cbor_read_uint(c, ai, &n);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
if (c->pos + (size_t)n > c->len) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
size_t hex_len = (size_t)n * 2;
|
||||
char* hex = (char*)malloc(hex_len + 1);
|
||||
if (!hex) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
nostr_bytes_to_hex(c->data + c->pos, (size_t)n, hex);
|
||||
c->pos += (size_t)n;
|
||||
|
||||
*out = cJSON_CreateString(hex);
|
||||
free(hex);
|
||||
return *out ? NOSTR_SUCCESS : NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
static int cashu_cbor_parse_array(cashu_cbor_cursor_t* c, uint8_t ai, cJSON** out) {
|
||||
uint64_t n = 0;
|
||||
int rc = cashu_cbor_read_uint(c, ai, &n);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
|
||||
cJSON* arr = cJSON_CreateArray();
|
||||
if (!arr) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
|
||||
for (uint64_t i = 0; i < n; i++) {
|
||||
cJSON* item = NULL;
|
||||
rc = cashu_cbor_parse_item(c, &item);
|
||||
if (rc != NOSTR_SUCCESS || !item) {
|
||||
cJSON_Delete(arr);
|
||||
return rc != NOSTR_SUCCESS ? rc : NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
cJSON_AddItemToArray(arr, item);
|
||||
}
|
||||
|
||||
*out = arr;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static int cashu_cbor_parse_map(cashu_cbor_cursor_t* c, uint8_t ai, cJSON** out) {
|
||||
uint64_t n = 0;
|
||||
int rc = cashu_cbor_read_uint(c, ai, &n);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
|
||||
cJSON* obj = cJSON_CreateObject();
|
||||
if (!obj) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
|
||||
for (uint64_t i = 0; i < n; i++) {
|
||||
cJSON* k = NULL;
|
||||
cJSON* v = NULL;
|
||||
rc = cashu_cbor_parse_item(c, &k);
|
||||
if (rc != NOSTR_SUCCESS || !k) {
|
||||
cJSON_Delete(obj);
|
||||
return rc != NOSTR_SUCCESS ? rc : NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
rc = cashu_cbor_parse_item(c, &v);
|
||||
if (rc != NOSTR_SUCCESS || !v) {
|
||||
cJSON_Delete(k);
|
||||
cJSON_Delete(obj);
|
||||
return rc != NOSTR_SUCCESS ? rc : NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
char keybuf[32];
|
||||
const char* key = NULL;
|
||||
if (cJSON_IsString(k) && k->valuestring) {
|
||||
key = k->valuestring;
|
||||
} else if (cJSON_IsNumber(k)) {
|
||||
snprintf(keybuf, sizeof(keybuf), "%.0f", cJSON_GetNumberValue(k));
|
||||
key = keybuf;
|
||||
}
|
||||
|
||||
if (!key || key[0] == '\0') {
|
||||
cJSON_Delete(k);
|
||||
cJSON_Delete(v);
|
||||
cJSON_Delete(obj);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(obj, key, v);
|
||||
cJSON_Delete(k);
|
||||
}
|
||||
|
||||
*out = obj;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static int cashu_cbor_parse_item(cashu_cbor_cursor_t* c, cJSON** out) {
|
||||
if (!c || !out || c->pos >= c->len) return NOSTR_ERROR_INVALID_INPUT;
|
||||
if (c->depth > 64) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
uint8_t ib = c->data[c->pos++];
|
||||
uint8_t mt = (uint8_t)(ib >> 5);
|
||||
uint8_t ai = (uint8_t)(ib & 0x1f);
|
||||
|
||||
c->depth++;
|
||||
|
||||
int rc = NOSTR_SUCCESS;
|
||||
cJSON* node = NULL;
|
||||
|
||||
if (mt == 0) {
|
||||
uint64_t v = 0;
|
||||
rc = cashu_cbor_read_uint(c, ai, &v);
|
||||
if (rc == NOSTR_SUCCESS) node = cJSON_CreateNumber((double)v);
|
||||
} else if (mt == 1) {
|
||||
uint64_t v = 0;
|
||||
rc = cashu_cbor_read_uint(c, ai, &v);
|
||||
if (rc == NOSTR_SUCCESS) node = cJSON_CreateNumber(-(double)(v + 1));
|
||||
} else if (mt == 2) {
|
||||
rc = cashu_cbor_parse_bytes(c, ai, &node);
|
||||
} else if (mt == 3) {
|
||||
rc = cashu_cbor_parse_text(c, ai, &node);
|
||||
} else if (mt == 4) {
|
||||
rc = cashu_cbor_parse_array(c, ai, &node);
|
||||
} else if (mt == 5) {
|
||||
rc = cashu_cbor_parse_map(c, ai, &node);
|
||||
} else if (mt == 7 && ai == 20) {
|
||||
node = cJSON_CreateFalse();
|
||||
} else if (mt == 7 && ai == 21) {
|
||||
node = cJSON_CreateTrue();
|
||||
} else if (mt == 7 && ai == 22) {
|
||||
node = cJSON_CreateNull();
|
||||
} else {
|
||||
rc = NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
c->depth--;
|
||||
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cJSON_Delete(node);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!node) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
*out = node;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static cJSON* cashu_token_get_case(cJSON* obj, const char* a, const char* b) {
|
||||
if (!obj || !cJSON_IsObject(obj)) return NULL;
|
||||
cJSON* x = cJSON_GetObjectItemCaseSensitive(obj, a);
|
||||
if (x) return x;
|
||||
return cJSON_GetObjectItemCaseSensitive(obj, b);
|
||||
}
|
||||
|
||||
static int cashu_extract_token_json(cJSON* root, cashu_decoded_token_t* out) {
|
||||
if (!root || !out) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
cJSON* mint = cashu_token_get_case(root, "mint", "m");
|
||||
cJSON* token_arr = cashu_token_get_case(root, "token", "t");
|
||||
|
||||
cJSON* token_entry = NULL;
|
||||
if (token_arr && cJSON_IsArray(token_arr) && cJSON_GetArraySize(token_arr) > 0) {
|
||||
token_entry = cJSON_GetArrayItem(token_arr, 0);
|
||||
} else if (cJSON_IsObject(root)) {
|
||||
token_entry = root;
|
||||
}
|
||||
|
||||
if (!token_entry || !cJSON_IsObject(token_entry)) {
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
cJSON* entry_mint = cashu_token_get_case(token_entry, "mint", "m");
|
||||
if (entry_mint && cJSON_IsString(entry_mint) && entry_mint->valuestring) mint = entry_mint;
|
||||
|
||||
if (!mint || !cJSON_IsString(mint) || !mint->valuestring || mint->valuestring[0] == '\0') {
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
out->mint_url = cashu_strdup(mint->valuestring);
|
||||
if (!out->mint_url) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
|
||||
cJSON* proofs = cashu_token_get_case(token_entry, "proofs", "p");
|
||||
if (!proofs || !cJSON_IsArray(proofs)) {
|
||||
cashu_free_decoded_token(out);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
int n = cJSON_GetArraySize(proofs);
|
||||
if (n <= 0) {
|
||||
cashu_free_decoded_token(out);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
out->proofs = (nostr_cashu_proof_t*)calloc((size_t)n, sizeof(nostr_cashu_proof_t));
|
||||
if (!out->proofs) {
|
||||
cashu_free_decoded_token(out);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
cJSON* token_entry_id = cashu_token_get_case(token_entry, "id", "i");
|
||||
const char* inherited_id = (token_entry_id && cJSON_IsString(token_entry_id) && token_entry_id->valuestring)
|
||||
? token_entry_id->valuestring
|
||||
: NULL;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* p = cJSON_GetArrayItem(proofs, i);
|
||||
if (!p || !cJSON_IsObject(p)) {
|
||||
cashu_free_decoded_token(out);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
cJSON* id = cashu_token_get_case(p, "id", "i");
|
||||
cJSON* amount = cashu_token_get_case(p, "amount", "a");
|
||||
cJSON* secret = cashu_token_get_case(p, "secret", "s");
|
||||
cJSON* C = cashu_token_get_case(p, "C", "c");
|
||||
|
||||
const char* id_s = (id && cJSON_IsString(id) && id->valuestring) ? id->valuestring : inherited_id;
|
||||
|
||||
if (!id_s || id_s[0] == '\0' || !amount || !cJSON_IsNumber(amount) ||
|
||||
!secret || !cJSON_IsString(secret) || !secret->valuestring ||
|
||||
!C || !cJSON_IsString(C) || !C->valuestring) {
|
||||
cashu_free_decoded_token(out);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
snprintf(out->proofs[i].id, sizeof(out->proofs[i].id), "%s", id_s);
|
||||
out->proofs[i].amount = (uint64_t)cJSON_GetNumberValue(amount);
|
||||
out->proofs[i].secret = cashu_strdup(secret->valuestring);
|
||||
out->proofs[i].C = cashu_strdup(C->valuestring);
|
||||
if (!out->proofs[i].secret || !out->proofs[i].C) {
|
||||
cashu_free_decoded_token(out);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
out->proof_count = n;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static int cashu_decode_token_a(const char* token_payload, cashu_decoded_token_t* token_out) {
|
||||
unsigned char* decoded = NULL;
|
||||
size_t decoded_len = 0;
|
||||
int rc = cashu_base64url_decode_alloc(token_payload, &decoded, &decoded_len);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
|
||||
char* json_text = (char*)malloc(decoded_len + 1);
|
||||
if (!json_text) {
|
||||
free(decoded);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
memcpy(json_text, decoded, decoded_len);
|
||||
json_text[decoded_len] = '\0';
|
||||
free(decoded);
|
||||
|
||||
cJSON* root = cJSON_Parse(json_text);
|
||||
free(json_text);
|
||||
if (!root) return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
|
||||
rc = cashu_extract_token_json(root, token_out);
|
||||
cJSON_Delete(root);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int cashu_decode_token_b(const char* token_payload, cashu_decoded_token_t* token_out) {
|
||||
unsigned char* decoded = NULL;
|
||||
size_t decoded_len = 0;
|
||||
int rc = cashu_base64url_decode_alloc(token_payload, &decoded, &decoded_len);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
|
||||
cashu_cbor_cursor_t cur;
|
||||
memset(&cur, 0, sizeof(cur));
|
||||
cur.data = decoded;
|
||||
cur.len = decoded_len;
|
||||
|
||||
cJSON* root = NULL;
|
||||
rc = cashu_cbor_parse_item(&cur, &root);
|
||||
free(decoded);
|
||||
if (rc != NOSTR_SUCCESS || !root) {
|
||||
cJSON_Delete(root);
|
||||
return rc != NOSTR_SUCCESS ? rc : NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
rc = cashu_extract_token_json(root, token_out);
|
||||
cJSON_Delete(root);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int cashu_decode_token(const char* token_string,
|
||||
cashu_decoded_token_t* token_out) {
|
||||
if (!token_string || !token_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
char* t = cashu_trim_copy(token_string);
|
||||
if (!t) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
|
||||
const char* s = t;
|
||||
if (strncmp(s, "cashu:", 6) == 0) {
|
||||
s += 6;
|
||||
}
|
||||
|
||||
int rc = NOSTR_ERROR_INVALID_INPUT;
|
||||
if (strncmp(s, "cashuA", 6) == 0) {
|
||||
rc = cashu_decode_token_a(s + 6, token_out);
|
||||
} else if (strncmp(s, "cashuB", 6) == 0) {
|
||||
rc = cashu_decode_token_b(s + 6, token_out);
|
||||
}
|
||||
|
||||
free(t);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void cashu_free_decoded_token(cashu_decoded_token_t* token) {
|
||||
if (!token) return;
|
||||
|
||||
free(token->mint_url);
|
||||
if (token->proofs) {
|
||||
for (int i = 0; i < token->proof_count; i++) {
|
||||
free(token->proofs[i].secret);
|
||||
free(token->proofs[i].C);
|
||||
}
|
||||
}
|
||||
free(token->proofs);
|
||||
memset(token, 0, sizeof(*token));
|
||||
}
|
||||
|
||||
@@ -106,6 +106,12 @@ typedef struct {
|
||||
int state_count;
|
||||
} cashu_checkstate_response_t;
|
||||
|
||||
typedef struct {
|
||||
char* mint_url;
|
||||
nostr_cashu_proof_t* proofs;
|
||||
int proof_count;
|
||||
} cashu_decoded_token_t;
|
||||
|
||||
int cashu_mint_get_info(const char* mint_url,
|
||||
cashu_mint_info_t* info_out,
|
||||
int timeout_seconds);
|
||||
@@ -203,6 +209,11 @@ int cashu_parse_checkstate_response(cJSON* response_body,
|
||||
|
||||
void cashu_mint_free_checkstate_response(cashu_checkstate_response_t* response);
|
||||
|
||||
int cashu_decode_token(const char* token_string,
|
||||
cashu_decoded_token_t* token_out);
|
||||
|
||||
void cashu_free_decoded_token(cashu_decoded_token_t* token);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user