/* * Copyright (c) 2025 by Legiteum, Inc. Permission to use subject to terms below. * * Version: 1.0.2 / 20250510 * * Hello there. This is the Haypenny C implementation. Feel free to use this module * for your own project, and/or learn from it. We've made this module as developer-friendly * as we could so you can see how the Haypenny API works behind the scenes if you want. * * Do not modify this module and then redistribute it to others under the same name. Otherwise feel * free to use it any way you wish. * * To get started, just include this module in your program like so: * * #include "hblock.h" * * main(){ * void *hblock_handle = hblock_init("prod"); * HBlock block, newBlock; * * strcpy(block.string, "923kjsd9823jkEdkjXdq0P"); // your block string * * int rv = hblock_split(hblock_handle, &block, &newBlock, 11000000); * if(rv == HBLOCK_OK){ * rv = hblock_combine(hblock_handle, &block, &newBlock, 0); * if(rv != HBLOCK_OK) { * printf("There was a problem splitting the block: %s\n", hblock_error_message(hblock_handle)); * } * } * else { * printf("There was a problem splitting the block: %s\n", hblock_error_message(hblock_handle)); * } * } * * THIS HEADER FILE IS ALL YOU NEED. * * However, this module requires CURL, which you need to install in your system (read more: https://ec.haxx.se/install/). * * After CURL is installed, you should be able to compile your module like so: * * $ gcc -o myprogram myprogram.c -lcurl * */ #include #include #include #include #include #include #include #include #define HBLOCK_PROD_STEM "https://tx.haypenny.com" #define HBLOCK_SANDBOX_STEM "https://sbtx.haypenny.com" #define HBLOCK_MAX_MESSAGE_SIZE 1023 #define HBLOCK_JSON_VAL_MAX_SIZE 127 #define HBLOCK_OK 1 #define HBLOCK_ERR_NETWORK -1 #define HBLOCK_ERR_NOTFOUND -2 #define HBLOCK_ERR_INVARG -3 #define HBLOCK_ERR_INS_BALANCE -4 #define HBLOCK_ERR_OTHER -99 typedef struct _hblock { char string[23]; char realmId[7]; uint64_t balance; double usdBalance; char iblock[64]; char lockedUntil[64]; uint64_t secretUsed; // secret (recovery nonce) used in the last operation } HBlock; void *hblock_init(char *urlStem); int hblock_info(void *hblock_handle, HBlock *block); int hblock_split(void *hblock_handle, HBlock *block, HBlock *newBlock, uint64_t amount); int hblock_combine(void *hblock_handle, HBlock *block, HBlock *combineBlock, uint64_t amount); uint64_t hblock_gen_secret(); char * hblock_error_message(void *hblock_handle); void hblock_close(void *hblock_handle); typedef struct _hblockContext { CURL *curl; char urlStem[256]; char errmsg[1024]; } HBlockContext; typedef struct _curlBuf { char *buf; int idx; } CurlBuf; static char *jsonErrorFields[] = {"name", "message", "error", 0}; /* * Subroutines.... * */ static int _jsonObjectGetter(char **fieldNames, char fieldValues[][HBLOCK_JSON_VAL_MAX_SIZE+1], char *jsonObjectString) { /* * A very simple (and extremely fast) single-object JSON parser scanning for a fixed set of fields. Returns the number of fields found. */ char stringCopy[HBLOCK_MAX_MESSAGE_SIZE]; char *nameStart; char *nameEnd, *valStart, *valEnd, *p; int i, found = 0; char quoteChar; strcpy(stringCopy, jsonObjectString); nameStart = strchr(stringCopy, '{'); if(nameStart == 0 || *nameStart == 0){ return 0; } nameStart++; while(*nameStart){ for(; *nameStart && *nameStart != '"'; nameStart++); if(!(*nameStart)){ break; } nameStart++; if(!(*nameStart)){ break; } if(!(nameEnd = strchr(nameStart, ':'))){ break; } valStart = nameEnd; for(nameEnd--; *nameEnd == '\'' || *nameEnd == ' ' || *nameEnd == '"'; nameEnd--); nameEnd++; *nameEnd = 0; quoteChar = 0; for(valStart++; *valStart == ' '; valStart++); if(!(*valStart)){ break; } if(*valStart == '"' || *valStart == '\''){ quoteChar = *valStart; valStart++; } if(quoteChar){ for(valEnd = valStart; *valEnd; valEnd++){ if(*valEnd == quoteChar){ if(*(valEnd-1) != '\\'){ break; } } } } else { for(valEnd = valStart+1; *valEnd; valEnd++){ if(*valEnd == ',' || *valEnd == '}' || *valEnd == ' '){ break; } } } if(!(*valEnd)){ break; } *valEnd = 0; for(i = 0; fieldNames[i]; i++){ if(!(strcmp(fieldNames[i], nameStart))){ strcpy(fieldValues[i], valStart); found++; break; } } nameStart = valEnd+1; } return found; } static size_t _curlWrite(void *contents, size_t size, size_t nmemb, void *userp){ CurlBuf *cb = (CurlBuf *) userp; size_t count = size * nmemb; if(count + cb->idx > HBLOCK_MAX_MESSAGE_SIZE){ count = HBLOCK_MAX_MESSAGE_SIZE - cb->idx; } memcpy(cb->buf + cb->idx, contents, count); cb->idx += count; cb->buf[cb->idx] = 0; return count; } static int _getUrl(HBlockContext *ctx, char *output, char *format, ...) { CURLcode res; char url[1024]; va_list args; CurlBuf curlBuf; curlBuf.buf = output; curlBuf.idx = 0; curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, &curlBuf); va_start(args, format); vsnprintf(url, sizeof(url), format, args); va_end(args); curl_easy_setopt(ctx->curl, CURLOPT_URL, url); res = curl_easy_perform(ctx->curl); if(res != CURLE_OK){ snprintf(ctx->errmsg, sizeof(ctx->errmsg), "HTTP network error: %s", curl_easy_strerror(res)); return 0; } return 1; } /* * External API.... * */ void * hblock_init(char *urlStem) { HBlockContext *rv = calloc(1, sizeof(HBlockContext)); if(urlStem){ if(!(strcasecmp(urlStem, "sandbox"))){ strcpy(rv->urlStem, HBLOCK_SANDBOX_STEM); } else if(!(strcasecmp(urlStem, "prod"))){ strcpy(rv->urlStem, HBLOCK_PROD_STEM); } else { strncpy(rv->urlStem, urlStem, sizeof(rv->urlStem)); } } else { strcpy(rv->urlStem, HBLOCK_PROD_STEM); } rv->curl = curl_easy_init(); // we're just going to assume here that this never fails curl_easy_setopt(rv->curl, CURLOPT_WRITEFUNCTION, _curlWrite); return rv; } int hblock_info(void *hblock_handle, HBlock *block) { char *infoFields[] = {"iblock", "coin_id", "balance", "usd_balance", "locked_until", 0}; char infoVals[5][HBLOCK_JSON_VAL_MAX_SIZE+1]; char errVals[3][HBLOCK_JSON_VAL_MAX_SIZE+1]; HBlockContext *ctx = (HBlockContext *) hblock_handle; char jsonString[HBLOCK_MAX_MESSAGE_SIZE+1]; int fieldsFound; if(!(_getUrl(ctx, jsonString, "%s/api/Info?block=%s", ctx->urlStem, block))){ return HBLOCK_ERR_NETWORK; } fieldsFound = _jsonObjectGetter(infoFields, infoVals, jsonString); if(fieldsFound == 5){ // got the fields, use them to populate the return Block strcpy(block->iblock, infoVals[0]); strcpy(block->realmId, infoVals[1]); block->balance = atol(infoVals[2]); block->usdBalance = atof(infoVals[3]); strcpy(block->lockedUntil, infoVals[4]); return HBLOCK_OK; } else { fieldsFound = _jsonObjectGetter(jsonErrorFields, errVals, jsonString); if(fieldsFound == 3){ snprintf(ctx->errmsg, sizeof(ctx->errmsg), "%s: %s", errVals[0], errVals[1]); if(!(strcmp(errVals[0], "block_not_found"))){ return HBLOCK_ERR_NOTFOUND; } else if(!(strcmp(errVals[0], "invalid_block_format"))){ return HBLOCK_ERR_INVARG; } else { return HBLOCK_ERR_OTHER; } } else { sprintf(ctx->errmsg, "Malformed JSON returned from server: [%s]", jsonString); return HBLOCK_ERR_NETWORK; } } } int hblock_split(void *hblock_handle, HBlock *block, HBlock *newBlock, uint64_t amount) { char *splitFields[] = {"new_balance", "usd_balance", "new_block", 0}; char splitVals[3][HBLOCK_JSON_VAL_MAX_SIZE+1]; char errVals[3][HBLOCK_JSON_VAL_MAX_SIZE+1]; HBlockContext *ctx = (HBlockContext *) hblock_handle; char jsonString[HBLOCK_MAX_MESSAGE_SIZE+1]; int fieldsFound; uint64_t secret = hblock_gen_secret(); block->secretUsed = secret; if(!(_getUrl(ctx, jsonString, "%s/api/Split?block=%s&amount=%lld&secret=%lld&coin_id=%s", ctx->urlStem, block->string, amount, secret, block->realmId))){ return HBLOCK_ERR_NETWORK; } fieldsFound = _jsonObjectGetter(splitFields, splitVals, jsonString); if(fieldsFound == 3){ block->balance = atol(splitVals[0]); block->usdBalance = atof(splitVals[1]); strcpy(newBlock->string, splitVals[2]); strcpy(newBlock->realmId, block->realmId); newBlock->balance = amount; return HBLOCK_OK; } else { fieldsFound = _jsonObjectGetter(jsonErrorFields, errVals, jsonString); if(fieldsFound == 3){ snprintf(ctx->errmsg, sizeof(ctx->errmsg), "%s: %s", errVals[0], errVals[1]); if(!(strcmp(errVals[0], "not_found"))){ return HBLOCK_ERR_NOTFOUND; } else if(!(strcmp(errVals[0], "insufficient_balance"))){ return HBLOCK_ERR_INS_BALANCE; } else if(!(strcmp(errVals[0], "invalid_block_format"))){ return HBLOCK_ERR_INVARG; } else { return HBLOCK_ERR_OTHER; } } else { sprintf(ctx->errmsg, "Malformed JSON returned from server: [%s]", jsonString); return HBLOCK_ERR_NETWORK; } } } int hblock_combine(void *hblock_handle, HBlock *block, HBlock *combineBlock, uint64_t amount) { char *combineFields[] = {"coin_id", "balance", "usd_balance", "new_block", 0}; char combineVals[4][HBLOCK_JSON_VAL_MAX_SIZE+1]; char errVals[3][HBLOCK_JSON_VAL_MAX_SIZE+1]; HBlockContext *ctx = (HBlockContext *) hblock_handle; char jsonString[HBLOCK_MAX_MESSAGE_SIZE+1]; char url[256]; int fieldsFound; uint64_t secret = hblock_gen_secret(); block->secretUsed = secret; if(block->string[0]){ snprintf(url, sizeof(url), "%s/api/Combine?block=%s&combine_block=%s&secret=%lld", ctx->urlStem, block->string, combineBlock->string, secret); } else { snprintf(url, sizeof(url), "%s/api/Combine?combine_block=%s&secret=%lld", ctx->urlStem, combineBlock->string, secret); } if(!(_getUrl(ctx, jsonString, url))){ return HBLOCK_ERR_NETWORK; } fieldsFound = _jsonObjectGetter(combineFields, combineVals, jsonString); if(fieldsFound > 2){ strcpy(block->realmId, combineVals[0]); block->balance = atol(combineVals[1]); block->usdBalance = atof(combineVals[2]); if(!(block->string[0])){ strcpy(block->string, combineVals[3]); } return HBLOCK_OK; } else { fieldsFound = _jsonObjectGetter(jsonErrorFields, errVals, jsonString); if(fieldsFound == 3){ snprintf(ctx->errmsg, sizeof(ctx->errmsg), "%s: %s", errVals[0], errVals[1]); if(!(strcmp(errVals[0], "not_found"))){ return HBLOCK_ERR_NOTFOUND; } else if(!(strcmp(errVals[0], "invalid_block_format"))){ return HBLOCK_ERR_INVARG; } else { return HBLOCK_ERR_OTHER; } } else { sprintf(ctx->errmsg, "Malformed JSON returned from server: [%s]", jsonString); return HBLOCK_ERR_NETWORK; } } } uint64_t hblock_gen_secret() { uint64_t rv; getentropy(&rv, 8); return rv; } void hblock_close(void *hblock_handle) { HBlockContext *ctx = (HBlockContext *) hblock_handle; curl_easy_cleanup(ctx->curl); free(hblock_handle); } char * hblock_error_message(void *hblock_handle) { HBlockContext *ctx = (HBlockContext *) hblock_handle; return ctx->errmsg; }