Add Economics, Governance, and Mining SDKs for: - Java: Full SDK with CompletableFuture async operations - Kotlin: Coroutine-based SDK with suspend functions - Swift: Modern Swift SDK with async/await - Flutter/Dart: Complete Dart SDK with Future-based API - C: Header files and implementations with opaque handles - C++: Modern C++17 with std::future and PIMPL pattern - C#: Records, async/await Tasks, and IDisposable - Ruby: Struct-based types with Faraday HTTP client Also includes minor Dart lint fixes (const exceptions).
524 lines
18 KiB
C
524 lines
18 KiB
C
/**
|
|
* @file governance.c
|
|
* @brief Synor Governance SDK implementation
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include "synor/governance.h"
|
|
|
|
/* Forward declarations from common.c */
|
|
typedef struct synor_http_client synor_http_client_t;
|
|
synor_http_client_t* synor_http_client_create(
|
|
const char* api_key, const char* endpoint,
|
|
uint32_t timeout_ms, uint32_t retries, bool debug);
|
|
void synor_http_client_destroy(synor_http_client_t* client);
|
|
int synor_http_get(synor_http_client_t* client, const char* path,
|
|
char** response, size_t* response_size);
|
|
int synor_http_post(synor_http_client_t* client, const char* path,
|
|
const char* body, char** response, size_t* response_size);
|
|
char* synor_strdup(const char* s);
|
|
char* synor_url_encode(const char* s);
|
|
|
|
/* Internal governance structure */
|
|
struct synor_governance {
|
|
synor_http_client_t* http;
|
|
bool closed;
|
|
bool debug;
|
|
};
|
|
|
|
static const synor_proposal_status_t FINAL_STATUSES[] = {
|
|
SYNOR_PROPOSAL_PASSED,
|
|
SYNOR_PROPOSAL_REJECTED,
|
|
SYNOR_PROPOSAL_EXECUTED,
|
|
SYNOR_PROPOSAL_CANCELLED
|
|
};
|
|
static const size_t FINAL_STATUSES_COUNT = sizeof(FINAL_STATUSES) / sizeof(FINAL_STATUSES[0]);
|
|
|
|
static bool is_final_status(synor_proposal_status_t status) {
|
|
for (size_t i = 0; i < FINAL_STATUSES_COUNT; i++) {
|
|
if (FINAL_STATUSES[i] == status) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static const char* vote_choice_to_string(synor_vote_choice_t choice) {
|
|
switch (choice) {
|
|
case SYNOR_VOTE_FOR: return "for";
|
|
case SYNOR_VOTE_AGAINST: return "against";
|
|
case SYNOR_VOTE_ABSTAIN: return "abstain";
|
|
default: return "abstain";
|
|
}
|
|
}
|
|
|
|
static const char* dao_type_to_string(synor_dao_type_t type) {
|
|
switch (type) {
|
|
case SYNOR_DAO_TOKEN: return "token";
|
|
case SYNOR_DAO_MULTISIG: return "multisig";
|
|
case SYNOR_DAO_HYBRID: return "hybrid";
|
|
default: return "token";
|
|
}
|
|
}
|
|
|
|
synor_governance_t* synor_governance_create(const synor_governance_config_t* config) {
|
|
if (!config || !config->api_key) {
|
|
return NULL;
|
|
}
|
|
|
|
synor_governance_t* governance = calloc(1, sizeof(synor_governance_t));
|
|
if (!governance) return NULL;
|
|
|
|
const char* endpoint = config->endpoint ?
|
|
config->endpoint : "https://governance.synor.io/v1";
|
|
uint32_t timeout = config->timeout_ms > 0 ? config->timeout_ms : 30000;
|
|
uint32_t retries = config->retries > 0 ? config->retries : 3;
|
|
|
|
governance->http = synor_http_client_create(
|
|
config->api_key, endpoint, timeout, retries, config->debug);
|
|
|
|
if (!governance->http) {
|
|
free(governance);
|
|
return NULL;
|
|
}
|
|
|
|
governance->closed = false;
|
|
governance->debug = config->debug;
|
|
return governance;
|
|
}
|
|
|
|
void synor_governance_destroy(synor_governance_t* governance) {
|
|
if (!governance) return;
|
|
governance->closed = true;
|
|
synor_http_client_destroy(governance->http);
|
|
free(governance);
|
|
}
|
|
|
|
bool synor_governance_is_closed(synor_governance_t* governance) {
|
|
return governance ? governance->closed : true;
|
|
}
|
|
|
|
bool synor_governance_health_check(synor_governance_t* governance) {
|
|
if (!governance || governance->closed) return false;
|
|
|
|
char* response = NULL;
|
|
size_t response_size = 0;
|
|
|
|
if (synor_http_get(governance->http, "/health", &response, &response_size) != 0) {
|
|
return false;
|
|
}
|
|
|
|
bool healthy = response && strstr(response, "\"healthy\"") != NULL;
|
|
free(response);
|
|
return healthy;
|
|
}
|
|
|
|
/* ==================== Proposal Operations ==================== */
|
|
|
|
synor_governance_error_t synor_governance_create_proposal(synor_governance_t* governance,
|
|
const synor_proposal_draft_t* draft, synor_proposal_t* result) {
|
|
if (!governance || governance->closed) return SYNOR_GOVERNANCE_ERROR_CLIENT_CLOSED;
|
|
if (!draft || !result) return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
|
|
char body[4096];
|
|
snprintf(body, sizeof(body),
|
|
"{\"title\":\"%s\",\"description\":\"%s\"}",
|
|
draft->title, draft->description);
|
|
|
|
char* response = NULL;
|
|
size_t response_size = 0;
|
|
|
|
int err = synor_http_post(governance->http, "/proposals", body, &response, &response_size);
|
|
if (err != 0) {
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_HTTP;
|
|
}
|
|
|
|
/* TODO: Parse JSON response */
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_proposal(synor_governance_t* governance,
|
|
const char* proposal_id, synor_proposal_t* result) {
|
|
(void)governance; (void)proposal_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_list_proposals(synor_governance_t* governance,
|
|
const synor_proposal_filter_t* filter, synor_proposal_list_t* result) {
|
|
(void)governance; (void)filter; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_cancel_proposal(synor_governance_t* governance,
|
|
const char* proposal_id, synor_proposal_t* result) {
|
|
(void)governance; (void)proposal_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_execute_proposal(synor_governance_t* governance,
|
|
const char* proposal_id, synor_proposal_t* result) {
|
|
(void)governance; (void)proposal_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_wait_for_proposal(synor_governance_t* governance,
|
|
const char* proposal_id, uint32_t poll_interval_ms, uint32_t max_wait_ms,
|
|
synor_proposal_t* result) {
|
|
(void)governance; (void)proposal_id; (void)poll_interval_ms;
|
|
(void)max_wait_ms; (void)result;
|
|
(void)is_final_status;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
void synor_proposal_free(synor_proposal_t* proposal) {
|
|
if (!proposal) return;
|
|
free(proposal->id);
|
|
free(proposal->title);
|
|
free(proposal->description);
|
|
free(proposal->discussion_url);
|
|
free(proposal->proposer);
|
|
free(proposal->vote_tally.for_votes);
|
|
free(proposal->vote_tally.against_votes);
|
|
free(proposal->vote_tally.abstain_votes);
|
|
free(proposal->vote_tally.quorum);
|
|
free(proposal->vote_tally.quorum_required);
|
|
for (size_t i = 0; i < proposal->actions_count; i++) {
|
|
free(proposal->actions[i].target);
|
|
free(proposal->actions[i].method);
|
|
free(proposal->actions[i].data);
|
|
free(proposal->actions[i].value);
|
|
}
|
|
free(proposal->actions);
|
|
free(proposal->dao_id);
|
|
}
|
|
|
|
void synor_proposal_list_free(synor_proposal_list_t* list) {
|
|
if (!list) return;
|
|
for (size_t i = 0; i < list->count; i++) {
|
|
synor_proposal_free(&list->proposals[i]);
|
|
}
|
|
free(list->proposals);
|
|
list->proposals = NULL;
|
|
list->count = 0;
|
|
}
|
|
|
|
/* ==================== Voting Operations ==================== */
|
|
|
|
synor_governance_error_t synor_governance_vote(synor_governance_t* governance,
|
|
const char* proposal_id, const synor_vote_t* vote, const char* weight,
|
|
synor_vote_receipt_t* result) {
|
|
if (!governance || governance->closed) return SYNOR_GOVERNANCE_ERROR_CLIENT_CLOSED;
|
|
if (!proposal_id || !vote || !result) return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
|
|
char body[512];
|
|
if (vote->reason && weight) {
|
|
snprintf(body, sizeof(body),
|
|
"{\"choice\":\"%s\",\"reason\":\"%s\",\"weight\":\"%s\"}",
|
|
vote_choice_to_string(vote->choice), vote->reason, weight);
|
|
} else if (vote->reason) {
|
|
snprintf(body, sizeof(body),
|
|
"{\"choice\":\"%s\",\"reason\":\"%s\"}",
|
|
vote_choice_to_string(vote->choice), vote->reason);
|
|
} else if (weight) {
|
|
snprintf(body, sizeof(body),
|
|
"{\"choice\":\"%s\",\"weight\":\"%s\"}",
|
|
vote_choice_to_string(vote->choice), weight);
|
|
} else {
|
|
snprintf(body, sizeof(body), "{\"choice\":\"%s\"}", vote_choice_to_string(vote->choice));
|
|
}
|
|
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/proposals/%s/vote", proposal_id);
|
|
|
|
char* response = NULL;
|
|
size_t response_size = 0;
|
|
|
|
int err = synor_http_post(governance->http, path, body, &response, &response_size);
|
|
if (err != 0) {
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_HTTP;
|
|
}
|
|
|
|
/* TODO: Parse JSON response */
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_votes(synor_governance_t* governance,
|
|
const char* proposal_id, synor_vote_receipt_list_t* result) {
|
|
(void)governance; (void)proposal_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_my_vote(synor_governance_t* governance,
|
|
const char* proposal_id, synor_vote_receipt_t* result) {
|
|
(void)governance; (void)proposal_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_delegate(synor_governance_t* governance,
|
|
const char* delegatee, const char* amount, synor_delegation_receipt_t* result) {
|
|
if (!governance || governance->closed) return SYNOR_GOVERNANCE_ERROR_CLIENT_CLOSED;
|
|
if (!delegatee || !result) return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
|
|
char body[256];
|
|
if (amount) {
|
|
snprintf(body, sizeof(body), "{\"delegatee\":\"%s\",\"amount\":\"%s\"}", delegatee, amount);
|
|
} else {
|
|
snprintf(body, sizeof(body), "{\"delegatee\":\"%s\"}", delegatee);
|
|
}
|
|
|
|
char* response = NULL;
|
|
size_t response_size = 0;
|
|
|
|
int err = synor_http_post(governance->http, "/voting/delegate", body, &response, &response_size);
|
|
if (err != 0) {
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_HTTP;
|
|
}
|
|
|
|
/* TODO: Parse JSON response */
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_undelegate(synor_governance_t* governance,
|
|
const char* delegatee, synor_delegation_receipt_t* result) {
|
|
(void)governance; (void)delegatee; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_voting_power(synor_governance_t* governance,
|
|
const char* address, synor_voting_power_t* result) {
|
|
(void)governance; (void)address; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_delegations(synor_governance_t* governance,
|
|
const char* address, synor_delegation_receipt_list_t* result) {
|
|
(void)governance; (void)address; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
void synor_vote_receipt_free(synor_vote_receipt_t* receipt) {
|
|
if (!receipt) return;
|
|
free(receipt->id);
|
|
free(receipt->proposal_id);
|
|
free(receipt->voter);
|
|
free(receipt->weight);
|
|
free(receipt->reason);
|
|
free(receipt->tx_hash);
|
|
}
|
|
|
|
void synor_vote_receipt_list_free(synor_vote_receipt_list_t* list) {
|
|
if (!list) return;
|
|
for (size_t i = 0; i < list->count; i++) {
|
|
synor_vote_receipt_free(&list->votes[i]);
|
|
}
|
|
free(list->votes);
|
|
list->votes = NULL;
|
|
list->count = 0;
|
|
}
|
|
|
|
void synor_voting_power_free(synor_voting_power_t* power) {
|
|
if (!power) return;
|
|
free(power->address);
|
|
free(power->delegated_power);
|
|
free(power->own_power);
|
|
free(power->total_power);
|
|
for (size_t i = 0; i < power->delegators_count; i++) {
|
|
free(power->delegators[i]);
|
|
}
|
|
free(power->delegators);
|
|
}
|
|
|
|
void synor_delegation_receipt_free(synor_delegation_receipt_t* receipt) {
|
|
if (!receipt) return;
|
|
free(receipt->id);
|
|
free(receipt->from);
|
|
free(receipt->to);
|
|
free(receipt->amount);
|
|
free(receipt->tx_hash);
|
|
}
|
|
|
|
void synor_delegation_receipt_list_free(synor_delegation_receipt_list_t* list) {
|
|
if (!list) return;
|
|
for (size_t i = 0; i < list->count; i++) {
|
|
synor_delegation_receipt_free(&list->delegations[i]);
|
|
}
|
|
free(list->delegations);
|
|
list->delegations = NULL;
|
|
list->count = 0;
|
|
}
|
|
|
|
/* ==================== DAO Operations ==================== */
|
|
|
|
synor_governance_error_t synor_governance_create_dao(synor_governance_t* governance,
|
|
const synor_dao_config_t* config, synor_dao_t* result) {
|
|
if (!governance || governance->closed) return SYNOR_GOVERNANCE_ERROR_CLIENT_CLOSED;
|
|
if (!config || !result) return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
|
|
char body[2048];
|
|
snprintf(body, sizeof(body),
|
|
"{\"name\":\"%s\",\"description\":\"%s\",\"type\":\"%s\",\"votingPeriodDays\":%d,\"timelockDays\":%d}",
|
|
config->name, config->description, dao_type_to_string(config->type),
|
|
config->voting_period_days, config->timelock_days);
|
|
|
|
char* response = NULL;
|
|
size_t response_size = 0;
|
|
|
|
int err = synor_http_post(governance->http, "/daos", body, &response, &response_size);
|
|
if (err != 0) {
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_HTTP;
|
|
}
|
|
|
|
/* TODO: Parse JSON response */
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_dao(synor_governance_t* governance,
|
|
const char* dao_id, synor_dao_t* result) {
|
|
(void)governance; (void)dao_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_list_daos(synor_governance_t* governance,
|
|
int32_t limit, int32_t offset, synor_dao_list_t* result) {
|
|
(void)governance; (void)limit; (void)offset; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_dao_treasury(synor_governance_t* governance,
|
|
const char* dao_id, synor_dao_treasury_t* result) {
|
|
(void)governance; (void)dao_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_dao_members(synor_governance_t* governance,
|
|
const char* dao_id, char*** members, size_t* count) {
|
|
(void)governance; (void)dao_id; (void)members; (void)count;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
void synor_dao_free(synor_dao_t* dao) {
|
|
if (!dao) return;
|
|
free(dao->id);
|
|
free(dao->name);
|
|
free(dao->description);
|
|
free(dao->token_address);
|
|
free(dao->proposal_threshold);
|
|
free(dao->treasury_value);
|
|
}
|
|
|
|
void synor_dao_list_free(synor_dao_list_t* list) {
|
|
if (!list) return;
|
|
for (size_t i = 0; i < list->count; i++) {
|
|
synor_dao_free(&list->daos[i]);
|
|
}
|
|
free(list->daos);
|
|
list->daos = NULL;
|
|
list->count = 0;
|
|
}
|
|
|
|
void synor_dao_treasury_free(synor_dao_treasury_t* treasury) {
|
|
if (!treasury) return;
|
|
free(treasury->dao_id);
|
|
free(treasury->total_value);
|
|
for (size_t i = 0; i < treasury->tokens_count; i++) {
|
|
free(treasury->tokens[i].address);
|
|
free(treasury->tokens[i].balance);
|
|
free(treasury->tokens[i].name);
|
|
free(treasury->tokens[i].symbol);
|
|
}
|
|
free(treasury->tokens);
|
|
}
|
|
|
|
/* ==================== Vesting Operations ==================== */
|
|
|
|
synor_governance_error_t synor_governance_create_vesting(synor_governance_t* governance,
|
|
const synor_vesting_schedule_t* schedule, synor_vesting_contract_t* result) {
|
|
if (!governance || governance->closed) return SYNOR_GOVERNANCE_ERROR_CLIENT_CLOSED;
|
|
if (!schedule || !result) return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
|
|
char body[1024];
|
|
snprintf(body, sizeof(body),
|
|
"{\"beneficiary\":\"%s\",\"totalAmount\":\"%s\",\"startTime\":%lld,\"cliffDuration\":%lld,\"vestingDuration\":%lld,\"revocable\":%s}",
|
|
schedule->beneficiary, schedule->total_amount,
|
|
(long long)schedule->start_time, (long long)schedule->cliff_duration,
|
|
(long long)schedule->vesting_duration, schedule->revocable ? "true" : "false");
|
|
|
|
char* response = NULL;
|
|
size_t response_size = 0;
|
|
|
|
int err = synor_http_post(governance->http, "/vesting", body, &response, &response_size);
|
|
if (err != 0) {
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_HTTP;
|
|
}
|
|
|
|
/* TODO: Parse JSON response */
|
|
free(response);
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_vesting(synor_governance_t* governance,
|
|
const char* contract_id, synor_vesting_contract_t* result) {
|
|
(void)governance; (void)contract_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_list_vesting(synor_governance_t* governance,
|
|
const char* beneficiary, synor_vesting_contract_list_t* result) {
|
|
(void)governance; (void)beneficiary; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_claim_vested(synor_governance_t* governance,
|
|
const char* contract_id, synor_claim_receipt_t* result) {
|
|
(void)governance; (void)contract_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_revoke_vesting(synor_governance_t* governance,
|
|
const char* contract_id, synor_vesting_contract_t* result) {
|
|
(void)governance; (void)contract_id; (void)result;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
synor_governance_error_t synor_governance_get_releasable(synor_governance_t* governance,
|
|
const char* contract_id, char** amount) {
|
|
(void)governance; (void)contract_id; (void)amount;
|
|
return SYNOR_GOVERNANCE_ERROR_UNKNOWN;
|
|
}
|
|
|
|
void synor_vesting_contract_free(synor_vesting_contract_t* contract) {
|
|
if (!contract) return;
|
|
free(contract->id);
|
|
free(contract->beneficiary);
|
|
free(contract->grantor);
|
|
free(contract->total_amount);
|
|
free(contract->released_amount);
|
|
free(contract->releasable_amount);
|
|
}
|
|
|
|
void synor_vesting_contract_list_free(synor_vesting_contract_list_t* list) {
|
|
if (!list) return;
|
|
for (size_t i = 0; i < list->count; i++) {
|
|
synor_vesting_contract_free(&list->contracts[i]);
|
|
}
|
|
free(list->contracts);
|
|
list->contracts = NULL;
|
|
list->count = 0;
|
|
}
|
|
|
|
void synor_claim_receipt_free(synor_claim_receipt_t* receipt) {
|
|
if (!receipt) return;
|
|
free(receipt->id);
|
|
free(receipt->contract_id);
|
|
free(receipt->amount);
|
|
free(receipt->tx_hash);
|
|
}
|