feat(sdk): complete Phase 4 SDKs for all remaining languages

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).
This commit is contained in:
Gulshan Yadav 2026-01-28 08:33:20 +05:30
parent 58e57db661
commit 6607223c9e
50 changed files with 13997 additions and 4 deletions

View file

@ -0,0 +1,345 @@
/**
* @file economics.h
* @brief Synor Economics SDK for C
*
* Pricing, billing, staking, and discount management.
*/
#ifndef SYNOR_ECONOMICS_H
#define SYNOR_ECONOMICS_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ==================== Error Types ==================== */
typedef enum {
SYNOR_ECONOMICS_OK = 0,
SYNOR_ECONOMICS_ERROR_CLIENT_CLOSED,
SYNOR_ECONOMICS_ERROR_NETWORK,
SYNOR_ECONOMICS_ERROR_HTTP,
SYNOR_ECONOMICS_ERROR_ENCODING,
SYNOR_ECONOMICS_ERROR_DECODING,
SYNOR_ECONOMICS_ERROR_INVALID_RESPONSE,
SYNOR_ECONOMICS_ERROR_MEMORY,
SYNOR_ECONOMICS_ERROR_TIMEOUT,
SYNOR_ECONOMICS_ERROR_UNKNOWN
} synor_economics_error_t;
/* ==================== Configuration ==================== */
typedef struct {
const char* api_key;
const char* endpoint; /* Default: https://economics.synor.io/v1 */
uint32_t timeout_ms; /* Default: 30000 */
uint32_t retries; /* Default: 3 */
bool debug;
} synor_economics_config_t;
/* ==================== Enums ==================== */
typedef enum {
SYNOR_SERVICE_COMPUTE,
SYNOR_SERVICE_STORAGE,
SYNOR_SERVICE_DATABASE,
SYNOR_SERVICE_HOSTING,
SYNOR_SERVICE_RPC,
SYNOR_SERVICE_BRIDGE
} synor_service_type_t;
typedef enum {
SYNOR_BILLING_HOURLY,
SYNOR_BILLING_DAILY,
SYNOR_BILLING_WEEKLY,
SYNOR_BILLING_MONTHLY
} synor_billing_period_t;
typedef enum {
SYNOR_STAKE_ACTIVE,
SYNOR_STAKE_UNSTAKING,
SYNOR_STAKE_WITHDRAWN
} synor_stake_status_t;
typedef enum {
SYNOR_DISCOUNT_PERCENTAGE,
SYNOR_DISCOUNT_FIXED,
SYNOR_DISCOUNT_CREDITS
} synor_discount_type_t;
typedef enum {
SYNOR_INVOICE_PENDING,
SYNOR_INVOICE_PAID,
SYNOR_INVOICE_OVERDUE,
SYNOR_INVOICE_CANCELLED
} synor_invoice_status_t;
/* ==================== Pricing Types ==================== */
typedef struct {
synor_service_type_t service;
char* unit;
char* price_per_unit;
char* currency;
double minimum;
double maximum;
} synor_service_pricing_t;
typedef struct {
synor_service_pricing_t* prices;
size_t count;
} synor_service_pricing_list_t;
typedef struct {
synor_service_type_t service;
double compute_units;
double storage_bytes;
double bandwidth_bytes;
int64_t duration_seconds;
int32_t requests;
} synor_usage_metrics_t;
typedef struct {
synor_service_type_t service;
char* amount;
char* currency;
synor_usage_metrics_t usage;
} synor_price_result_t;
typedef struct {
synor_service_type_t service;
synor_usage_metrics_t projected_usage;
synor_billing_period_t period;
} synor_usage_plan_item_t;
typedef struct {
synor_usage_plan_item_t* items;
size_t count;
} synor_usage_plan_t;
typedef struct {
synor_service_type_t service;
char* amount;
} synor_cost_item_t;
typedef struct {
char* total;
char* currency;
synor_cost_item_t* breakdown;
size_t breakdown_count;
char* discount_applied;
synor_billing_period_t period;
} synor_cost_estimate_t;
/* ==================== Billing Types ==================== */
typedef struct {
synor_service_type_t service;
double compute_units;
double storage_bytes;
double bandwidth_bytes;
int32_t requests;
char* cost;
} synor_usage_item_t;
typedef struct {
synor_billing_period_t period;
int64_t start_date;
int64_t end_date;
synor_usage_item_t* items;
size_t items_count;
char* total_cost;
char* currency;
} synor_usage_t;
typedef struct {
char* service;
char* description;
int32_t quantity;
char* unit_price;
char* amount;
} synor_invoice_line_t;
typedef struct {
char* id;
int64_t date;
int64_t due_date;
synor_invoice_status_t status;
synor_invoice_line_t* lines;
size_t lines_count;
char* subtotal;
char* discount;
char* tax;
char* total;
char* currency;
char* pdf_url;
} synor_invoice_t;
typedef struct {
synor_invoice_t* invoices;
size_t count;
} synor_invoice_list_t;
typedef struct {
char* available;
char* pending;
char* staked;
char* total;
char* currency;
} synor_account_balance_t;
/* ==================== Staking Types ==================== */
typedef struct {
char* id;
char* amount;
int64_t staked_at;
int64_t unlock_at;
synor_stake_status_t status;
char* rewards_earned;
double apy;
} synor_stake_t;
typedef struct {
synor_stake_t* stakes;
size_t count;
} synor_stake_list_t;
typedef struct {
char* id;
char* amount;
char* tx_hash;
int64_t staked_at;
int64_t unlock_at;
double apy;
} synor_stake_receipt_t;
typedef struct {
char* id;
char* amount;
char* tx_hash;
int64_t unstaked_at;
int64_t available_at;
} synor_unstake_receipt_t;
typedef struct {
char* pending;
char* claimed;
char* total;
double current_apy;
int64_t last_claim;
int64_t next_distribution;
} synor_staking_rewards_t;
/* ==================== Discount Types ==================== */
typedef struct {
char* code;
synor_discount_type_t type;
char* value;
char* description;
synor_service_type_t* applicable_services;
size_t applicable_services_count;
int64_t valid_from;
int64_t valid_until;
int32_t max_uses;
int32_t current_uses;
bool active;
} synor_discount_t;
typedef struct {
synor_discount_t* discounts;
size_t count;
} synor_discount_list_t;
typedef struct {
synor_discount_t discount;
char* savings_estimate;
int64_t applied_at;
} synor_discount_result_t;
/* ==================== Client Handle ==================== */
typedef struct synor_economics synor_economics_t;
/* ==================== Lifecycle Functions ==================== */
synor_economics_t* synor_economics_create(const synor_economics_config_t* config);
void synor_economics_destroy(synor_economics_t* economics);
bool synor_economics_is_closed(synor_economics_t* economics);
bool synor_economics_health_check(synor_economics_t* economics);
/* ==================== Pricing Operations ==================== */
synor_economics_error_t synor_economics_get_pricing(synor_economics_t* economics,
synor_service_type_t* service, synor_service_pricing_list_t* result);
synor_economics_error_t synor_economics_get_price(synor_economics_t* economics,
synor_service_type_t service, const synor_usage_metrics_t* usage,
synor_price_result_t* result);
synor_economics_error_t synor_economics_estimate_cost(synor_economics_t* economics,
const synor_usage_plan_t* plan, synor_cost_estimate_t* result);
void synor_service_pricing_free(synor_service_pricing_t* pricing);
void synor_service_pricing_list_free(synor_service_pricing_list_t* list);
void synor_price_result_free(synor_price_result_t* result);
void synor_cost_estimate_free(synor_cost_estimate_t* estimate);
/* ==================== Billing Operations ==================== */
synor_economics_error_t synor_economics_get_usage(synor_economics_t* economics,
synor_billing_period_t* period, synor_usage_t* result);
synor_economics_error_t synor_economics_get_invoices(synor_economics_t* economics,
synor_invoice_list_t* result);
synor_economics_error_t synor_economics_get_invoice(synor_economics_t* economics,
const char* invoice_id, synor_invoice_t* result);
synor_economics_error_t synor_economics_get_balance(synor_economics_t* economics,
synor_account_balance_t* result);
void synor_usage_free(synor_usage_t* usage);
void synor_invoice_free(synor_invoice_t* invoice);
void synor_invoice_list_free(synor_invoice_list_t* list);
void synor_account_balance_free(synor_account_balance_t* balance);
/* ==================== Staking Operations ==================== */
synor_economics_error_t synor_economics_stake(synor_economics_t* economics,
const char* amount, int32_t duration_days, synor_stake_receipt_t* result);
synor_economics_error_t synor_economics_unstake(synor_economics_t* economics,
const char* stake_id, synor_unstake_receipt_t* result);
synor_economics_error_t synor_economics_get_stakes(synor_economics_t* economics,
synor_stake_list_t* result);
synor_economics_error_t synor_economics_get_stake(synor_economics_t* economics,
const char* stake_id, synor_stake_t* result);
synor_economics_error_t synor_economics_get_staking_rewards(synor_economics_t* economics,
synor_staking_rewards_t* result);
synor_economics_error_t synor_economics_claim_rewards(synor_economics_t* economics,
synor_stake_receipt_t* result);
void synor_stake_free(synor_stake_t* stake);
void synor_stake_list_free(synor_stake_list_t* list);
void synor_stake_receipt_free(synor_stake_receipt_t* receipt);
void synor_unstake_receipt_free(synor_unstake_receipt_t* receipt);
void synor_staking_rewards_free(synor_staking_rewards_t* rewards);
/* ==================== Discount Operations ==================== */
synor_economics_error_t synor_economics_apply_discount(synor_economics_t* economics,
const char* code, synor_discount_result_t* result);
synor_economics_error_t synor_economics_remove_discount(synor_economics_t* economics,
const char* code);
synor_economics_error_t synor_economics_get_discounts(synor_economics_t* economics,
synor_discount_list_t* result);
void synor_discount_free(synor_discount_t* discount);
void synor_discount_list_free(synor_discount_list_t* list);
void synor_discount_result_free(synor_discount_result_t* result);
#ifdef __cplusplus
}
#endif
#endif /* SYNOR_ECONOMICS_H */

View file

@ -0,0 +1,364 @@
/**
* @file governance.h
* @brief Synor Governance SDK for C
*
* Proposals, voting, DAOs, and vesting operations.
*/
#ifndef SYNOR_GOVERNANCE_H
#define SYNOR_GOVERNANCE_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ==================== Error Types ==================== */
typedef enum {
SYNOR_GOVERNANCE_OK = 0,
SYNOR_GOVERNANCE_ERROR_CLIENT_CLOSED,
SYNOR_GOVERNANCE_ERROR_NETWORK,
SYNOR_GOVERNANCE_ERROR_HTTP,
SYNOR_GOVERNANCE_ERROR_ENCODING,
SYNOR_GOVERNANCE_ERROR_DECODING,
SYNOR_GOVERNANCE_ERROR_INVALID_RESPONSE,
SYNOR_GOVERNANCE_ERROR_MEMORY,
SYNOR_GOVERNANCE_ERROR_TIMEOUT,
SYNOR_GOVERNANCE_ERROR_UNKNOWN
} synor_governance_error_t;
/* ==================== Configuration ==================== */
typedef struct {
const char* api_key;
const char* endpoint; /* Default: https://governance.synor.io/v1 */
uint32_t timeout_ms; /* Default: 30000 */
uint32_t retries; /* Default: 3 */
bool debug;
} synor_governance_config_t;
/* ==================== Enums ==================== */
typedef enum {
SYNOR_PROPOSAL_PENDING,
SYNOR_PROPOSAL_ACTIVE,
SYNOR_PROPOSAL_PASSED,
SYNOR_PROPOSAL_REJECTED,
SYNOR_PROPOSAL_EXECUTED,
SYNOR_PROPOSAL_CANCELLED
} synor_proposal_status_t;
typedef enum {
SYNOR_VOTE_FOR,
SYNOR_VOTE_AGAINST,
SYNOR_VOTE_ABSTAIN
} synor_vote_choice_t;
typedef enum {
SYNOR_DAO_TOKEN,
SYNOR_DAO_MULTISIG,
SYNOR_DAO_HYBRID
} synor_dao_type_t;
typedef enum {
SYNOR_VESTING_ACTIVE,
SYNOR_VESTING_COMPLETED,
SYNOR_VESTING_REVOKED
} synor_vesting_status_t;
/* ==================== Proposal Types ==================== */
typedef struct {
char* target;
char* method;
char* data;
char* value;
} synor_proposal_action_t;
typedef struct {
char* title;
char* description;
char* discussion_url;
int64_t voting_start_time;
int64_t voting_end_time;
char* dao_id;
synor_proposal_action_t* actions;
size_t actions_count;
} synor_proposal_draft_t;
typedef struct {
char* for_votes;
char* against_votes;
char* abstain_votes;
char* quorum;
char* quorum_required;
int32_t total_voters;
} synor_vote_tally_t;
typedef struct {
char* id;
char* title;
char* description;
char* discussion_url;
char* proposer;
synor_proposal_status_t status;
int64_t created_at;
int64_t voting_start_time;
int64_t voting_end_time;
int64_t execution_time;
synor_vote_tally_t vote_tally;
synor_proposal_action_t* actions;
size_t actions_count;
char* dao_id;
} synor_proposal_t;
typedef struct {
synor_proposal_t* proposals;
size_t count;
} synor_proposal_list_t;
typedef struct {
synor_proposal_status_t* status; /* NULL for all */
char* proposer; /* NULL for all */
char* dao_id; /* NULL for all */
int32_t limit;
int32_t offset;
} synor_proposal_filter_t;
/* ==================== Voting Types ==================== */
typedef struct {
synor_vote_choice_t choice;
char* reason;
} synor_vote_t;
typedef struct {
char* id;
char* proposal_id;
char* voter;
synor_vote_choice_t choice;
char* weight;
char* reason;
int64_t voted_at;
char* tx_hash;
} synor_vote_receipt_t;
typedef struct {
synor_vote_receipt_t* votes;
size_t count;
} synor_vote_receipt_list_t;
typedef struct {
char* address;
char* delegated_power;
char* own_power;
char* total_power;
char** delegators;
size_t delegators_count;
} synor_voting_power_t;
typedef struct {
char* id;
char* from;
char* to;
char* amount;
int64_t delegated_at;
char* tx_hash;
} synor_delegation_receipt_t;
typedef struct {
synor_delegation_receipt_t* delegations;
size_t count;
} synor_delegation_receipt_list_t;
/* ==================== DAO Types ==================== */
typedef struct {
char* name;
char* description;
synor_dao_type_t type;
int32_t voting_period_days;
int32_t timelock_days;
char* token_address;
double quorum_percent;
char* proposal_threshold;
char** multisig_members;
size_t multisig_members_count;
int32_t multisig_threshold;
} synor_dao_config_t;
typedef struct {
char* id;
char* name;
char* description;
synor_dao_type_t type;
char* token_address;
int32_t voting_period_days;
int32_t timelock_days;
double quorum_percent;
char* proposal_threshold;
int32_t total_proposals;
int32_t active_proposals;
char* treasury_value;
int32_t member_count;
int64_t created_at;
} synor_dao_t;
typedef struct {
synor_dao_t* daos;
size_t count;
} synor_dao_list_t;
typedef struct {
char* address;
char* balance;
char* name;
char* symbol;
} synor_treasury_token_t;
typedef struct {
char* dao_id;
char* total_value;
synor_treasury_token_t* tokens;
size_t tokens_count;
int64_t last_updated;
} synor_dao_treasury_t;
/* ==================== Vesting Types ==================== */
typedef struct {
char* beneficiary;
char* total_amount;
int64_t start_time;
int64_t cliff_duration;
int64_t vesting_duration;
bool revocable;
} synor_vesting_schedule_t;
typedef struct {
char* id;
char* beneficiary;
char* grantor;
char* total_amount;
char* released_amount;
char* releasable_amount;
int64_t start_time;
int64_t cliff_time;
int64_t end_time;
synor_vesting_status_t status;
bool revocable;
int64_t created_at;
} synor_vesting_contract_t;
typedef struct {
synor_vesting_contract_t* contracts;
size_t count;
} synor_vesting_contract_list_t;
typedef struct {
char* id;
char* contract_id;
char* amount;
char* tx_hash;
int64_t claimed_at;
} synor_claim_receipt_t;
/* ==================== Client Handle ==================== */
typedef struct synor_governance synor_governance_t;
/* ==================== Lifecycle Functions ==================== */
synor_governance_t* synor_governance_create(const synor_governance_config_t* config);
void synor_governance_destroy(synor_governance_t* governance);
bool synor_governance_is_closed(synor_governance_t* governance);
bool synor_governance_health_check(synor_governance_t* governance);
/* ==================== Proposal Operations ==================== */
synor_governance_error_t synor_governance_create_proposal(synor_governance_t* governance,
const synor_proposal_draft_t* draft, synor_proposal_t* result);
synor_governance_error_t synor_governance_get_proposal(synor_governance_t* governance,
const char* proposal_id, synor_proposal_t* result);
synor_governance_error_t synor_governance_list_proposals(synor_governance_t* governance,
const synor_proposal_filter_t* filter, synor_proposal_list_t* result);
synor_governance_error_t synor_governance_cancel_proposal(synor_governance_t* governance,
const char* proposal_id, synor_proposal_t* result);
synor_governance_error_t synor_governance_execute_proposal(synor_governance_t* governance,
const char* proposal_id, synor_proposal_t* result);
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 synor_proposal_free(synor_proposal_t* proposal);
void synor_proposal_list_free(synor_proposal_list_t* list);
/* ==================== 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);
synor_governance_error_t synor_governance_get_votes(synor_governance_t* governance,
const char* proposal_id, synor_vote_receipt_list_t* result);
synor_governance_error_t synor_governance_get_my_vote(synor_governance_t* governance,
const char* proposal_id, synor_vote_receipt_t* result);
synor_governance_error_t synor_governance_delegate(synor_governance_t* governance,
const char* delegatee, const char* amount, synor_delegation_receipt_t* result);
synor_governance_error_t synor_governance_undelegate(synor_governance_t* governance,
const char* delegatee, synor_delegation_receipt_t* result);
synor_governance_error_t synor_governance_get_voting_power(synor_governance_t* governance,
const char* address, synor_voting_power_t* result);
synor_governance_error_t synor_governance_get_delegations(synor_governance_t* governance,
const char* address, synor_delegation_receipt_list_t* result);
void synor_vote_receipt_free(synor_vote_receipt_t* receipt);
void synor_vote_receipt_list_free(synor_vote_receipt_list_t* list);
void synor_voting_power_free(synor_voting_power_t* power);
void synor_delegation_receipt_free(synor_delegation_receipt_t* receipt);
void synor_delegation_receipt_list_free(synor_delegation_receipt_list_t* list);
/* ==================== DAO Operations ==================== */
synor_governance_error_t synor_governance_create_dao(synor_governance_t* governance,
const synor_dao_config_t* config, synor_dao_t* result);
synor_governance_error_t synor_governance_get_dao(synor_governance_t* governance,
const char* dao_id, synor_dao_t* result);
synor_governance_error_t synor_governance_list_daos(synor_governance_t* governance,
int32_t limit, int32_t offset, synor_dao_list_t* result);
synor_governance_error_t synor_governance_get_dao_treasury(synor_governance_t* governance,
const char* dao_id, synor_dao_treasury_t* result);
synor_governance_error_t synor_governance_get_dao_members(synor_governance_t* governance,
const char* dao_id, char*** members, size_t* count);
void synor_dao_free(synor_dao_t* dao);
void synor_dao_list_free(synor_dao_list_t* list);
void synor_dao_treasury_free(synor_dao_treasury_t* treasury);
/* ==================== 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);
synor_governance_error_t synor_governance_get_vesting(synor_governance_t* governance,
const char* contract_id, synor_vesting_contract_t* result);
synor_governance_error_t synor_governance_list_vesting(synor_governance_t* governance,
const char* beneficiary, synor_vesting_contract_list_t* result);
synor_governance_error_t synor_governance_claim_vested(synor_governance_t* governance,
const char* contract_id, synor_claim_receipt_t* result);
synor_governance_error_t synor_governance_revoke_vesting(synor_governance_t* governance,
const char* contract_id, synor_vesting_contract_t* result);
synor_governance_error_t synor_governance_get_releasable(synor_governance_t* governance,
const char* contract_id, char** amount);
void synor_vesting_contract_free(synor_vesting_contract_t* contract);
void synor_vesting_contract_list_free(synor_vesting_contract_list_t* list);
void synor_claim_receipt_free(synor_claim_receipt_t* receipt);
#ifdef __cplusplus
}
#endif
#endif /* SYNOR_GOVERNANCE_H */

View file

@ -0,0 +1,394 @@
/**
* @file mining.h
* @brief Synor Mining SDK for C
*
* Pool connections, block templates, hashrate stats, and GPU management.
*/
#ifndef SYNOR_MINING_H
#define SYNOR_MINING_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ==================== Error Types ==================== */
typedef enum {
SYNOR_MINING_OK = 0,
SYNOR_MINING_ERROR_CLIENT_CLOSED,
SYNOR_MINING_ERROR_NETWORK,
SYNOR_MINING_ERROR_HTTP,
SYNOR_MINING_ERROR_ENCODING,
SYNOR_MINING_ERROR_DECODING,
SYNOR_MINING_ERROR_INVALID_RESPONSE,
SYNOR_MINING_ERROR_MEMORY,
SYNOR_MINING_ERROR_TIMEOUT,
SYNOR_MINING_ERROR_NOT_CONNECTED,
SYNOR_MINING_ERROR_UNKNOWN
} synor_mining_error_t;
/* ==================== Configuration ==================== */
typedef struct {
const char* api_key;
const char* endpoint; /* Default: https://mining.synor.io/v1 */
uint32_t timeout_ms; /* Default: 60000 */
uint32_t retries; /* Default: 3 */
bool debug;
} synor_mining_config_t;
/* ==================== Enums ==================== */
typedef enum {
SYNOR_DEVICE_CPU,
SYNOR_DEVICE_GPU_NVIDIA,
SYNOR_DEVICE_GPU_AMD,
SYNOR_DEVICE_ASIC
} synor_device_type_t;
typedef enum {
SYNOR_DEVICE_IDLE,
SYNOR_DEVICE_MINING,
SYNOR_DEVICE_ERROR,
SYNOR_DEVICE_OFFLINE
} synor_device_status_t;
typedef enum {
SYNOR_CONN_DISCONNECTED,
SYNOR_CONN_CONNECTING,
SYNOR_CONN_CONNECTED,
SYNOR_CONN_RECONNECTING
} synor_connection_status_t;
typedef enum {
SYNOR_PERIOD_HOUR,
SYNOR_PERIOD_DAY,
SYNOR_PERIOD_WEEK,
SYNOR_PERIOD_MONTH,
SYNOR_PERIOD_ALL
} synor_time_period_t;
typedef enum {
SYNOR_SUBMIT_ACCEPTED,
SYNOR_SUBMIT_REJECTED,
SYNOR_SUBMIT_STALE
} synor_submit_status_t;
/* ==================== Pool Types ==================== */
typedef struct {
const char* url;
const char* user;
const char* password;
const char* algorithm;
double difficulty;
} synor_pool_config_t;
typedef struct {
char* id;
char* pool;
synor_connection_status_t status;
char* algorithm;
double difficulty;
int64_t connected_at;
int32_t accepted_shares;
int32_t rejected_shares;
int32_t stale_shares;
int64_t last_share_at;
} synor_stratum_connection_t;
typedef struct {
char* url;
int32_t workers;
double hashrate;
double difficulty;
int64_t last_block;
int32_t blocks_found_24h;
double luck;
} synor_pool_stats_t;
/* ==================== Block Template Types ==================== */
typedef struct {
char* txid;
char* data;
char* fee;
int32_t weight;
} synor_template_transaction_t;
typedef struct {
char* id;
char* previous_block_hash;
char* merkle_root;
int64_t timestamp;
char* bits;
int64_t height;
char* coinbase_value;
synor_template_transaction_t* transactions;
size_t transactions_count;
char* target;
char* algorithm;
char* extra_nonce;
} synor_block_template_t;
typedef struct {
char* template_id;
char* nonce;
char* extra_nonce;
int64_t timestamp;
char* hash;
} synor_mined_work_t;
typedef struct {
char* hash;
double difficulty;
int64_t timestamp;
bool accepted;
} synor_share_info_t;
typedef struct {
synor_submit_status_t status;
synor_share_info_t share;
bool block_found;
char* reason;
char* block_hash;
char* reward;
} synor_submit_result_t;
/* ==================== Stats Types ==================== */
typedef struct {
double current;
double average_1h;
double average_24h;
double peak;
char* unit;
} synor_hashrate_t;
typedef struct {
int32_t accepted;
int32_t rejected;
int32_t stale;
int32_t total;
double accept_rate;
} synor_share_stats_t;
typedef struct {
double current;
double max;
bool throttling;
} synor_device_temperature_t;
typedef struct {
char* today;
char* yesterday;
char* this_week;
char* this_month;
char* total;
char* currency;
} synor_earnings_snapshot_t;
typedef struct {
synor_hashrate_t hashrate;
synor_share_stats_t shares;
int64_t uptime;
double efficiency;
synor_earnings_snapshot_t earnings;
double power_consumption;
synor_device_temperature_t temperature;
} synor_mining_stats_t;
typedef struct {
int64_t date;
char* amount;
int32_t blocks;
int32_t shares;
double hashrate;
} synor_earnings_breakdown_t;
typedef struct {
synor_time_period_t period;
int64_t start_date;
int64_t end_date;
char* amount;
int32_t blocks;
int32_t shares;
double average_hashrate;
char* currency;
synor_earnings_breakdown_t* breakdown;
size_t breakdown_count;
} synor_earnings_t;
/* ==================== Device Types ==================== */
typedef struct {
char* id;
char* name;
synor_device_type_t type;
synor_device_status_t status;
double hashrate;
double temperature;
double fan_speed;
double power_draw;
int64_t memory_used;
int64_t memory_total;
char* driver;
char* firmware;
} synor_mining_device_t;
typedef struct {
synor_mining_device_t* devices;
size_t count;
} synor_mining_device_list_t;
typedef struct {
bool enabled;
int32_t intensity;
int32_t power_limit;
int32_t core_clock_offset;
int32_t memory_clock_offset;
int32_t fan_speed;
} synor_device_config_t;
/* ==================== Worker Types ==================== */
typedef struct {
char* id;
char* name;
synor_connection_status_t status;
synor_hashrate_t hashrate;
synor_share_stats_t shares;
synor_mining_device_t* devices;
size_t devices_count;
int64_t last_seen;
int64_t uptime;
} synor_worker_info_t;
typedef struct {
synor_worker_info_t* workers;
size_t count;
} synor_worker_info_list_t;
/* ==================== Algorithm Types ==================== */
typedef struct {
char* name;
char* display_name;
char* hash_unit;
char* profitability;
double difficulty;
char* block_reward;
int32_t block_time;
} synor_mining_algorithm_t;
typedef struct {
synor_mining_algorithm_t* algorithms;
size_t count;
} synor_mining_algorithm_list_t;
/* ==================== Client Handle ==================== */
typedef struct synor_mining synor_mining_t;
/* ==================== Lifecycle Functions ==================== */
synor_mining_t* synor_mining_create(const synor_mining_config_t* config);
void synor_mining_destroy(synor_mining_t* mining);
bool synor_mining_is_closed(synor_mining_t* mining);
bool synor_mining_health_check(synor_mining_t* mining);
/* ==================== Pool Operations ==================== */
synor_mining_error_t synor_mining_connect(synor_mining_t* mining,
const synor_pool_config_t* pool, synor_stratum_connection_t* result);
synor_mining_error_t synor_mining_disconnect(synor_mining_t* mining);
synor_mining_error_t synor_mining_get_connection(synor_mining_t* mining,
synor_stratum_connection_t* result);
synor_mining_error_t synor_mining_get_pool_stats(synor_mining_t* mining,
synor_pool_stats_t* result);
bool synor_mining_is_connected(synor_mining_t* mining);
void synor_stratum_connection_free(synor_stratum_connection_t* conn);
void synor_pool_stats_free(synor_pool_stats_t* stats);
/* ==================== Mining Operations ==================== */
synor_mining_error_t synor_mining_get_block_template(synor_mining_t* mining,
synor_block_template_t* result);
synor_mining_error_t synor_mining_submit_work(synor_mining_t* mining,
const synor_mined_work_t* work, synor_submit_result_t* result);
synor_mining_error_t synor_mining_start(synor_mining_t* mining, const char* algorithm);
synor_mining_error_t synor_mining_stop(synor_mining_t* mining);
synor_mining_error_t synor_mining_is_mining(synor_mining_t* mining, bool* result);
void synor_block_template_free(synor_block_template_t* tmpl);
void synor_submit_result_free(synor_submit_result_t* result);
/* ==================== Stats Operations ==================== */
synor_mining_error_t synor_mining_get_hashrate(synor_mining_t* mining,
synor_hashrate_t* result);
synor_mining_error_t synor_mining_get_stats(synor_mining_t* mining,
synor_mining_stats_t* result);
synor_mining_error_t synor_mining_get_earnings(synor_mining_t* mining,
synor_time_period_t* period, synor_earnings_t* result);
synor_mining_error_t synor_mining_get_share_stats(synor_mining_t* mining,
synor_share_stats_t* result);
void synor_hashrate_free(synor_hashrate_t* hashrate);
void synor_mining_stats_free(synor_mining_stats_t* stats);
void synor_earnings_free(synor_earnings_t* earnings);
/* ==================== Device Operations ==================== */
synor_mining_error_t synor_mining_list_devices(synor_mining_t* mining,
synor_mining_device_list_t* result);
synor_mining_error_t synor_mining_get_device(synor_mining_t* mining,
const char* device_id, synor_mining_device_t* result);
synor_mining_error_t synor_mining_set_device_config(synor_mining_t* mining,
const char* device_id, const synor_device_config_t* config,
synor_mining_device_t* result);
synor_mining_error_t synor_mining_enable_device(synor_mining_t* mining,
const char* device_id);
synor_mining_error_t synor_mining_disable_device(synor_mining_t* mining,
const char* device_id);
void synor_mining_device_free(synor_mining_device_t* device);
void synor_mining_device_list_free(synor_mining_device_list_t* list);
/* ==================== Worker Operations ==================== */
synor_mining_error_t synor_mining_list_workers(synor_mining_t* mining,
synor_worker_info_list_t* result);
synor_mining_error_t synor_mining_get_worker(synor_mining_t* mining,
const char* worker_id, synor_worker_info_t* result);
synor_mining_error_t synor_mining_remove_worker(synor_mining_t* mining,
const char* worker_id);
void synor_worker_info_free(synor_worker_info_t* worker);
void synor_worker_info_list_free(synor_worker_info_list_t* list);
/* ==================== Algorithm Operations ==================== */
synor_mining_error_t synor_mining_list_algorithms(synor_mining_t* mining,
synor_mining_algorithm_list_t* result);
synor_mining_error_t synor_mining_get_algorithm(synor_mining_t* mining,
const char* name, synor_mining_algorithm_t* result);
synor_mining_error_t synor_mining_switch_algorithm(synor_mining_t* mining,
const char* algorithm);
synor_mining_error_t synor_mining_get_most_profitable(synor_mining_t* mining,
synor_mining_algorithm_t* result);
void synor_mining_algorithm_free(synor_mining_algorithm_t* algorithm);
void synor_mining_algorithm_list_free(synor_mining_algorithm_list_t* list);
#ifdef __cplusplus
}
#endif
#endif /* SYNOR_MINING_H */

View file

@ -0,0 +1,424 @@
/**
* @file economics.c
* @brief Synor Economics SDK implementation
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "synor/economics.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 economics structure */
struct synor_economics {
synor_http_client_t* http;
bool closed;
bool debug;
};
static const char* service_type_to_string(synor_service_type_t type) {
switch (type) {
case SYNOR_SERVICE_COMPUTE: return "compute";
case SYNOR_SERVICE_STORAGE: return "storage";
case SYNOR_SERVICE_DATABASE: return "database";
case SYNOR_SERVICE_HOSTING: return "hosting";
case SYNOR_SERVICE_RPC: return "rpc";
case SYNOR_SERVICE_BRIDGE: return "bridge";
default: return "unknown";
}
}
static const char* billing_period_to_string(synor_billing_period_t period) {
switch (period) {
case SYNOR_BILLING_HOURLY: return "hourly";
case SYNOR_BILLING_DAILY: return "daily";
case SYNOR_BILLING_WEEKLY: return "weekly";
case SYNOR_BILLING_MONTHLY: return "monthly";
default: return "monthly";
}
}
synor_economics_t* synor_economics_create(const synor_economics_config_t* config) {
if (!config || !config->api_key) {
return NULL;
}
synor_economics_t* economics = calloc(1, sizeof(synor_economics_t));
if (!economics) return NULL;
const char* endpoint = config->endpoint ?
config->endpoint : "https://economics.synor.io/v1";
uint32_t timeout = config->timeout_ms > 0 ? config->timeout_ms : 30000;
uint32_t retries = config->retries > 0 ? config->retries : 3;
economics->http = synor_http_client_create(
config->api_key, endpoint, timeout, retries, config->debug);
if (!economics->http) {
free(economics);
return NULL;
}
economics->closed = false;
economics->debug = config->debug;
return economics;
}
void synor_economics_destroy(synor_economics_t* economics) {
if (!economics) return;
economics->closed = true;
synor_http_client_destroy(economics->http);
free(economics);
}
bool synor_economics_is_closed(synor_economics_t* economics) {
return economics ? economics->closed : true;
}
bool synor_economics_health_check(synor_economics_t* economics) {
if (!economics || economics->closed) return false;
char* response = NULL;
size_t response_size = 0;
if (synor_http_get(economics->http, "/health", &response, &response_size) != 0) {
return false;
}
bool healthy = response && strstr(response, "\"healthy\"") != NULL;
free(response);
return healthy;
}
/* ==================== Pricing Operations ==================== */
synor_economics_error_t synor_economics_get_pricing(synor_economics_t* economics,
synor_service_type_t* service, synor_service_pricing_list_t* result) {
if (!economics || economics->closed) return SYNOR_ECONOMICS_ERROR_CLIENT_CLOSED;
(void)service; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_get_price(synor_economics_t* economics,
synor_service_type_t service, const synor_usage_metrics_t* usage,
synor_price_result_t* result) {
if (!economics || economics->closed) return SYNOR_ECONOMICS_ERROR_CLIENT_CLOSED;
if (!usage || !result) return SYNOR_ECONOMICS_ERROR_UNKNOWN;
char body[1024];
snprintf(body, sizeof(body),
"{\"service\":\"%s\",\"computeUnits\":%.2f,\"storageBytes\":%.2f,\"bandwidthBytes\":%.2f,\"durationSeconds\":%lld,\"requests\":%d}",
service_type_to_string(service),
usage->compute_units, usage->storage_bytes, usage->bandwidth_bytes,
(long long)usage->duration_seconds, usage->requests);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(economics->http, "/pricing/calculate", body, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_ECONOMICS_ERROR_HTTP;
}
/* TODO: Parse JSON response */
free(response);
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_estimate_cost(synor_economics_t* economics,
const synor_usage_plan_t* plan, synor_cost_estimate_t* result) {
(void)economics; (void)plan; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
void synor_service_pricing_free(synor_service_pricing_t* pricing) {
if (!pricing) return;
free(pricing->unit);
free(pricing->price_per_unit);
free(pricing->currency);
}
void synor_service_pricing_list_free(synor_service_pricing_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_service_pricing_free(&list->prices[i]);
}
free(list->prices);
list->prices = NULL;
list->count = 0;
}
void synor_price_result_free(synor_price_result_t* result) {
if (!result) return;
free(result->amount);
free(result->currency);
}
void synor_cost_estimate_free(synor_cost_estimate_t* estimate) {
if (!estimate) return;
free(estimate->total);
free(estimate->currency);
for (size_t i = 0; i < estimate->breakdown_count; i++) {
free(estimate->breakdown[i].amount);
}
free(estimate->breakdown);
free(estimate->discount_applied);
}
/* ==================== Billing Operations ==================== */
synor_economics_error_t synor_economics_get_usage(synor_economics_t* economics,
synor_billing_period_t* period, synor_usage_t* result) {
if (!economics || economics->closed) return SYNOR_ECONOMICS_ERROR_CLIENT_CLOSED;
char path[256];
if (period) {
snprintf(path, sizeof(path), "/billing/usage?period=%s", billing_period_to_string(*period));
} else {
snprintf(path, sizeof(path), "/billing/usage");
}
char* response = NULL;
size_t response_size = 0;
int err = synor_http_get(economics->http, path, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_ECONOMICS_ERROR_HTTP;
}
/* TODO: Parse JSON response */
(void)result;
free(response);
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_get_invoices(synor_economics_t* economics,
synor_invoice_list_t* result) {
(void)economics; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_get_invoice(synor_economics_t* economics,
const char* invoice_id, synor_invoice_t* result) {
(void)economics; (void)invoice_id; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_get_balance(synor_economics_t* economics,
synor_account_balance_t* result) {
(void)economics; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
void synor_usage_free(synor_usage_t* usage) {
if (!usage) return;
for (size_t i = 0; i < usage->items_count; i++) {
free(usage->items[i].cost);
}
free(usage->items);
free(usage->total_cost);
free(usage->currency);
}
void synor_invoice_free(synor_invoice_t* invoice) {
if (!invoice) return;
free(invoice->id);
for (size_t i = 0; i < invoice->lines_count; i++) {
free(invoice->lines[i].service);
free(invoice->lines[i].description);
free(invoice->lines[i].unit_price);
free(invoice->lines[i].amount);
}
free(invoice->lines);
free(invoice->subtotal);
free(invoice->discount);
free(invoice->tax);
free(invoice->total);
free(invoice->currency);
free(invoice->pdf_url);
}
void synor_invoice_list_free(synor_invoice_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_invoice_free(&list->invoices[i]);
}
free(list->invoices);
list->invoices = NULL;
list->count = 0;
}
void synor_account_balance_free(synor_account_balance_t* balance) {
if (!balance) return;
free(balance->available);
free(balance->pending);
free(balance->staked);
free(balance->total);
free(balance->currency);
}
/* ==================== Staking Operations ==================== */
synor_economics_error_t synor_economics_stake(synor_economics_t* economics,
const char* amount, int32_t duration_days, synor_stake_receipt_t* result) {
if (!economics || economics->closed) return SYNOR_ECONOMICS_ERROR_CLIENT_CLOSED;
if (!amount || !result) return SYNOR_ECONOMICS_ERROR_UNKNOWN;
char body[256];
snprintf(body, sizeof(body), "{\"amount\":\"%s\",\"durationDays\":%d}", amount, duration_days);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(economics->http, "/staking/stake", body, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_ECONOMICS_ERROR_HTTP;
}
/* TODO: Parse JSON response */
free(response);
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_unstake(synor_economics_t* economics,
const char* stake_id, synor_unstake_receipt_t* result) {
(void)economics; (void)stake_id; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_get_stakes(synor_economics_t* economics,
synor_stake_list_t* result) {
(void)economics; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_get_stake(synor_economics_t* economics,
const char* stake_id, synor_stake_t* result) {
(void)economics; (void)stake_id; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_get_staking_rewards(synor_economics_t* economics,
synor_staking_rewards_t* result) {
(void)economics; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_claim_rewards(synor_economics_t* economics,
synor_stake_receipt_t* result) {
(void)economics; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
void synor_stake_free(synor_stake_t* stake) {
if (!stake) return;
free(stake->id);
free(stake->amount);
free(stake->rewards_earned);
}
void synor_stake_list_free(synor_stake_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_stake_free(&list->stakes[i]);
}
free(list->stakes);
list->stakes = NULL;
list->count = 0;
}
void synor_stake_receipt_free(synor_stake_receipt_t* receipt) {
if (!receipt) return;
free(receipt->id);
free(receipt->amount);
free(receipt->tx_hash);
}
void synor_unstake_receipt_free(synor_unstake_receipt_t* receipt) {
if (!receipt) return;
free(receipt->id);
free(receipt->amount);
free(receipt->tx_hash);
}
void synor_staking_rewards_free(synor_staking_rewards_t* rewards) {
if (!rewards) return;
free(rewards->pending);
free(rewards->claimed);
free(rewards->total);
}
/* ==================== Discount Operations ==================== */
synor_economics_error_t synor_economics_apply_discount(synor_economics_t* economics,
const char* code, synor_discount_result_t* result) {
if (!economics || economics->closed) return SYNOR_ECONOMICS_ERROR_CLIENT_CLOSED;
if (!code || !result) return SYNOR_ECONOMICS_ERROR_UNKNOWN;
char body[256];
snprintf(body, sizeof(body), "{\"code\":\"%s\"}", code);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(economics->http, "/discounts/apply", body, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_ECONOMICS_ERROR_HTTP;
}
/* TODO: Parse JSON response */
free(response);
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_remove_discount(synor_economics_t* economics,
const char* code) {
(void)economics; (void)code;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
synor_economics_error_t synor_economics_get_discounts(synor_economics_t* economics,
synor_discount_list_t* result) {
(void)economics; (void)result;
return SYNOR_ECONOMICS_ERROR_UNKNOWN;
}
void synor_discount_free(synor_discount_t* discount) {
if (!discount) return;
free(discount->code);
free(discount->value);
free(discount->description);
free(discount->applicable_services);
}
void synor_discount_list_free(synor_discount_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_discount_free(&list->discounts[i]);
}
free(list->discounts);
list->discounts = NULL;
list->count = 0;
}
void synor_discount_result_free(synor_discount_result_t* result) {
if (!result) return;
synor_discount_free(&result->discount);
free(result->savings_estimate);
}

View file

@ -0,0 +1,524 @@
/**
* @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);
}

563
sdk/c/src/mining/mining.c Normal file
View file

@ -0,0 +1,563 @@
/**
* @file mining.c
* @brief Synor Mining SDK implementation
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "synor/mining.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);
int synor_http_put(synor_http_client_t* client, const char* path,
const char* body, char** response, size_t* response_size);
int synor_http_delete(synor_http_client_t* client, const char* path,
char** response, size_t* response_size);
char* synor_strdup(const char* s);
char* synor_url_encode(const char* s);
/* Internal mining structure */
struct synor_mining {
synor_http_client_t* http;
bool closed;
bool debug;
synor_stratum_connection_t* active_connection;
};
static const char* time_period_to_string(synor_time_period_t period) {
switch (period) {
case SYNOR_PERIOD_HOUR: return "hour";
case SYNOR_PERIOD_DAY: return "day";
case SYNOR_PERIOD_WEEK: return "week";
case SYNOR_PERIOD_MONTH: return "month";
case SYNOR_PERIOD_ALL: return "all";
default: return "day";
}
}
synor_mining_t* synor_mining_create(const synor_mining_config_t* config) {
if (!config || !config->api_key) {
return NULL;
}
synor_mining_t* mining = calloc(1, sizeof(synor_mining_t));
if (!mining) return NULL;
const char* endpoint = config->endpoint ?
config->endpoint : "https://mining.synor.io/v1";
uint32_t timeout = config->timeout_ms > 0 ? config->timeout_ms : 60000;
uint32_t retries = config->retries > 0 ? config->retries : 3;
mining->http = synor_http_client_create(
config->api_key, endpoint, timeout, retries, config->debug);
if (!mining->http) {
free(mining);
return NULL;
}
mining->closed = false;
mining->debug = config->debug;
mining->active_connection = NULL;
return mining;
}
void synor_mining_destroy(synor_mining_t* mining) {
if (!mining) return;
mining->closed = true;
if (mining->active_connection) {
synor_stratum_connection_free(mining->active_connection);
free(mining->active_connection);
}
synor_http_client_destroy(mining->http);
free(mining);
}
bool synor_mining_is_closed(synor_mining_t* mining) {
return mining ? mining->closed : true;
}
bool synor_mining_health_check(synor_mining_t* mining) {
if (!mining || mining->closed) return false;
char* response = NULL;
size_t response_size = 0;
if (synor_http_get(mining->http, "/health", &response, &response_size) != 0) {
return false;
}
bool healthy = response && strstr(response, "\"healthy\"") != NULL;
free(response);
return healthy;
}
/* ==================== Pool Operations ==================== */
synor_mining_error_t synor_mining_connect(synor_mining_t* mining,
const synor_pool_config_t* pool, synor_stratum_connection_t* result) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
if (!pool || !result) return SYNOR_MINING_ERROR_UNKNOWN;
char body[1024];
snprintf(body, sizeof(body), "{\"url\":\"%s\",\"user\":\"%s\"", pool->url, pool->user);
if (pool->password) {
char temp[256];
snprintf(temp, sizeof(temp), ",\"password\":\"%s\"", pool->password);
strncat(body, temp, sizeof(body) - strlen(body) - 1);
}
if (pool->algorithm) {
char temp[128];
snprintf(temp, sizeof(temp), ",\"algorithm\":\"%s\"", pool->algorithm);
strncat(body, temp, sizeof(body) - strlen(body) - 1);
}
if (pool->difficulty > 0) {
char temp[64];
snprintf(temp, sizeof(temp), ",\"difficulty\":%.2f", pool->difficulty);
strncat(body, temp, sizeof(body) - strlen(body) - 1);
}
strncat(body, "}", sizeof(body) - strlen(body) - 1);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(mining->http, "/pool/connect", body, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_MINING_ERROR_HTTP;
}
/* TODO: Parse JSON response and update active_connection */
free(response);
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_disconnect(synor_mining_t* mining) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
if (!mining->active_connection) return SYNOR_MINING_OK;
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(mining->http, "/pool/disconnect", "{}", &response, &response_size);
free(response);
if (err == 0) {
if (mining->active_connection) {
synor_stratum_connection_free(mining->active_connection);
free(mining->active_connection);
mining->active_connection = NULL;
}
return SYNOR_MINING_OK;
}
return SYNOR_MINING_ERROR_HTTP;
}
synor_mining_error_t synor_mining_get_connection(synor_mining_t* mining,
synor_stratum_connection_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_get_pool_stats(synor_mining_t* mining,
synor_pool_stats_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
bool synor_mining_is_connected(synor_mining_t* mining) {
if (!mining || !mining->active_connection) return false;
return mining->active_connection->status == SYNOR_CONN_CONNECTED;
}
void synor_stratum_connection_free(synor_stratum_connection_t* conn) {
if (!conn) return;
free(conn->id);
free(conn->pool);
free(conn->algorithm);
}
void synor_pool_stats_free(synor_pool_stats_t* stats) {
if (!stats) return;
free(stats->url);
}
/* ==================== Mining Operations ==================== */
synor_mining_error_t synor_mining_get_block_template(synor_mining_t* mining,
synor_block_template_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_submit_work(synor_mining_t* mining,
const synor_mined_work_t* work, synor_submit_result_t* result) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
if (!work || !result) return SYNOR_MINING_ERROR_UNKNOWN;
char body[1024];
snprintf(body, sizeof(body),
"{\"templateId\":\"%s\",\"nonce\":\"%s\",\"extraNonce\":\"%s\",\"timestamp\":%lld,\"hash\":\"%s\"}",
work->template_id, work->nonce, work->extra_nonce,
(long long)work->timestamp, work->hash);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(mining->http, "/mining/submit", body, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_MINING_ERROR_HTTP;
}
/* TODO: Parse JSON response */
free(response);
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_start(synor_mining_t* mining, const char* algorithm) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
char body[256];
if (algorithm) {
snprintf(body, sizeof(body), "{\"algorithm\":\"%s\"}", algorithm);
} else {
snprintf(body, sizeof(body), "{}");
}
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(mining->http, "/mining/start", body, &response, &response_size);
free(response);
return err == 0 ? SYNOR_MINING_OK : SYNOR_MINING_ERROR_HTTP;
}
synor_mining_error_t synor_mining_stop(synor_mining_t* mining) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(mining->http, "/mining/stop", "{}", &response, &response_size);
free(response);
return err == 0 ? SYNOR_MINING_OK : SYNOR_MINING_ERROR_HTTP;
}
synor_mining_error_t synor_mining_is_mining(synor_mining_t* mining, bool* result) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
if (!result) return SYNOR_MINING_ERROR_UNKNOWN;
char* response = NULL;
size_t response_size = 0;
int err = synor_http_get(mining->http, "/mining/status", &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_MINING_ERROR_HTTP;
}
*result = response && strstr(response, "\"mining\":true") != NULL;
free(response);
return SYNOR_MINING_OK;
}
void synor_block_template_free(synor_block_template_t* tmpl) {
if (!tmpl) return;
free(tmpl->id);
free(tmpl->previous_block_hash);
free(tmpl->merkle_root);
free(tmpl->bits);
free(tmpl->coinbase_value);
for (size_t i = 0; i < tmpl->transactions_count; i++) {
free(tmpl->transactions[i].txid);
free(tmpl->transactions[i].data);
free(tmpl->transactions[i].fee);
}
free(tmpl->transactions);
free(tmpl->target);
free(tmpl->algorithm);
free(tmpl->extra_nonce);
}
void synor_submit_result_free(synor_submit_result_t* result) {
if (!result) return;
free(result->share.hash);
free(result->reason);
free(result->block_hash);
free(result->reward);
}
/* ==================== Stats Operations ==================== */
synor_mining_error_t synor_mining_get_hashrate(synor_mining_t* mining,
synor_hashrate_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_get_stats(synor_mining_t* mining,
synor_mining_stats_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_get_earnings(synor_mining_t* mining,
synor_time_period_t* period, synor_earnings_t* result) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
char path[128];
if (period) {
snprintf(path, sizeof(path), "/stats/earnings?period=%s", time_period_to_string(*period));
} else {
snprintf(path, sizeof(path), "/stats/earnings");
}
char* response = NULL;
size_t response_size = 0;
int err = synor_http_get(mining->http, path, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_MINING_ERROR_HTTP;
}
/* TODO: Parse JSON response */
(void)result;
free(response);
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_get_share_stats(synor_mining_t* mining,
synor_share_stats_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
void synor_hashrate_free(synor_hashrate_t* hashrate) {
if (!hashrate) return;
free(hashrate->unit);
}
void synor_mining_stats_free(synor_mining_stats_t* stats) {
if (!stats) return;
synor_hashrate_free(&stats->hashrate);
free(stats->earnings.today);
free(stats->earnings.yesterday);
free(stats->earnings.this_week);
free(stats->earnings.this_month);
free(stats->earnings.total);
free(stats->earnings.currency);
}
void synor_earnings_free(synor_earnings_t* earnings) {
if (!earnings) return;
free(earnings->amount);
free(earnings->currency);
for (size_t i = 0; i < earnings->breakdown_count; i++) {
free(earnings->breakdown[i].amount);
}
free(earnings->breakdown);
}
/* ==================== Device Operations ==================== */
synor_mining_error_t synor_mining_list_devices(synor_mining_t* mining,
synor_mining_device_list_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_get_device(synor_mining_t* mining,
const char* device_id, synor_mining_device_t* result) {
(void)mining; (void)device_id; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_set_device_config(synor_mining_t* mining,
const char* device_id, const synor_device_config_t* config,
synor_mining_device_t* result) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
if (!device_id || !config || !result) return SYNOR_MINING_ERROR_UNKNOWN;
char body[512];
snprintf(body, sizeof(body), "{\"enabled\":%s", config->enabled ? "true" : "false");
if (config->intensity > 0) {
char temp[32];
snprintf(temp, sizeof(temp), ",\"intensity\":%d", config->intensity);
strncat(body, temp, sizeof(body) - strlen(body) - 1);
}
if (config->power_limit > 0) {
char temp[32];
snprintf(temp, sizeof(temp), ",\"powerLimit\":%d", config->power_limit);
strncat(body, temp, sizeof(body) - strlen(body) - 1);
}
strncat(body, "}", sizeof(body) - strlen(body) - 1);
char path[256];
char* encoded = synor_url_encode(device_id);
snprintf(path, sizeof(path), "/devices/%s/config", encoded);
free(encoded);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_put(mining->http, path, body, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_MINING_ERROR_HTTP;
}
/* TODO: Parse JSON response */
free(response);
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_enable_device(synor_mining_t* mining,
const char* device_id) {
synor_device_config_t config = { .enabled = true };
synor_mining_device_t result;
return synor_mining_set_device_config(mining, device_id, &config, &result);
}
synor_mining_error_t synor_mining_disable_device(synor_mining_t* mining,
const char* device_id) {
synor_device_config_t config = { .enabled = false };
synor_mining_device_t result;
return synor_mining_set_device_config(mining, device_id, &config, &result);
}
void synor_mining_device_free(synor_mining_device_t* device) {
if (!device) return;
free(device->id);
free(device->name);
free(device->driver);
free(device->firmware);
}
void synor_mining_device_list_free(synor_mining_device_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_mining_device_free(&list->devices[i]);
}
free(list->devices);
list->devices = NULL;
list->count = 0;
}
/* ==================== Worker Operations ==================== */
synor_mining_error_t synor_mining_list_workers(synor_mining_t* mining,
synor_worker_info_list_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_get_worker(synor_mining_t* mining,
const char* worker_id, synor_worker_info_t* result) {
(void)mining; (void)worker_id; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_remove_worker(synor_mining_t* mining,
const char* worker_id) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
if (!worker_id) return SYNOR_MINING_ERROR_UNKNOWN;
char path[256];
char* encoded = synor_url_encode(worker_id);
snprintf(path, sizeof(path), "/workers/%s", encoded);
free(encoded);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_delete(mining->http, path, &response, &response_size);
free(response);
return err == 0 ? SYNOR_MINING_OK : SYNOR_MINING_ERROR_HTTP;
}
void synor_worker_info_free(synor_worker_info_t* worker) {
if (!worker) return;
free(worker->id);
free(worker->name);
synor_hashrate_free(&worker->hashrate);
for (size_t i = 0; i < worker->devices_count; i++) {
synor_mining_device_free(&worker->devices[i]);
}
free(worker->devices);
}
void synor_worker_info_list_free(synor_worker_info_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_worker_info_free(&list->workers[i]);
}
free(list->workers);
list->workers = NULL;
list->count = 0;
}
/* ==================== Algorithm Operations ==================== */
synor_mining_error_t synor_mining_list_algorithms(synor_mining_t* mining,
synor_mining_algorithm_list_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_get_algorithm(synor_mining_t* mining,
const char* name, synor_mining_algorithm_t* result) {
(void)mining; (void)name; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
synor_mining_error_t synor_mining_switch_algorithm(synor_mining_t* mining,
const char* algorithm) {
if (!mining || mining->closed) return SYNOR_MINING_ERROR_CLIENT_CLOSED;
if (!algorithm) return SYNOR_MINING_ERROR_UNKNOWN;
char body[128];
snprintf(body, sizeof(body), "{\"algorithm\":\"%s\"}", algorithm);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(mining->http, "/algorithms/switch", body, &response, &response_size);
free(response);
return err == 0 ? SYNOR_MINING_OK : SYNOR_MINING_ERROR_HTTP;
}
synor_mining_error_t synor_mining_get_most_profitable(synor_mining_t* mining,
synor_mining_algorithm_t* result) {
(void)mining; (void)result;
return SYNOR_MINING_ERROR_UNKNOWN;
}
void synor_mining_algorithm_free(synor_mining_algorithm_t* algorithm) {
if (!algorithm) return;
free(algorithm->name);
free(algorithm->display_name);
free(algorithm->hash_unit);
free(algorithm->profitability);
free(algorithm->block_reward);
}
void synor_mining_algorithm_list_free(synor_mining_algorithm_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_mining_algorithm_free(&list->algorithms[i]);
}
free(list->algorithms);
list->algorithms = NULL;
list->count = 0;
}

View file

@ -0,0 +1,282 @@
/**
* @file economics.hpp
* @brief Synor Economics SDK for C++
*
* Modern C++17 SDK for pricing, billing, staking, and discount management.
*/
#pragma once
#include <string>
#include <vector>
#include <optional>
#include <future>
#include <memory>
#include <cstdint>
#include "wallet.hpp" // For SynorException
namespace synor {
namespace economics {
/// Service type
enum class ServiceType {
Compute,
Storage,
Database,
Hosting,
Rpc,
Bridge
};
/// Billing period
enum class BillingPeriod {
Hourly,
Daily,
Weekly,
Monthly
};
/// Stake status
enum class StakeStatus {
Active,
Unstaking,
Withdrawn
};
/// Discount type
enum class DiscountType {
Percentage,
Fixed,
Credits
};
/// Invoice status
enum class InvoiceStatus {
Pending,
Paid,
Overdue,
Cancelled
};
/// Economics configuration
struct Config {
std::string api_key;
std::string endpoint = "https://economics.synor.io/v1";
uint32_t timeout_ms = 30000;
uint32_t retries = 3;
bool debug = false;
};
/// Service pricing
struct ServicePricing {
ServiceType service;
std::string unit;
std::string price_per_unit;
std::string currency;
double minimum = 0.0;
double maximum = 0.0;
};
/// Usage metrics for pricing calculation
struct UsageMetrics {
ServiceType service;
double compute_units = 0.0;
double storage_bytes = 0.0;
double bandwidth_bytes = 0.0;
int64_t duration_seconds = 0;
int32_t requests = 0;
};
/// Price calculation result
struct PriceResult {
ServiceType service;
std::string amount;
std::string currency;
UsageMetrics usage;
};
/// Usage plan item for cost estimation
struct UsagePlanItem {
ServiceType service;
UsageMetrics projected_usage;
BillingPeriod period;
};
/// Cost breakdown item
struct CostItem {
ServiceType service;
std::string amount;
};
/// Cost estimate result
struct CostEstimate {
std::string total;
std::string currency;
std::vector<CostItem> breakdown;
std::optional<std::string> discount_applied;
BillingPeriod period;
};
/// Usage item
struct UsageItem {
ServiceType service;
double compute_units;
double storage_bytes;
double bandwidth_bytes;
int32_t requests;
std::string cost;
};
/// Usage report
struct Usage {
BillingPeriod period;
int64_t start_date;
int64_t end_date;
std::vector<UsageItem> items;
std::string total_cost;
std::string currency;
};
/// Invoice line item
struct InvoiceLine {
std::string service;
std::string description;
int32_t quantity;
std::string unit_price;
std::string amount;
};
/// Invoice
struct Invoice {
std::string id;
int64_t date;
int64_t due_date;
InvoiceStatus status;
std::vector<InvoiceLine> lines;
std::string subtotal;
std::string discount;
std::string tax;
std::string total;
std::string currency;
std::optional<std::string> pdf_url;
};
/// Account balance
struct AccountBalance {
std::string available;
std::string pending;
std::string staked;
std::string total;
std::string currency;
};
/// Stake information
struct Stake {
std::string id;
std::string amount;
int64_t staked_at;
int64_t unlock_at;
StakeStatus status;
std::string rewards_earned;
double apy;
};
/// Stake receipt
struct StakeReceipt {
std::string id;
std::string amount;
std::string tx_hash;
int64_t staked_at;
int64_t unlock_at;
double apy;
};
/// Unstake receipt
struct UnstakeReceipt {
std::string id;
std::string amount;
std::string tx_hash;
int64_t unstaked_at;
int64_t available_at;
};
/// Staking rewards
struct StakingRewards {
std::string pending;
std::string claimed;
std::string total;
double current_apy;
int64_t last_claim;
int64_t next_distribution;
};
/// Discount information
struct Discount {
std::string code;
DiscountType type;
std::string value;
std::string description;
std::vector<ServiceType> applicable_services;
int64_t valid_from;
int64_t valid_until;
int32_t max_uses;
int32_t current_uses;
bool active;
};
/// Discount application result
struct DiscountResult {
Discount discount;
std::string savings_estimate;
int64_t applied_at;
};
// Forward declaration
class SynorEconomicsImpl;
/// Synor Economics client
class SynorEconomics {
public:
explicit SynorEconomics(const Config& config);
~SynorEconomics();
SynorEconomics(const SynorEconomics&) = delete;
SynorEconomics& operator=(const SynorEconomics&) = delete;
SynorEconomics(SynorEconomics&&) noexcept;
SynorEconomics& operator=(SynorEconomics&&) noexcept;
// Pricing operations
std::future<std::vector<ServicePricing>> get_pricing(std::optional<ServiceType> service = std::nullopt);
std::future<PriceResult> get_price(ServiceType service, const UsageMetrics& usage);
std::future<CostEstimate> estimate_cost(const std::vector<UsagePlanItem>& plan);
// Billing operations
std::future<Usage> get_usage(std::optional<BillingPeriod> period = std::nullopt);
std::future<std::vector<Invoice>> get_invoices();
std::future<Invoice> get_invoice(const std::string& invoice_id);
std::future<AccountBalance> get_balance();
// Staking operations
std::future<StakeReceipt> stake(const std::string& amount, int32_t duration_days = 0);
std::future<UnstakeReceipt> unstake(const std::string& stake_id);
std::future<std::vector<Stake>> get_stakes();
std::future<Stake> get_stake(const std::string& stake_id);
std::future<StakingRewards> get_staking_rewards();
std::future<StakeReceipt> claim_rewards();
// Discount operations
std::future<DiscountResult> apply_discount(const std::string& code);
std::future<void> remove_discount(const std::string& code);
std::future<std::vector<Discount>> get_discounts();
// Lifecycle
void close();
bool is_closed() const;
std::future<bool> health_check();
private:
std::unique_ptr<SynorEconomicsImpl> impl_;
};
} // namespace economics
} // namespace synor

View file

@ -0,0 +1,300 @@
/**
* @file governance.hpp
* @brief Synor Governance SDK for C++
*
* Modern C++17 SDK for proposals, voting, DAOs, and vesting operations.
*/
#pragma once
#include <string>
#include <vector>
#include <optional>
#include <future>
#include <memory>
#include <cstdint>
#include <chrono>
#include "wallet.hpp" // For SynorException
namespace synor {
namespace governance {
/// Proposal status
enum class ProposalStatus {
Pending,
Active,
Passed,
Rejected,
Executed,
Cancelled
};
/// Vote choice
enum class VoteChoice {
For,
Against,
Abstain
};
/// DAO type
enum class DaoType {
Token,
Multisig,
Hybrid
};
/// Vesting status
enum class VestingStatus {
Active,
Completed,
Revoked
};
/// Governance configuration
struct Config {
std::string api_key;
std::string endpoint = "https://governance.synor.io/v1";
uint32_t timeout_ms = 30000;
uint32_t retries = 3;
bool debug = false;
};
/// Proposal action
struct ProposalAction {
std::string target;
std::string method;
std::string data;
std::optional<std::string> value;
};
/// Proposal draft
struct ProposalDraft {
std::string title;
std::string description;
std::optional<std::string> discussion_url;
std::optional<int64_t> voting_start_time;
std::optional<int64_t> voting_end_time;
std::optional<std::string> dao_id;
std::vector<ProposalAction> actions;
};
/// Vote tally
struct VoteTally {
std::string for_votes;
std::string against_votes;
std::string abstain_votes;
std::string quorum;
std::string quorum_required;
int32_t total_voters;
};
/// Proposal
struct Proposal {
std::string id;
std::string title;
std::string description;
std::optional<std::string> discussion_url;
std::string proposer;
ProposalStatus status;
int64_t created_at;
int64_t voting_start_time;
int64_t voting_end_time;
std::optional<int64_t> execution_time;
VoteTally vote_tally;
std::vector<ProposalAction> actions;
std::optional<std::string> dao_id;
};
/// Proposal filter
struct ProposalFilter {
std::optional<ProposalStatus> status;
std::optional<std::string> proposer;
std::optional<std::string> dao_id;
std::optional<int32_t> limit;
std::optional<int32_t> offset;
};
/// Vote
struct Vote {
VoteChoice choice;
std::optional<std::string> reason;
};
/// Vote receipt
struct VoteReceipt {
std::string id;
std::string proposal_id;
std::string voter;
VoteChoice choice;
std::string weight;
std::optional<std::string> reason;
int64_t voted_at;
std::string tx_hash;
};
/// Voting power
struct VotingPower {
std::string address;
std::string delegated_power;
std::string own_power;
std::string total_power;
std::vector<std::string> delegators;
};
/// Delegation receipt
struct DelegationReceipt {
std::string id;
std::string from;
std::string to;
std::string amount;
int64_t delegated_at;
std::string tx_hash;
};
/// DAO configuration
struct DaoConfig {
std::string name;
std::string description;
DaoType type;
int32_t voting_period_days;
int32_t timelock_days;
std::optional<std::string> token_address;
std::optional<double> quorum_percent;
std::optional<std::string> proposal_threshold;
std::vector<std::string> multisig_members;
std::optional<int32_t> multisig_threshold;
};
/// DAO
struct Dao {
std::string id;
std::string name;
std::string description;
DaoType type;
std::optional<std::string> token_address;
int32_t voting_period_days;
int32_t timelock_days;
double quorum_percent;
std::string proposal_threshold;
int32_t total_proposals;
int32_t active_proposals;
std::string treasury_value;
int32_t member_count;
int64_t created_at;
};
/// Treasury token
struct TreasuryToken {
std::string address;
std::string balance;
std::string name;
std::string symbol;
};
/// DAO treasury
struct DaoTreasury {
std::string dao_id;
std::string total_value;
std::vector<TreasuryToken> tokens;
int64_t last_updated;
};
/// Vesting schedule
struct VestingSchedule {
std::string beneficiary;
std::string total_amount;
int64_t start_time;
int64_t cliff_duration;
int64_t vesting_duration;
bool revocable;
};
/// Vesting contract
struct VestingContract {
std::string id;
std::string beneficiary;
std::string grantor;
std::string total_amount;
std::string released_amount;
std::string releasable_amount;
int64_t start_time;
int64_t cliff_time;
int64_t end_time;
VestingStatus status;
bool revocable;
int64_t created_at;
};
/// Claim receipt
struct ClaimReceipt {
std::string id;
std::string contract_id;
std::string amount;
std::string tx_hash;
int64_t claimed_at;
};
// Forward declaration
class SynorGovernanceImpl;
/// Synor Governance client
class SynorGovernance {
public:
explicit SynorGovernance(const Config& config);
~SynorGovernance();
SynorGovernance(const SynorGovernance&) = delete;
SynorGovernance& operator=(const SynorGovernance&) = delete;
SynorGovernance(SynorGovernance&&) noexcept;
SynorGovernance& operator=(SynorGovernance&&) noexcept;
// Proposal operations
std::future<Proposal> create_proposal(const ProposalDraft& draft);
std::future<Proposal> get_proposal(const std::string& proposal_id);
std::future<std::vector<Proposal>> list_proposals(const ProposalFilter& filter = {});
std::future<Proposal> cancel_proposal(const std::string& proposal_id);
std::future<Proposal> execute_proposal(const std::string& proposal_id);
std::future<Proposal> wait_for_proposal(
const std::string& proposal_id,
std::chrono::milliseconds poll_interval = std::chrono::minutes(1),
std::chrono::milliseconds max_wait = std::chrono::hours(168));
// Voting operations
std::future<VoteReceipt> vote(const std::string& proposal_id, const Vote& vote,
std::optional<std::string> weight = std::nullopt);
std::future<std::vector<VoteReceipt>> get_votes(const std::string& proposal_id);
std::future<VoteReceipt> get_my_vote(const std::string& proposal_id);
std::future<DelegationReceipt> delegate(const std::string& delegatee,
std::optional<std::string> amount = std::nullopt);
std::future<DelegationReceipt> undelegate(const std::string& delegatee);
std::future<VotingPower> get_voting_power(const std::string& address);
std::future<std::vector<DelegationReceipt>> get_delegations(const std::string& address);
// DAO operations
std::future<Dao> create_dao(const DaoConfig& config);
std::future<Dao> get_dao(const std::string& dao_id);
std::future<std::vector<Dao>> list_daos(std::optional<int32_t> limit = std::nullopt,
std::optional<int32_t> offset = std::nullopt);
std::future<DaoTreasury> get_dao_treasury(const std::string& dao_id);
std::future<std::vector<std::string>> get_dao_members(const std::string& dao_id);
// Vesting operations
std::future<VestingContract> create_vesting_schedule(const VestingSchedule& schedule);
std::future<VestingContract> get_vesting_contract(const std::string& contract_id);
std::future<std::vector<VestingContract>> list_vesting_contracts(
std::optional<std::string> beneficiary = std::nullopt);
std::future<ClaimReceipt> claim_vested(const std::string& contract_id);
std::future<VestingContract> revoke_vesting(const std::string& contract_id);
std::future<std::string> get_releasable_amount(const std::string& contract_id);
// Lifecycle
void close();
bool is_closed() const;
std::future<bool> health_check();
private:
std::unique_ptr<SynorGovernanceImpl> impl_;
};
} // namespace governance
} // namespace synor

View file

@ -0,0 +1,334 @@
/**
* @file mining.hpp
* @brief Synor Mining SDK for C++
*
* Modern C++17 SDK for pool connections, block templates, hashrate stats, and GPU management.
*/
#pragma once
#include <string>
#include <vector>
#include <optional>
#include <future>
#include <memory>
#include <cstdint>
#include "wallet.hpp" // For SynorException
namespace synor {
namespace mining {
/// Device type
enum class DeviceType {
Cpu,
GpuNvidia,
GpuAmd,
Asic
};
/// Device status
enum class DeviceStatus {
Idle,
Mining,
Error,
Offline
};
/// Connection status
enum class ConnectionStatus {
Disconnected,
Connecting,
Connected,
Reconnecting
};
/// Time period for stats
enum class TimePeriod {
Hour,
Day,
Week,
Month,
All
};
/// Submit result status
enum class SubmitStatus {
Accepted,
Rejected,
Stale
};
/// Mining configuration
struct Config {
std::string api_key;
std::string endpoint = "https://mining.synor.io/v1";
uint32_t timeout_ms = 60000;
uint32_t retries = 3;
bool debug = false;
};
/// Pool configuration
struct PoolConfig {
std::string url;
std::string user;
std::optional<std::string> password;
std::optional<std::string> algorithm;
std::optional<double> difficulty;
};
/// Stratum connection
struct StratumConnection {
std::string id;
std::string pool;
ConnectionStatus status;
std::string algorithm;
double difficulty;
int64_t connected_at;
int32_t accepted_shares;
int32_t rejected_shares;
int32_t stale_shares;
std::optional<int64_t> last_share_at;
};
/// Pool stats
struct PoolStats {
std::string url;
int32_t workers;
double hashrate;
double difficulty;
int64_t last_block;
int32_t blocks_found_24h;
double luck;
};
/// Template transaction
struct TemplateTransaction {
std::string txid;
std::string data;
std::string fee;
int32_t weight;
};
/// Block template
struct BlockTemplate {
std::string id;
std::string previous_block_hash;
std::string merkle_root;
int64_t timestamp;
std::string bits;
int64_t height;
std::string coinbase_value;
std::vector<TemplateTransaction> transactions;
std::string target;
std::string algorithm;
std::string extra_nonce;
};
/// Mined work
struct MinedWork {
std::string template_id;
std::string nonce;
std::string extra_nonce;
int64_t timestamp;
std::string hash;
};
/// Share info
struct ShareInfo {
std::string hash;
double difficulty;
int64_t timestamp;
bool accepted;
};
/// Submit result
struct SubmitResult {
SubmitStatus status;
ShareInfo share;
bool block_found;
std::optional<std::string> reason;
std::optional<std::string> block_hash;
std::optional<std::string> reward;
};
/// Hashrate
struct Hashrate {
double current;
double average_1h;
double average_24h;
double peak;
std::string unit;
};
/// Share stats
struct ShareStats {
int32_t accepted;
int32_t rejected;
int32_t stale;
int32_t total;
double accept_rate;
};
/// Device temperature
struct DeviceTemperature {
double current;
double max;
bool throttling;
};
/// Earnings snapshot
struct EarningsSnapshot {
std::string today;
std::string yesterday;
std::string this_week;
std::string this_month;
std::string total;
std::string currency;
};
/// Mining stats
struct MiningStats {
Hashrate hashrate;
ShareStats shares;
int64_t uptime;
double efficiency;
EarningsSnapshot earnings;
std::optional<double> power_consumption;
std::optional<DeviceTemperature> temperature;
};
/// Earnings breakdown
struct EarningsBreakdown {
int64_t date;
std::string amount;
int32_t blocks;
int32_t shares;
double hashrate;
};
/// Earnings report
struct Earnings {
TimePeriod period;
int64_t start_date;
int64_t end_date;
std::string amount;
int32_t blocks;
int32_t shares;
double average_hashrate;
std::string currency;
std::vector<EarningsBreakdown> breakdown;
};
/// Mining device
struct MiningDevice {
std::string id;
std::string name;
DeviceType type;
DeviceStatus status;
double hashrate;
double temperature;
double fan_speed;
double power_draw;
int64_t memory_used;
int64_t memory_total;
std::optional<std::string> driver;
std::optional<std::string> firmware;
};
/// Device configuration
struct DeviceConfig {
bool enabled;
std::optional<int32_t> intensity;
std::optional<int32_t> power_limit;
std::optional<int32_t> core_clock_offset;
std::optional<int32_t> memory_clock_offset;
std::optional<int32_t> fan_speed;
};
/// Worker info
struct WorkerInfo {
std::string id;
std::string name;
ConnectionStatus status;
Hashrate hashrate;
ShareStats shares;
std::vector<MiningDevice> devices;
int64_t last_seen;
int64_t uptime;
};
/// Mining algorithm
struct MiningAlgorithm {
std::string name;
std::string display_name;
std::string hash_unit;
std::string profitability;
double difficulty;
std::string block_reward;
int32_t block_time;
};
// Forward declaration
class SynorMiningImpl;
/// Synor Mining client
class SynorMining {
public:
explicit SynorMining(const Config& config);
~SynorMining();
SynorMining(const SynorMining&) = delete;
SynorMining& operator=(const SynorMining&) = delete;
SynorMining(SynorMining&&) noexcept;
SynorMining& operator=(SynorMining&&) noexcept;
// Pool operations
std::future<StratumConnection> connect(const PoolConfig& pool);
std::future<void> disconnect();
std::future<StratumConnection> get_connection();
std::future<PoolStats> get_pool_stats();
bool is_connected() const;
// Mining operations
std::future<BlockTemplate> get_block_template();
std::future<SubmitResult> submit_work(const MinedWork& work);
std::future<void> start_mining(std::optional<std::string> algorithm = std::nullopt);
std::future<void> stop_mining();
std::future<bool> is_mining();
// Stats operations
std::future<Hashrate> get_hashrate();
std::future<MiningStats> get_stats();
std::future<Earnings> get_earnings(std::optional<TimePeriod> period = std::nullopt);
std::future<ShareStats> get_share_stats();
// Device operations
std::future<std::vector<MiningDevice>> list_devices();
std::future<MiningDevice> get_device(const std::string& device_id);
std::future<MiningDevice> set_device_config(const std::string& device_id,
const DeviceConfig& config);
std::future<void> enable_device(const std::string& device_id);
std::future<void> disable_device(const std::string& device_id);
// Worker operations
std::future<std::vector<WorkerInfo>> list_workers();
std::future<WorkerInfo> get_worker(const std::string& worker_id);
std::future<void> remove_worker(const std::string& worker_id);
// Algorithm operations
std::future<std::vector<MiningAlgorithm>> list_algorithms();
std::future<MiningAlgorithm> get_algorithm(const std::string& name);
std::future<void> switch_algorithm(const std::string& algorithm);
std::future<MiningAlgorithm> get_most_profitable();
// Lifecycle
void close();
bool is_closed() const;
std::future<bool> health_check();
private:
std::unique_ptr<SynorMiningImpl> impl_;
};
} // namespace mining
} // namespace synor

View file

@ -0,0 +1,165 @@
using System.Net.Http.Json;
using System.Text.Json;
namespace Synor.Sdk.Economics;
/// <summary>
/// Synor Economics SDK client for C#.
/// Pricing, billing, staking, and discount management.
/// </summary>
public class SynorEconomics : IDisposable
{
private readonly EconomicsConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
public SynorEconomics(EconomicsConfig config)
{
_config = config;
_httpClient = new HttpClient { BaseAddress = new Uri(config.Endpoint), Timeout = config.Timeout };
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
_httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");
_jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true };
}
// ==================== Pricing Operations ====================
public async Task<List<ServicePricing>> GetPricingAsync(ServiceType? service = null, CancellationToken ct = default)
{
var path = service.HasValue ? $"/pricing?service={service.Value.ToString().ToLower()}" : "/pricing";
var r = await GetAsync<PricingResponse>(path, ct);
return r.Pricing ?? new List<ServicePricing>();
}
public async Task<PriceResult> GetPriceAsync(ServiceType service, UsageMetrics usage, CancellationToken ct = default)
{
var body = new { service = service.ToString().ToLower(), usage };
return await PostAsync<PriceResult>("/pricing/calculate", body, ct);
}
public async Task<CostEstimate> EstimateCostAsync(List<UsagePlanItem> plan, CancellationToken ct = default)
=> await PostAsync<CostEstimate>("/pricing/estimate", new { plan }, ct);
// ==================== Billing Operations ====================
public async Task<Usage> GetUsageAsync(BillingPeriod? period = null, CancellationToken ct = default)
{
var path = period.HasValue ? $"/billing/usage?period={period.Value.ToString().ToLower()}" : "/billing/usage";
return await GetAsync<Usage>(path, ct);
}
public async Task<List<Invoice>> GetInvoicesAsync(CancellationToken ct = default)
{
var r = await GetAsync<InvoicesResponse>("/billing/invoices", ct);
return r.Invoices ?? new List<Invoice>();
}
public async Task<Invoice> GetInvoiceAsync(string invoiceId, CancellationToken ct = default)
=> await GetAsync<Invoice>($"/billing/invoices/{Uri.EscapeDataString(invoiceId)}", ct);
public async Task<AccountBalance> GetBalanceAsync(CancellationToken ct = default)
=> await GetAsync<AccountBalance>("/billing/balance", ct);
// ==================== Staking Operations ====================
public async Task<StakeReceipt> StakeAsync(string amount, int durationDays = 0, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["amount"] = amount };
if (durationDays > 0) body["durationDays"] = durationDays;
return await PostAsync<StakeReceipt>("/staking/stake", body, ct);
}
public async Task<UnstakeReceipt> UnstakeAsync(string stakeId, CancellationToken ct = default)
=> await PostAsync<UnstakeReceipt>($"/staking/{Uri.EscapeDataString(stakeId)}/unstake", new { }, ct);
public async Task<List<Stake>> GetStakesAsync(CancellationToken ct = default)
{
var r = await GetAsync<StakesResponse>("/staking", ct);
return r.Stakes ?? new List<Stake>();
}
public async Task<Stake> GetStakeAsync(string stakeId, CancellationToken ct = default)
=> await GetAsync<Stake>($"/staking/{Uri.EscapeDataString(stakeId)}", ct);
public async Task<StakingRewards> GetStakingRewardsAsync(CancellationToken ct = default)
=> await GetAsync<StakingRewards>("/staking/rewards", ct);
public async Task<StakeReceipt> ClaimRewardsAsync(CancellationToken ct = default)
=> await PostAsync<StakeReceipt>("/staking/rewards/claim", new { }, ct);
// ==================== Discount Operations ====================
public async Task<DiscountResult> ApplyDiscountAsync(string code, CancellationToken ct = default)
=> await PostAsync<DiscountResult>("/discounts/apply", new { code }, ct);
public async Task RemoveDiscountAsync(string code, CancellationToken ct = default)
=> await DeleteAsync($"/discounts/{Uri.EscapeDataString(code)}", ct);
public async Task<List<Discount>> GetDiscountsAsync(CancellationToken ct = default)
{
var r = await GetAsync<DiscountsResponse>("/discounts", ct);
return r.Discounts ?? new List<Discount>();
}
// ==================== Lifecycle ====================
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
try { var r = await GetAsync<HealthResponse>("/health", ct); return r.Status == "healthy"; }
catch { return false; }
}
public bool IsClosed => _disposed;
public void Dispose()
{
if (!_disposed) { _httpClient.Dispose(); _disposed = true; }
GC.SuppressFinalize(this);
}
// ==================== Private HTTP Methods ====================
private async Task<T> GetAsync<T>(string path, CancellationToken ct)
=> await ExecuteAsync(async () => {
var r = await _httpClient.GetAsync(path, ct);
await EnsureSuccessAsync(r);
return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
});
private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
=> await ExecuteAsync(async () => {
var r = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
await EnsureSuccessAsync(r);
return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
});
private async Task DeleteAsync(string path, CancellationToken ct)
=> await ExecuteAsync(async () => {
var r = await _httpClient.DeleteAsync(path, ct);
await EnsureSuccessAsync(r);
return true;
});
private async Task<T> ExecuteAsync<T>(Func<Task<T>> op)
{
Exception? err = null;
for (int i = 0; i < _config.Retries; i++)
{
try { return await op(); }
catch (Exception ex) { err = ex; if (i < _config.Retries - 1) await Task.Delay(1000 << i); }
}
throw err!;
}
private async Task EnsureSuccessAsync(HttpResponseMessage r)
{
if (!r.IsSuccessStatusCode)
{
var c = await r.Content.ReadAsStringAsync();
var e = JsonSerializer.Deserialize<Dictionary<string, object>>(c, _jsonOptions);
throw new EconomicsException(e?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)r.StatusCode}",
e?.GetValueOrDefault("code")?.ToString(), (int)r.StatusCode);
}
}
}

View file

@ -0,0 +1,181 @@
namespace Synor.Sdk.Economics;
public enum ServiceType { Compute, Storage, Database, Hosting, Rpc, Bridge }
public enum BillingPeriod { Hourly, Daily, Weekly, Monthly }
public enum StakeStatus { Active, Unstaking, Withdrawn }
public enum DiscountType { Percentage, Fixed, Credits }
public enum InvoiceStatus { Pending, Paid, Overdue, Cancelled }
public record EconomicsConfig(
string ApiKey,
string Endpoint = "https://economics.synor.io/v1",
TimeSpan? Timeout = null,
int Retries = 3,
bool Debug = false
) {
public TimeSpan Timeout { get; init; } = Timeout ?? TimeSpan.FromSeconds(30);
}
public record ServicePricing(
ServiceType Service,
string Unit,
string PricePerUnit,
string Currency,
double Minimum = 0.0,
double Maximum = 0.0
);
public record UsageMetrics(
ServiceType Service,
double ComputeUnits = 0.0,
double StorageBytes = 0.0,
double BandwidthBytes = 0.0,
long DurationSeconds = 0,
int Requests = 0
);
public record PriceResult(
ServiceType Service,
string Amount,
string Currency,
UsageMetrics Usage
);
public record UsagePlanItem(
ServiceType Service,
UsageMetrics ProjectedUsage,
BillingPeriod Period
);
public record CostItem(ServiceType Service, string Amount);
public record CostEstimate(
string Total,
string Currency,
List<CostItem> Breakdown,
string? DiscountApplied,
BillingPeriod Period
);
public record UsageItem(
ServiceType Service,
double ComputeUnits,
double StorageBytes,
double BandwidthBytes,
int Requests,
string Cost
);
public record Usage(
BillingPeriod Period,
long StartDate,
long EndDate,
List<UsageItem> Items,
string TotalCost,
string Currency
);
public record InvoiceLine(
string Service,
string Description,
int Quantity,
string UnitPrice,
string Amount
);
public record Invoice(
string Id,
long Date,
long DueDate,
InvoiceStatus Status,
List<InvoiceLine> Lines,
string Subtotal,
string Discount,
string Tax,
string Total,
string Currency,
string? PdfUrl = null
);
public record AccountBalance(
string Available,
string Pending,
string Staked,
string Total,
string Currency
);
public record Stake(
string Id,
string Amount,
long StakedAt,
long UnlockAt,
StakeStatus Status,
string RewardsEarned,
double Apy
);
public record StakeReceipt(
string Id,
string Amount,
string TxHash,
long StakedAt,
long UnlockAt,
double Apy
);
public record UnstakeReceipt(
string Id,
string Amount,
string TxHash,
long UnstakedAt,
long AvailableAt
);
public record StakingRewards(
string Pending,
string Claimed,
string Total,
double CurrentApy,
long LastClaim,
long NextDistribution
);
public record Discount(
string Code,
DiscountType Type,
string Value,
string Description,
List<ServiceType> ApplicableServices,
long ValidFrom,
long ValidUntil,
int MaxUses,
int CurrentUses,
bool Active
);
public record DiscountResult(
Discount Discount,
string SavingsEstimate,
long AppliedAt
);
// Response wrappers
internal record PricingResponse(List<ServicePricing>? Pricing);
internal record InvoicesResponse(List<Invoice>? Invoices);
internal record StakesResponse(List<Stake>? Stakes);
internal record DiscountsResponse(List<Discount>? Discounts);
internal record HealthResponse(string Status);
public class EconomicsException : Exception
{
public string? Code { get; }
public int StatusCode { get; }
public EconomicsException(string message, string? code = null, int statusCode = 0)
: base(message)
{
Code = code;
StatusCode = statusCode;
}
}

View file

@ -0,0 +1,212 @@
using System.Net.Http.Json;
using System.Text.Json;
namespace Synor.Sdk.Governance;
/// <summary>
/// Synor Governance SDK client for C#.
/// Proposals, voting, DAOs, and vesting operations.
/// </summary>
public class SynorGovernance : IDisposable
{
private readonly GovernanceConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
private static readonly HashSet<ProposalStatus> FinalStatuses = new() { ProposalStatus.Passed, ProposalStatus.Rejected, ProposalStatus.Executed, ProposalStatus.Cancelled };
public SynorGovernance(GovernanceConfig config)
{
_config = config;
_httpClient = new HttpClient { BaseAddress = new Uri(config.Endpoint), Timeout = config.Timeout };
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
_httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");
_jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true };
}
// ==================== Proposal Operations ====================
public async Task<Proposal> CreateProposalAsync(ProposalDraft draft, CancellationToken ct = default)
=> await PostAsync<Proposal>("/proposals", draft, ct);
public async Task<Proposal> GetProposalAsync(string proposalId, CancellationToken ct = default)
=> await GetAsync<Proposal>($"/proposals/{Uri.EscapeDataString(proposalId)}", ct);
public async Task<List<Proposal>> ListProposalsAsync(ProposalFilter? filter = null, CancellationToken ct = default)
{
var q = new List<string>();
if (filter?.Status != null) q.Add($"status={filter.Status.ToString()!.ToLower()}");
if (filter?.Proposer != null) q.Add($"proposer={Uri.EscapeDataString(filter.Proposer)}");
if (filter?.DaoId != null) q.Add($"daoId={Uri.EscapeDataString(filter.DaoId)}");
if (filter?.Limit != null) q.Add($"limit={filter.Limit}");
if (filter?.Offset != null) q.Add($"offset={filter.Offset}");
var path = q.Count > 0 ? $"/proposals?{string.Join("&", q)}" : "/proposals";
var r = await GetAsync<ProposalsResponse>(path, ct);
return r.Proposals ?? new List<Proposal>();
}
public async Task<Proposal> CancelProposalAsync(string proposalId, CancellationToken ct = default)
=> await PostAsync<Proposal>($"/proposals/{Uri.EscapeDataString(proposalId)}/cancel", new { }, ct);
public async Task<Proposal> ExecuteProposalAsync(string proposalId, CancellationToken ct = default)
=> await PostAsync<Proposal>($"/proposals/{Uri.EscapeDataString(proposalId)}/execute", new { }, ct);
public async Task<Proposal> WaitForProposalAsync(string proposalId, TimeSpan? pollInterval = null, TimeSpan? maxWait = null, CancellationToken ct = default)
{
var interval = pollInterval ?? TimeSpan.FromMinutes(1);
var deadline = DateTime.UtcNow + (maxWait ?? TimeSpan.FromDays(7));
while (DateTime.UtcNow < deadline)
{
var p = await GetProposalAsync(proposalId, ct);
if (FinalStatuses.Contains(p.Status)) return p;
await Task.Delay(interval, ct);
}
throw new GovernanceException("Timeout waiting for proposal completion");
}
// ==================== Voting Operations ====================
public async Task<VoteReceipt> VoteAsync(string proposalId, Vote vote, string? weight = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["choice"] = vote.Choice.ToString().ToLower() };
if (vote.Reason != null) body["reason"] = vote.Reason;
if (weight != null) body["weight"] = weight;
return await PostAsync<VoteReceipt>($"/proposals/{Uri.EscapeDataString(proposalId)}/vote", body, ct);
}
public async Task<List<VoteReceipt>> GetVotesAsync(string proposalId, CancellationToken ct = default)
{
var r = await GetAsync<VotesResponse>($"/proposals/{Uri.EscapeDataString(proposalId)}/votes", ct);
return r.Votes ?? new List<VoteReceipt>();
}
public async Task<VoteReceipt> GetMyVoteAsync(string proposalId, CancellationToken ct = default)
=> await GetAsync<VoteReceipt>($"/proposals/{Uri.EscapeDataString(proposalId)}/votes/me", ct);
public async Task<DelegationReceipt> DelegateAsync(string delegatee, string? amount = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["delegatee"] = delegatee };
if (amount != null) body["amount"] = amount;
return await PostAsync<DelegationReceipt>("/voting/delegate", body, ct);
}
public async Task<DelegationReceipt> UndelegateAsync(string delegatee, CancellationToken ct = default)
=> await PostAsync<DelegationReceipt>("/voting/undelegate", new { delegatee }, ct);
public async Task<VotingPower> GetVotingPowerAsync(string address, CancellationToken ct = default)
=> await GetAsync<VotingPower>($"/voting/power/{Uri.EscapeDataString(address)}", ct);
public async Task<List<DelegationReceipt>> GetDelegationsAsync(string address, CancellationToken ct = default)
{
var r = await GetAsync<DelegationsResponse>($"/voting/delegations/{Uri.EscapeDataString(address)}", ct);
return r.Delegations ?? new List<DelegationReceipt>();
}
// ==================== DAO Operations ====================
public async Task<Dao> CreateDaoAsync(DaoConfig config, CancellationToken ct = default)
=> await PostAsync<Dao>("/daos", config, ct);
public async Task<Dao> GetDaoAsync(string daoId, CancellationToken ct = default)
=> await GetAsync<Dao>($"/daos/{Uri.EscapeDataString(daoId)}", ct);
public async Task<List<Dao>> ListDaosAsync(int? limit = null, int? offset = null, CancellationToken ct = default)
{
var q = new List<string>();
if (limit != null) q.Add($"limit={limit}");
if (offset != null) q.Add($"offset={offset}");
var path = q.Count > 0 ? $"/daos?{string.Join("&", q)}" : "/daos";
var r = await GetAsync<DaosResponse>(path, ct);
return r.Daos ?? new List<Dao>();
}
public async Task<DaoTreasury> GetDaoTreasuryAsync(string daoId, CancellationToken ct = default)
=> await GetAsync<DaoTreasury>($"/daos/{Uri.EscapeDataString(daoId)}/treasury", ct);
public async Task<List<string>> GetDaoMembersAsync(string daoId, CancellationToken ct = default)
{
var r = await GetAsync<MembersResponse>($"/daos/{Uri.EscapeDataString(daoId)}/members", ct);
return r.Members ?? new List<string>();
}
// ==================== Vesting Operations ====================
public async Task<VestingContract> CreateVestingScheduleAsync(VestingSchedule schedule, CancellationToken ct = default)
=> await PostAsync<VestingContract>("/vesting", schedule, ct);
public async Task<VestingContract> GetVestingContractAsync(string contractId, CancellationToken ct = default)
=> await GetAsync<VestingContract>($"/vesting/{Uri.EscapeDataString(contractId)}", ct);
public async Task<List<VestingContract>> ListVestingContractsAsync(string? beneficiary = null, CancellationToken ct = default)
{
var path = beneficiary != null ? $"/vesting?beneficiary={Uri.EscapeDataString(beneficiary)}" : "/vesting";
var r = await GetAsync<VestingContractsResponse>(path, ct);
return r.Contracts ?? new List<VestingContract>();
}
public async Task<ClaimReceipt> ClaimVestedAsync(string contractId, CancellationToken ct = default)
=> await PostAsync<ClaimReceipt>($"/vesting/{Uri.EscapeDataString(contractId)}/claim", new { }, ct);
public async Task<VestingContract> RevokeVestingAsync(string contractId, CancellationToken ct = default)
=> await PostAsync<VestingContract>($"/vesting/{Uri.EscapeDataString(contractId)}/revoke", new { }, ct);
public async Task<string> GetReleasableAmountAsync(string contractId, CancellationToken ct = default)
{
var r = await GetAsync<ReleasableResponse>($"/vesting/{Uri.EscapeDataString(contractId)}/releasable", ct);
return r.Amount;
}
// ==================== Lifecycle ====================
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
try { var r = await GetAsync<GovernanceHealthResponse>("/health", ct); return r.Status == "healthy"; }
catch { return false; }
}
public bool IsClosed => _disposed;
public void Dispose()
{
if (!_disposed) { _httpClient.Dispose(); _disposed = true; }
GC.SuppressFinalize(this);
}
// ==================== Private HTTP Methods ====================
private async Task<T> GetAsync<T>(string path, CancellationToken ct)
=> await ExecuteAsync(async () => {
var r = await _httpClient.GetAsync(path, ct);
await EnsureSuccessAsync(r);
return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
});
private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
=> await ExecuteAsync(async () => {
var r = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
await EnsureSuccessAsync(r);
return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
});
private async Task<T> ExecuteAsync<T>(Func<Task<T>> op)
{
Exception? err = null;
for (int i = 0; i < _config.Retries; i++)
{
try { return await op(); }
catch (Exception ex) { err = ex; if (i < _config.Retries - 1) await Task.Delay(1000 << i); }
}
throw err!;
}
private async Task EnsureSuccessAsync(HttpResponseMessage r)
{
if (!r.IsSuccessStatusCode)
{
var c = await r.Content.ReadAsStringAsync();
var e = JsonSerializer.Deserialize<Dictionary<string, object>>(c, _jsonOptions);
throw new GovernanceException(e?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)r.StatusCode}",
e?.GetValueOrDefault("code")?.ToString(), (int)r.StatusCode);
}
}
}

View file

@ -0,0 +1,195 @@
namespace Synor.Sdk.Governance;
public enum ProposalStatus { Pending, Active, Passed, Rejected, Executed, Cancelled }
public enum VoteChoice { For, Against, Abstain }
public enum DaoType { Token, Multisig, Hybrid }
public enum VestingStatus { Active, Completed, Revoked }
public record GovernanceConfig(
string ApiKey,
string Endpoint = "https://governance.synor.io/v1",
TimeSpan? Timeout = null,
int Retries = 3,
bool Debug = false
) {
public TimeSpan Timeout { get; init; } = Timeout ?? TimeSpan.FromSeconds(30);
}
public record ProposalAction(
string Target,
string Method,
string Data,
string? Value = null
);
public record ProposalDraft(
string Title,
string Description,
string? DiscussionUrl = null,
long? VotingStartTime = null,
long? VotingEndTime = null,
string? DaoId = null,
List<ProposalAction>? Actions = null
);
public record VoteTally(
string ForVotes,
string AgainstVotes,
string AbstainVotes,
string Quorum,
string QuorumRequired,
int TotalVoters
);
public record Proposal(
string Id,
string Title,
string Description,
string? DiscussionUrl,
string Proposer,
ProposalStatus Status,
long CreatedAt,
long VotingStartTime,
long VotingEndTime,
long? ExecutionTime,
VoteTally VoteTally,
List<ProposalAction> Actions,
string? DaoId = null
);
public record ProposalFilter(
ProposalStatus? Status = null,
string? Proposer = null,
string? DaoId = null,
int? Limit = null,
int? Offset = null
);
public record Vote(VoteChoice Choice, string? Reason = null);
public record VoteReceipt(
string Id,
string ProposalId,
string Voter,
VoteChoice Choice,
string Weight,
string? Reason,
long VotedAt,
string TxHash
);
public record VotingPower(
string Address,
string DelegatedPower,
string OwnPower,
string TotalPower,
List<string> Delegators
);
public record DelegationReceipt(
string Id,
string From,
string To,
string Amount,
long DelegatedAt,
string TxHash
);
public record DaoConfig(
string Name,
string Description,
DaoType Type,
int VotingPeriodDays,
int TimelockDays,
string? TokenAddress = null,
double? QuorumPercent = null,
string? ProposalThreshold = null,
List<string>? MultisigMembers = null,
int? MultisigThreshold = null
);
public record Dao(
string Id,
string Name,
string Description,
DaoType Type,
string? TokenAddress,
int VotingPeriodDays,
int TimelockDays,
double QuorumPercent,
string ProposalThreshold,
int TotalProposals,
int ActiveProposals,
string TreasuryValue,
int MemberCount,
long CreatedAt
);
public record TreasuryToken(
string Address,
string Balance,
string Name,
string Symbol
);
public record DaoTreasury(
string DaoId,
string TotalValue,
List<TreasuryToken> Tokens,
long LastUpdated
);
public record VestingSchedule(
string Beneficiary,
string TotalAmount,
long StartTime,
long CliffDuration,
long VestingDuration,
bool Revocable
);
public record VestingContract(
string Id,
string Beneficiary,
string Grantor,
string TotalAmount,
string ReleasedAmount,
string ReleasableAmount,
long StartTime,
long CliffTime,
long EndTime,
VestingStatus Status,
bool Revocable,
long CreatedAt
);
public record ClaimReceipt(
string Id,
string ContractId,
string Amount,
string TxHash,
long ClaimedAt
);
// Response wrappers
internal record ProposalsResponse(List<Proposal>? Proposals);
internal record VotesResponse(List<VoteReceipt>? Votes);
internal record DaosResponse(List<Dao>? Daos);
internal record MembersResponse(List<string>? Members);
internal record DelegationsResponse(List<DelegationReceipt>? Delegations);
internal record VestingContractsResponse(List<VestingContract>? Contracts);
internal record ReleasableResponse(string Amount);
internal record GovernanceHealthResponse(string Status);
public class GovernanceException : Exception
{
public string? Code { get; }
public int StatusCode { get; }
public GovernanceException(string message, string? code = null, int statusCode = 0)
: base(message)
{
Code = code;
StatusCode = statusCode;
}
}

View file

@ -0,0 +1,215 @@
using System.Net.Http.Json;
using System.Text.Json;
namespace Synor.Sdk.Mining;
/// <summary>
/// Synor Mining SDK client for C#.
/// Pool connections, block templates, hashrate stats, and GPU management.
/// </summary>
public class SynorMining : IDisposable
{
private readonly MiningConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
private StratumConnection? _activeConnection;
public SynorMining(MiningConfig config)
{
_config = config;
_httpClient = new HttpClient { BaseAddress = new Uri(config.Endpoint), Timeout = config.Timeout };
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
_httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");
_jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true };
}
// ==================== Pool Operations ====================
public async Task<StratumConnection> ConnectAsync(PoolConfig pool, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["url"] = pool.Url, ["user"] = pool.User };
if (pool.Password != null) body["password"] = pool.Password;
if (pool.Algorithm != null) body["algorithm"] = pool.Algorithm;
if (pool.Difficulty != null) body["difficulty"] = pool.Difficulty;
_activeConnection = await PostAsync<StratumConnection>("/pool/connect", body, ct);
return _activeConnection;
}
public async Task DisconnectAsync(CancellationToken ct = default)
{
if (_activeConnection == null) return;
await PostAsync<object>("/pool/disconnect", new { }, ct);
_activeConnection = null;
}
public async Task<StratumConnection> GetConnectionAsync(CancellationToken ct = default)
{
_activeConnection = await GetAsync<StratumConnection>("/pool/connection", ct);
return _activeConnection;
}
public async Task<PoolStats> GetPoolStatsAsync(CancellationToken ct = default)
=> await GetAsync<PoolStats>("/pool/stats", ct);
public bool IsConnected => _activeConnection?.Status == ConnectionStatus.Connected;
// ==================== Mining Operations ====================
public async Task<BlockTemplate> GetBlockTemplateAsync(CancellationToken ct = default)
=> await GetAsync<BlockTemplate>("/mining/template", ct);
public async Task<SubmitResult> SubmitWorkAsync(MinedWork work, CancellationToken ct = default)
=> await PostAsync<SubmitResult>("/mining/submit", work, ct);
public async Task StartMiningAsync(string? algorithm = null, CancellationToken ct = default)
{
var body = algorithm != null ? new { algorithm } : (object)new { };
await PostAsync<object>("/mining/start", body, ct);
}
public async Task StopMiningAsync(CancellationToken ct = default)
=> await PostAsync<object>("/mining/stop", new { }, ct);
public async Task<bool> IsMiningAsync(CancellationToken ct = default)
{
var r = await GetAsync<MiningStatusResponse>("/mining/status", ct);
return r.Mining;
}
// ==================== Stats Operations ====================
public async Task<Hashrate> GetHashrateAsync(CancellationToken ct = default)
=> await GetAsync<Hashrate>("/stats/hashrate", ct);
public async Task<MiningStats> GetStatsAsync(CancellationToken ct = default)
=> await GetAsync<MiningStats>("/stats", ct);
public async Task<Earnings> GetEarningsAsync(TimePeriod? period = null, CancellationToken ct = default)
{
var path = period.HasValue ? $"/stats/earnings?period={period.Value.ToString().ToLower()}" : "/stats/earnings";
return await GetAsync<Earnings>(path, ct);
}
public async Task<ShareStats> GetShareStatsAsync(CancellationToken ct = default)
=> await GetAsync<ShareStats>("/stats/shares", ct);
// ==================== Device Operations ====================
public async Task<List<MiningDevice>> ListDevicesAsync(CancellationToken ct = default)
{
var r = await GetAsync<DevicesResponse>("/devices", ct);
return r.Devices ?? new List<MiningDevice>();
}
public async Task<MiningDevice> GetDeviceAsync(string deviceId, CancellationToken ct = default)
=> await GetAsync<MiningDevice>($"/devices/{Uri.EscapeDataString(deviceId)}", ct);
public async Task<MiningDevice> SetDeviceConfigAsync(string deviceId, DeviceConfig config, CancellationToken ct = default)
=> await PutAsync<MiningDevice>($"/devices/{Uri.EscapeDataString(deviceId)}/config", config, ct);
public async Task EnableDeviceAsync(string deviceId, CancellationToken ct = default)
=> await SetDeviceConfigAsync(deviceId, new DeviceConfig(true), ct);
public async Task DisableDeviceAsync(string deviceId, CancellationToken ct = default)
=> await SetDeviceConfigAsync(deviceId, new DeviceConfig(false), ct);
// ==================== Worker Operations ====================
public async Task<List<WorkerInfo>> ListWorkersAsync(CancellationToken ct = default)
{
var r = await GetAsync<WorkersResponse>("/workers", ct);
return r.Workers ?? new List<WorkerInfo>();
}
public async Task<WorkerInfo> GetWorkerAsync(string workerId, CancellationToken ct = default)
=> await GetAsync<WorkerInfo>($"/workers/{Uri.EscapeDataString(workerId)}", ct);
public async Task RemoveWorkerAsync(string workerId, CancellationToken ct = default)
=> await DeleteAsync($"/workers/{Uri.EscapeDataString(workerId)}", ct);
// ==================== Algorithm Operations ====================
public async Task<List<MiningAlgorithm>> ListAlgorithmsAsync(CancellationToken ct = default)
{
var r = await GetAsync<AlgorithmsResponse>("/algorithms", ct);
return r.Algorithms ?? new List<MiningAlgorithm>();
}
public async Task<MiningAlgorithm> GetAlgorithmAsync(string name, CancellationToken ct = default)
=> await GetAsync<MiningAlgorithm>($"/algorithms/{Uri.EscapeDataString(name)}", ct);
public async Task SwitchAlgorithmAsync(string algorithm, CancellationToken ct = default)
=> await PostAsync<object>("/algorithms/switch", new { algorithm }, ct);
public async Task<MiningAlgorithm> GetMostProfitableAsync(CancellationToken ct = default)
=> await GetAsync<MiningAlgorithm>("/algorithms/profitable", ct);
// ==================== Lifecycle ====================
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
try { var r = await GetAsync<MiningHealthResponse>("/health", ct); return r.Status == "healthy"; }
catch { return false; }
}
public bool IsClosed => _disposed;
public void Dispose()
{
if (!_disposed) { _httpClient.Dispose(); _activeConnection = null; _disposed = true; }
GC.SuppressFinalize(this);
}
// ==================== Private HTTP Methods ====================
private async Task<T> GetAsync<T>(string path, CancellationToken ct)
=> await ExecuteAsync(async () => {
var r = await _httpClient.GetAsync(path, ct);
await EnsureSuccessAsync(r);
return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
});
private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
=> await ExecuteAsync(async () => {
var r = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
await EnsureSuccessAsync(r);
return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
});
private async Task<T> PutAsync<T>(string path, object body, CancellationToken ct)
=> await ExecuteAsync(async () => {
var r = await _httpClient.PutAsJsonAsync(path, body, _jsonOptions, ct);
await EnsureSuccessAsync(r);
return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
});
private async Task DeleteAsync(string path, CancellationToken ct)
=> await ExecuteAsync(async () => {
var r = await _httpClient.DeleteAsync(path, ct);
await EnsureSuccessAsync(r);
return true;
});
private async Task<T> ExecuteAsync<T>(Func<Task<T>> op)
{
Exception? err = null;
for (int i = 0; i < _config.Retries; i++)
{
try { return await op(); }
catch (Exception ex) { err = ex; if (i < _config.Retries - 1) await Task.Delay(1000 << i); }
}
throw err!;
}
private async Task EnsureSuccessAsync(HttpResponseMessage r)
{
if (!r.IsSuccessStatusCode)
{
var c = await r.Content.ReadAsStringAsync();
var e = JsonSerializer.Deserialize<Dictionary<string, object>>(c, _jsonOptions);
throw new MiningException(e?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)r.StatusCode}",
e?.GetValueOrDefault("code")?.ToString(), (int)r.StatusCode);
}
}
}

View file

@ -0,0 +1,219 @@
namespace Synor.Sdk.Mining;
public enum DeviceType { Cpu, GpuNvidia, GpuAmd, Asic }
public enum DeviceStatus { Idle, Mining, Error, Offline }
public enum ConnectionStatus { Disconnected, Connecting, Connected, Reconnecting }
public enum TimePeriod { Hour, Day, Week, Month, All }
public enum SubmitStatus { Accepted, Rejected, Stale }
public record MiningConfig(
string ApiKey,
string Endpoint = "https://mining.synor.io/v1",
TimeSpan? Timeout = null,
int Retries = 3,
bool Debug = false
) {
public TimeSpan Timeout { get; init; } = Timeout ?? TimeSpan.FromSeconds(60);
}
public record PoolConfig(
string Url,
string User,
string? Password = null,
string? Algorithm = null,
double? Difficulty = null
);
public record StratumConnection(
string Id,
string Pool,
ConnectionStatus Status,
string Algorithm,
double Difficulty,
long ConnectedAt,
int AcceptedShares,
int RejectedShares,
int StaleShares,
long? LastShareAt = null
);
public record PoolStats(
string Url,
int Workers,
double Hashrate,
double Difficulty,
long LastBlock,
int BlocksFound24h,
double Luck
);
public record TemplateTransaction(
string Txid,
string Data,
string Fee,
int Weight
);
public record BlockTemplate(
string Id,
string PreviousBlockHash,
string MerkleRoot,
long Timestamp,
string Bits,
long Height,
string CoinbaseValue,
List<TemplateTransaction> Transactions,
string Target,
string Algorithm,
string ExtraNonce
);
public record MinedWork(
string TemplateId,
string Nonce,
string ExtraNonce,
long Timestamp,
string Hash
);
public record ShareInfo(
string Hash,
double Difficulty,
long Timestamp,
bool Accepted
);
public record SubmitResult(
SubmitStatus Status,
ShareInfo Share,
bool BlockFound,
string? Reason = null,
string? BlockHash = null,
string? Reward = null
);
public record Hashrate(
double Current,
double Average1h,
double Average24h,
double Peak,
string Unit
);
public record ShareStats(
int Accepted,
int Rejected,
int Stale,
int Total,
double AcceptRate
);
public record DeviceTemperature(
double Current,
double Max,
bool Throttling
);
public record EarningsSnapshot(
string Today,
string Yesterday,
string ThisWeek,
string ThisMonth,
string Total,
string Currency
);
public record MiningStats(
Hashrate Hashrate,
ShareStats Shares,
long Uptime,
double Efficiency,
EarningsSnapshot Earnings,
double? PowerConsumption = null,
DeviceTemperature? Temperature = null
);
public record EarningsBreakdown(
long Date,
string Amount,
int Blocks,
int Shares,
double Hashrate
);
public record Earnings(
TimePeriod Period,
long StartDate,
long EndDate,
string Amount,
int Blocks,
int Shares,
double AverageHashrate,
string Currency,
List<EarningsBreakdown> Breakdown
);
public record MiningDevice(
string Id,
string Name,
DeviceType Type,
DeviceStatus Status,
double Hashrate,
double Temperature,
double FanSpeed,
double PowerDraw,
long MemoryUsed,
long MemoryTotal,
string? Driver = null,
string? Firmware = null
);
public record DeviceConfig(
bool Enabled,
int? Intensity = null,
int? PowerLimit = null,
int? CoreClockOffset = null,
int? MemoryClockOffset = null,
int? FanSpeed = null
);
public record WorkerInfo(
string Id,
string Name,
ConnectionStatus Status,
Hashrate Hashrate,
ShareStats Shares,
List<MiningDevice> Devices,
long LastSeen,
long Uptime
);
public record MiningAlgorithm(
string Name,
string DisplayName,
string HashUnit,
string Profitability,
double Difficulty,
string BlockReward,
int BlockTime
);
// Response wrappers
internal record DevicesResponse(List<MiningDevice>? Devices);
internal record WorkersResponse(List<WorkerInfo>? Workers);
internal record AlgorithmsResponse(List<MiningAlgorithm>? Algorithms);
internal record MiningStatusResponse(bool Mining);
internal record MiningHealthResponse(string Status);
public class MiningException : Exception
{
public string? Code { get; }
public int StatusCode { get; }
public MiningException(string message, string? code = null, int statusCode = 0)
: base(message)
{
Code = code;
StatusCode = statusCode;
}
}

View file

@ -51,7 +51,7 @@ class SynorDatabase {
String path, [ String path, [
Map<String, dynamic>? body, Map<String, dynamic>? body,
]) async { ]) async {
if (_closed) throw DatabaseException('Client has been closed'); if (_closed) throw const DatabaseException('Client has been closed');
Exception? lastError; Exception? lastError;
for (var attempt = 0; attempt < config.retries; attempt++) { for (var attempt = 0; attempt < config.retries; attempt++) {
@ -64,7 +64,7 @@ class SynorDatabase {
} }
} }
} }
throw lastError ?? DatabaseException('Unknown error'); throw lastError ?? const DatabaseException('Unknown error');
} }
Future<Map<String, dynamic>> _doRequest( Future<Map<String, dynamic>> _doRequest(

View file

@ -0,0 +1,217 @@
/// Synor Economics SDK Client for Flutter
library synor_economics;
import 'dart:convert';
import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'types.dart';
export 'types.dart';
/// Synor Economics Client
class SynorEconomics {
final EconomicsConfig config;
final http.Client _client;
bool _closed = false;
SynorEconomics(this.config) : _client = http.Client();
// ==================== Pricing Operations ====================
Future<Price> getPrice(ServiceType service, UsageMetrics usage) async {
final resp = await _request('POST', '/pricing/calculate', {
'service': service.name,
'usage': usage.toJson(),
});
return Price.fromJson(resp);
}
Future<CostEstimate> estimateCost(UsagePlan plan) async {
final resp = await _request('POST', '/pricing/estimate', plan.toJson());
return CostEstimate.fromJson(resp);
}
Future<List<PricingTier>> getPricingTiers(ServiceType service) async {
final resp = await _request('GET', '/pricing/tiers/${service.name}');
return (resp['tiers'] as List).map((e) => PricingTier.fromJson(e)).toList();
}
// ==================== Billing Operations ====================
Future<Usage> getUsage({BillingPeriod? period}) async {
final path = period != null ? '/billing/usage?period=${period.name}' : '/billing/usage';
final resp = await _request('GET', path);
return Usage.fromJson(resp);
}
Future<Usage> getUsageByDateRange(int startDate, int endDate) async {
final resp = await _request('GET', '/billing/usage?startDate=$startDate&endDate=$endDate');
return Usage.fromJson(resp);
}
Future<List<Invoice>> getInvoices() async {
final resp = await _request('GET', '/billing/invoices');
return (resp['invoices'] as List).map((e) => Invoice.fromJson(e)).toList();
}
Future<Invoice> getInvoice(String invoiceId) async {
final resp = await _request('GET', '/billing/invoices/${Uri.encodeComponent(invoiceId)}');
return Invoice.fromJson(resp);
}
Future<Uint8List> downloadInvoice(String invoiceId) async {
final resp = await _request('GET', '/billing/invoices/${Uri.encodeComponent(invoiceId)}/pdf');
return base64Decode(resp['data'] as String);
}
Future<AccountBalance> getBalance() async {
final resp = await _request('GET', '/billing/balance');
return AccountBalance.fromJson(resp);
}
Future<void> addCredits(String amount, String paymentMethod) async {
await _request('POST', '/billing/credits', {
'amount': amount,
'paymentMethod': paymentMethod,
});
}
// ==================== Staking Operations ====================
Future<StakeReceipt> stake(String amount, {int? durationDays}) async {
final body = <String, dynamic>{'amount': amount};
if (durationDays != null) body['durationDays'] = durationDays;
final resp = await _request('POST', '/staking/stake', body);
return StakeReceipt.fromJson(resp);
}
Future<UnstakeReceipt> unstake(String stakeId) async {
final resp = await _request('POST', '/staking/unstake/${Uri.encodeComponent(stakeId)}');
return UnstakeReceipt.fromJson(resp);
}
Future<List<StakeInfo>> getStakes() async {
final resp = await _request('GET', '/staking/stakes');
return (resp['stakes'] as List).map((e) => StakeInfo.fromJson(e)).toList();
}
Future<StakeInfo> getStake(String stakeId) async {
final resp = await _request('GET', '/staking/stakes/${Uri.encodeComponent(stakeId)}');
return StakeInfo.fromJson(resp);
}
Future<Rewards> getStakingRewards() async {
final resp = await _request('GET', '/staking/rewards');
return Rewards.fromJson(resp);
}
Future<String> claimRewards() async {
final resp = await _request('POST', '/staking/rewards/claim');
return resp['txHash'] as String;
}
Future<double> getCurrentApy() async {
final resp = await _request('GET', '/staking/apy');
return (resp['apy'] as num).toDouble();
}
// ==================== Discount Operations ====================
Future<AppliedDiscount> applyDiscount(String code) async {
final resp = await _request('POST', '/discounts/apply', {'code': code});
return AppliedDiscount.fromJson(resp);
}
Future<List<Discount>> getAvailableDiscounts() async {
final resp = await _request('GET', '/discounts/available');
return (resp['discounts'] as List).map((e) => Discount.fromJson(e)).toList();
}
Future<Discount> validateDiscount(String code) async {
final resp = await _request('GET', '/discounts/validate/${Uri.encodeComponent(code)}');
return Discount.fromJson(resp);
}
Future<List<AppliedDiscount>> getAppliedDiscounts() async {
final resp = await _request('GET', '/discounts/applied');
return (resp['discounts'] as List).map((e) => AppliedDiscount.fromJson(e)).toList();
}
Future<void> removeDiscount(String discountId) async {
await _request('DELETE', '/discounts/${Uri.encodeComponent(discountId)}');
}
// ==================== Lifecycle ====================
void close() {
_closed = true;
_client.close();
}
bool get isClosed => _closed;
Future<bool> healthCheck() async {
try {
final resp = await _request('GET', '/health');
return resp['status'] == 'healthy';
} catch (_) {
return false;
}
}
// ==================== Private Methods ====================
Future<Map<String, dynamic>> _request(String method, String path, [Map<String, dynamic>? body]) async {
if (_closed) throw const EconomicsException('Client has been closed');
Exception? lastError;
for (var attempt = 0; attempt < config.retries; attempt++) {
try {
return await _doRequest(method, path, body);
} catch (e) {
lastError = e as Exception;
if (attempt < config.retries - 1) {
await Future.delayed(Duration(seconds: 1 << attempt));
}
}
}
throw lastError ?? const EconomicsException('Unknown error');
}
Future<Map<String, dynamic>> _doRequest(String method, String path, Map<String, dynamic>? body) async {
final uri = Uri.parse('${config.endpoint}$path');
final headers = {
'Authorization': 'Bearer ${config.apiKey}',
'Content-Type': 'application/json',
'X-SDK-Version': 'flutter/0.1.0',
};
http.Response response;
switch (method) {
case 'GET':
response = await _client.get(uri, headers: headers).timeout(config.timeout);
break;
case 'POST':
response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null).timeout(config.timeout);
break;
case 'PUT':
response = await _client.put(uri, headers: headers, body: body != null ? jsonEncode(body) : null).timeout(config.timeout);
break;
case 'DELETE':
response = await _client.delete(uri, headers: headers).timeout(config.timeout);
break;
default:
throw EconomicsException('Unknown method: $method');
}
final respBody = jsonDecode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 400) {
throw EconomicsException(
respBody['message'] as String? ?? 'HTTP ${response.statusCode}',
code: respBody['code'] as String?,
statusCode: response.statusCode,
);
}
return respBody;
}
}

View file

@ -0,0 +1,532 @@
/// Synor Economics SDK Types for Flutter
library synor_economics_types;
// ==================== Enums ====================
enum ServiceType { compute, storage, database, hosting, bridge, mining, rpc }
enum BillingPeriod { daily, weekly, monthly, yearly }
enum StakeStatus { active, unstaking, unlocked, slashed }
enum DiscountType { percentage, fixed, volume }
// ==================== Pricing Types ====================
class UsageMetrics {
final double computeHours;
final double storageGb;
final int requests;
final double bandwidthGb;
final double gpuHours;
const UsageMetrics({
this.computeHours = 0,
this.storageGb = 0,
this.requests = 0,
this.bandwidthGb = 0,
this.gpuHours = 0,
});
Map<String, dynamic> toJson() => {
'computeHours': computeHours,
'storageGb': storageGb,
'requests': requests,
'bandwidthGb': bandwidthGb,
'gpuHours': gpuHours,
};
}
class PricingTier {
final String name;
final double upTo;
final double pricePerUnit;
final String unit;
const PricingTier({
required this.name,
required this.upTo,
required this.pricePerUnit,
required this.unit,
});
factory PricingTier.fromJson(Map<String, dynamic> json) => PricingTier(
name: json['name'] as String,
upTo: (json['upTo'] as num).toDouble(),
pricePerUnit: (json['pricePerUnit'] as num).toDouble(),
unit: json['unit'] as String,
);
}
class Price {
final ServiceType service;
final String amount;
final String currency;
final String amountUsd;
final List<PricingTier>? breakdown;
final String? discount;
final String finalAmount;
const Price({
required this.service,
required this.amount,
required this.currency,
required this.amountUsd,
this.breakdown,
this.discount,
required this.finalAmount,
});
factory Price.fromJson(Map<String, dynamic> json) => Price(
service: ServiceType.values.firstWhere((e) => e.name == json['service']),
amount: json['amount'] as String,
currency: json['currency'] as String,
amountUsd: json['amountUsd'] as String,
breakdown: (json['breakdown'] as List?)?.map((e) => PricingTier.fromJson(e)).toList(),
discount: json['discount'] as String?,
finalAmount: json['finalAmount'] as String,
);
}
class UsagePlan {
final ServiceType service;
final UsageMetrics estimatedUsage;
final BillingPeriod period;
const UsagePlan({required this.service, required this.estimatedUsage, required this.period});
Map<String, dynamic> toJson() => {
'service': service.name,
'estimatedUsage': estimatedUsage.toJson(),
'period': period.name,
};
}
class CostBreakdown {
final String category;
final String amount;
final double percentage;
const CostBreakdown({required this.category, required this.amount, required this.percentage});
factory CostBreakdown.fromJson(Map<String, dynamic> json) => CostBreakdown(
category: json['category'] as String,
amount: json['amount'] as String,
percentage: (json['percentage'] as num).toDouble(),
);
}
class CostEstimate {
final ServiceType service;
final BillingPeriod period;
final String estimatedCost;
final String estimatedCostUsd;
final String currency;
final String confidenceLevel;
final List<CostBreakdown>? breakdown;
final String? savingsFromStaking;
const CostEstimate({
required this.service,
required this.period,
required this.estimatedCost,
required this.estimatedCostUsd,
required this.currency,
required this.confidenceLevel,
this.breakdown,
this.savingsFromStaking,
});
factory CostEstimate.fromJson(Map<String, dynamic> json) => CostEstimate(
service: ServiceType.values.firstWhere((e) => e.name == json['service']),
period: BillingPeriod.values.firstWhere((e) => e.name == json['period']),
estimatedCost: json['estimatedCost'] as String,
estimatedCostUsd: json['estimatedCostUsd'] as String,
currency: json['currency'] as String,
confidenceLevel: json['confidenceLevel'] as String,
breakdown: (json['breakdown'] as List?)?.map((e) => CostBreakdown.fromJson(e)).toList(),
savingsFromStaking: json['savingsFromStaking'] as String?,
);
}
// ==================== Billing Types ====================
class UsageRecord {
final ServiceType service;
final String resource;
final double quantity;
final String unit;
final int timestamp;
final String cost;
const UsageRecord({
required this.service,
required this.resource,
required this.quantity,
required this.unit,
required this.timestamp,
required this.cost,
});
factory UsageRecord.fromJson(Map<String, dynamic> json) => UsageRecord(
service: ServiceType.values.firstWhere((e) => e.name == json['service']),
resource: json['resource'] as String,
quantity: (json['quantity'] as num).toDouble(),
unit: json['unit'] as String,
timestamp: json['timestamp'] as int,
cost: json['cost'] as String,
);
}
class Usage {
final BillingPeriod period;
final int startDate;
final int endDate;
final List<UsageRecord> records;
final String totalCost;
final String currency;
const Usage({
required this.period,
required this.startDate,
required this.endDate,
required this.records,
required this.totalCost,
required this.currency,
});
factory Usage.fromJson(Map<String, dynamic> json) => Usage(
period: BillingPeriod.values.firstWhere((e) => e.name == json['period']),
startDate: json['startDate'] as int,
endDate: json['endDate'] as int,
records: (json['records'] as List).map((e) => UsageRecord.fromJson(e)).toList(),
totalCost: json['totalCost'] as String,
currency: json['currency'] as String,
);
}
class InvoiceLineItem {
final String description;
final double quantity;
final String unit;
final String unitPrice;
final String amount;
const InvoiceLineItem({
required this.description,
required this.quantity,
required this.unit,
required this.unitPrice,
required this.amount,
});
factory InvoiceLineItem.fromJson(Map<String, dynamic> json) => InvoiceLineItem(
description: json['description'] as String,
quantity: (json['quantity'] as num).toDouble(),
unit: json['unit'] as String,
unitPrice: json['unitPrice'] as String,
amount: json['amount'] as String,
);
}
class Invoice {
final String id;
final String number;
final int periodStart;
final int periodEnd;
final String subtotal;
final String tax;
final String discount;
final String total;
final String currency;
final String status;
final int? dueDate;
final int? paidAt;
final List<InvoiceLineItem> lineItems;
final String? pdfUrl;
const Invoice({
required this.id,
required this.number,
required this.periodStart,
required this.periodEnd,
required this.subtotal,
required this.tax,
required this.discount,
required this.total,
required this.currency,
required this.status,
this.dueDate,
this.paidAt,
required this.lineItems,
this.pdfUrl,
});
factory Invoice.fromJson(Map<String, dynamic> json) => Invoice(
id: json['id'] as String,
number: json['number'] as String,
periodStart: json['periodStart'] as int,
periodEnd: json['periodEnd'] as int,
subtotal: json['subtotal'] as String,
tax: json['tax'] as String,
discount: json['discount'] as String,
total: json['total'] as String,
currency: json['currency'] as String,
status: json['status'] as String,
dueDate: json['dueDate'] as int?,
paidAt: json['paidAt'] as int?,
lineItems: (json['lineItems'] as List).map((e) => InvoiceLineItem.fromJson(e)).toList(),
pdfUrl: json['pdfUrl'] as String?,
);
}
class AccountBalance {
final String available;
final String pending;
final String reserved;
final String staked;
final String currency;
final int lastUpdated;
const AccountBalance({
required this.available,
required this.pending,
required this.reserved,
required this.staked,
required this.currency,
required this.lastUpdated,
});
factory AccountBalance.fromJson(Map<String, dynamic> json) => AccountBalance(
available: json['available'] as String,
pending: json['pending'] as String,
reserved: json['reserved'] as String,
staked: json['staked'] as String,
currency: json['currency'] as String,
lastUpdated: json['lastUpdated'] as int,
);
}
// ==================== Staking Types ====================
class StakeReceipt {
final String id;
final String txHash;
final String amount;
final int startDate;
final int endDate;
final double apy;
final StakeStatus status;
const StakeReceipt({
required this.id,
required this.txHash,
required this.amount,
required this.startDate,
required this.endDate,
required this.apy,
required this.status,
});
factory StakeReceipt.fromJson(Map<String, dynamic> json) => StakeReceipt(
id: json['id'] as String,
txHash: json['txHash'] as String,
amount: json['amount'] as String,
startDate: json['startDate'] as int,
endDate: json['endDate'] as int,
apy: (json['apy'] as num).toDouble(),
status: StakeStatus.values.firstWhere((e) => e.name == json['status']),
);
}
class StakeInfo {
final String id;
final String amount;
final StakeStatus status;
final int startDate;
final int endDate;
final double apy;
final String earnedRewards;
final String pendingRewards;
final int? unlockDate;
final int discountPercent;
const StakeInfo({
required this.id,
required this.amount,
required this.status,
required this.startDate,
required this.endDate,
required this.apy,
required this.earnedRewards,
required this.pendingRewards,
this.unlockDate,
this.discountPercent = 0,
});
factory StakeInfo.fromJson(Map<String, dynamic> json) => StakeInfo(
id: json['id'] as String,
amount: json['amount'] as String,
status: StakeStatus.values.firstWhere((e) => e.name == json['status']),
startDate: json['startDate'] as int,
endDate: json['endDate'] as int,
apy: (json['apy'] as num).toDouble(),
earnedRewards: json['earnedRewards'] as String,
pendingRewards: json['pendingRewards'] as String,
unlockDate: json['unlockDate'] as int?,
discountPercent: json['discountPercent'] as int? ?? 0,
);
}
class UnstakeReceipt {
final String id;
final String txHash;
final String amount;
final int initiatedAt;
final int availableAt;
final String? penalty;
const UnstakeReceipt({
required this.id,
required this.txHash,
required this.amount,
required this.initiatedAt,
required this.availableAt,
this.penalty,
});
factory UnstakeReceipt.fromJson(Map<String, dynamic> json) => UnstakeReceipt(
id: json['id'] as String,
txHash: json['txHash'] as String,
amount: json['amount'] as String,
initiatedAt: json['initiatedAt'] as int,
availableAt: json['availableAt'] as int,
penalty: json['penalty'] as String?,
);
}
class RewardRecord {
final int date;
final String amount;
final String type;
final String? txHash;
const RewardRecord({required this.date, required this.amount, required this.type, this.txHash});
factory RewardRecord.fromJson(Map<String, dynamic> json) => RewardRecord(
date: json['date'] as int,
amount: json['amount'] as String,
type: json['type'] as String,
txHash: json['txHash'] as String?,
);
}
class Rewards {
final String totalEarned;
final String pendingClaim;
final String? lastClaimDate;
final double currentApy;
final List<RewardRecord> history;
const Rewards({
required this.totalEarned,
required this.pendingClaim,
this.lastClaimDate,
required this.currentApy,
required this.history,
});
factory Rewards.fromJson(Map<String, dynamic> json) => Rewards(
totalEarned: json['totalEarned'] as String,
pendingClaim: json['pendingClaim'] as String,
lastClaimDate: json['lastClaimDate'] as String?,
currentApy: (json['currentApy'] as num).toDouble(),
history: (json['history'] as List).map((e) => RewardRecord.fromJson(e)).toList(),
);
}
// ==================== Discount Types ====================
class Discount {
final String id;
final String code;
final DiscountType type;
final String value;
final int? validFrom;
final int? validUntil;
final int? usageLimit;
final int usageCount;
final List<ServiceType>? applicableServices;
final String? minimumSpend;
final bool active;
const Discount({
required this.id,
required this.code,
required this.type,
required this.value,
this.validFrom,
this.validUntil,
this.usageLimit,
this.usageCount = 0,
this.applicableServices,
this.minimumSpend,
this.active = true,
});
factory Discount.fromJson(Map<String, dynamic> json) => Discount(
id: json['id'] as String,
code: json['code'] as String,
type: DiscountType.values.firstWhere((e) => e.name == json['type']),
value: json['value'] as String,
validFrom: json['validFrom'] as int?,
validUntil: json['validUntil'] as int?,
usageLimit: json['usageLimit'] as int?,
usageCount: json['usageCount'] as int? ?? 0,
applicableServices: (json['applicableServices'] as List?)
?.map((e) => ServiceType.values.firstWhere((s) => s.name == e))
.toList(),
minimumSpend: json['minimumSpend'] as String?,
active: json['active'] as bool? ?? true,
);
}
class AppliedDiscount {
final Discount discount;
final String savedAmount;
final int appliedAt;
const AppliedDiscount({required this.discount, required this.savedAmount, required this.appliedAt});
factory AppliedDiscount.fromJson(Map<String, dynamic> json) => AppliedDiscount(
discount: Discount.fromJson(json['discount']),
savedAmount: json['savedAmount'] as String,
appliedAt: json['appliedAt'] as int,
);
}
// ==================== Config & Error ====================
class EconomicsConfig {
final String apiKey;
final String endpoint;
final Duration timeout;
final int retries;
final bool debug;
const EconomicsConfig({
required this.apiKey,
this.endpoint = 'https://economics.synor.io/v1',
this.timeout = const Duration(seconds: 60),
this.retries = 3,
this.debug = false,
});
}
class EconomicsException implements Exception {
final String message;
final String? code;
final int statusCode;
const EconomicsException(this.message, {this.code, this.statusCode = 0});
@override
String toString() => 'EconomicsException: $message';
}

View file

@ -0,0 +1,250 @@
/// Synor Governance SDK Client for Flutter
library synor_governance;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'types.dart';
export 'types.dart';
/// Synor Governance Client
class SynorGovernance {
final GovernanceConfig config;
final http.Client _client;
bool _closed = false;
static const _finalStatuses = {
ProposalStatus.passed,
ProposalStatus.rejected,
ProposalStatus.executed,
ProposalStatus.cancelled,
};
SynorGovernance(this.config) : _client = http.Client();
// ==================== Proposal Operations ====================
Future<Proposal> createProposal(ProposalDraft proposal) async {
final resp = await _request('POST', '/proposals', proposal.toJson());
return Proposal.fromJson(resp);
}
Future<Proposal> getProposal(String proposalId) async {
final resp = await _request('GET', '/proposals/${Uri.encodeComponent(proposalId)}');
return Proposal.fromJson(resp);
}
Future<List<Proposal>> listProposals({ProposalFilter? filter}) async {
final params = <String>[];
if (filter?.status != null) params.add('status=${filter!.status!.name}');
if (filter?.proposer != null) params.add('proposer=${Uri.encodeComponent(filter!.proposer!)}');
if (filter?.daoId != null) params.add('daoId=${Uri.encodeComponent(filter!.daoId!)}');
if (filter?.limit != null) params.add('limit=${filter!.limit}');
if (filter?.offset != null) params.add('offset=${filter!.offset}');
final path = params.isEmpty ? '/proposals' : '/proposals?${params.join('&')}';
final resp = await _request('GET', path);
return (resp['proposals'] as List).map((e) => Proposal.fromJson(e)).toList();
}
Future<Proposal> cancelProposal(String proposalId) async {
final resp = await _request('POST', '/proposals/${Uri.encodeComponent(proposalId)}/cancel');
return Proposal.fromJson(resp);
}
Future<Proposal> executeProposal(String proposalId) async {
final resp = await _request('POST', '/proposals/${Uri.encodeComponent(proposalId)}/execute');
return Proposal.fromJson(resp);
}
Future<Proposal> waitForProposal(
String proposalId, {
Duration pollInterval = const Duration(minutes: 1),
Duration maxWait = const Duration(days: 7),
}) async {
final deadline = DateTime.now().add(maxWait);
while (DateTime.now().isBefore(deadline)) {
final proposal = await getProposal(proposalId);
if (_finalStatuses.contains(proposal.status)) return proposal;
await Future.delayed(pollInterval);
}
throw const GovernanceException('Timeout waiting for proposal completion');
}
// ==================== Voting Operations ====================
Future<VoteReceipt> vote(String proposalId, Vote vote, {String? weight}) async {
final body = <String, dynamic>{'choice': vote.choice.name};
if (vote.reason != null) body['reason'] = vote.reason;
if (weight != null) body['weight'] = weight;
final resp = await _request('POST', '/proposals/${Uri.encodeComponent(proposalId)}/vote', body);
return VoteReceipt.fromJson(resp);
}
Future<List<VoteReceipt>> getVotes(String proposalId) async {
final resp = await _request('GET', '/proposals/${Uri.encodeComponent(proposalId)}/votes');
return (resp['votes'] as List).map((e) => VoteReceipt.fromJson(e)).toList();
}
Future<VoteReceipt> getMyVote(String proposalId) async {
final resp = await _request('GET', '/proposals/${Uri.encodeComponent(proposalId)}/votes/me');
return VoteReceipt.fromJson(resp);
}
Future<DelegationReceipt> delegate(String delegatee, {String? amount}) async {
final body = <String, dynamic>{'delegatee': delegatee};
if (amount != null) body['amount'] = amount;
final resp = await _request('POST', '/voting/delegate', body);
return DelegationReceipt.fromJson(resp);
}
Future<DelegationReceipt> undelegate(String delegatee) async {
final resp = await _request('POST', '/voting/undelegate', {'delegatee': delegatee});
return DelegationReceipt.fromJson(resp);
}
Future<VotingPower> getVotingPower(String address) async {
final resp = await _request('GET', '/voting/power/${Uri.encodeComponent(address)}');
return VotingPower.fromJson(resp);
}
Future<List<DelegationReceipt>> getDelegations(String address) async {
final resp = await _request('GET', '/voting/delegations/${Uri.encodeComponent(address)}');
return (resp['delegations'] as List).map((e) => DelegationReceipt.fromJson(e)).toList();
}
// ==================== DAO Operations ====================
Future<Dao> createDao(DaoConfig daoConfig) async {
final resp = await _request('POST', '/daos', daoConfig.toJson());
return Dao.fromJson(resp);
}
Future<Dao> getDao(String daoId) async {
final resp = await _request('GET', '/daos/${Uri.encodeComponent(daoId)}');
return Dao.fromJson(resp);
}
Future<List<Dao>> listDaos({int? limit, int? offset}) async {
final params = <String>[];
if (limit != null) params.add('limit=$limit');
if (offset != null) params.add('offset=$offset');
final path = params.isEmpty ? '/daos' : '/daos?${params.join('&')}';
final resp = await _request('GET', path);
return (resp['daos'] as List).map((e) => Dao.fromJson(e)).toList();
}
Future<DaoTreasury> getDaoTreasury(String daoId) async {
final resp = await _request('GET', '/daos/${Uri.encodeComponent(daoId)}/treasury');
return DaoTreasury.fromJson(resp);
}
Future<List<String>> getDaoMembers(String daoId) async {
final resp = await _request('GET', '/daos/${Uri.encodeComponent(daoId)}/members');
return (resp['members'] as List).cast<String>();
}
// ==================== Vesting Operations ====================
Future<VestingContract> createVestingSchedule(VestingSchedule schedule) async {
final resp = await _request('POST', '/vesting', schedule.toJson());
return VestingContract.fromJson(resp);
}
Future<VestingContract> getVestingContract(String contractId) async {
final resp = await _request('GET', '/vesting/${Uri.encodeComponent(contractId)}');
return VestingContract.fromJson(resp);
}
Future<List<VestingContract>> listVestingContracts({String? beneficiary}) async {
final path = beneficiary != null ? '/vesting?beneficiary=${Uri.encodeComponent(beneficiary)}' : '/vesting';
final resp = await _request('GET', path);
return (resp['contracts'] as List).map((e) => VestingContract.fromJson(e)).toList();
}
Future<ClaimReceipt> claimVested(String contractId) async {
final resp = await _request('POST', '/vesting/${Uri.encodeComponent(contractId)}/claim');
return ClaimReceipt.fromJson(resp);
}
Future<VestingContract> revokeVesting(String contractId) async {
final resp = await _request('POST', '/vesting/${Uri.encodeComponent(contractId)}/revoke');
return VestingContract.fromJson(resp);
}
Future<String> getReleasableAmount(String contractId) async {
final resp = await _request('GET', '/vesting/${Uri.encodeComponent(contractId)}/releasable');
return resp['amount'] as String;
}
// ==================== Lifecycle ====================
void close() {
_closed = true;
_client.close();
}
bool get isClosed => _closed;
Future<bool> healthCheck() async {
try {
final resp = await _request('GET', '/health');
return resp['status'] == 'healthy';
} catch (_) {
return false;
}
}
// ==================== Private Methods ====================
Future<Map<String, dynamic>> _request(String method, String path, [Map<String, dynamic>? body]) async {
if (_closed) throw const GovernanceException('Client has been closed');
Exception? lastError;
for (var attempt = 0; attempt < config.retries; attempt++) {
try {
return await _doRequest(method, path, body);
} catch (e) {
lastError = e as Exception;
if (attempt < config.retries - 1) {
await Future.delayed(Duration(seconds: 1 << attempt));
}
}
}
throw lastError ?? const GovernanceException('Unknown error');
}
Future<Map<String, dynamic>> _doRequest(String method, String path, Map<String, dynamic>? body) async {
final uri = Uri.parse('${config.endpoint}$path');
final headers = {
'Authorization': 'Bearer ${config.apiKey}',
'Content-Type': 'application/json',
'X-SDK-Version': 'flutter/0.1.0',
};
http.Response response;
switch (method) {
case 'GET':
response = await _client.get(uri, headers: headers).timeout(config.timeout);
break;
case 'POST':
response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null).timeout(config.timeout);
break;
case 'DELETE':
response = await _client.delete(uri, headers: headers).timeout(config.timeout);
break;
default:
throw GovernanceException('Unknown method: $method');
}
final respBody = jsonDecode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 400) {
throw GovernanceException(
respBody['message'] as String? ?? 'HTTP ${response.statusCode}',
code: respBody['code'] as String?,
statusCode: response.statusCode,
);
}
return respBody;
}
}

View file

@ -0,0 +1,497 @@
/// Synor Governance SDK Types for Flutter
library synor_governance_types;
// ==================== Enums ====================
enum ProposalStatus { draft, active, passed, rejected, executed, cancelled }
enum VoteChoice { yes, no, abstain }
enum DaoType { token, multisig, hybrid }
enum VestingStatus { pending, active, paused, completed, revoked }
// ==================== Proposal Types ====================
class ProposalAction {
final String target;
final String method;
final String data;
final String? value;
const ProposalAction({required this.target, required this.method, required this.data, this.value});
Map<String, dynamic> toJson() => {'target': target, 'method': method, 'data': data, if (value != null) 'value': value};
factory ProposalAction.fromJson(Map<String, dynamic> json) => ProposalAction(
target: json['target'] as String,
method: json['method'] as String,
data: json['data'] as String,
value: json['value'] as String?,
);
}
class ProposalDraft {
final String title;
final String description;
final String? discussionUrl;
final int? votingStartTime;
final int? votingEndTime;
final List<ProposalAction>? actions;
final String? daoId;
const ProposalDraft({
required this.title,
required this.description,
this.discussionUrl,
this.votingStartTime,
this.votingEndTime,
this.actions,
this.daoId,
});
Map<String, dynamic> toJson() => {
'title': title,
'description': description,
if (discussionUrl != null) 'discussionUrl': discussionUrl,
if (votingStartTime != null) 'votingStartTime': votingStartTime,
if (votingEndTime != null) 'votingEndTime': votingEndTime,
if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(),
if (daoId != null) 'daoId': daoId,
};
}
class VoteBreakdown {
final String yes;
final String no;
final String abstain;
final String quorum;
final double quorumPercent;
final bool quorumReached;
const VoteBreakdown({
required this.yes,
required this.no,
required this.abstain,
required this.quorum,
required this.quorumPercent,
required this.quorumReached,
});
factory VoteBreakdown.fromJson(Map<String, dynamic> json) => VoteBreakdown(
yes: json['yes'] as String,
no: json['no'] as String,
abstain: json['abstain'] as String,
quorum: json['quorum'] as String,
quorumPercent: (json['quorumPercent'] as num).toDouble(),
quorumReached: json['quorumReached'] as bool,
);
}
class Proposal {
final String id;
final String title;
final String description;
final String proposer;
final ProposalStatus status;
final int createdAt;
final int votingStartTime;
final int votingEndTime;
final VoteBreakdown votes;
final String? discussionUrl;
final List<ProposalAction>? actions;
final String? daoId;
final String? snapshotBlock;
final int? executedAt;
final String? executedTxHash;
const Proposal({
required this.id,
required this.title,
required this.description,
required this.proposer,
required this.status,
required this.createdAt,
required this.votingStartTime,
required this.votingEndTime,
required this.votes,
this.discussionUrl,
this.actions,
this.daoId,
this.snapshotBlock,
this.executedAt,
this.executedTxHash,
});
factory Proposal.fromJson(Map<String, dynamic> json) => Proposal(
id: json['id'] as String,
title: json['title'] as String,
description: json['description'] as String,
proposer: json['proposer'] as String,
status: ProposalStatus.values.firstWhere((e) => e.name == json['status']),
createdAt: json['createdAt'] as int,
votingStartTime: json['votingStartTime'] as int,
votingEndTime: json['votingEndTime'] as int,
votes: VoteBreakdown.fromJson(json['votes']),
discussionUrl: json['discussionUrl'] as String?,
actions: (json['actions'] as List?)?.map((e) => ProposalAction.fromJson(e)).toList(),
daoId: json['daoId'] as String?,
snapshotBlock: json['snapshotBlock'] as String?,
executedAt: json['executedAt'] as int?,
executedTxHash: json['executedTxHash'] as String?,
);
}
class ProposalFilter {
final ProposalStatus? status;
final String? proposer;
final String? daoId;
final int? limit;
final int? offset;
const ProposalFilter({this.status, this.proposer, this.daoId, this.limit, this.offset});
}
// ==================== Voting Types ====================
class Vote {
final VoteChoice choice;
final String? reason;
const Vote({required this.choice, this.reason});
}
class VoteReceipt {
final String id;
final String proposalId;
final String voter;
final VoteChoice choice;
final String weight;
final int timestamp;
final String txHash;
final String? reason;
const VoteReceipt({
required this.id,
required this.proposalId,
required this.voter,
required this.choice,
required this.weight,
required this.timestamp,
required this.txHash,
this.reason,
});
factory VoteReceipt.fromJson(Map<String, dynamic> json) => VoteReceipt(
id: json['id'] as String,
proposalId: json['proposalId'] as String,
voter: json['voter'] as String,
choice: VoteChoice.values.firstWhere((e) => e.name == json['choice']),
weight: json['weight'] as String,
timestamp: json['timestamp'] as int,
txHash: json['txHash'] as String,
reason: json['reason'] as String?,
);
}
class VotingPower {
final String address;
final String balance;
final String? delegatedTo;
final String delegatedFrom;
final String totalPower;
final int blockNumber;
const VotingPower({
required this.address,
required this.balance,
this.delegatedTo,
required this.delegatedFrom,
required this.totalPower,
required this.blockNumber,
});
factory VotingPower.fromJson(Map<String, dynamic> json) => VotingPower(
address: json['address'] as String,
balance: json['balance'] as String,
delegatedTo: json['delegatedTo'] as String?,
delegatedFrom: json['delegatedFrom'] as String,
totalPower: json['totalPower'] as String,
blockNumber: json['blockNumber'] as int,
);
}
class DelegationReceipt {
final String id;
final String delegator;
final String delegatee;
final String amount;
final int timestamp;
final String txHash;
const DelegationReceipt({
required this.id,
required this.delegator,
required this.delegatee,
required this.amount,
required this.timestamp,
required this.txHash,
});
factory DelegationReceipt.fromJson(Map<String, dynamic> json) => DelegationReceipt(
id: json['id'] as String,
delegator: json['delegator'] as String,
delegatee: json['delegatee'] as String,
amount: json['amount'] as String,
timestamp: json['timestamp'] as int,
txHash: json['txHash'] as String,
);
}
// ==================== DAO Types ====================
class DaoConfig {
final String name;
final String description;
final DaoType type;
final String? tokenAddress;
final String? quorumPercent;
final int votingPeriodDays;
final int timelockDays;
final int? proposalThreshold;
final List<String>? multisigMembers;
final int? multisigThreshold;
const DaoConfig({
required this.name,
required this.description,
required this.type,
this.tokenAddress,
this.quorumPercent,
this.votingPeriodDays = 7,
this.timelockDays = 2,
this.proposalThreshold,
this.multisigMembers,
this.multisigThreshold,
});
Map<String, dynamic> toJson() => {
'name': name,
'description': description,
'type': type.name,
if (tokenAddress != null) 'tokenAddress': tokenAddress,
if (quorumPercent != null) 'quorumPercent': quorumPercent,
'votingPeriodDays': votingPeriodDays,
'timelockDays': timelockDays,
if (proposalThreshold != null) 'proposalThreshold': proposalThreshold,
if (multisigMembers != null) 'multisigMembers': multisigMembers,
if (multisigThreshold != null) 'multisigThreshold': multisigThreshold,
};
}
class TreasuryAsset {
final String address;
final String symbol;
final String balance;
final String valueUsd;
const TreasuryAsset({required this.address, required this.symbol, required this.balance, required this.valueUsd});
factory TreasuryAsset.fromJson(Map<String, dynamic> json) => TreasuryAsset(
address: json['address'] as String,
symbol: json['symbol'] as String,
balance: json['balance'] as String,
valueUsd: json['valueUsd'] as String,
);
}
class DaoTreasury {
final String address;
final String balance;
final String currency;
final List<TreasuryAsset> assets;
const DaoTreasury({required this.address, required this.balance, required this.currency, required this.assets});
factory DaoTreasury.fromJson(Map<String, dynamic> json) => DaoTreasury(
address: json['address'] as String,
balance: json['balance'] as String,
currency: json['currency'] as String,
assets: (json['assets'] as List).map((e) => TreasuryAsset.fromJson(e)).toList(),
);
}
class Dao {
final String id;
final String name;
final String description;
final DaoType type;
final String? tokenAddress;
final String governorAddress;
final String timelockAddress;
final DaoTreasury treasury;
final String quorumPercent;
final int votingPeriodDays;
final int timelockDays;
final int proposalCount;
final int memberCount;
final int createdAt;
const Dao({
required this.id,
required this.name,
required this.description,
required this.type,
this.tokenAddress,
required this.governorAddress,
required this.timelockAddress,
required this.treasury,
required this.quorumPercent,
required this.votingPeriodDays,
required this.timelockDays,
required this.proposalCount,
required this.memberCount,
required this.createdAt,
});
factory Dao.fromJson(Map<String, dynamic> json) => Dao(
id: json['id'] as String,
name: json['name'] as String,
description: json['description'] as String,
type: DaoType.values.firstWhere((e) => e.name == json['type']),
tokenAddress: json['tokenAddress'] as String?,
governorAddress: json['governorAddress'] as String,
timelockAddress: json['timelockAddress'] as String,
treasury: DaoTreasury.fromJson(json['treasury']),
quorumPercent: json['quorumPercent'] as String,
votingPeriodDays: json['votingPeriodDays'] as int,
timelockDays: json['timelockDays'] as int,
proposalCount: json['proposalCount'] as int,
memberCount: json['memberCount'] as int,
createdAt: json['createdAt'] as int,
);
}
// ==================== Vesting Types ====================
class VestingSchedule {
final String beneficiary;
final String totalAmount;
final int startTime;
final int cliffDuration;
final int vestingDuration;
final bool revocable;
const VestingSchedule({
required this.beneficiary,
required this.totalAmount,
required this.startTime,
required this.cliffDuration,
required this.vestingDuration,
this.revocable = false,
});
Map<String, dynamic> toJson() => {
'beneficiary': beneficiary,
'totalAmount': totalAmount,
'startTime': startTime,
'cliffDuration': cliffDuration,
'vestingDuration': vestingDuration,
'revocable': revocable,
};
}
class VestingContract {
final String id;
final String contractAddress;
final String beneficiary;
final String totalAmount;
final String releasedAmount;
final String releasableAmount;
final int startTime;
final int cliffEnd;
final int vestingEnd;
final VestingStatus status;
final int createdAt;
final String txHash;
const VestingContract({
required this.id,
required this.contractAddress,
required this.beneficiary,
required this.totalAmount,
required this.releasedAmount,
required this.releasableAmount,
required this.startTime,
required this.cliffEnd,
required this.vestingEnd,
required this.status,
required this.createdAt,
required this.txHash,
});
factory VestingContract.fromJson(Map<String, dynamic> json) => VestingContract(
id: json['id'] as String,
contractAddress: json['contractAddress'] as String,
beneficiary: json['beneficiary'] as String,
totalAmount: json['totalAmount'] as String,
releasedAmount: json['releasedAmount'] as String,
releasableAmount: json['releasableAmount'] as String,
startTime: json['startTime'] as int,
cliffEnd: json['cliffEnd'] as int,
vestingEnd: json['vestingEnd'] as int,
status: VestingStatus.values.firstWhere((e) => e.name == json['status']),
createdAt: json['createdAt'] as int,
txHash: json['txHash'] as String,
);
}
class ClaimReceipt {
final String vestingContractId;
final String amount;
final String txHash;
final int timestamp;
final String remainingAmount;
const ClaimReceipt({
required this.vestingContractId,
required this.amount,
required this.txHash,
required this.timestamp,
required this.remainingAmount,
});
factory ClaimReceipt.fromJson(Map<String, dynamic> json) => ClaimReceipt(
vestingContractId: json['vestingContractId'] as String,
amount: json['amount'] as String,
txHash: json['txHash'] as String,
timestamp: json['timestamp'] as int,
remainingAmount: json['remainingAmount'] as String,
);
}
// ==================== Config & Error ====================
class GovernanceConfig {
final String apiKey;
final String endpoint;
final Duration timeout;
final int retries;
final bool debug;
const GovernanceConfig({
required this.apiKey,
this.endpoint = 'https://governance.synor.io/v1',
this.timeout = const Duration(seconds: 60),
this.retries = 3,
this.debug = false,
});
}
class GovernanceException implements Exception {
final String message;
final String? code;
final int statusCode;
const GovernanceException(this.message, {this.code, this.statusCode = 0});
@override
String toString() => 'GovernanceException: $message';
}

View file

@ -189,7 +189,7 @@ class SynorHosting {
String path, [ String path, [
Map<String, dynamic>? body, Map<String, dynamic>? body,
]) async { ]) async {
if (_closed) throw HostingException('Client has been closed'); if (_closed) throw const HostingException('Client has been closed');
Exception? lastError; Exception? lastError;
for (var attempt = 0; attempt < config.retries; attempt++) { for (var attempt = 0; attempt < config.retries; attempt++) {
@ -202,7 +202,7 @@ class SynorHosting {
} }
} }
} }
throw lastError ?? HostingException('Unknown error'); throw lastError ?? const HostingException('Unknown error');
} }
Future<Map<String, dynamic>> _doRequest( Future<Map<String, dynamic>> _doRequest(

View file

@ -0,0 +1,231 @@
/// Synor Mining SDK Client for Flutter
library synor_mining;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'types.dart';
export 'types.dart';
/// Synor Mining Client
class SynorMining {
final MiningConfig config;
final http.Client _client;
bool _closed = false;
StratumConnection? _activeConnection;
SynorMining(this.config) : _client = http.Client();
// ==================== Pool Operations ====================
Future<StratumConnection> connect(PoolConfig pool) async {
final resp = await _request('POST', '/pool/connect', pool.toJson());
_activeConnection = StratumConnection.fromJson(resp);
return _activeConnection!;
}
Future<void> disconnect() async {
if (_activeConnection == null) return;
await _request('POST', '/pool/disconnect');
_activeConnection = null;
}
Future<StratumConnection> getConnection() async {
final resp = await _request('GET', '/pool/connection');
_activeConnection = StratumConnection.fromJson(resp);
return _activeConnection!;
}
Future<PoolStats> getPoolStats() async {
final resp = await _request('GET', '/pool/stats');
return PoolStats.fromJson(resp);
}
bool get isConnected => _activeConnection?.status == ConnectionStatus.connected;
// ==================== Mining Operations ====================
Future<BlockTemplate> getBlockTemplate() async {
final resp = await _request('GET', '/mining/template');
return BlockTemplate.fromJson(resp);
}
Future<SubmitResult> submitWork(MinedWork work) async {
final resp = await _request('POST', '/mining/submit', work.toJson());
return SubmitResult.fromJson(resp);
}
Future<void> startMining({String? algorithm}) async {
final body = algorithm != null ? {'algorithm': algorithm} : null;
await _request('POST', '/mining/start', body);
}
Future<void> stopMining() async {
await _request('POST', '/mining/stop');
}
Future<bool> isMining() async {
final resp = await _request('GET', '/mining/status');
return resp['mining'] as bool;
}
// ==================== Stats Operations ====================
Future<Hashrate> getHashrate() async {
final resp = await _request('GET', '/stats/hashrate');
return Hashrate.fromJson(resp);
}
Future<MiningStats> getStats() async {
final resp = await _request('GET', '/stats');
return MiningStats.fromJson(resp);
}
Future<Earnings> getEarnings({TimePeriod? period}) async {
final path = period != null ? '/stats/earnings?period=${period.name}' : '/stats/earnings';
final resp = await _request('GET', path);
return Earnings.fromJson(resp);
}
Future<ShareStats> getShareStats() async {
final resp = await _request('GET', '/stats/shares');
return ShareStats.fromJson(resp);
}
// ==================== Device Operations ====================
Future<List<MiningDevice>> listDevices() async {
final resp = await _request('GET', '/devices');
return (resp['devices'] as List).map((e) => MiningDevice.fromJson(e)).toList();
}
Future<MiningDevice> getDevice(String deviceId) async {
final resp = await _request('GET', '/devices/${Uri.encodeComponent(deviceId)}');
return MiningDevice.fromJson(resp);
}
Future<MiningDevice> setDeviceConfig(String deviceId, DeviceConfig deviceConfig) async {
final resp = await _request('PUT', '/devices/${Uri.encodeComponent(deviceId)}/config', deviceConfig.toJson());
return MiningDevice.fromJson(resp);
}
Future<void> enableDevice(String deviceId) async {
await setDeviceConfig(deviceId, const DeviceConfig(enabled: true));
}
Future<void> disableDevice(String deviceId) async {
await setDeviceConfig(deviceId, const DeviceConfig(enabled: false));
}
// ==================== Worker Operations ====================
Future<List<WorkerInfo>> listWorkers() async {
final resp = await _request('GET', '/workers');
return (resp['workers'] as List).map((e) => WorkerInfo.fromJson(e)).toList();
}
Future<WorkerInfo> getWorker(String workerId) async {
final resp = await _request('GET', '/workers/${Uri.encodeComponent(workerId)}');
return WorkerInfo.fromJson(resp);
}
Future<void> removeWorker(String workerId) async {
await _request('DELETE', '/workers/${Uri.encodeComponent(workerId)}');
}
// ==================== Algorithm Operations ====================
Future<List<MiningAlgorithm>> listAlgorithms() async {
final resp = await _request('GET', '/algorithms');
return (resp['algorithms'] as List).map((e) => MiningAlgorithm.fromJson(e)).toList();
}
Future<MiningAlgorithm> getAlgorithm(String name) async {
final resp = await _request('GET', '/algorithms/${Uri.encodeComponent(name)}');
return MiningAlgorithm.fromJson(resp);
}
Future<void> switchAlgorithm(String algorithm) async {
await _request('POST', '/algorithms/switch', {'algorithm': algorithm});
}
Future<MiningAlgorithm> getMostProfitable() async {
final resp = await _request('GET', '/algorithms/profitable');
return MiningAlgorithm.fromJson(resp);
}
// ==================== Lifecycle ====================
void close() {
_closed = true;
_activeConnection = null;
_client.close();
}
bool get isClosed => _closed;
Future<bool> healthCheck() async {
try {
final resp = await _request('GET', '/health');
return resp['status'] == 'healthy';
} catch (_) {
return false;
}
}
// ==================== Private Methods ====================
Future<Map<String, dynamic>> _request(String method, String path, [Map<String, dynamic>? body]) async {
if (_closed) throw const MiningException('Client has been closed');
Exception? lastError;
for (var attempt = 0; attempt < config.retries; attempt++) {
try {
return await _doRequest(method, path, body);
} catch (e) {
lastError = e as Exception;
if (attempt < config.retries - 1) {
await Future.delayed(Duration(seconds: 1 << attempt));
}
}
}
throw lastError ?? const MiningException('Unknown error');
}
Future<Map<String, dynamic>> _doRequest(String method, String path, Map<String, dynamic>? body) async {
final uri = Uri.parse('${config.endpoint}$path');
final headers = {
'Authorization': 'Bearer ${config.apiKey}',
'Content-Type': 'application/json',
'X-SDK-Version': 'flutter/0.1.0',
};
http.Response response;
switch (method) {
case 'GET':
response = await _client.get(uri, headers: headers).timeout(config.timeout);
break;
case 'POST':
response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null).timeout(config.timeout);
break;
case 'PUT':
response = await _client.put(uri, headers: headers, body: body != null ? jsonEncode(body) : null).timeout(config.timeout);
break;
case 'DELETE':
response = await _client.delete(uri, headers: headers).timeout(config.timeout);
break;
default:
throw MiningException('Unknown method: $method');
}
final respBody = jsonDecode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 400) {
throw MiningException(
respBody['message'] as String? ?? 'HTTP ${response.statusCode}',
code: respBody['code'] as String?,
statusCode: response.statusCode,
);
}
return respBody;
}
}

View file

@ -0,0 +1,555 @@
/// Synor Mining SDK Types for Flutter
library synor_mining_types;
// ==================== Enums ====================
enum DeviceType { cpu, gpu_nvidia, gpu_amd, asic }
enum DeviceStatus { idle, mining, error, offline }
enum ConnectionStatus { disconnected, connecting, connected, reconnecting }
enum TimePeriod { hour, day, week, month, all }
enum SubmitResultStatus { accepted, rejected, stale }
// ==================== Pool Types ====================
class PoolConfig {
final String url;
final String user;
final String? password;
final String? algorithm;
final double? difficulty;
const PoolConfig({required this.url, required this.user, this.password, this.algorithm, this.difficulty});
Map<String, dynamic> toJson() => {
'url': url,
'user': user,
if (password != null) 'password': password,
if (algorithm != null) 'algorithm': algorithm,
if (difficulty != null) 'difficulty': difficulty,
};
}
class StratumConnection {
final String id;
final String pool;
final ConnectionStatus status;
final String algorithm;
final double difficulty;
final int connectedAt;
final int acceptedShares;
final int rejectedShares;
final int staleShares;
final int? lastShareAt;
const StratumConnection({
required this.id,
required this.pool,
required this.status,
required this.algorithm,
required this.difficulty,
required this.connectedAt,
required this.acceptedShares,
required this.rejectedShares,
required this.staleShares,
this.lastShareAt,
});
factory StratumConnection.fromJson(Map<String, dynamic> json) => StratumConnection(
id: json['id'] as String,
pool: json['pool'] as String,
status: ConnectionStatus.values.firstWhere((e) => e.name == json['status']),
algorithm: json['algorithm'] as String,
difficulty: (json['difficulty'] as num).toDouble(),
connectedAt: json['connectedAt'] as int,
acceptedShares: json['acceptedShares'] as int,
rejectedShares: json['rejectedShares'] as int,
staleShares: json['staleShares'] as int,
lastShareAt: json['lastShareAt'] as int?,
);
}
class PoolStats {
final String url;
final int workers;
final double hashrate;
final double difficulty;
final int lastBlock;
final int blocksFound24h;
final double luck;
const PoolStats({
required this.url,
required this.workers,
required this.hashrate,
required this.difficulty,
required this.lastBlock,
required this.blocksFound24h,
required this.luck,
});
factory PoolStats.fromJson(Map<String, dynamic> json) => PoolStats(
url: json['url'] as String,
workers: json['workers'] as int,
hashrate: (json['hashrate'] as num).toDouble(),
difficulty: (json['difficulty'] as num).toDouble(),
lastBlock: json['lastBlock'] as int,
blocksFound24h: json['blocksFound24h'] as int,
luck: (json['luck'] as num).toDouble(),
);
}
// ==================== Mining Types ====================
class TemplateTransaction {
final String txid;
final String data;
final String fee;
final int weight;
const TemplateTransaction({required this.txid, required this.data, required this.fee, required this.weight});
factory TemplateTransaction.fromJson(Map<String, dynamic> json) => TemplateTransaction(
txid: json['txid'] as String,
data: json['data'] as String,
fee: json['fee'] as String,
weight: json['weight'] as int,
);
}
class BlockTemplate {
final String id;
final String previousBlockHash;
final String merkleRoot;
final int timestamp;
final String bits;
final int height;
final String coinbaseValue;
final List<TemplateTransaction> transactions;
final String target;
final String algorithm;
final String extraNonce;
const BlockTemplate({
required this.id,
required this.previousBlockHash,
required this.merkleRoot,
required this.timestamp,
required this.bits,
required this.height,
required this.coinbaseValue,
required this.transactions,
required this.target,
required this.algorithm,
required this.extraNonce,
});
factory BlockTemplate.fromJson(Map<String, dynamic> json) => BlockTemplate(
id: json['id'] as String,
previousBlockHash: json['previousBlockHash'] as String,
merkleRoot: json['merkleRoot'] as String,
timestamp: json['timestamp'] as int,
bits: json['bits'] as String,
height: json['height'] as int,
coinbaseValue: json['coinbaseValue'] as String,
transactions: (json['transactions'] as List).map((e) => TemplateTransaction.fromJson(e)).toList(),
target: json['target'] as String,
algorithm: json['algorithm'] as String,
extraNonce: json['extraNonce'] as String,
);
}
class MinedWork {
final String templateId;
final String nonce;
final String extraNonce;
final int timestamp;
final String hash;
const MinedWork({
required this.templateId,
required this.nonce,
required this.extraNonce,
required this.timestamp,
required this.hash,
});
Map<String, dynamic> toJson() => {
'templateId': templateId,
'nonce': nonce,
'extraNonce': extraNonce,
'timestamp': timestamp,
'hash': hash,
};
}
class ShareInfo {
final String hash;
final double difficulty;
final int timestamp;
final bool accepted;
const ShareInfo({required this.hash, required this.difficulty, required this.timestamp, required this.accepted});
factory ShareInfo.fromJson(Map<String, dynamic> json) => ShareInfo(
hash: json['hash'] as String,
difficulty: (json['difficulty'] as num).toDouble(),
timestamp: json['timestamp'] as int,
accepted: json['accepted'] as bool,
);
}
class SubmitResult {
final SubmitResultStatus status;
final ShareInfo share;
final bool blockFound;
final String? reason;
final String? blockHash;
final String? reward;
const SubmitResult({
required this.status,
required this.share,
required this.blockFound,
this.reason,
this.blockHash,
this.reward,
});
factory SubmitResult.fromJson(Map<String, dynamic> json) => SubmitResult(
status: SubmitResultStatus.values.firstWhere((e) => e.name == json['status']),
share: ShareInfo.fromJson(json['share']),
blockFound: json['blockFound'] as bool,
reason: json['reason'] as String?,
blockHash: json['blockHash'] as String?,
reward: json['reward'] as String?,
);
}
// ==================== Stats Types ====================
class Hashrate {
final double current;
final double average1h;
final double average24h;
final double peak;
final String unit;
const Hashrate({required this.current, required this.average1h, required this.average24h, required this.peak, required this.unit});
factory Hashrate.fromJson(Map<String, dynamic> json) => Hashrate(
current: (json['current'] as num).toDouble(),
average1h: (json['average1h'] as num).toDouble(),
average24h: (json['average24h'] as num).toDouble(),
peak: (json['peak'] as num).toDouble(),
unit: json['unit'] as String,
);
}
class ShareStats {
final int accepted;
final int rejected;
final int stale;
final int total;
final double acceptRate;
const ShareStats({required this.accepted, required this.rejected, required this.stale, required this.total, required this.acceptRate});
factory ShareStats.fromJson(Map<String, dynamic> json) => ShareStats(
accepted: json['accepted'] as int,
rejected: json['rejected'] as int,
stale: json['stale'] as int,
total: json['total'] as int,
acceptRate: (json['acceptRate'] as num).toDouble(),
);
}
class DeviceTemperature {
final double current;
final double max;
final bool throttling;
const DeviceTemperature({required this.current, required this.max, required this.throttling});
factory DeviceTemperature.fromJson(Map<String, dynamic> json) => DeviceTemperature(
current: (json['current'] as num).toDouble(),
max: (json['max'] as num).toDouble(),
throttling: json['throttling'] as bool,
);
}
class EarningsSnapshot {
final String today;
final String yesterday;
final String thisWeek;
final String thisMonth;
final String total;
final String currency;
const EarningsSnapshot({
required this.today,
required this.yesterday,
required this.thisWeek,
required this.thisMonth,
required this.total,
required this.currency,
});
factory EarningsSnapshot.fromJson(Map<String, dynamic> json) => EarningsSnapshot(
today: json['today'] as String,
yesterday: json['yesterday'] as String,
thisWeek: json['thisWeek'] as String,
thisMonth: json['thisMonth'] as String,
total: json['total'] as String,
currency: json['currency'] as String,
);
}
class MiningStats {
final Hashrate hashrate;
final ShareStats shares;
final int uptime;
final double efficiency;
final EarningsSnapshot earnings;
final double? powerConsumption;
final DeviceTemperature? temperature;
const MiningStats({
required this.hashrate,
required this.shares,
required this.uptime,
required this.efficiency,
required this.earnings,
this.powerConsumption,
this.temperature,
});
factory MiningStats.fromJson(Map<String, dynamic> json) => MiningStats(
hashrate: Hashrate.fromJson(json['hashrate']),
shares: ShareStats.fromJson(json['shares']),
uptime: json['uptime'] as int,
efficiency: (json['efficiency'] as num).toDouble(),
earnings: EarningsSnapshot.fromJson(json['earnings']),
powerConsumption: (json['powerConsumption'] as num?)?.toDouble(),
temperature: json['temperature'] != null ? DeviceTemperature.fromJson(json['temperature']) : null,
);
}
class EarningsBreakdown {
final int date;
final String amount;
final int blocks;
final int shares;
final double hashrate;
const EarningsBreakdown({required this.date, required this.amount, required this.blocks, required this.shares, required this.hashrate});
factory EarningsBreakdown.fromJson(Map<String, dynamic> json) => EarningsBreakdown(
date: json['date'] as int,
amount: json['amount'] as String,
blocks: json['blocks'] as int,
shares: json['shares'] as int,
hashrate: (json['hashrate'] as num).toDouble(),
);
}
class Earnings {
final TimePeriod period;
final int startDate;
final int endDate;
final String amount;
final int blocks;
final int shares;
final double averageHashrate;
final String currency;
final List<EarningsBreakdown> breakdown;
const Earnings({
required this.period,
required this.startDate,
required this.endDate,
required this.amount,
required this.blocks,
required this.shares,
required this.averageHashrate,
required this.currency,
required this.breakdown,
});
factory Earnings.fromJson(Map<String, dynamic> json) => Earnings(
period: TimePeriod.values.firstWhere((e) => e.name == json['period']),
startDate: json['startDate'] as int,
endDate: json['endDate'] as int,
amount: json['amount'] as String,
blocks: json['blocks'] as int,
shares: json['shares'] as int,
averageHashrate: (json['averageHashrate'] as num).toDouble(),
currency: json['currency'] as String,
breakdown: (json['breakdown'] as List).map((e) => EarningsBreakdown.fromJson(e)).toList(),
);
}
// ==================== Device Types ====================
class MiningDevice {
final String id;
final String name;
final DeviceType type;
final DeviceStatus status;
final double hashrate;
final double temperature;
final double fanSpeed;
final double powerDraw;
final int memoryUsed;
final int memoryTotal;
final String? driver;
final String? firmware;
const MiningDevice({
required this.id,
required this.name,
required this.type,
required this.status,
required this.hashrate,
required this.temperature,
required this.fanSpeed,
required this.powerDraw,
required this.memoryUsed,
required this.memoryTotal,
this.driver,
this.firmware,
});
factory MiningDevice.fromJson(Map<String, dynamic> json) => MiningDevice(
id: json['id'] as String,
name: json['name'] as String,
type: DeviceType.values.firstWhere((e) => e.name == json['type']),
status: DeviceStatus.values.firstWhere((e) => e.name == json['status']),
hashrate: (json['hashrate'] as num).toDouble(),
temperature: (json['temperature'] as num).toDouble(),
fanSpeed: (json['fanSpeed'] as num).toDouble(),
powerDraw: (json['powerDraw'] as num).toDouble(),
memoryUsed: json['memoryUsed'] as int,
memoryTotal: json['memoryTotal'] as int,
driver: json['driver'] as String?,
firmware: json['firmware'] as String?,
);
}
class DeviceConfig {
final bool enabled;
final int? intensity;
final int? powerLimit;
final int? coreClockOffset;
final int? memoryClockOffset;
final int? fanSpeed;
const DeviceConfig({
required this.enabled,
this.intensity,
this.powerLimit,
this.coreClockOffset,
this.memoryClockOffset,
this.fanSpeed,
});
Map<String, dynamic> toJson() => {
'enabled': enabled,
if (intensity != null) 'intensity': intensity,
if (powerLimit != null) 'powerLimit': powerLimit,
if (coreClockOffset != null) 'coreClockOffset': coreClockOffset,
if (memoryClockOffset != null) 'memoryClockOffset': memoryClockOffset,
if (fanSpeed != null) 'fanSpeed': fanSpeed,
};
}
class WorkerInfo {
final String id;
final String name;
final ConnectionStatus status;
final Hashrate hashrate;
final ShareStats shares;
final List<MiningDevice> devices;
final int lastSeen;
final int uptime;
const WorkerInfo({
required this.id,
required this.name,
required this.status,
required this.hashrate,
required this.shares,
required this.devices,
required this.lastSeen,
required this.uptime,
});
factory WorkerInfo.fromJson(Map<String, dynamic> json) => WorkerInfo(
id: json['id'] as String,
name: json['name'] as String,
status: ConnectionStatus.values.firstWhere((e) => e.name == json['status']),
hashrate: Hashrate.fromJson(json['hashrate']),
shares: ShareStats.fromJson(json['shares']),
devices: (json['devices'] as List).map((e) => MiningDevice.fromJson(e)).toList(),
lastSeen: json['lastSeen'] as int,
uptime: json['uptime'] as int,
);
}
class MiningAlgorithm {
final String name;
final String displayName;
final String hashUnit;
final String profitability;
final double difficulty;
final String blockReward;
final int blockTime;
const MiningAlgorithm({
required this.name,
required this.displayName,
required this.hashUnit,
required this.profitability,
required this.difficulty,
required this.blockReward,
required this.blockTime,
});
factory MiningAlgorithm.fromJson(Map<String, dynamic> json) => MiningAlgorithm(
name: json['name'] as String,
displayName: json['displayName'] as String,
hashUnit: json['hashUnit'] as String,
profitability: json['profitability'] as String,
difficulty: (json['difficulty'] as num).toDouble(),
blockReward: json['blockReward'] as String,
blockTime: json['blockTime'] as int,
);
}
// ==================== Config & Error ====================
class MiningConfig {
final String apiKey;
final String endpoint;
final Duration timeout;
final int retries;
final bool debug;
const MiningConfig({
required this.apiKey,
this.endpoint = 'https://mining.synor.io/v1',
this.timeout = const Duration(seconds: 60),
this.retries = 3,
this.debug = false,
});
}
class MiningException implements Exception {
final String message;
final String? code;
final int statusCode;
const MiningException(this.message, {this.code, this.statusCode = 0});
@override
String toString() => 'MiningException: $message';
}

View file

@ -0,0 +1,298 @@
package io.synor.economics;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Synor Economics SDK for Java.
* Pricing, billing, staking, and discount operations.
*/
public class SynorEconomics implements AutoCloseable {
private static final String DEFAULT_ENDPOINT = "https://economics.synor.io/v1";
private static final Gson gson = new GsonBuilder().create();
private final EconomicsConfig config;
private final HttpClient httpClient;
private final AtomicBoolean closed = new AtomicBoolean(false);
public SynorEconomics(String apiKey) {
this(new EconomicsConfig(apiKey, DEFAULT_ENDPOINT, 60, 3, false));
}
public SynorEconomics(EconomicsConfig config) {
this.config = config;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(config.getTimeoutSecs()))
.build();
}
// ==================== Pricing Operations ====================
public CompletableFuture<Price> getPrice(ServiceType service, UsageMetrics usage) {
Map<String, Object> body = new HashMap<>();
body.put("service", service.name().toLowerCase());
body.put("usage", usage);
return request("POST", "/pricing/calculate", body)
.thenApply(resp -> gson.fromJson(resp, Price.class));
}
public CompletableFuture<CostEstimate> estimateCost(UsagePlan plan) {
Map<String, Object> body = new HashMap<>();
body.put("service", plan.getService().name().toLowerCase());
body.put("estimatedUsage", plan.getEstimatedUsage());
body.put("period", plan.getPeriod().name().toLowerCase());
return request("POST", "/pricing/estimate", body)
.thenApply(resp -> gson.fromJson(resp, CostEstimate.class));
}
public CompletableFuture<List<PricingTier>> getPricingTiers(ServiceType service) {
return request("GET", "/pricing/tiers/" + service.name().toLowerCase(), null)
.thenApply(resp -> {
Type type = new TypeToken<TiersResponse>(){}.getType();
TiersResponse result = gson.fromJson(resp, type);
return result.tiers;
});
}
// ==================== Billing Operations ====================
public CompletableFuture<Usage> getUsage(BillingPeriod period) {
String path = "/billing/usage";
if (period != null) {
path += "?period=" + period.name().toLowerCase();
}
return request("GET", path, null)
.thenApply(resp -> gson.fromJson(resp, Usage.class));
}
public CompletableFuture<Usage> getUsageByDateRange(long startDate, long endDate) {
String path = "/billing/usage?startDate=" + startDate + "&endDate=" + endDate;
return request("GET", path, null)
.thenApply(resp -> gson.fromJson(resp, Usage.class));
}
public CompletableFuture<List<Invoice>> getInvoices() {
return request("GET", "/billing/invoices", null)
.thenApply(resp -> {
Type type = new TypeToken<InvoicesResponse>(){}.getType();
InvoicesResponse result = gson.fromJson(resp, type);
return result.invoices;
});
}
public CompletableFuture<Invoice> getInvoice(String invoiceId) {
return request("GET", "/billing/invoices/" + encode(invoiceId), null)
.thenApply(resp -> gson.fromJson(resp, Invoice.class));
}
public CompletableFuture<byte[]> downloadInvoice(String invoiceId) {
return request("GET", "/billing/invoices/" + encode(invoiceId) + "/pdf", null)
.thenApply(resp -> {
Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
String base64 = (String) map.get("data");
return java.util.Base64.getDecoder().decode(base64);
});
}
public CompletableFuture<AccountBalance> getBalance() {
return request("GET", "/billing/balance", null)
.thenApply(resp -> gson.fromJson(resp, AccountBalance.class));
}
public CompletableFuture<Void> addCredits(String amount, String paymentMethod) {
Map<String, Object> body = new HashMap<>();
body.put("amount", amount);
body.put("paymentMethod", paymentMethod);
return request("POST", "/billing/credits", body)
.thenApply(resp -> null);
}
// ==================== Staking Operations ====================
public CompletableFuture<StakeReceipt> stake(String amount, Integer durationDays) {
Map<String, Object> body = new HashMap<>();
body.put("amount", amount);
if (durationDays != null) {
body.put("durationDays", durationDays);
}
return request("POST", "/staking/stake", body)
.thenApply(resp -> gson.fromJson(resp, StakeReceipt.class));
}
public CompletableFuture<UnstakeReceipt> unstake(String stakeId) {
return request("POST", "/staking/unstake/" + encode(stakeId), null)
.thenApply(resp -> gson.fromJson(resp, UnstakeReceipt.class));
}
public CompletableFuture<List<StakeInfo>> getStakes() {
return request("GET", "/staking/stakes", null)
.thenApply(resp -> {
Type type = new TypeToken<StakesResponse>(){}.getType();
StakesResponse result = gson.fromJson(resp, type);
return result.stakes;
});
}
public CompletableFuture<StakeInfo> getStake(String stakeId) {
return request("GET", "/staking/stakes/" + encode(stakeId), null)
.thenApply(resp -> gson.fromJson(resp, StakeInfo.class));
}
public CompletableFuture<Rewards> getStakingRewards() {
return request("GET", "/staking/rewards", null)
.thenApply(resp -> gson.fromJson(resp, Rewards.class));
}
public CompletableFuture<String> claimRewards() {
return request("POST", "/staking/rewards/claim", null)
.thenApply(resp -> {
Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
return (String) map.get("txHash");
});
}
public CompletableFuture<Double> getCurrentApy() {
return request("GET", "/staking/apy", null)
.thenApply(resp -> {
Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
return ((Number) map.get("apy")).doubleValue();
});
}
// ==================== Discount Operations ====================
public CompletableFuture<AppliedDiscount> applyDiscount(String code) {
Map<String, Object> body = new HashMap<>();
body.put("code", code);
return request("POST", "/discounts/apply", body)
.thenApply(resp -> gson.fromJson(resp, AppliedDiscount.class));
}
public CompletableFuture<List<Discount>> getAvailableDiscounts() {
return request("GET", "/discounts/available", null)
.thenApply(resp -> {
Type type = new TypeToken<DiscountsResponse>(){}.getType();
DiscountsResponse result = gson.fromJson(resp, type);
return result.discounts;
});
}
public CompletableFuture<Discount> validateDiscount(String code) {
return request("GET", "/discounts/validate/" + encode(code), null)
.thenApply(resp -> gson.fromJson(resp, Discount.class));
}
public CompletableFuture<List<AppliedDiscount>> getAppliedDiscounts() {
return request("GET", "/discounts/applied", null)
.thenApply(resp -> {
Type type = new TypeToken<AppliedDiscountsResponse>(){}.getType();
AppliedDiscountsResponse result = gson.fromJson(resp, type);
return result.discounts;
});
}
public CompletableFuture<Void> removeDiscount(String discountId) {
return request("DELETE", "/discounts/" + encode(discountId), null)
.thenApply(resp -> null);
}
// ==================== Lifecycle ====================
@Override
public void close() {
closed.set(true);
}
public boolean isClosed() {
return closed.get();
}
public CompletableFuture<Boolean> healthCheck() {
return request("GET", "/health", null)
.thenApply(resp -> {
Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
return "healthy".equals(map.get("status"));
})
.exceptionally(e -> false);
}
// ==================== Private Methods ====================
private CompletableFuture<String> request(String method, String path, Object body) {
if (closed.get()) {
return CompletableFuture.failedFuture(new EconomicsException("Client has been closed"));
}
return doRequest(method, path, body, 0);
}
private CompletableFuture<String> doRequest(String method, String path, Object body, int attempt) {
String url = config.getEndpoint() + path;
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + config.getApiKey())
.header("Content-Type", "application/json")
.header("X-SDK-Version", "java/0.1.0")
.timeout(Duration.ofSeconds(config.getTimeoutSecs()));
HttpRequest.BodyPublisher bodyPub = body != null
? HttpRequest.BodyPublishers.ofString(gson.toJson(body))
: HttpRequest.BodyPublishers.noBody();
switch (method) {
case "GET": builder.GET(); break;
case "POST": builder.POST(bodyPub); break;
case "PUT": builder.PUT(bodyPub); break;
case "DELETE": builder.method("DELETE", bodyPub); break;
}
return httpClient.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString())
.thenCompose(response -> {
if (response.statusCode() >= 400) {
Map<String, Object> errorBody = gson.fromJson(response.body(),
new TypeToken<Map<String, Object>>(){}.getType());
String message = errorBody != null && errorBody.get("message") != null
? (String) errorBody.get("message") : "HTTP " + response.statusCode();
String code = errorBody != null ? (String) errorBody.get("code") : null;
return CompletableFuture.failedFuture(
new EconomicsException(message, code, response.statusCode()));
}
return CompletableFuture.completedFuture(response.body());
})
.exceptionally(e -> {
if (attempt < config.getRetries() - 1) {
sleep((long) Math.pow(2, attempt) * 1000);
return doRequest(method, path, body, attempt + 1).join();
}
throw new RuntimeException(e);
});
}
private static String encode(String value) {
return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
}
private static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
// Helper response types
private static class TiersResponse { List<PricingTier> tiers; }
private static class InvoicesResponse { List<Invoice> invoices; }
private static class StakesResponse { List<StakeInfo> stakes; }
private static class DiscountsResponse { List<Discount> discounts; }
private static class AppliedDiscountsResponse { List<AppliedDiscount> discounts; }
}

View file

@ -0,0 +1,383 @@
package io.synor.economics;
import java.util.List;
/**
* Economics SDK Types for Java.
*/
public class Types {
// Organizational class
}
// ==================== Enums ====================
enum ServiceType { compute, storage, database, hosting, bridge, mining, rpc }
enum BillingPeriod { daily, weekly, monthly, yearly }
enum StakeStatus { active, unstaking, unlocked, slashed }
enum DiscountType { percentage, fixed, volume }
// ==================== Config ====================
class EconomicsConfig {
private final String apiKey;
private final String endpoint;
private final int timeoutSecs;
private final int retries;
private final boolean debug;
public EconomicsConfig(String apiKey) {
this(apiKey, "https://economics.synor.io/v1", 60, 3, false);
}
public EconomicsConfig(String apiKey, String endpoint, int timeoutSecs, int retries, boolean debug) {
this.apiKey = apiKey;
this.endpoint = endpoint;
this.timeoutSecs = timeoutSecs;
this.retries = retries;
this.debug = debug;
}
public String getApiKey() { return apiKey; }
public String getEndpoint() { return endpoint; }
public int getTimeoutSecs() { return timeoutSecs; }
public int getRetries() { return retries; }
public boolean isDebug() { return debug; }
}
// ==================== Pricing Types ====================
class UsageMetrics {
private double computeHours;
private double storageGb;
private long requests;
private double bandwidthGb;
private double gpuHours;
public double getComputeHours() { return computeHours; }
public double getStorageGb() { return storageGb; }
public long getRequests() { return requests; }
public double getBandwidthGb() { return bandwidthGb; }
public double getGpuHours() { return gpuHours; }
public void setComputeHours(double computeHours) { this.computeHours = computeHours; }
public void setStorageGb(double storageGb) { this.storageGb = storageGb; }
public void setRequests(long requests) { this.requests = requests; }
public void setBandwidthGb(double bandwidthGb) { this.bandwidthGb = bandwidthGb; }
public void setGpuHours(double gpuHours) { this.gpuHours = gpuHours; }
}
class PricingTier {
private String name;
private double upTo;
private double pricePerUnit;
private String unit;
public String getName() { return name; }
public double getUpTo() { return upTo; }
public double getPricePerUnit() { return pricePerUnit; }
public String getUnit() { return unit; }
}
class Price {
private ServiceType service;
private String amount;
private String currency;
private String amountUsd;
private List<PricingTier> breakdown;
private String discount;
private String finalAmount;
public ServiceType getService() { return service; }
public String getAmount() { return amount; }
public String getCurrency() { return currency; }
public String getAmountUsd() { return amountUsd; }
public List<PricingTier> getBreakdown() { return breakdown; }
public String getDiscount() { return discount; }
public String getFinalAmount() { return finalAmount; }
}
class UsagePlan {
private ServiceType service;
private UsageMetrics estimatedUsage;
private BillingPeriod period;
public ServiceType getService() { return service; }
public UsageMetrics getEstimatedUsage() { return estimatedUsage; }
public BillingPeriod getPeriod() { return period; }
public void setService(ServiceType service) { this.service = service; }
public void setEstimatedUsage(UsageMetrics estimatedUsage) { this.estimatedUsage = estimatedUsage; }
public void setPeriod(BillingPeriod period) { this.period = period; }
}
class CostEstimate {
private ServiceType service;
private BillingPeriod period;
private String estimatedCost;
private String estimatedCostUsd;
private String currency;
private String confidenceLevel;
private List<CostBreakdown> breakdown;
private String savingsFromStaking;
public ServiceType getService() { return service; }
public BillingPeriod getPeriod() { return period; }
public String getEstimatedCost() { return estimatedCost; }
public String getEstimatedCostUsd() { return estimatedCostUsd; }
public String getCurrency() { return currency; }
public String getConfidenceLevel() { return confidenceLevel; }
public List<CostBreakdown> getBreakdown() { return breakdown; }
public String getSavingsFromStaking() { return savingsFromStaking; }
}
class CostBreakdown {
private String category;
private String amount;
private double percentage;
public String getCategory() { return category; }
public String getAmount() { return amount; }
public double getPercentage() { return percentage; }
}
// ==================== Billing Types ====================
class UsageRecord {
private ServiceType service;
private String resource;
private double quantity;
private String unit;
private long timestamp;
private String cost;
public ServiceType getService() { return service; }
public String getResource() { return resource; }
public double getQuantity() { return quantity; }
public String getUnit() { return unit; }
public long getTimestamp() { return timestamp; }
public String getCost() { return cost; }
}
class Usage {
private BillingPeriod period;
private long startDate;
private long endDate;
private List<UsageRecord> records;
private String totalCost;
private String currency;
public BillingPeriod getPeriod() { return period; }
public long getStartDate() { return startDate; }
public long getEndDate() { return endDate; }
public List<UsageRecord> getRecords() { return records; }
public String getTotalCost() { return totalCost; }
public String getCurrency() { return currency; }
}
class InvoiceLineItem {
private String description;
private double quantity;
private String unit;
private String unitPrice;
private String amount;
public String getDescription() { return description; }
public double getQuantity() { return quantity; }
public String getUnit() { return unit; }
public String getUnitPrice() { return unitPrice; }
public String getAmount() { return amount; }
}
class Invoice {
private String id;
private String number;
private long periodStart;
private long periodEnd;
private String subtotal;
private String tax;
private String discount;
private String total;
private String currency;
private String status;
private Long dueDate;
private Long paidAt;
private List<InvoiceLineItem> lineItems;
private String pdfUrl;
public String getId() { return id; }
public String getNumber() { return number; }
public long getPeriodStart() { return periodStart; }
public long getPeriodEnd() { return periodEnd; }
public String getSubtotal() { return subtotal; }
public String getTax() { return tax; }
public String getDiscount() { return discount; }
public String getTotal() { return total; }
public String getCurrency() { return currency; }
public String getStatus() { return status; }
public Long getDueDate() { return dueDate; }
public Long getPaidAt() { return paidAt; }
public List<InvoiceLineItem> getLineItems() { return lineItems; }
public String getPdfUrl() { return pdfUrl; }
}
class AccountBalance {
private String available;
private String pending;
private String reserved;
private String staked;
private String currency;
private long lastUpdated;
public String getAvailable() { return available; }
public String getPending() { return pending; }
public String getReserved() { return reserved; }
public String getStaked() { return staked; }
public String getCurrency() { return currency; }
public long getLastUpdated() { return lastUpdated; }
}
// ==================== Staking Types ====================
class StakeReceipt {
private String id;
private String txHash;
private String amount;
private long startDate;
private long endDate;
private double apy;
private StakeStatus status;
public String getId() { return id; }
public String getTxHash() { return txHash; }
public String getAmount() { return amount; }
public long getStartDate() { return startDate; }
public long getEndDate() { return endDate; }
public double getApy() { return apy; }
public StakeStatus getStatus() { return status; }
}
class StakeInfo {
private String id;
private String amount;
private StakeStatus status;
private long startDate;
private long endDate;
private double apy;
private String earnedRewards;
private String pendingRewards;
private Long unlockDate;
private int discountPercent;
public String getId() { return id; }
public String getAmount() { return amount; }
public StakeStatus getStatus() { return status; }
public long getStartDate() { return startDate; }
public long getEndDate() { return endDate; }
public double getApy() { return apy; }
public String getEarnedRewards() { return earnedRewards; }
public String getPendingRewards() { return pendingRewards; }
public Long getUnlockDate() { return unlockDate; }
public int getDiscountPercent() { return discountPercent; }
}
class UnstakeReceipt {
private String id;
private String txHash;
private String amount;
private long initiatedAt;
private long availableAt;
private String penalty;
public String getId() { return id; }
public String getTxHash() { return txHash; }
public String getAmount() { return amount; }
public long getInitiatedAt() { return initiatedAt; }
public long getAvailableAt() { return availableAt; }
public String getPenalty() { return penalty; }
}
class Rewards {
private String totalEarned;
private String pendingClaim;
private String lastClaimDate;
private double currentApy;
private List<RewardRecord> history;
public String getTotalEarned() { return totalEarned; }
public String getPendingClaim() { return pendingClaim; }
public String getLastClaimDate() { return lastClaimDate; }
public double getCurrentApy() { return currentApy; }
public List<RewardRecord> getHistory() { return history; }
}
class RewardRecord {
private long date;
private String amount;
private String type;
private String txHash;
public long getDate() { return date; }
public String getAmount() { return amount; }
public String getType() { return type; }
public String getTxHash() { return txHash; }
}
// ==================== Discount Types ====================
class Discount {
private String id;
private String code;
private DiscountType type;
private String value;
private Long validFrom;
private Long validUntil;
private Integer usageLimit;
private int usageCount;
private List<ServiceType> applicableServices;
private String minimumSpend;
private boolean active;
public String getId() { return id; }
public String getCode() { return code; }
public DiscountType getType() { return type; }
public String getValue() { return value; }
public Long getValidFrom() { return validFrom; }
public Long getValidUntil() { return validUntil; }
public Integer getUsageLimit() { return usageLimit; }
public int getUsageCount() { return usageCount; }
public List<ServiceType> getApplicableServices() { return applicableServices; }
public String getMinimumSpend() { return minimumSpend; }
public boolean isActive() { return active; }
}
class AppliedDiscount {
private Discount discount;
private String savedAmount;
private long appliedAt;
public Discount getDiscount() { return discount; }
public String getSavedAmount() { return savedAmount; }
public long getAppliedAt() { return appliedAt; }
}
// ==================== Error ====================
class EconomicsException extends RuntimeException {
private final String code;
private final int statusCode;
public EconomicsException(String message) {
super(message);
this.code = null;
this.statusCode = 0;
}
public EconomicsException(String message, String code, int statusCode) {
super(message);
this.code = code;
this.statusCode = statusCode;
}
public String getCode() { return code; }
public int getStatusCode() { return statusCode; }
}

View file

@ -0,0 +1,369 @@
package io.synor.governance;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Synor Governance SDK for Java.
* Proposals, voting, DAOs, and vesting operations.
*/
public class SynorGovernance implements AutoCloseable {
private static final String DEFAULT_ENDPOINT = "https://governance.synor.io/v1";
private static final Gson gson = new GsonBuilder().create();
private static final Set<ProposalStatus> FINAL_STATUSES = Set.of(
ProposalStatus.passed, ProposalStatus.rejected, ProposalStatus.executed, ProposalStatus.cancelled
);
private final GovernanceConfig config;
private final HttpClient httpClient;
private final AtomicBoolean closed = new AtomicBoolean(false);
public SynorGovernance(String apiKey) {
this(new GovernanceConfig(apiKey, DEFAULT_ENDPOINT, 60, 3, false));
}
public SynorGovernance(GovernanceConfig config) {
this.config = config;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(config.getTimeoutSecs()))
.build();
}
// ==================== Proposal Operations ====================
public CompletableFuture<Proposal> createProposal(ProposalDraft proposal) {
Map<String, Object> body = new HashMap<>();
body.put("title", proposal.getTitle());
body.put("description", proposal.getDescription());
if (proposal.getDiscussionUrl() != null) body.put("discussionUrl", proposal.getDiscussionUrl());
if (proposal.getVotingStartTime() != null) body.put("votingStartTime", proposal.getVotingStartTime());
if (proposal.getVotingEndTime() != null) body.put("votingEndTime", proposal.getVotingEndTime());
if (proposal.getActions() != null) body.put("actions", proposal.getActions());
if (proposal.getDaoId() != null) body.put("daoId", proposal.getDaoId());
return request("POST", "/proposals", body)
.thenApply(resp -> gson.fromJson(resp, Proposal.class));
}
public CompletableFuture<Proposal> getProposal(String proposalId) {
return request("GET", "/proposals/" + encode(proposalId), null)
.thenApply(resp -> gson.fromJson(resp, Proposal.class));
}
public CompletableFuture<List<Proposal>> listProposals(ProposalFilter filter) {
StringBuilder path = new StringBuilder("/proposals");
if (filter != null) {
StringBuilder params = new StringBuilder();
if (filter.getStatus() != null) params.append("status=").append(filter.getStatus().name().toLowerCase());
if (filter.getProposer() != null) {
if (params.length() > 0) params.append("&");
params.append("proposer=").append(encode(filter.getProposer()));
}
if (filter.getDaoId() != null) {
if (params.length() > 0) params.append("&");
params.append("daoId=").append(encode(filter.getDaoId()));
}
if (filter.getLimit() != null) {
if (params.length() > 0) params.append("&");
params.append("limit=").append(filter.getLimit());
}
if (filter.getOffset() != null) {
if (params.length() > 0) params.append("&");
params.append("offset=").append(filter.getOffset());
}
if (params.length() > 0) path.append("?").append(params);
}
return request("GET", path.toString(), null)
.thenApply(resp -> {
Type type = new TypeToken<ProposalsResponse>(){}.getType();
ProposalsResponse result = gson.fromJson(resp, type);
return result.proposals;
});
}
public CompletableFuture<Proposal> cancelProposal(String proposalId) {
return request("POST", "/proposals/" + encode(proposalId) + "/cancel", null)
.thenApply(resp -> gson.fromJson(resp, Proposal.class));
}
public CompletableFuture<Proposal> executeProposal(String proposalId) {
return request("POST", "/proposals/" + encode(proposalId) + "/execute", null)
.thenApply(resp -> gson.fromJson(resp, Proposal.class));
}
public CompletableFuture<Proposal> waitForProposal(String proposalId, long pollIntervalMs, long maxWaitMs) {
long deadline = System.currentTimeMillis() + maxWaitMs;
return waitForProposalInternal(proposalId, pollIntervalMs, deadline);
}
private CompletableFuture<Proposal> waitForProposalInternal(String id, long pollMs, long deadline) {
if (System.currentTimeMillis() >= deadline) {
return CompletableFuture.failedFuture(new GovernanceException("Timeout waiting for proposal completion"));
}
return getProposal(id).thenCompose(proposal -> {
if (FINAL_STATUSES.contains(proposal.getStatus())) {
return CompletableFuture.completedFuture(proposal);
}
return CompletableFuture.runAsync(() -> sleep(pollMs))
.thenCompose(v -> waitForProposalInternal(id, pollMs, deadline));
});
}
// ==================== Voting Operations ====================
public CompletableFuture<VoteReceipt> vote(String proposalId, Vote vote, String weight) {
Map<String, Object> body = new HashMap<>();
body.put("choice", vote.getChoice().name().toLowerCase());
if (vote.getReason() != null) body.put("reason", vote.getReason());
if (weight != null) body.put("weight", weight);
return request("POST", "/proposals/" + encode(proposalId) + "/vote", body)
.thenApply(resp -> gson.fromJson(resp, VoteReceipt.class));
}
public CompletableFuture<List<VoteReceipt>> getVotes(String proposalId) {
return request("GET", "/proposals/" + encode(proposalId) + "/votes", null)
.thenApply(resp -> {
Type type = new TypeToken<VotesResponse>(){}.getType();
VotesResponse result = gson.fromJson(resp, type);
return result.votes;
});
}
public CompletableFuture<VoteReceipt> getMyVote(String proposalId) {
return request("GET", "/proposals/" + encode(proposalId) + "/votes/me", null)
.thenApply(resp -> gson.fromJson(resp, VoteReceipt.class));
}
public CompletableFuture<DelegationReceipt> delegate(String delegatee, String amount) {
Map<String, Object> body = new HashMap<>();
body.put("delegatee", delegatee);
if (amount != null) body.put("amount", amount);
return request("POST", "/voting/delegate", body)
.thenApply(resp -> gson.fromJson(resp, DelegationReceipt.class));
}
public CompletableFuture<DelegationReceipt> undelegate(String delegatee) {
return request("POST", "/voting/undelegate", Map.of("delegatee", delegatee))
.thenApply(resp -> gson.fromJson(resp, DelegationReceipt.class));
}
public CompletableFuture<VotingPower> getVotingPower(String address) {
return request("GET", "/voting/power/" + encode(address), null)
.thenApply(resp -> gson.fromJson(resp, VotingPower.class));
}
public CompletableFuture<List<DelegationReceipt>> getDelegations(String address) {
return request("GET", "/voting/delegations/" + encode(address), null)
.thenApply(resp -> {
Type type = new TypeToken<DelegationsResponse>(){}.getType();
DelegationsResponse result = gson.fromJson(resp, type);
return result.delegations;
});
}
// ==================== DAO Operations ====================
public CompletableFuture<Dao> createDao(DaoConfig daoConfig) {
Map<String, Object> body = new HashMap<>();
body.put("name", daoConfig.getName());
body.put("description", daoConfig.getDescription());
body.put("type", daoConfig.getType().name().toLowerCase());
if (daoConfig.getTokenAddress() != null) body.put("tokenAddress", daoConfig.getTokenAddress());
if (daoConfig.getQuorumPercent() != null) body.put("quorumPercent", daoConfig.getQuorumPercent());
body.put("votingPeriodDays", daoConfig.getVotingPeriodDays());
body.put("timelockDays", daoConfig.getTimelockDays());
if (daoConfig.getProposalThreshold() != null) body.put("proposalThreshold", daoConfig.getProposalThreshold());
if (daoConfig.getMultisigMembers() != null) body.put("multisigMembers", daoConfig.getMultisigMembers());
if (daoConfig.getMultisigThreshold() != null) body.put("multisigThreshold", daoConfig.getMultisigThreshold());
return request("POST", "/daos", body)
.thenApply(resp -> gson.fromJson(resp, Dao.class));
}
public CompletableFuture<Dao> getDao(String daoId) {
return request("GET", "/daos/" + encode(daoId), null)
.thenApply(resp -> gson.fromJson(resp, Dao.class));
}
public CompletableFuture<List<Dao>> listDaos(Integer limit, Integer offset) {
StringBuilder path = new StringBuilder("/daos");
if (limit != null || offset != null) {
path.append("?");
if (limit != null) path.append("limit=").append(limit);
if (offset != null) {
if (limit != null) path.append("&");
path.append("offset=").append(offset);
}
}
return request("GET", path.toString(), null)
.thenApply(resp -> {
Type type = new TypeToken<DaosResponse>(){}.getType();
DaosResponse result = gson.fromJson(resp, type);
return result.daos;
});
}
public CompletableFuture<DaoTreasury> getDaoTreasury(String daoId) {
return request("GET", "/daos/" + encode(daoId) + "/treasury", null)
.thenApply(resp -> gson.fromJson(resp, DaoTreasury.class));
}
public CompletableFuture<List<String>> getDaoMembers(String daoId) {
return request("GET", "/daos/" + encode(daoId) + "/members", null)
.thenApply(resp -> {
Type type = new TypeToken<MembersResponse>(){}.getType();
MembersResponse result = gson.fromJson(resp, type);
return result.members;
});
}
// ==================== Vesting Operations ====================
public CompletableFuture<VestingContract> createVestingSchedule(VestingSchedule schedule) {
Map<String, Object> body = new HashMap<>();
body.put("beneficiary", schedule.getBeneficiary());
body.put("totalAmount", schedule.getTotalAmount());
body.put("startTime", schedule.getStartTime());
body.put("cliffDuration", schedule.getCliffDuration());
body.put("vestingDuration", schedule.getVestingDuration());
body.put("revocable", schedule.isRevocable());
return request("POST", "/vesting", body)
.thenApply(resp -> gson.fromJson(resp, VestingContract.class));
}
public CompletableFuture<VestingContract> getVestingContract(String contractId) {
return request("GET", "/vesting/" + encode(contractId), null)
.thenApply(resp -> gson.fromJson(resp, VestingContract.class));
}
public CompletableFuture<List<VestingContract>> listVestingContracts(String beneficiary) {
StringBuilder path = new StringBuilder("/vesting");
if (beneficiary != null) {
path.append("?beneficiary=").append(encode(beneficiary));
}
return request("GET", path.toString(), null)
.thenApply(resp -> {
Type type = new TypeToken<VestingContractsResponse>(){}.getType();
VestingContractsResponse result = gson.fromJson(resp, type);
return result.contracts;
});
}
public CompletableFuture<ClaimReceipt> claimVested(String contractId) {
return request("POST", "/vesting/" + encode(contractId) + "/claim", null)
.thenApply(resp -> gson.fromJson(resp, ClaimReceipt.class));
}
public CompletableFuture<VestingContract> revokeVesting(String contractId) {
return request("POST", "/vesting/" + encode(contractId) + "/revoke", null)
.thenApply(resp -> gson.fromJson(resp, VestingContract.class));
}
public CompletableFuture<String> getReleasableAmount(String contractId) {
return request("GET", "/vesting/" + encode(contractId) + "/releasable", null)
.thenApply(resp -> {
Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
return (String) map.get("amount");
});
}
// ==================== Lifecycle ====================
@Override
public void close() {
closed.set(true);
}
public boolean isClosed() {
return closed.get();
}
public CompletableFuture<Boolean> healthCheck() {
return request("GET", "/health", null)
.thenApply(resp -> {
Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
return "healthy".equals(map.get("status"));
})
.exceptionally(e -> false);
}
// ==================== Private Methods ====================
private CompletableFuture<String> request(String method, String path, Object body) {
if (closed.get()) {
return CompletableFuture.failedFuture(new GovernanceException("Client has been closed"));
}
return doRequest(method, path, body, 0);
}
private CompletableFuture<String> doRequest(String method, String path, Object body, int attempt) {
String url = config.getEndpoint() + path;
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + config.getApiKey())
.header("Content-Type", "application/json")
.header("X-SDK-Version", "java/0.1.0")
.timeout(Duration.ofSeconds(config.getTimeoutSecs()));
HttpRequest.BodyPublisher bodyPub = body != null
? HttpRequest.BodyPublishers.ofString(gson.toJson(body))
: HttpRequest.BodyPublishers.noBody();
switch (method) {
case "GET": builder.GET(); break;
case "POST": builder.POST(bodyPub); break;
case "PUT": builder.PUT(bodyPub); break;
case "DELETE": builder.method("DELETE", bodyPub); break;
}
return httpClient.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString())
.thenCompose(response -> {
if (response.statusCode() >= 400) {
Map<String, Object> errorBody = gson.fromJson(response.body(),
new TypeToken<Map<String, Object>>(){}.getType());
String message = errorBody != null && errorBody.get("message") != null
? (String) errorBody.get("message") : "HTTP " + response.statusCode();
String code = errorBody != null ? (String) errorBody.get("code") : null;
return CompletableFuture.failedFuture(
new GovernanceException(message, code, response.statusCode()));
}
return CompletableFuture.completedFuture(response.body());
})
.exceptionally(e -> {
if (attempt < config.getRetries() - 1) {
sleep((long) Math.pow(2, attempt) * 1000);
return doRequest(method, path, body, attempt + 1).join();
}
throw new RuntimeException(e);
});
}
private static String encode(String value) {
return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
}
private static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
// Helper response types
private static class ProposalsResponse { List<Proposal> proposals; }
private static class VotesResponse { List<VoteReceipt> votes; }
private static class DelegationsResponse { List<DelegationReceipt> delegations; }
private static class DaosResponse { List<Dao> daos; }
private static class MembersResponse { List<String> members; }
private static class VestingContractsResponse { List<VestingContract> contracts; }
}

View file

@ -0,0 +1,407 @@
package io.synor.governance;
import java.util.List;
/**
* Governance SDK Types for Java.
*/
public class Types {
// Organizational class
}
// ==================== Enums ====================
enum ProposalStatus { draft, active, passed, rejected, executed, cancelled }
enum VoteChoice { yes, no, abstain }
enum DaoType { token, multisig, hybrid }
enum VestingStatus { pending, active, paused, completed, revoked }
// ==================== Config ====================
class GovernanceConfig {
private final String apiKey;
private final String endpoint;
private final int timeoutSecs;
private final int retries;
private final boolean debug;
public GovernanceConfig(String apiKey) {
this(apiKey, "https://governance.synor.io/v1", 60, 3, false);
}
public GovernanceConfig(String apiKey, String endpoint, int timeoutSecs, int retries, boolean debug) {
this.apiKey = apiKey;
this.endpoint = endpoint;
this.timeoutSecs = timeoutSecs;
this.retries = retries;
this.debug = debug;
}
public String getApiKey() { return apiKey; }
public String getEndpoint() { return endpoint; }
public int getTimeoutSecs() { return timeoutSecs; }
public int getRetries() { return retries; }
public boolean isDebug() { return debug; }
}
// ==================== Proposal Types ====================
class ProposalAction {
private String target;
private String method;
private String data;
private String value;
public String getTarget() { return target; }
public String getMethod() { return method; }
public String getData() { return data; }
public String getValue() { return value; }
public void setTarget(String target) { this.target = target; }
public void setMethod(String method) { this.method = method; }
public void setData(String data) { this.data = data; }
public void setValue(String value) { this.value = value; }
}
class ProposalDraft {
private String title;
private String description;
private String discussionUrl;
private Long votingStartTime;
private Long votingEndTime;
private List<ProposalAction> actions;
private String daoId;
public String getTitle() { return title; }
public String getDescription() { return description; }
public String getDiscussionUrl() { return discussionUrl; }
public Long getVotingStartTime() { return votingStartTime; }
public Long getVotingEndTime() { return votingEndTime; }
public List<ProposalAction> getActions() { return actions; }
public String getDaoId() { return daoId; }
public void setTitle(String title) { this.title = title; }
public void setDescription(String description) { this.description = description; }
public void setDiscussionUrl(String discussionUrl) { this.discussionUrl = discussionUrl; }
public void setVotingStartTime(Long votingStartTime) { this.votingStartTime = votingStartTime; }
public void setVotingEndTime(Long votingEndTime) { this.votingEndTime = votingEndTime; }
public void setActions(List<ProposalAction> actions) { this.actions = actions; }
public void setDaoId(String daoId) { this.daoId = daoId; }
}
class VoteBreakdown {
private String yes;
private String no;
private String abstain;
private String quorum;
private double quorumPercent;
private boolean quorumReached;
public String getYes() { return yes; }
public String getNo() { return no; }
public String getAbstain() { return abstain; }
public String getQuorum() { return quorum; }
public double getQuorumPercent() { return quorumPercent; }
public boolean isQuorumReached() { return quorumReached; }
}
class Proposal {
private String id;
private String title;
private String description;
private String proposer;
private ProposalStatus status;
private long createdAt;
private long votingStartTime;
private long votingEndTime;
private VoteBreakdown votes;
private String discussionUrl;
private List<ProposalAction> actions;
private String daoId;
private String snapshotBlock;
private Long executedAt;
private String executedTxHash;
public String getId() { return id; }
public String getTitle() { return title; }
public String getDescription() { return description; }
public String getProposer() { return proposer; }
public ProposalStatus getStatus() { return status; }
public long getCreatedAt() { return createdAt; }
public long getVotingStartTime() { return votingStartTime; }
public long getVotingEndTime() { return votingEndTime; }
public VoteBreakdown getVotes() { return votes; }
public String getDiscussionUrl() { return discussionUrl; }
public List<ProposalAction> getActions() { return actions; }
public String getDaoId() { return daoId; }
public String getSnapshotBlock() { return snapshotBlock; }
public Long getExecutedAt() { return executedAt; }
public String getExecutedTxHash() { return executedTxHash; }
}
class ProposalFilter {
private ProposalStatus status;
private String proposer;
private String daoId;
private Integer limit;
private Integer offset;
public ProposalStatus getStatus() { return status; }
public String getProposer() { return proposer; }
public String getDaoId() { return daoId; }
public Integer getLimit() { return limit; }
public Integer getOffset() { return offset; }
public void setStatus(ProposalStatus status) { this.status = status; }
public void setProposer(String proposer) { this.proposer = proposer; }
public void setDaoId(String daoId) { this.daoId = daoId; }
public void setLimit(Integer limit) { this.limit = limit; }
public void setOffset(Integer offset) { this.offset = offset; }
}
// ==================== Voting Types ====================
class Vote {
private VoteChoice choice;
private String reason;
public VoteChoice getChoice() { return choice; }
public String getReason() { return reason; }
public void setChoice(VoteChoice choice) { this.choice = choice; }
public void setReason(String reason) { this.reason = reason; }
}
class VoteReceipt {
private String id;
private String proposalId;
private String voter;
private VoteChoice choice;
private String weight;
private long timestamp;
private String txHash;
private String reason;
public String getId() { return id; }
public String getProposalId() { return proposalId; }
public String getVoter() { return voter; }
public VoteChoice getChoice() { return choice; }
public String getWeight() { return weight; }
public long getTimestamp() { return timestamp; }
public String getTxHash() { return txHash; }
public String getReason() { return reason; }
}
class VotingPower {
private String address;
private String balance;
private String delegatedTo;
private String delegatedFrom;
private String totalPower;
private long blockNumber;
public String getAddress() { return address; }
public String getBalance() { return balance; }
public String getDelegatedTo() { return delegatedTo; }
public String getDelegatedFrom() { return delegatedFrom; }
public String getTotalPower() { return totalPower; }
public long getBlockNumber() { return blockNumber; }
}
class DelegationReceipt {
private String id;
private String delegator;
private String delegatee;
private String amount;
private long timestamp;
private String txHash;
public String getId() { return id; }
public String getDelegator() { return delegator; }
public String getDelegatee() { return delegatee; }
public String getAmount() { return amount; }
public long getTimestamp() { return timestamp; }
public String getTxHash() { return txHash; }
}
// ==================== DAO Types ====================
class DaoConfig {
private String name;
private String description;
private DaoType type;
private String tokenAddress;
private String quorumPercent;
private int votingPeriodDays;
private int timelockDays;
private Integer proposalThreshold;
private List<String> multisigMembers;
private Integer multisigThreshold;
public String getName() { return name; }
public String getDescription() { return description; }
public DaoType getType() { return type; }
public String getTokenAddress() { return tokenAddress; }
public String getQuorumPercent() { return quorumPercent; }
public int getVotingPeriodDays() { return votingPeriodDays; }
public int getTimelockDays() { return timelockDays; }
public Integer getProposalThreshold() { return proposalThreshold; }
public List<String> getMultisigMembers() { return multisigMembers; }
public Integer getMultisigThreshold() { return multisigThreshold; }
public void setName(String name) { this.name = name; }
public void setDescription(String description) { this.description = description; }
public void setType(DaoType type) { this.type = type; }
public void setTokenAddress(String tokenAddress) { this.tokenAddress = tokenAddress; }
public void setQuorumPercent(String quorumPercent) { this.quorumPercent = quorumPercent; }
public void setVotingPeriodDays(int votingPeriodDays) { this.votingPeriodDays = votingPeriodDays; }
public void setTimelockDays(int timelockDays) { this.timelockDays = timelockDays; }
public void setProposalThreshold(Integer proposalThreshold) { this.proposalThreshold = proposalThreshold; }
public void setMultisigMembers(List<String> multisigMembers) { this.multisigMembers = multisigMembers; }
public void setMultisigThreshold(Integer multisigThreshold) { this.multisigThreshold = multisigThreshold; }
}
class DaoTreasury {
private String address;
private String balance;
private String currency;
private List<TreasuryAsset> assets;
public String getAddress() { return address; }
public String getBalance() { return balance; }
public String getCurrency() { return currency; }
public List<TreasuryAsset> getAssets() { return assets; }
}
class TreasuryAsset {
private String address;
private String symbol;
private String balance;
private String valueUsd;
public String getAddress() { return address; }
public String getSymbol() { return symbol; }
public String getBalance() { return balance; }
public String getValueUsd() { return valueUsd; }
}
class Dao {
private String id;
private String name;
private String description;
private DaoType type;
private String tokenAddress;
private String governorAddress;
private String timelockAddress;
private DaoTreasury treasury;
private String quorumPercent;
private int votingPeriodDays;
private int timelockDays;
private int proposalCount;
private int memberCount;
private long createdAt;
public String getId() { return id; }
public String getName() { return name; }
public String getDescription() { return description; }
public DaoType getType() { return type; }
public String getTokenAddress() { return tokenAddress; }
public String getGovernorAddress() { return governorAddress; }
public String getTimelockAddress() { return timelockAddress; }
public DaoTreasury getTreasury() { return treasury; }
public String getQuorumPercent() { return quorumPercent; }
public int getVotingPeriodDays() { return votingPeriodDays; }
public int getTimelockDays() { return timelockDays; }
public int getProposalCount() { return proposalCount; }
public int getMemberCount() { return memberCount; }
public long getCreatedAt() { return createdAt; }
}
// ==================== Vesting Types ====================
class VestingSchedule {
private String beneficiary;
private String totalAmount;
private long startTime;
private long cliffDuration;
private long vestingDuration;
private boolean revocable;
public String getBeneficiary() { return beneficiary; }
public String getTotalAmount() { return totalAmount; }
public long getStartTime() { return startTime; }
public long getCliffDuration() { return cliffDuration; }
public long getVestingDuration() { return vestingDuration; }
public boolean isRevocable() { return revocable; }
public void setBeneficiary(String beneficiary) { this.beneficiary = beneficiary; }
public void setTotalAmount(String totalAmount) { this.totalAmount = totalAmount; }
public void setStartTime(long startTime) { this.startTime = startTime; }
public void setCliffDuration(long cliffDuration) { this.cliffDuration = cliffDuration; }
public void setVestingDuration(long vestingDuration) { this.vestingDuration = vestingDuration; }
public void setRevocable(boolean revocable) { this.revocable = revocable; }
}
class VestingContract {
private String id;
private String contractAddress;
private String beneficiary;
private String totalAmount;
private String releasedAmount;
private String releasableAmount;
private long startTime;
private long cliffEnd;
private long vestingEnd;
private VestingStatus status;
private long createdAt;
private String txHash;
public String getId() { return id; }
public String getContractAddress() { return contractAddress; }
public String getBeneficiary() { return beneficiary; }
public String getTotalAmount() { return totalAmount; }
public String getReleasedAmount() { return releasedAmount; }
public String getReleasableAmount() { return releasableAmount; }
public long getStartTime() { return startTime; }
public long getCliffEnd() { return cliffEnd; }
public long getVestingEnd() { return vestingEnd; }
public VestingStatus getStatus() { return status; }
public long getCreatedAt() { return createdAt; }
public String getTxHash() { return txHash; }
}
class ClaimReceipt {
private String vestingContractId;
private String amount;
private String txHash;
private long timestamp;
private String remainingAmount;
public String getVestingContractId() { return vestingContractId; }
public String getAmount() { return amount; }
public String getTxHash() { return txHash; }
public long getTimestamp() { return timestamp; }
public String getRemainingAmount() { return remainingAmount; }
}
// ==================== Error ====================
class GovernanceException extends RuntimeException {
private final String code;
private final int statusCode;
public GovernanceException(String message) {
super(message);
this.code = null;
this.statusCode = 0;
}
public GovernanceException(String message, String code, int statusCode) {
super(message);
this.code = code;
this.statusCode = statusCode;
}
public String getCode() { return code; }
public int getStatusCode() { return statusCode; }
}

View file

@ -0,0 +1,329 @@
package io.synor.mining;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
* Synor Mining SDK for Java.
* Pool connections, block templates, hashrate stats, and GPU management.
*/
public class SynorMining implements AutoCloseable {
private static final String DEFAULT_ENDPOINT = "https://mining.synor.io/v1";
private static final Gson gson = new GsonBuilder().create();
private final MiningConfig config;
private final HttpClient httpClient;
private final AtomicBoolean closed = new AtomicBoolean(false);
private final AtomicReference<StratumConnection> activeConnection = new AtomicReference<>();
public SynorMining(String apiKey) {
this(new MiningConfig(apiKey, DEFAULT_ENDPOINT, 60, 3, false));
}
public SynorMining(MiningConfig config) {
this.config = config;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(config.getTimeoutSecs()))
.build();
}
// ==================== Pool Operations ====================
public CompletableFuture<StratumConnection> connect(PoolConfig pool) {
Map<String, Object> body = new HashMap<>();
body.put("url", pool.getUrl());
body.put("user", pool.getUser());
if (pool.getPassword() != null) body.put("password", pool.getPassword());
if (pool.getAlgorithm() != null) body.put("algorithm", pool.getAlgorithm());
if (pool.getDifficulty() != null) body.put("difficulty", pool.getDifficulty());
return request("POST", "/pool/connect", body)
.thenApply(resp -> {
StratumConnection conn = gson.fromJson(resp, StratumConnection.class);
activeConnection.set(conn);
return conn;
});
}
public CompletableFuture<Void> disconnect() {
StratumConnection conn = activeConnection.get();
if (conn == null) {
return CompletableFuture.completedFuture(null);
}
return request("POST", "/pool/disconnect", null)
.thenApply(resp -> {
activeConnection.set(null);
return null;
});
}
public CompletableFuture<StratumConnection> getConnection() {
return request("GET", "/pool/connection", null)
.thenApply(resp -> {
StratumConnection conn = gson.fromJson(resp, StratumConnection.class);
activeConnection.set(conn);
return conn;
});
}
public CompletableFuture<PoolStats> getPoolStats() {
return request("GET", "/pool/stats", null)
.thenApply(resp -> gson.fromJson(resp, PoolStats.class));
}
public boolean isConnected() {
StratumConnection conn = activeConnection.get();
return conn != null && conn.getStatus() == ConnectionStatus.connected;
}
// ==================== Mining Operations ====================
public CompletableFuture<BlockTemplate> getBlockTemplate() {
return request("GET", "/mining/template", null)
.thenApply(resp -> gson.fromJson(resp, BlockTemplate.class));
}
public CompletableFuture<SubmitResult> submitWork(MinedWork work) {
Map<String, Object> body = new HashMap<>();
body.put("templateId", work.getTemplateId());
body.put("nonce", work.getNonce());
body.put("extraNonce", work.getExtraNonce());
body.put("timestamp", work.getTimestamp());
body.put("hash", work.getHash());
return request("POST", "/mining/submit", body)
.thenApply(resp -> gson.fromJson(resp, SubmitResult.class));
}
public CompletableFuture<Void> startMining(String algorithm) {
Map<String, Object> body = new HashMap<>();
if (algorithm != null) body.put("algorithm", algorithm);
return request("POST", "/mining/start", body)
.thenApply(resp -> null);
}
public CompletableFuture<Void> stopMining() {
return request("POST", "/mining/stop", null)
.thenApply(resp -> null);
}
public CompletableFuture<Boolean> isMining() {
return request("GET", "/mining/status", null)
.thenApply(resp -> {
Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
return Boolean.TRUE.equals(map.get("mining"));
});
}
// ==================== Stats Operations ====================
public CompletableFuture<Hashrate> getHashrate() {
return request("GET", "/stats/hashrate", null)
.thenApply(resp -> gson.fromJson(resp, Hashrate.class));
}
public CompletableFuture<MiningStats> getStats() {
return request("GET", "/stats", null)
.thenApply(resp -> gson.fromJson(resp, MiningStats.class));
}
public CompletableFuture<Earnings> getEarnings(TimePeriod period) {
String path = "/stats/earnings";
if (period != null) {
path += "?period=" + period.name().toLowerCase();
}
return request("GET", path, null)
.thenApply(resp -> gson.fromJson(resp, Earnings.class));
}
public CompletableFuture<ShareStats> getShareStats() {
return request("GET", "/stats/shares", null)
.thenApply(resp -> gson.fromJson(resp, ShareStats.class));
}
// ==================== Device Operations ====================
public CompletableFuture<List<MiningDevice>> listDevices() {
return request("GET", "/devices", null)
.thenApply(resp -> {
Type type = new TypeToken<DevicesResponse>(){}.getType();
DevicesResponse result = gson.fromJson(resp, type);
return result.devices;
});
}
public CompletableFuture<MiningDevice> getDevice(String deviceId) {
return request("GET", "/devices/" + encode(deviceId), null)
.thenApply(resp -> gson.fromJson(resp, MiningDevice.class));
}
public CompletableFuture<MiningDevice> setDeviceConfig(String deviceId, DeviceConfig deviceConfig) {
Map<String, Object> body = new HashMap<>();
body.put("enabled", deviceConfig.isEnabled());
if (deviceConfig.getIntensity() != null) body.put("intensity", deviceConfig.getIntensity());
if (deviceConfig.getPowerLimit() != null) body.put("powerLimit", deviceConfig.getPowerLimit());
if (deviceConfig.getCoreClockOffset() != null) body.put("coreClockOffset", deviceConfig.getCoreClockOffset());
if (deviceConfig.getMemoryClockOffset() != null) body.put("memoryClockOffset", deviceConfig.getMemoryClockOffset());
if (deviceConfig.getFanSpeed() != null) body.put("fanSpeed", deviceConfig.getFanSpeed());
return request("PUT", "/devices/" + encode(deviceId) + "/config", body)
.thenApply(resp -> gson.fromJson(resp, MiningDevice.class));
}
public CompletableFuture<Void> enableDevice(String deviceId) {
DeviceConfig cfg = new DeviceConfig();
cfg.setEnabled(true);
return setDeviceConfig(deviceId, cfg).thenApply(d -> null);
}
public CompletableFuture<Void> disableDevice(String deviceId) {
DeviceConfig cfg = new DeviceConfig();
cfg.setEnabled(false);
return setDeviceConfig(deviceId, cfg).thenApply(d -> null);
}
// ==================== Worker Operations ====================
public CompletableFuture<List<WorkerInfo>> listWorkers() {
return request("GET", "/workers", null)
.thenApply(resp -> {
Type type = new TypeToken<WorkersResponse>(){}.getType();
WorkersResponse result = gson.fromJson(resp, type);
return result.workers;
});
}
public CompletableFuture<WorkerInfo> getWorker(String workerId) {
return request("GET", "/workers/" + encode(workerId), null)
.thenApply(resp -> gson.fromJson(resp, WorkerInfo.class));
}
public CompletableFuture<Void> removeWorker(String workerId) {
return request("DELETE", "/workers/" + encode(workerId), null)
.thenApply(resp -> null);
}
// ==================== Algorithm Operations ====================
public CompletableFuture<List<MiningAlgorithm>> listAlgorithms() {
return request("GET", "/algorithms", null)
.thenApply(resp -> {
Type type = new TypeToken<AlgorithmsResponse>(){}.getType();
AlgorithmsResponse result = gson.fromJson(resp, type);
return result.algorithms;
});
}
public CompletableFuture<MiningAlgorithm> getAlgorithm(String name) {
return request("GET", "/algorithms/" + encode(name), null)
.thenApply(resp -> gson.fromJson(resp, MiningAlgorithm.class));
}
public CompletableFuture<Void> switchAlgorithm(String algorithm) {
return request("POST", "/algorithms/switch", Map.of("algorithm", algorithm))
.thenApply(resp -> null);
}
public CompletableFuture<MiningAlgorithm> getMostProfitable() {
return request("GET", "/algorithms/profitable", null)
.thenApply(resp -> gson.fromJson(resp, MiningAlgorithm.class));
}
// ==================== Lifecycle ====================
@Override
public void close() {
if (closed.compareAndSet(false, true)) {
activeConnection.set(null);
}
}
public boolean isClosed() {
return closed.get();
}
public CompletableFuture<Boolean> healthCheck() {
return request("GET", "/health", null)
.thenApply(resp -> {
Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
return "healthy".equals(map.get("status"));
})
.exceptionally(e -> false);
}
// ==================== Private Methods ====================
private CompletableFuture<String> request(String method, String path, Object body) {
if (closed.get()) {
return CompletableFuture.failedFuture(new MiningException("Client has been closed"));
}
return doRequest(method, path, body, 0);
}
private CompletableFuture<String> doRequest(String method, String path, Object body, int attempt) {
String url = config.getEndpoint() + path;
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + config.getApiKey())
.header("Content-Type", "application/json")
.header("X-SDK-Version", "java/0.1.0")
.timeout(Duration.ofSeconds(config.getTimeoutSecs()));
HttpRequest.BodyPublisher bodyPub = body != null
? HttpRequest.BodyPublishers.ofString(gson.toJson(body))
: HttpRequest.BodyPublishers.noBody();
switch (method) {
case "GET": builder.GET(); break;
case "POST": builder.POST(bodyPub); break;
case "PUT": builder.PUT(bodyPub); break;
case "DELETE": builder.method("DELETE", bodyPub); break;
}
return httpClient.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString())
.thenCompose(response -> {
if (response.statusCode() >= 400) {
Map<String, Object> errorBody = gson.fromJson(response.body(),
new TypeToken<Map<String, Object>>(){}.getType());
String message = errorBody != null && errorBody.get("message") != null
? (String) errorBody.get("message") : "HTTP " + response.statusCode();
String code = errorBody != null ? (String) errorBody.get("code") : null;
return CompletableFuture.failedFuture(
new MiningException(message, code, response.statusCode()));
}
return CompletableFuture.completedFuture(response.body());
})
.exceptionally(e -> {
if (attempt < config.getRetries() - 1) {
sleep((long) Math.pow(2, attempt) * 1000);
return doRequest(method, path, body, attempt + 1).join();
}
throw new RuntimeException(e);
});
}
private static String encode(String value) {
return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
}
private static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
// Helper response types
private static class DevicesResponse { List<MiningDevice> devices; }
private static class WorkersResponse { List<WorkerInfo> workers; }
private static class AlgorithmsResponse { List<MiningAlgorithm> algorithms; }
}

View file

@ -0,0 +1,425 @@
package io.synor.mining;
import java.util.List;
/**
* Mining SDK Types for Java.
*/
public class Types {
// Organizational class
}
// ==================== Enums ====================
enum DeviceType { cpu, gpu_nvidia, gpu_amd, asic }
enum DeviceStatus { idle, mining, error, offline }
enum ConnectionStatus { disconnected, connecting, connected, reconnecting }
enum TimePeriod { hour, day, week, month, all }
enum SubmitResultStatus { accepted, rejected, stale }
// ==================== Config ====================
class MiningConfig {
private final String apiKey;
private final String endpoint;
private final int timeoutSecs;
private final int retries;
private final boolean debug;
public MiningConfig(String apiKey) {
this(apiKey, "https://mining.synor.io/v1", 60, 3, false);
}
public MiningConfig(String apiKey, String endpoint, int timeoutSecs, int retries, boolean debug) {
this.apiKey = apiKey;
this.endpoint = endpoint;
this.timeoutSecs = timeoutSecs;
this.retries = retries;
this.debug = debug;
}
public String getApiKey() { return apiKey; }
public String getEndpoint() { return endpoint; }
public int getTimeoutSecs() { return timeoutSecs; }
public int getRetries() { return retries; }
public boolean isDebug() { return debug; }
}
// ==================== Pool Types ====================
class PoolConfig {
private String url;
private String user;
private String password;
private String algorithm;
private Double difficulty;
public String getUrl() { return url; }
public String getUser() { return user; }
public String getPassword() { return password; }
public String getAlgorithm() { return algorithm; }
public Double getDifficulty() { return difficulty; }
public void setUrl(String url) { this.url = url; }
public void setUser(String user) { this.user = user; }
public void setPassword(String password) { this.password = password; }
public void setAlgorithm(String algorithm) { this.algorithm = algorithm; }
public void setDifficulty(Double difficulty) { this.difficulty = difficulty; }
}
class StratumConnection {
private String id;
private String pool;
private ConnectionStatus status;
private String algorithm;
private double difficulty;
private long connectedAt;
private int acceptedShares;
private int rejectedShares;
private int staleShares;
private Long lastShareAt;
public String getId() { return id; }
public String getPool() { return pool; }
public ConnectionStatus getStatus() { return status; }
public String getAlgorithm() { return algorithm; }
public double getDifficulty() { return difficulty; }
public long getConnectedAt() { return connectedAt; }
public int getAcceptedShares() { return acceptedShares; }
public int getRejectedShares() { return rejectedShares; }
public int getStaleShares() { return staleShares; }
public Long getLastShareAt() { return lastShareAt; }
}
class PoolStats {
private String url;
private int workers;
private double hashrate;
private double difficulty;
private long lastBlock;
private int blocksFound24h;
private double luck;
public String getUrl() { return url; }
public int getWorkers() { return workers; }
public double getHashrate() { return hashrate; }
public double getDifficulty() { return difficulty; }
public long getLastBlock() { return lastBlock; }
public int getBlocksFound24h() { return blocksFound24h; }
public double getLuck() { return luck; }
}
// ==================== Mining Types ====================
class TemplateTransaction {
private String txid;
private String data;
private String fee;
private int weight;
public String getTxid() { return txid; }
public String getData() { return data; }
public String getFee() { return fee; }
public int getWeight() { return weight; }
}
class BlockTemplate {
private String id;
private String previousBlockHash;
private String merkleRoot;
private long timestamp;
private String bits;
private long height;
private String coinbaseValue;
private List<TemplateTransaction> transactions;
private String target;
private String algorithm;
private String extraNonce;
public String getId() { return id; }
public String getPreviousBlockHash() { return previousBlockHash; }
public String getMerkleRoot() { return merkleRoot; }
public long getTimestamp() { return timestamp; }
public String getBits() { return bits; }
public long getHeight() { return height; }
public String getCoinbaseValue() { return coinbaseValue; }
public List<TemplateTransaction> getTransactions() { return transactions; }
public String getTarget() { return target; }
public String getAlgorithm() { return algorithm; }
public String getExtraNonce() { return extraNonce; }
}
class MinedWork {
private String templateId;
private String nonce;
private String extraNonce;
private long timestamp;
private String hash;
public String getTemplateId() { return templateId; }
public String getNonce() { return nonce; }
public String getExtraNonce() { return extraNonce; }
public long getTimestamp() { return timestamp; }
public String getHash() { return hash; }
public void setTemplateId(String templateId) { this.templateId = templateId; }
public void setNonce(String nonce) { this.nonce = nonce; }
public void setExtraNonce(String extraNonce) { this.extraNonce = extraNonce; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
public void setHash(String hash) { this.hash = hash; }
}
class ShareInfo {
private String hash;
private double difficulty;
private long timestamp;
private boolean accepted;
public String getHash() { return hash; }
public double getDifficulty() { return difficulty; }
public long getTimestamp() { return timestamp; }
public boolean isAccepted() { return accepted; }
}
class SubmitResult {
private SubmitResultStatus status;
private ShareInfo share;
private boolean blockFound;
private String reason;
private String blockHash;
private String reward;
public SubmitResultStatus getStatus() { return status; }
public ShareInfo getShare() { return share; }
public boolean isBlockFound() { return blockFound; }
public String getReason() { return reason; }
public String getBlockHash() { return blockHash; }
public String getReward() { return reward; }
}
// ==================== Stats Types ====================
class Hashrate {
private double current;
private double average1h;
private double average24h;
private double peak;
private String unit;
public double getCurrent() { return current; }
public double getAverage1h() { return average1h; }
public double getAverage24h() { return average24h; }
public double getPeak() { return peak; }
public String getUnit() { return unit; }
}
class ShareStats {
private int accepted;
private int rejected;
private int stale;
private int total;
private double acceptRate;
public int getAccepted() { return accepted; }
public int getRejected() { return rejected; }
public int getStale() { return stale; }
public int getTotal() { return total; }
public double getAcceptRate() { return acceptRate; }
}
class DeviceTemperature {
private double current;
private double max;
private boolean throttling;
public double getCurrent() { return current; }
public double getMax() { return max; }
public boolean isThrottling() { return throttling; }
}
class EarningsSnapshot {
private String today;
private String yesterday;
private String thisWeek;
private String thisMonth;
private String total;
private String currency;
public String getToday() { return today; }
public String getYesterday() { return yesterday; }
public String getThisWeek() { return thisWeek; }
public String getThisMonth() { return thisMonth; }
public String getTotal() { return total; }
public String getCurrency() { return currency; }
}
class MiningStats {
private Hashrate hashrate;
private ShareStats shares;
private long uptime;
private double efficiency;
private EarningsSnapshot earnings;
private Double powerConsumption;
private DeviceTemperature temperature;
public Hashrate getHashrate() { return hashrate; }
public ShareStats getShares() { return shares; }
public long getUptime() { return uptime; }
public double getEfficiency() { return efficiency; }
public EarningsSnapshot getEarnings() { return earnings; }
public Double getPowerConsumption() { return powerConsumption; }
public DeviceTemperature getTemperature() { return temperature; }
}
class EarningsBreakdown {
private long date;
private String amount;
private int blocks;
private int shares;
private double hashrate;
public long getDate() { return date; }
public String getAmount() { return amount; }
public int getBlocks() { return blocks; }
public int getShares() { return shares; }
public double getHashrate() { return hashrate; }
}
class Earnings {
private TimePeriod period;
private long startDate;
private long endDate;
private String amount;
private int blocks;
private int shares;
private double averageHashrate;
private String currency;
private List<EarningsBreakdown> breakdown;
public TimePeriod getPeriod() { return period; }
public long getStartDate() { return startDate; }
public long getEndDate() { return endDate; }
public String getAmount() { return amount; }
public int getBlocks() { return blocks; }
public int getShares() { return shares; }
public double getAverageHashrate() { return averageHashrate; }
public String getCurrency() { return currency; }
public List<EarningsBreakdown> getBreakdown() { return breakdown; }
}
// ==================== Device Types ====================
class MiningDevice {
private String id;
private String name;
private DeviceType type;
private DeviceStatus status;
private double hashrate;
private double temperature;
private double fanSpeed;
private double powerDraw;
private long memoryUsed;
private long memoryTotal;
private String driver;
private String firmware;
public String getId() { return id; }
public String getName() { return name; }
public DeviceType getType() { return type; }
public DeviceStatus getStatus() { return status; }
public double getHashrate() { return hashrate; }
public double getTemperature() { return temperature; }
public double getFanSpeed() { return fanSpeed; }
public double getPowerDraw() { return powerDraw; }
public long getMemoryUsed() { return memoryUsed; }
public long getMemoryTotal() { return memoryTotal; }
public String getDriver() { return driver; }
public String getFirmware() { return firmware; }
}
class DeviceConfig {
private boolean enabled;
private Integer intensity;
private Integer powerLimit;
private Integer coreClockOffset;
private Integer memoryClockOffset;
private Integer fanSpeed;
public boolean isEnabled() { return enabled; }
public Integer getIntensity() { return intensity; }
public Integer getPowerLimit() { return powerLimit; }
public Integer getCoreClockOffset() { return coreClockOffset; }
public Integer getMemoryClockOffset() { return memoryClockOffset; }
public Integer getFanSpeed() { return fanSpeed; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public void setIntensity(Integer intensity) { this.intensity = intensity; }
public void setPowerLimit(Integer powerLimit) { this.powerLimit = powerLimit; }
public void setCoreClockOffset(Integer coreClockOffset) { this.coreClockOffset = coreClockOffset; }
public void setMemoryClockOffset(Integer memoryClockOffset) { this.memoryClockOffset = memoryClockOffset; }
public void setFanSpeed(Integer fanSpeed) { this.fanSpeed = fanSpeed; }
}
// ==================== Worker Types ====================
class WorkerInfo {
private String id;
private String name;
private ConnectionStatus status;
private Hashrate hashrate;
private ShareStats shares;
private List<MiningDevice> devices;
private long lastSeen;
private long uptime;
public String getId() { return id; }
public String getName() { return name; }
public ConnectionStatus getStatus() { return status; }
public Hashrate getHashrate() { return hashrate; }
public ShareStats getShares() { return shares; }
public List<MiningDevice> getDevices() { return devices; }
public long getLastSeen() { return lastSeen; }
public long getUptime() { return uptime; }
}
// ==================== Algorithm Types ====================
class MiningAlgorithm {
private String name;
private String displayName;
private String hashUnit;
private String profitability;
private double difficulty;
private String blockReward;
private int blockTime;
public String getName() { return name; }
public String getDisplayName() { return displayName; }
public String getHashUnit() { return hashUnit; }
public String getProfitability() { return profitability; }
public double getDifficulty() { return difficulty; }
public String getBlockReward() { return blockReward; }
public int getBlockTime() { return blockTime; }
}
// ==================== Error ====================
class MiningException extends RuntimeException {
private final String code;
private final int statusCode;
public MiningException(String message) {
super(message);
this.code = null;
this.statusCode = 0;
}
public MiningException(String message, String code, int statusCode) {
super(message);
this.code = code;
this.statusCode = statusCode;
}
public String getCode() { return code; }
public int getStatusCode() { return statusCode; }
}

View file

@ -0,0 +1,415 @@
package io.synor.economics
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.delay
import java.util.concurrent.atomic.AtomicBoolean
/**
* Synor Economics SDK for Kotlin.
* Pricing, billing, staking, and discount operations.
*/
class SynorEconomics(private val config: EconomicsConfig) : AutoCloseable {
companion object {
private val gson: Gson = GsonBuilder().create()
}
private val httpClient = HttpClient(CIO) {
engine { requestTimeout = config.timeoutSecs * 1000 }
}
private val closed = AtomicBoolean(false)
constructor(apiKey: String) : this(EconomicsConfig(apiKey))
// ==================== Pricing Operations ====================
suspend fun getPrice(service: ServiceType, usage: UsageMetrics): Price {
val body = mapOf("service" to service.name.lowercase(), "usage" to usage)
val resp = request("POST", "/pricing/calculate", body)
return gson.fromJson(resp, Price::class.java)
}
suspend fun estimateCost(plan: UsagePlan): CostEstimate {
val body = mapOf(
"service" to plan.service.name.lowercase(),
"estimatedUsage" to plan.estimatedUsage,
"period" to plan.period.name.lowercase()
)
val resp = request("POST", "/pricing/estimate", body)
return gson.fromJson(resp, CostEstimate::class.java)
}
suspend fun getPricingTiers(service: ServiceType): List<PricingTier> {
val resp = request("GET", "/pricing/tiers/${service.name.lowercase()}")
val result = gson.fromJson<TiersResponse>(resp, TiersResponse::class.java)
return result.tiers ?: emptyList()
}
// ==================== Billing Operations ====================
suspend fun getUsage(period: BillingPeriod? = null): Usage {
val path = if (period != null) "/billing/usage?period=${period.name.lowercase()}" else "/billing/usage"
val resp = request("GET", path)
return gson.fromJson(resp, Usage::class.java)
}
suspend fun getUsageByDateRange(startDate: Long, endDate: Long): Usage {
val resp = request("GET", "/billing/usage?startDate=$startDate&endDate=$endDate")
return gson.fromJson(resp, Usage::class.java)
}
suspend fun getInvoices(): List<Invoice> {
val resp = request("GET", "/billing/invoices")
val result = gson.fromJson<InvoicesResponse>(resp, InvoicesResponse::class.java)
return result.invoices ?: emptyList()
}
suspend fun getInvoice(invoiceId: String): Invoice {
val resp = request("GET", "/billing/invoices/${invoiceId.urlEncode()}")
return gson.fromJson(resp, Invoice::class.java)
}
suspend fun downloadInvoice(invoiceId: String): ByteArray {
val resp = request("GET", "/billing/invoices/${invoiceId.urlEncode()}/pdf")
val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
return java.util.Base64.getDecoder().decode(map["data"] as String)
}
suspend fun getBalance(): AccountBalance {
val resp = request("GET", "/billing/balance")
return gson.fromJson(resp, AccountBalance::class.java)
}
suspend fun addCredits(amount: String, paymentMethod: String) {
request("POST", "/billing/credits", mapOf("amount" to amount, "paymentMethod" to paymentMethod))
}
// ==================== Staking Operations ====================
suspend fun stake(amount: String, durationDays: Int? = null): StakeReceipt {
val body = mutableMapOf<String, Any>("amount" to amount)
durationDays?.let { body["durationDays"] = it }
val resp = request("POST", "/staking/stake", body)
return gson.fromJson(resp, StakeReceipt::class.java)
}
suspend fun unstake(stakeId: String): UnstakeReceipt {
val resp = request("POST", "/staking/unstake/${stakeId.urlEncode()}")
return gson.fromJson(resp, UnstakeReceipt::class.java)
}
suspend fun getStakes(): List<StakeInfo> {
val resp = request("GET", "/staking/stakes")
val result = gson.fromJson<StakesResponse>(resp, StakesResponse::class.java)
return result.stakes ?: emptyList()
}
suspend fun getStake(stakeId: String): StakeInfo {
val resp = request("GET", "/staking/stakes/${stakeId.urlEncode()}")
return gson.fromJson(resp, StakeInfo::class.java)
}
suspend fun getStakingRewards(): Rewards {
val resp = request("GET", "/staking/rewards")
return gson.fromJson(resp, Rewards::class.java)
}
suspend fun claimRewards(): String {
val resp = request("POST", "/staking/rewards/claim")
val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
return map["txHash"] as String
}
suspend fun getCurrentApy(): Double {
val resp = request("GET", "/staking/apy")
val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
return (map["apy"] as Number).toDouble()
}
// ==================== Discount Operations ====================
suspend fun applyDiscount(code: String): AppliedDiscount {
val resp = request("POST", "/discounts/apply", mapOf("code" to code))
return gson.fromJson(resp, AppliedDiscount::class.java)
}
suspend fun getAvailableDiscounts(): List<Discount> {
val resp = request("GET", "/discounts/available")
val result = gson.fromJson<DiscountsResponse>(resp, DiscountsResponse::class.java)
return result.discounts ?: emptyList()
}
suspend fun validateDiscount(code: String): Discount {
val resp = request("GET", "/discounts/validate/${code.urlEncode()}")
return gson.fromJson(resp, Discount::class.java)
}
suspend fun getAppliedDiscounts(): List<AppliedDiscount> {
val resp = request("GET", "/discounts/applied")
val result = gson.fromJson<AppliedDiscountsResponse>(resp, AppliedDiscountsResponse::class.java)
return result.discounts ?: emptyList()
}
suspend fun removeDiscount(discountId: String) {
request("DELETE", "/discounts/${discountId.urlEncode()}")
}
// ==================== Lifecycle ====================
override fun close() {
closed.set(true)
httpClient.close()
}
fun isClosed(): Boolean = closed.get()
suspend fun healthCheck(): Boolean = try {
val resp = request("GET", "/health")
val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
map["status"] == "healthy"
} catch (e: Exception) { false }
// ==================== Private Methods ====================
private suspend fun request(method: String, path: String, body: Any? = null): String {
if (closed.get()) throw EconomicsException("Client has been closed")
var lastError: Exception? = null
repeat(config.retries) { attempt ->
try {
return doRequest(method, path, body)
} catch (e: Exception) {
lastError = e
if (attempt < config.retries - 1) delay((1L shl attempt) * 1000)
}
}
throw lastError ?: EconomicsException("Unknown error")
}
private suspend fun doRequest(method: String, path: String, body: Any?): String {
val url = "${config.endpoint}$path"
val response: HttpResponse = httpClient.request(url) {
this.method = HttpMethod.parse(method)
header("Authorization", "Bearer ${config.apiKey}")
header("Content-Type", "application/json")
header("X-SDK-Version", "kotlin/0.1.0")
body?.let { setBody(gson.toJson(it)) }
}
val responseBody = response.bodyAsText()
if (response.status.value >= 400) {
val errorMap = try {
gson.fromJson<Map<String, Any>>(responseBody, object : TypeToken<Map<String, Any>>() {}.type)
} catch (e: Exception) { emptyMap() }
throw EconomicsException(
errorMap["message"] as? String ?: "HTTP ${response.status.value}",
errorMap["code"] as? String,
response.status.value
)
}
return responseBody
}
private fun String.urlEncode(): String = java.net.URLEncoder.encode(this, "UTF-8")
}
// ==================== Types ====================
data class EconomicsConfig(
val apiKey: String,
val endpoint: String = "https://economics.synor.io/v1",
val timeoutSecs: Long = 60,
val retries: Int = 3,
val debug: Boolean = false
)
enum class ServiceType { compute, storage, database, hosting, bridge, mining, rpc }
enum class BillingPeriod { daily, weekly, monthly, yearly }
enum class StakeStatus { active, unstaking, unlocked, slashed }
enum class DiscountType { percentage, fixed, volume }
data class UsageMetrics(
val computeHours: Double = 0.0,
val storageGb: Double = 0.0,
val requests: Long = 0,
val bandwidthGb: Double = 0.0,
val gpuHours: Double = 0.0
)
data class PricingTier(
val name: String,
val upTo: Double,
val pricePerUnit: Double,
val unit: String
)
data class Price(
val service: ServiceType,
val amount: String,
val currency: String,
val amountUsd: String,
val breakdown: List<PricingTier>? = null,
val discount: String? = null,
val finalAmount: String
)
data class UsagePlan(
val service: ServiceType,
val estimatedUsage: UsageMetrics,
val period: BillingPeriod
)
data class CostBreakdown(
val category: String,
val amount: String,
val percentage: Double
)
data class CostEstimate(
val service: ServiceType,
val period: BillingPeriod,
val estimatedCost: String,
val estimatedCostUsd: String,
val currency: String,
val confidenceLevel: String,
val breakdown: List<CostBreakdown>? = null,
val savingsFromStaking: String? = null
)
data class UsageRecord(
val service: ServiceType,
val resource: String,
val quantity: Double,
val unit: String,
val timestamp: Long,
val cost: String
)
data class Usage(
val period: BillingPeriod,
val startDate: Long,
val endDate: Long,
val records: List<UsageRecord>,
val totalCost: String,
val currency: String
)
data class InvoiceLineItem(
val description: String,
val quantity: Double,
val unit: String,
val unitPrice: String,
val amount: String
)
data class Invoice(
val id: String,
val number: String,
val periodStart: Long,
val periodEnd: Long,
val subtotal: String,
val tax: String,
val discount: String,
val total: String,
val currency: String,
val status: String,
val dueDate: Long? = null,
val paidAt: Long? = null,
val lineItems: List<InvoiceLineItem>,
val pdfUrl: String? = null
)
data class AccountBalance(
val available: String,
val pending: String,
val reserved: String,
val staked: String,
val currency: String,
val lastUpdated: Long
)
data class StakeReceipt(
val id: String,
val txHash: String,
val amount: String,
val startDate: Long,
val endDate: Long,
val apy: Double,
val status: StakeStatus
)
data class StakeInfo(
val id: String,
val amount: String,
val status: StakeStatus,
val startDate: Long,
val endDate: Long,
val apy: Double,
val earnedRewards: String,
val pendingRewards: String,
val unlockDate: Long? = null,
val discountPercent: Int = 0
)
data class UnstakeReceipt(
val id: String,
val txHash: String,
val amount: String,
val initiatedAt: Long,
val availableAt: Long,
val penalty: String? = null
)
data class RewardRecord(
val date: Long,
val amount: String,
val type: String,
val txHash: String? = null
)
data class Rewards(
val totalEarned: String,
val pendingClaim: String,
val lastClaimDate: String? = null,
val currentApy: Double,
val history: List<RewardRecord>
)
data class Discount(
val id: String,
val code: String,
val type: DiscountType,
val value: String,
val validFrom: Long? = null,
val validUntil: Long? = null,
val usageLimit: Int? = null,
val usageCount: Int = 0,
val applicableServices: List<ServiceType>? = null,
val minimumSpend: String? = null,
val active: Boolean = true
)
data class AppliedDiscount(
val discount: Discount,
val savedAmount: String,
val appliedAt: Long
)
internal data class TiersResponse(val tiers: List<PricingTier>?)
internal data class InvoicesResponse(val invoices: List<Invoice>?)
internal data class StakesResponse(val stakes: List<StakeInfo>?)
internal data class DiscountsResponse(val discounts: List<Discount>?)
internal data class AppliedDiscountsResponse(val discounts: List<AppliedDiscount>?)
class EconomicsException(
message: String,
val code: String? = null,
val statusCode: Int = 0
) : RuntimeException(message)

View file

@ -0,0 +1,465 @@
package io.synor.governance
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.delay
import java.util.concurrent.atomic.AtomicBoolean
/**
* Synor Governance SDK for Kotlin.
* Proposals, voting, DAOs, and vesting operations.
*/
class SynorGovernance(private val config: GovernanceConfig) : AutoCloseable {
companion object {
private val gson: Gson = GsonBuilder().create()
private val FINAL_STATUSES = setOf(
ProposalStatus.passed, ProposalStatus.rejected, ProposalStatus.executed, ProposalStatus.cancelled
)
}
private val httpClient = HttpClient(CIO) {
engine { requestTimeout = config.timeoutSecs * 1000 }
}
private val closed = AtomicBoolean(false)
constructor(apiKey: String) : this(GovernanceConfig(apiKey))
// ==================== Proposal Operations ====================
suspend fun createProposal(proposal: ProposalDraft): Proposal {
val body = mutableMapOf<String, Any>(
"title" to proposal.title,
"description" to proposal.description
)
proposal.discussionUrl?.let { body["discussionUrl"] = it }
proposal.votingStartTime?.let { body["votingStartTime"] = it }
proposal.votingEndTime?.let { body["votingEndTime"] = it }
proposal.actions?.let { body["actions"] = it }
proposal.daoId?.let { body["daoId"] = it }
val resp = request("POST", "/proposals", body)
return gson.fromJson(resp, Proposal::class.java)
}
suspend fun getProposal(proposalId: String): Proposal {
val resp = request("GET", "/proposals/${proposalId.urlEncode()}")
return gson.fromJson(resp, Proposal::class.java)
}
suspend fun listProposals(filter: ProposalFilter? = null): List<Proposal> {
val params = mutableListOf<String>()
filter?.status?.let { params.add("status=${it.name.lowercase()}") }
filter?.proposer?.let { params.add("proposer=${it.urlEncode()}") }
filter?.daoId?.let { params.add("daoId=${it.urlEncode()}") }
filter?.limit?.let { params.add("limit=$it") }
filter?.offset?.let { params.add("offset=$it") }
val path = if (params.isNotEmpty()) "/proposals?${params.joinToString("&")}" else "/proposals"
val resp = request("GET", path)
val result = gson.fromJson<ProposalsResponse>(resp, ProposalsResponse::class.java)
return result.proposals ?: emptyList()
}
suspend fun cancelProposal(proposalId: String): Proposal {
val resp = request("POST", "/proposals/${proposalId.urlEncode()}/cancel")
return gson.fromJson(resp, Proposal::class.java)
}
suspend fun executeProposal(proposalId: String): Proposal {
val resp = request("POST", "/proposals/${proposalId.urlEncode()}/execute")
return gson.fromJson(resp, Proposal::class.java)
}
suspend fun waitForProposal(proposalId: String, pollIntervalMs: Long = 60000, maxWaitMs: Long = 604800000): Proposal {
val deadline = System.currentTimeMillis() + maxWaitMs
while (System.currentTimeMillis() < deadline) {
val proposal = getProposal(proposalId)
if (proposal.status in FINAL_STATUSES) return proposal
delay(pollIntervalMs)
}
throw GovernanceException("Timeout waiting for proposal completion")
}
// ==================== Voting Operations ====================
suspend fun vote(proposalId: String, vote: Vote, weight: String? = null): VoteReceipt {
val body = mutableMapOf<String, Any>("choice" to vote.choice.name.lowercase())
vote.reason?.let { body["reason"] = it }
weight?.let { body["weight"] = it }
val resp = request("POST", "/proposals/${proposalId.urlEncode()}/vote", body)
return gson.fromJson(resp, VoteReceipt::class.java)
}
suspend fun getVotes(proposalId: String): List<VoteReceipt> {
val resp = request("GET", "/proposals/${proposalId.urlEncode()}/votes")
val result = gson.fromJson<VotesResponse>(resp, VotesResponse::class.java)
return result.votes ?: emptyList()
}
suspend fun getMyVote(proposalId: String): VoteReceipt {
val resp = request("GET", "/proposals/${proposalId.urlEncode()}/votes/me")
return gson.fromJson(resp, VoteReceipt::class.java)
}
suspend fun delegate(delegatee: String, amount: String? = null): DelegationReceipt {
val body = mutableMapOf<String, Any>("delegatee" to delegatee)
amount?.let { body["amount"] = it }
val resp = request("POST", "/voting/delegate", body)
return gson.fromJson(resp, DelegationReceipt::class.java)
}
suspend fun undelegate(delegatee: String): DelegationReceipt {
val resp = request("POST", "/voting/undelegate", mapOf("delegatee" to delegatee))
return gson.fromJson(resp, DelegationReceipt::class.java)
}
suspend fun getVotingPower(address: String): VotingPower {
val resp = request("GET", "/voting/power/${address.urlEncode()}")
return gson.fromJson(resp, VotingPower::class.java)
}
suspend fun getDelegations(address: String): List<DelegationReceipt> {
val resp = request("GET", "/voting/delegations/${address.urlEncode()}")
val result = gson.fromJson<DelegationsResponse>(resp, DelegationsResponse::class.java)
return result.delegations ?: emptyList()
}
// ==================== DAO Operations ====================
suspend fun createDao(daoConfig: DaoConfig): Dao {
val body = mutableMapOf<String, Any>(
"name" to daoConfig.name,
"description" to daoConfig.description,
"type" to daoConfig.type.name.lowercase(),
"votingPeriodDays" to daoConfig.votingPeriodDays,
"timelockDays" to daoConfig.timelockDays
)
daoConfig.tokenAddress?.let { body["tokenAddress"] = it }
daoConfig.quorumPercent?.let { body["quorumPercent"] = it }
daoConfig.proposalThreshold?.let { body["proposalThreshold"] = it }
daoConfig.multisigMembers?.let { body["multisigMembers"] = it }
daoConfig.multisigThreshold?.let { body["multisigThreshold"] = it }
val resp = request("POST", "/daos", body)
return gson.fromJson(resp, Dao::class.java)
}
suspend fun getDao(daoId: String): Dao {
val resp = request("GET", "/daos/${daoId.urlEncode()}")
return gson.fromJson(resp, Dao::class.java)
}
suspend fun listDaos(limit: Int? = null, offset: Int? = null): List<Dao> {
val params = mutableListOf<String>()
limit?.let { params.add("limit=$it") }
offset?.let { params.add("offset=$it") }
val path = if (params.isNotEmpty()) "/daos?${params.joinToString("&")}" else "/daos"
val resp = request("GET", path)
val result = gson.fromJson<DaosResponse>(resp, DaosResponse::class.java)
return result.daos ?: emptyList()
}
suspend fun getDaoTreasury(daoId: String): DaoTreasury {
val resp = request("GET", "/daos/${daoId.urlEncode()}/treasury")
return gson.fromJson(resp, DaoTreasury::class.java)
}
suspend fun getDaoMembers(daoId: String): List<String> {
val resp = request("GET", "/daos/${daoId.urlEncode()}/members")
val result = gson.fromJson<MembersResponse>(resp, MembersResponse::class.java)
return result.members ?: emptyList()
}
// ==================== Vesting Operations ====================
suspend fun createVestingSchedule(schedule: VestingSchedule): VestingContract {
val body = mapOf(
"beneficiary" to schedule.beneficiary,
"totalAmount" to schedule.totalAmount,
"startTime" to schedule.startTime,
"cliffDuration" to schedule.cliffDuration,
"vestingDuration" to schedule.vestingDuration,
"revocable" to schedule.revocable
)
val resp = request("POST", "/vesting", body)
return gson.fromJson(resp, VestingContract::class.java)
}
suspend fun getVestingContract(contractId: String): VestingContract {
val resp = request("GET", "/vesting/${contractId.urlEncode()}")
return gson.fromJson(resp, VestingContract::class.java)
}
suspend fun listVestingContracts(beneficiary: String? = null): List<VestingContract> {
val path = if (beneficiary != null) "/vesting?beneficiary=${beneficiary.urlEncode()}" else "/vesting"
val resp = request("GET", path)
val result = gson.fromJson<VestingContractsResponse>(resp, VestingContractsResponse::class.java)
return result.contracts ?: emptyList()
}
suspend fun claimVested(contractId: String): ClaimReceipt {
val resp = request("POST", "/vesting/${contractId.urlEncode()}/claim")
return gson.fromJson(resp, ClaimReceipt::class.java)
}
suspend fun revokeVesting(contractId: String): VestingContract {
val resp = request("POST", "/vesting/${contractId.urlEncode()}/revoke")
return gson.fromJson(resp, VestingContract::class.java)
}
suspend fun getReleasableAmount(contractId: String): String {
val resp = request("GET", "/vesting/${contractId.urlEncode()}/releasable")
val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
return map["amount"] as String
}
// ==================== Lifecycle ====================
override fun close() {
closed.set(true)
httpClient.close()
}
fun isClosed(): Boolean = closed.get()
suspend fun healthCheck(): Boolean = try {
val resp = request("GET", "/health")
val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
map["status"] == "healthy"
} catch (e: Exception) { false }
// ==================== Private Methods ====================
private suspend fun request(method: String, path: String, body: Any? = null): String {
if (closed.get()) throw GovernanceException("Client has been closed")
var lastError: Exception? = null
repeat(config.retries) { attempt ->
try {
return doRequest(method, path, body)
} catch (e: Exception) {
lastError = e
if (attempt < config.retries - 1) delay((1L shl attempt) * 1000)
}
}
throw lastError ?: GovernanceException("Unknown error")
}
private suspend fun doRequest(method: String, path: String, body: Any?): String {
val url = "${config.endpoint}$path"
val response: HttpResponse = httpClient.request(url) {
this.method = HttpMethod.parse(method)
header("Authorization", "Bearer ${config.apiKey}")
header("Content-Type", "application/json")
header("X-SDK-Version", "kotlin/0.1.0")
body?.let { setBody(gson.toJson(it)) }
}
val responseBody = response.bodyAsText()
if (response.status.value >= 400) {
val errorMap = try {
gson.fromJson<Map<String, Any>>(responseBody, object : TypeToken<Map<String, Any>>() {}.type)
} catch (e: Exception) { emptyMap() }
throw GovernanceException(
errorMap["message"] as? String ?: "HTTP ${response.status.value}",
errorMap["code"] as? String,
response.status.value
)
}
return responseBody
}
private fun String.urlEncode(): String = java.net.URLEncoder.encode(this, "UTF-8")
}
// ==================== Types ====================
data class GovernanceConfig(
val apiKey: String,
val endpoint: String = "https://governance.synor.io/v1",
val timeoutSecs: Long = 60,
val retries: Int = 3,
val debug: Boolean = false
)
enum class ProposalStatus { draft, active, passed, rejected, executed, cancelled }
enum class VoteChoice { yes, no, abstain }
enum class DaoType { token, multisig, hybrid }
enum class VestingStatus { pending, active, paused, completed, revoked }
data class ProposalAction(
val target: String,
val method: String,
val data: String,
val value: String? = null
)
data class ProposalDraft(
val title: String,
val description: String,
val discussionUrl: String? = null,
val votingStartTime: Long? = null,
val votingEndTime: Long? = null,
val actions: List<ProposalAction>? = null,
val daoId: String? = null
)
data class VoteBreakdown(
val yes: String,
val no: String,
val abstain: String,
val quorum: String,
val quorumPercent: Double,
val quorumReached: Boolean
)
data class Proposal(
val id: String,
val title: String,
val description: String,
val proposer: String,
val status: ProposalStatus,
val createdAt: Long,
val votingStartTime: Long,
val votingEndTime: Long,
val votes: VoteBreakdown,
val discussionUrl: String? = null,
val actions: List<ProposalAction>? = null,
val daoId: String? = null,
val snapshotBlock: String? = null,
val executedAt: Long? = null,
val executedTxHash: String? = null
)
data class ProposalFilter(
val status: ProposalStatus? = null,
val proposer: String? = null,
val daoId: String? = null,
val limit: Int? = null,
val offset: Int? = null
)
data class Vote(val choice: VoteChoice, val reason: String? = null)
data class VoteReceipt(
val id: String,
val proposalId: String,
val voter: String,
val choice: VoteChoice,
val weight: String,
val timestamp: Long,
val txHash: String,
val reason: String? = null
)
data class VotingPower(
val address: String,
val balance: String,
val delegatedTo: String? = null,
val delegatedFrom: String,
val totalPower: String,
val blockNumber: Long
)
data class DelegationReceipt(
val id: String,
val delegator: String,
val delegatee: String,
val amount: String,
val timestamp: Long,
val txHash: String
)
data class DaoConfig(
val name: String,
val description: String,
val type: DaoType,
val tokenAddress: String? = null,
val quorumPercent: String? = null,
val votingPeriodDays: Int = 7,
val timelockDays: Int = 2,
val proposalThreshold: Int? = null,
val multisigMembers: List<String>? = null,
val multisigThreshold: Int? = null
)
data class TreasuryAsset(
val address: String,
val symbol: String,
val balance: String,
val valueUsd: String
)
data class DaoTreasury(
val address: String,
val balance: String,
val currency: String,
val assets: List<TreasuryAsset>
)
data class Dao(
val id: String,
val name: String,
val description: String,
val type: DaoType,
val tokenAddress: String? = null,
val governorAddress: String,
val timelockAddress: String,
val treasury: DaoTreasury,
val quorumPercent: String,
val votingPeriodDays: Int,
val timelockDays: Int,
val proposalCount: Int,
val memberCount: Int,
val createdAt: Long
)
data class VestingSchedule(
val beneficiary: String,
val totalAmount: String,
val startTime: Long,
val cliffDuration: Long,
val vestingDuration: Long,
val revocable: Boolean = false
)
data class VestingContract(
val id: String,
val contractAddress: String,
val beneficiary: String,
val totalAmount: String,
val releasedAmount: String,
val releasableAmount: String,
val startTime: Long,
val cliffEnd: Long,
val vestingEnd: Long,
val status: VestingStatus,
val createdAt: Long,
val txHash: String
)
data class ClaimReceipt(
val vestingContractId: String,
val amount: String,
val txHash: String,
val timestamp: Long,
val remainingAmount: String
)
internal data class ProposalsResponse(val proposals: List<Proposal>?)
internal data class VotesResponse(val votes: List<VoteReceipt>?)
internal data class DelegationsResponse(val delegations: List<DelegationReceipt>?)
internal data class DaosResponse(val daos: List<Dao>?)
internal data class MembersResponse(val members: List<String>?)
internal data class VestingContractsResponse(val contracts: List<VestingContract>?)
class GovernanceException(
message: String,
val code: String? = null,
val statusCode: Int = 0
) : RuntimeException(message)

View file

@ -0,0 +1,464 @@
package io.synor.mining
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.delay
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
/**
* Synor Mining SDK for Kotlin.
* Pool connections, block templates, hashrate stats, and GPU management.
*/
class SynorMining(private val config: MiningConfig) : AutoCloseable {
companion object {
private val gson: Gson = GsonBuilder().create()
}
private val httpClient = HttpClient(CIO) {
engine { requestTimeout = config.timeoutSecs * 1000 }
}
private val closed = AtomicBoolean(false)
private val activeConnection = AtomicReference<StratumConnection?>(null)
constructor(apiKey: String) : this(MiningConfig(apiKey))
// ==================== Pool Operations ====================
suspend fun connect(pool: PoolConfig): StratumConnection {
val body = mutableMapOf<String, Any>("url" to pool.url, "user" to pool.user)
pool.password?.let { body["password"] = it }
pool.algorithm?.let { body["algorithm"] = it }
pool.difficulty?.let { body["difficulty"] = it }
val resp = request("POST", "/pool/connect", body)
val conn = gson.fromJson(resp, StratumConnection::class.java)
activeConnection.set(conn)
return conn
}
suspend fun disconnect() {
if (activeConnection.get() == null) return
request("POST", "/pool/disconnect")
activeConnection.set(null)
}
suspend fun getConnection(): StratumConnection {
val resp = request("GET", "/pool/connection")
val conn = gson.fromJson(resp, StratumConnection::class.java)
activeConnection.set(conn)
return conn
}
suspend fun getPoolStats(): PoolStats {
val resp = request("GET", "/pool/stats")
return gson.fromJson(resp, PoolStats::class.java)
}
fun isConnected(): Boolean {
val conn = activeConnection.get()
return conn != null && conn.status == ConnectionStatus.connected
}
// ==================== Mining Operations ====================
suspend fun getBlockTemplate(): BlockTemplate {
val resp = request("GET", "/mining/template")
return gson.fromJson(resp, BlockTemplate::class.java)
}
suspend fun submitWork(work: MinedWork): SubmitResult {
val body = mapOf(
"templateId" to work.templateId,
"nonce" to work.nonce,
"extraNonce" to work.extraNonce,
"timestamp" to work.timestamp,
"hash" to work.hash
)
val resp = request("POST", "/mining/submit", body)
return gson.fromJson(resp, SubmitResult::class.java)
}
suspend fun startMining(algorithm: String? = null) {
val body = if (algorithm != null) mapOf("algorithm" to algorithm) else null
request("POST", "/mining/start", body)
}
suspend fun stopMining() {
request("POST", "/mining/stop")
}
suspend fun isMining(): Boolean {
val resp = request("GET", "/mining/status")
val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
return map["mining"] == true
}
// ==================== Stats Operations ====================
suspend fun getHashrate(): Hashrate {
val resp = request("GET", "/stats/hashrate")
return gson.fromJson(resp, Hashrate::class.java)
}
suspend fun getStats(): MiningStats {
val resp = request("GET", "/stats")
return gson.fromJson(resp, MiningStats::class.java)
}
suspend fun getEarnings(period: TimePeriod? = null): Earnings {
val path = if (period != null) "/stats/earnings?period=${period.name.lowercase()}" else "/stats/earnings"
val resp = request("GET", path)
return gson.fromJson(resp, Earnings::class.java)
}
suspend fun getShareStats(): ShareStats {
val resp = request("GET", "/stats/shares")
return gson.fromJson(resp, ShareStats::class.java)
}
// ==================== Device Operations ====================
suspend fun listDevices(): List<MiningDevice> {
val resp = request("GET", "/devices")
val result = gson.fromJson<DevicesResponse>(resp, DevicesResponse::class.java)
return result.devices ?: emptyList()
}
suspend fun getDevice(deviceId: String): MiningDevice {
val resp = request("GET", "/devices/${deviceId.urlEncode()}")
return gson.fromJson(resp, MiningDevice::class.java)
}
suspend fun setDeviceConfig(deviceId: String, deviceConfig: DeviceConfig): MiningDevice {
val body = mutableMapOf<String, Any>("enabled" to deviceConfig.enabled)
deviceConfig.intensity?.let { body["intensity"] = it }
deviceConfig.powerLimit?.let { body["powerLimit"] = it }
deviceConfig.coreClockOffset?.let { body["coreClockOffset"] = it }
deviceConfig.memoryClockOffset?.let { body["memoryClockOffset"] = it }
deviceConfig.fanSpeed?.let { body["fanSpeed"] = it }
val resp = request("PUT", "/devices/${deviceId.urlEncode()}/config", body)
return gson.fromJson(resp, MiningDevice::class.java)
}
suspend fun enableDevice(deviceId: String) {
setDeviceConfig(deviceId, DeviceConfig(enabled = true))
}
suspend fun disableDevice(deviceId: String) {
setDeviceConfig(deviceId, DeviceConfig(enabled = false))
}
// ==================== Worker Operations ====================
suspend fun listWorkers(): List<WorkerInfo> {
val resp = request("GET", "/workers")
val result = gson.fromJson<WorkersResponse>(resp, WorkersResponse::class.java)
return result.workers ?: emptyList()
}
suspend fun getWorker(workerId: String): WorkerInfo {
val resp = request("GET", "/workers/${workerId.urlEncode()}")
return gson.fromJson(resp, WorkerInfo::class.java)
}
suspend fun removeWorker(workerId: String) {
request("DELETE", "/workers/${workerId.urlEncode()}")
}
// ==================== Algorithm Operations ====================
suspend fun listAlgorithms(): List<MiningAlgorithm> {
val resp = request("GET", "/algorithms")
val result = gson.fromJson<AlgorithmsResponse>(resp, AlgorithmsResponse::class.java)
return result.algorithms ?: emptyList()
}
suspend fun getAlgorithm(name: String): MiningAlgorithm {
val resp = request("GET", "/algorithms/${name.urlEncode()}")
return gson.fromJson(resp, MiningAlgorithm::class.java)
}
suspend fun switchAlgorithm(algorithm: String) {
request("POST", "/algorithms/switch", mapOf("algorithm" to algorithm))
}
suspend fun getMostProfitable(): MiningAlgorithm {
val resp = request("GET", "/algorithms/profitable")
return gson.fromJson(resp, MiningAlgorithm::class.java)
}
// ==================== Lifecycle ====================
override fun close() {
if (closed.compareAndSet(false, true)) {
activeConnection.set(null)
httpClient.close()
}
}
fun isClosed(): Boolean = closed.get()
suspend fun healthCheck(): Boolean = try {
val resp = request("GET", "/health")
val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
map["status"] == "healthy"
} catch (e: Exception) { false }
// ==================== Private Methods ====================
private suspend fun request(method: String, path: String, body: Any? = null): String {
if (closed.get()) throw MiningException("Client has been closed")
var lastError: Exception? = null
repeat(config.retries) { attempt ->
try {
return doRequest(method, path, body)
} catch (e: Exception) {
lastError = e
if (attempt < config.retries - 1) delay((1L shl attempt) * 1000)
}
}
throw lastError ?: MiningException("Unknown error")
}
private suspend fun doRequest(method: String, path: String, body: Any?): String {
val url = "${config.endpoint}$path"
val response: HttpResponse = httpClient.request(url) {
this.method = HttpMethod.parse(method)
header("Authorization", "Bearer ${config.apiKey}")
header("Content-Type", "application/json")
header("X-SDK-Version", "kotlin/0.1.0")
body?.let { setBody(gson.toJson(it)) }
}
val responseBody = response.bodyAsText()
if (response.status.value >= 400) {
val errorMap = try {
gson.fromJson<Map<String, Any>>(responseBody, object : TypeToken<Map<String, Any>>() {}.type)
} catch (e: Exception) { emptyMap() }
throw MiningException(
errorMap["message"] as? String ?: "HTTP ${response.status.value}",
errorMap["code"] as? String,
response.status.value
)
}
return responseBody
}
private fun String.urlEncode(): String = java.net.URLEncoder.encode(this, "UTF-8")
}
// ==================== Types ====================
data class MiningConfig(
val apiKey: String,
val endpoint: String = "https://mining.synor.io/v1",
val timeoutSecs: Long = 60,
val retries: Int = 3,
val debug: Boolean = false
)
enum class DeviceType { cpu, gpu_nvidia, gpu_amd, asic }
enum class DeviceStatus { idle, mining, error, offline }
enum class ConnectionStatus { disconnected, connecting, connected, reconnecting }
enum class TimePeriod { hour, day, week, month, all }
enum class SubmitResultStatus { accepted, rejected, stale }
data class PoolConfig(
val url: String,
val user: String,
val password: String? = null,
val algorithm: String? = null,
val difficulty: Double? = null
)
data class StratumConnection(
val id: String,
val pool: String,
val status: ConnectionStatus,
val algorithm: String,
val difficulty: Double,
val connectedAt: Long,
val acceptedShares: Int,
val rejectedShares: Int,
val staleShares: Int,
val lastShareAt: Long? = null
)
data class PoolStats(
val url: String,
val workers: Int,
val hashrate: Double,
val difficulty: Double,
val lastBlock: Long,
val blocksFound24h: Int,
val luck: Double
)
data class TemplateTransaction(
val txid: String,
val data: String,
val fee: String,
val weight: Int
)
data class BlockTemplate(
val id: String,
val previousBlockHash: String,
val merkleRoot: String,
val timestamp: Long,
val bits: String,
val height: Long,
val coinbaseValue: String,
val transactions: List<TemplateTransaction>,
val target: String,
val algorithm: String,
val extraNonce: String
)
data class MinedWork(
val templateId: String,
val nonce: String,
val extraNonce: String,
val timestamp: Long,
val hash: String
)
data class ShareInfo(
val hash: String,
val difficulty: Double,
val timestamp: Long,
val accepted: Boolean
)
data class SubmitResult(
val status: SubmitResultStatus,
val share: ShareInfo,
val blockFound: Boolean,
val reason: String? = null,
val blockHash: String? = null,
val reward: String? = null
)
data class Hashrate(
val current: Double,
val average1h: Double,
val average24h: Double,
val peak: Double,
val unit: String
)
data class ShareStats(
val accepted: Int,
val rejected: Int,
val stale: Int,
val total: Int,
val acceptRate: Double
)
data class DeviceTemperature(
val current: Double,
val max: Double,
val throttling: Boolean
)
data class EarningsSnapshot(
val today: String,
val yesterday: String,
val thisWeek: String,
val thisMonth: String,
val total: String,
val currency: String
)
data class MiningStats(
val hashrate: Hashrate,
val shares: ShareStats,
val uptime: Long,
val efficiency: Double,
val earnings: EarningsSnapshot,
val powerConsumption: Double? = null,
val temperature: DeviceTemperature? = null
)
data class EarningsBreakdown(
val date: Long,
val amount: String,
val blocks: Int,
val shares: Int,
val hashrate: Double
)
data class Earnings(
val period: TimePeriod,
val startDate: Long,
val endDate: Long,
val amount: String,
val blocks: Int,
val shares: Int,
val averageHashrate: Double,
val currency: String,
val breakdown: List<EarningsBreakdown>
)
data class MiningDevice(
val id: String,
val name: String,
val type: DeviceType,
val status: DeviceStatus,
val hashrate: Double,
val temperature: Double,
val fanSpeed: Double,
val powerDraw: Double,
val memoryUsed: Long,
val memoryTotal: Long,
val driver: String? = null,
val firmware: String? = null
)
data class DeviceConfig(
val enabled: Boolean,
val intensity: Int? = null,
val powerLimit: Int? = null,
val coreClockOffset: Int? = null,
val memoryClockOffset: Int? = null,
val fanSpeed: Int? = null
)
data class WorkerInfo(
val id: String,
val name: String,
val status: ConnectionStatus,
val hashrate: Hashrate,
val shares: ShareStats,
val devices: List<MiningDevice>,
val lastSeen: Long,
val uptime: Long
)
data class MiningAlgorithm(
val name: String,
val displayName: String,
val hashUnit: String,
val profitability: String,
val difficulty: Double,
val blockReward: String,
val blockTime: Int
)
internal data class DevicesResponse(val devices: List<MiningDevice>?)
internal data class WorkersResponse(val workers: List<WorkerInfo>?)
internal data class AlgorithmsResponse(val algorithms: List<MiningAlgorithm>?)
class MiningException(
message: String,
val code: String? = null,
val statusCode: Int = 0
) : RuntimeException(message)

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative "synor_economics/version"
require_relative "synor_economics/types"
require_relative "synor_economics/client"
module SynorEconomics
class Error < StandardError; end
class ClientClosedError < Error; end
class HttpError < Error
attr_reader :status_code, :code
def initialize(message, status_code: nil, code: nil)
super(message)
@status_code = status_code
@code = code
end
end
end

View file

@ -0,0 +1,366 @@
# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorEconomics
# Synor Economics SDK client for Ruby.
# Pricing, billing, staking, and discount management.
class Client
attr_reader :closed
def initialize(config)
@config = config
@closed = false
@conn = Faraday.new(url: config.endpoint) do |f|
f.request :json
f.response :json
f.options.timeout = config.timeout
f.headers["Authorization"] = "Bearer #{config.api_key}"
f.headers["Content-Type"] = "application/json"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
end
# ==================== Pricing Operations ====================
def get_pricing(service: nil)
path = service ? "/pricing?service=#{service}" : "/pricing"
response = get(path)
(response["pricing"] || []).map { |p| parse_service_pricing(p) }
end
def get_price(service:, usage:)
body = { service: service, usage: usage_to_hash(usage) }
response = post("/pricing/calculate", body)
parse_price_result(response)
end
def estimate_cost(plan:)
body = { plan: plan.map { |p| usage_plan_item_to_hash(p) } }
response = post("/pricing/estimate", body)
parse_cost_estimate(response)
end
# ==================== Billing Operations ====================
def get_usage(period: nil)
path = period ? "/billing/usage?period=#{period}" : "/billing/usage"
response = get(path)
parse_usage(response)
end
def get_invoices
response = get("/billing/invoices")
(response["invoices"] || []).map { |i| parse_invoice(i) }
end
def get_invoice(invoice_id)
response = get("/billing/invoices/#{encode(invoice_id)}")
parse_invoice(response)
end
def get_balance
response = get("/billing/balance")
parse_account_balance(response)
end
# ==================== Staking Operations ====================
def stake(amount:, duration_days: nil)
body = { amount: amount }
body[:duration_days] = duration_days if duration_days
response = post("/staking/stake", body)
parse_stake_receipt(response)
end
def unstake(stake_id)
response = post("/staking/#{encode(stake_id)}/unstake", {})
parse_unstake_receipt(response)
end
def get_stakes
response = get("/staking")
(response["stakes"] || []).map { |s| parse_stake(s) }
end
def get_stake(stake_id)
response = get("/staking/#{encode(stake_id)}")
parse_stake(response)
end
def get_staking_rewards
response = get("/staking/rewards")
parse_staking_rewards(response)
end
def claim_rewards
response = post("/staking/rewards/claim", {})
parse_stake_receipt(response)
end
# ==================== Discount Operations ====================
def apply_discount(code)
response = post("/discounts/apply", { code: code })
parse_discount_result(response)
end
def remove_discount(code)
delete("/discounts/#{encode(code)}")
end
def get_discounts
response = get("/discounts")
(response["discounts"] || []).map { |d| parse_discount(d) }
end
# ==================== Lifecycle ====================
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
def close
@closed = true
@conn.close if @conn.respond_to?(:close)
end
private
def get(path, params = {})
execute { @conn.get(path, params).body }
end
def post(path, body)
execute { @conn.post(path, body).body }
end
def delete(path)
execute { @conn.delete(path).body }
end
def execute
raise ClientClosedError, "Client has been closed" if @closed
last_error = nil
@config.retries.times do |attempt|
begin
response = yield
check_error(response) if response.is_a?(Hash)
return response
rescue StandardError => e
last_error = e
sleep(2**attempt) if attempt < @config.retries - 1
end
end
raise last_error
end
def check_error(response)
return unless response["error"] || (response["code"] && response["message"])
message = response["message"] || response["error"] || "Unknown error"
code = response["code"]
status = response["status_code"] || 0
raise HttpError.new(message, status_code: status, code: code)
end
def encode(str)
URI.encode_www_form_component(str)
end
# Parsing methods
def parse_service_pricing(data)
ServicePricing.new(
service: data["service"],
unit: data["unit"],
price_per_unit: data["price_per_unit"],
currency: data["currency"],
minimum: data["minimum"],
maximum: data["maximum"]
)
end
def parse_price_result(data)
PriceResult.new(
service: data["service"],
amount: data["amount"],
currency: data["currency"],
usage: data["usage"] ? parse_usage_metrics(data["usage"]) : nil
)
end
def parse_usage_metrics(data)
UsageMetrics.new(
service: data["service"],
compute_units: data["compute_units"],
storage_bytes: data["storage_bytes"],
bandwidth_bytes: data["bandwidth_bytes"],
duration_seconds: data["duration_seconds"],
requests: data["requests"]
)
end
def parse_cost_estimate(data)
CostEstimate.new(
total: data["total"],
currency: data["currency"],
breakdown: (data["breakdown"] || []).map { |b| CostItem.new(service: b["service"], amount: b["amount"]) },
discount_applied: data["discount_applied"],
period: data["period"]
)
end
def parse_usage(data)
Usage.new(
period: data["period"],
start_date: data["start_date"],
end_date: data["end_date"],
items: (data["items"] || []).map { |i| parse_usage_item(i) },
total_cost: data["total_cost"],
currency: data["currency"]
)
end
def parse_usage_item(data)
UsageItem.new(
service: data["service"],
compute_units: data["compute_units"],
storage_bytes: data["storage_bytes"],
bandwidth_bytes: data["bandwidth_bytes"],
requests: data["requests"],
cost: data["cost"]
)
end
def parse_invoice(data)
Invoice.new(
id: data["id"],
date: data["date"],
due_date: data["due_date"],
status: data["status"],
lines: (data["lines"] || []).map { |l| parse_invoice_line(l) },
subtotal: data["subtotal"],
discount: data["discount"],
tax: data["tax"],
total: data["total"],
currency: data["currency"],
pdf_url: data["pdf_url"]
)
end
def parse_invoice_line(data)
InvoiceLine.new(
service: data["service"],
description: data["description"],
quantity: data["quantity"],
unit_price: data["unit_price"],
amount: data["amount"]
)
end
def parse_account_balance(data)
AccountBalance.new(
available: data["available"],
pending: data["pending"],
staked: data["staked"],
total: data["total"],
currency: data["currency"]
)
end
def parse_stake(data)
Stake.new(
id: data["id"],
amount: data["amount"],
staked_at: data["staked_at"],
unlock_at: data["unlock_at"],
status: data["status"],
rewards_earned: data["rewards_earned"],
apy: data["apy"]
)
end
def parse_stake_receipt(data)
StakeReceipt.new(
id: data["id"],
amount: data["amount"],
tx_hash: data["tx_hash"],
staked_at: data["staked_at"],
unlock_at: data["unlock_at"],
apy: data["apy"]
)
end
def parse_unstake_receipt(data)
UnstakeReceipt.new(
id: data["id"],
amount: data["amount"],
tx_hash: data["tx_hash"],
unstaked_at: data["unstaked_at"],
available_at: data["available_at"]
)
end
def parse_staking_rewards(data)
StakingRewards.new(
pending: data["pending"],
claimed: data["claimed"],
total: data["total"],
current_apy: data["current_apy"],
last_claim: data["last_claim"],
next_distribution: data["next_distribution"]
)
end
def parse_discount(data)
Discount.new(
code: data["code"],
type: data["type"],
value: data["value"],
description: data["description"],
applicable_services: data["applicable_services"],
valid_from: data["valid_from"],
valid_until: data["valid_until"],
max_uses: data["max_uses"],
current_uses: data["current_uses"],
active: data["active"]
)
end
def parse_discount_result(data)
DiscountResult.new(
discount: parse_discount(data["discount"]),
savings_estimate: data["savings_estimate"],
applied_at: data["applied_at"]
)
end
# Conversion methods
def usage_to_hash(usage)
{
service: usage.service,
compute_units: usage.compute_units,
storage_bytes: usage.storage_bytes,
bandwidth_bytes: usage.bandwidth_bytes,
duration_seconds: usage.duration_seconds,
requests: usage.requests
}
end
def usage_plan_item_to_hash(item)
{
service: item.service,
projected_usage: usage_to_hash(item.projected_usage),
period: item.period
}
end
end
end

View file

@ -0,0 +1,85 @@
# frozen_string_literal: true
module SynorEconomics
# Service types
module ServiceType
COMPUTE = "compute"
STORAGE = "storage"
DATABASE = "database"
HOSTING = "hosting"
RPC = "rpc"
BRIDGE = "bridge"
end
# Billing periods
module BillingPeriod
HOURLY = "hourly"
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"
end
# Stake status
module StakeStatus
ACTIVE = "active"
UNSTAKING = "unstaking"
WITHDRAWN = "withdrawn"
end
# Discount types
module DiscountType
PERCENTAGE = "percentage"
FIXED = "fixed"
CREDITS = "credits"
end
# Invoice status
module InvoiceStatus
PENDING = "pending"
PAID = "paid"
OVERDUE = "overdue"
CANCELLED = "cancelled"
end
# Configuration
Config = Struct.new(:api_key, :endpoint, :timeout, :retries, :debug, keyword_init: true) do
def initialize(api_key:, endpoint: "https://economics.synor.io/v1", timeout: 30, retries: 3, debug: false)
super
end
end
# Data types
ServicePricing = Struct.new(:service, :unit, :price_per_unit, :currency, :minimum, :maximum, keyword_init: true)
UsageMetrics = Struct.new(:service, :compute_units, :storage_bytes, :bandwidth_bytes, :duration_seconds, :requests, keyword_init: true)
PriceResult = Struct.new(:service, :amount, :currency, :usage, keyword_init: true)
UsagePlanItem = Struct.new(:service, :projected_usage, :period, keyword_init: true)
CostItem = Struct.new(:service, :amount, keyword_init: true)
CostEstimate = Struct.new(:total, :currency, :breakdown, :discount_applied, :period, keyword_init: true)
UsageItem = Struct.new(:service, :compute_units, :storage_bytes, :bandwidth_bytes, :requests, :cost, keyword_init: true)
Usage = Struct.new(:period, :start_date, :end_date, :items, :total_cost, :currency, keyword_init: true)
InvoiceLine = Struct.new(:service, :description, :quantity, :unit_price, :amount, keyword_init: true)
Invoice = Struct.new(:id, :date, :due_date, :status, :lines, :subtotal, :discount, :tax, :total, :currency, :pdf_url, keyword_init: true)
AccountBalance = Struct.new(:available, :pending, :staked, :total, :currency, keyword_init: true)
Stake = Struct.new(:id, :amount, :staked_at, :unlock_at, :status, :rewards_earned, :apy, keyword_init: true)
StakeReceipt = Struct.new(:id, :amount, :tx_hash, :staked_at, :unlock_at, :apy, keyword_init: true)
UnstakeReceipt = Struct.new(:id, :amount, :tx_hash, :unstaked_at, :available_at, keyword_init: true)
StakingRewards = Struct.new(:pending, :claimed, :total, :current_apy, :last_claim, :next_distribution, keyword_init: true)
Discount = Struct.new(:code, :type, :value, :description, :applicable_services, :valid_from, :valid_until, :max_uses, :current_uses, :active, keyword_init: true)
DiscountResult = Struct.new(:discount, :savings_estimate, :applied_at, keyword_init: true)
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
module SynorEconomics
VERSION = "0.1.0"
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative "synor_governance/version"
require_relative "synor_governance/types"
require_relative "synor_governance/client"
module SynorGovernance
class Error < StandardError; end
class ClientClosedError < Error; end
class HttpError < Error
attr_reader :status_code, :code
def initialize(message, status_code: nil, code: nil)
super(message)
@status_code = status_code
@code = code
end
end
end

View file

@ -0,0 +1,422 @@
# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorGovernance
# Synor Governance SDK client for Ruby.
# Proposals, voting, DAOs, and vesting operations.
class Client
FINAL_STATUSES = [
ProposalStatus::PASSED,
ProposalStatus::REJECTED,
ProposalStatus::EXECUTED,
ProposalStatus::CANCELLED
].freeze
attr_reader :closed
def initialize(config)
@config = config
@closed = false
@conn = Faraday.new(url: config.endpoint) do |f|
f.request :json
f.response :json
f.options.timeout = config.timeout
f.headers["Authorization"] = "Bearer #{config.api_key}"
f.headers["Content-Type"] = "application/json"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
end
# ==================== Proposal Operations ====================
def create_proposal(draft)
body = proposal_draft_to_hash(draft)
response = post("/proposals", body)
parse_proposal(response)
end
def get_proposal(proposal_id)
response = get("/proposals/#{encode(proposal_id)}")
parse_proposal(response)
end
def list_proposals(filter: nil)
params = {}
if filter
params[:status] = filter.status if filter.status
params[:proposer] = filter.proposer if filter.proposer
params[:dao_id] = filter.dao_id if filter.dao_id
params[:limit] = filter.limit if filter.limit
params[:offset] = filter.offset if filter.offset
end
response = get("/proposals", params)
(response["proposals"] || []).map { |p| parse_proposal(p) }
end
def cancel_proposal(proposal_id)
response = post("/proposals/#{encode(proposal_id)}/cancel", {})
parse_proposal(response)
end
def execute_proposal(proposal_id)
response = post("/proposals/#{encode(proposal_id)}/execute", {})
parse_proposal(response)
end
def wait_for_proposal(proposal_id, poll_interval: 60, max_wait: 604_800)
deadline = Time.now + max_wait
while Time.now < deadline
proposal = get_proposal(proposal_id)
return proposal if FINAL_STATUSES.include?(proposal.status)
sleep(poll_interval)
end
raise Error, "Timeout waiting for proposal completion"
end
# ==================== Voting Operations ====================
def vote(proposal_id:, vote:, weight: nil)
body = { choice: vote.choice }
body[:reason] = vote.reason if vote.reason
body[:weight] = weight if weight
response = post("/proposals/#{encode(proposal_id)}/vote", body)
parse_vote_receipt(response)
end
def get_votes(proposal_id)
response = get("/proposals/#{encode(proposal_id)}/votes")
(response["votes"] || []).map { |v| parse_vote_receipt(v) }
end
def get_my_vote(proposal_id)
response = get("/proposals/#{encode(proposal_id)}/votes/me")
parse_vote_receipt(response)
end
def delegate(delegatee:, amount: nil)
body = { delegatee: delegatee }
body[:amount] = amount if amount
response = post("/voting/delegate", body)
parse_delegation_receipt(response)
end
def undelegate(delegatee)
response = post("/voting/undelegate", { delegatee: delegatee })
parse_delegation_receipt(response)
end
def get_voting_power(address)
response = get("/voting/power/#{encode(address)}")
parse_voting_power(response)
end
def get_delegations(address)
response = get("/voting/delegations/#{encode(address)}")
(response["delegations"] || []).map { |d| parse_delegation_receipt(d) }
end
# ==================== DAO Operations ====================
def create_dao(config)
body = dao_config_to_hash(config)
response = post("/daos", body)
parse_dao(response)
end
def get_dao(dao_id)
response = get("/daos/#{encode(dao_id)}")
parse_dao(response)
end
def list_daos(limit: nil, offset: nil)
params = {}
params[:limit] = limit if limit
params[:offset] = offset if offset
response = get("/daos", params)
(response["daos"] || []).map { |d| parse_dao(d) }
end
def get_dao_treasury(dao_id)
response = get("/daos/#{encode(dao_id)}/treasury")
parse_dao_treasury(response)
end
def get_dao_members(dao_id)
response = get("/daos/#{encode(dao_id)}/members")
response["members"] || []
end
# ==================== Vesting Operations ====================
def create_vesting_schedule(schedule)
body = vesting_schedule_to_hash(schedule)
response = post("/vesting", body)
parse_vesting_contract(response)
end
def get_vesting_contract(contract_id)
response = get("/vesting/#{encode(contract_id)}")
parse_vesting_contract(response)
end
def list_vesting_contracts(beneficiary: nil)
path = beneficiary ? "/vesting?beneficiary=#{encode(beneficiary)}" : "/vesting"
response = get(path)
(response["contracts"] || []).map { |c| parse_vesting_contract(c) }
end
def claim_vested(contract_id)
response = post("/vesting/#{encode(contract_id)}/claim", {})
parse_claim_receipt(response)
end
def revoke_vesting(contract_id)
response = post("/vesting/#{encode(contract_id)}/revoke", {})
parse_vesting_contract(response)
end
def get_releasable_amount(contract_id)
response = get("/vesting/#{encode(contract_id)}/releasable")
response["amount"]
end
# ==================== Lifecycle ====================
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
def close
@closed = true
@conn.close if @conn.respond_to?(:close)
end
private
def get(path, params = {})
execute { @conn.get(path, params).body }
end
def post(path, body)
execute { @conn.post(path, body).body }
end
def execute
raise ClientClosedError, "Client has been closed" if @closed
last_error = nil
@config.retries.times do |attempt|
begin
response = yield
check_error(response) if response.is_a?(Hash)
return response
rescue StandardError => e
last_error = e
sleep(2**attempt) if attempt < @config.retries - 1
end
end
raise last_error
end
def check_error(response)
return unless response["error"] || (response["code"] && response["message"])
message = response["message"] || response["error"] || "Unknown error"
code = response["code"]
status = response["status_code"] || 0
raise HttpError.new(message, status_code: status, code: code)
end
def encode(str)
URI.encode_www_form_component(str)
end
# Parsing methods
def parse_proposal(data)
Proposal.new(
id: data["id"],
title: data["title"],
description: data["description"],
discussion_url: data["discussion_url"],
proposer: data["proposer"],
status: data["status"],
created_at: data["created_at"],
voting_start_time: data["voting_start_time"],
voting_end_time: data["voting_end_time"],
execution_time: data["execution_time"],
vote_tally: data["vote_tally"] ? parse_vote_tally(data["vote_tally"]) : nil,
actions: (data["actions"] || []).map { |a| parse_proposal_action(a) },
dao_id: data["dao_id"]
)
end
def parse_vote_tally(data)
VoteTally.new(
for_votes: data["for_votes"],
against_votes: data["against_votes"],
abstain_votes: data["abstain_votes"],
quorum: data["quorum"],
quorum_required: data["quorum_required"],
total_voters: data["total_voters"]
)
end
def parse_proposal_action(data)
ProposalAction.new(
target: data["target"],
method: data["method"],
data: data["data"],
value: data["value"]
)
end
def parse_vote_receipt(data)
VoteReceipt.new(
id: data["id"],
proposal_id: data["proposal_id"],
voter: data["voter"],
choice: data["choice"],
weight: data["weight"],
reason: data["reason"],
voted_at: data["voted_at"],
tx_hash: data["tx_hash"]
)
end
def parse_voting_power(data)
VotingPower.new(
address: data["address"],
delegated_power: data["delegated_power"],
own_power: data["own_power"],
total_power: data["total_power"],
delegators: data["delegators"] || []
)
end
def parse_delegation_receipt(data)
DelegationReceipt.new(
id: data["id"],
from: data["from"],
to: data["to"],
amount: data["amount"],
delegated_at: data["delegated_at"],
tx_hash: data["tx_hash"]
)
end
def parse_dao(data)
Dao.new(
id: data["id"],
name: data["name"],
description: data["description"],
type: data["type"],
token_address: data["token_address"],
voting_period_days: data["voting_period_days"],
timelock_days: data["timelock_days"],
quorum_percent: data["quorum_percent"],
proposal_threshold: data["proposal_threshold"],
total_proposals: data["total_proposals"],
active_proposals: data["active_proposals"],
treasury_value: data["treasury_value"],
member_count: data["member_count"],
created_at: data["created_at"]
)
end
def parse_dao_treasury(data)
DaoTreasury.new(
dao_id: data["dao_id"],
total_value: data["total_value"],
tokens: (data["tokens"] || []).map { |t| parse_treasury_token(t) },
last_updated: data["last_updated"]
)
end
def parse_treasury_token(data)
TreasuryToken.new(
address: data["address"],
balance: data["balance"],
name: data["name"],
symbol: data["symbol"]
)
end
def parse_vesting_contract(data)
VestingContract.new(
id: data["id"],
beneficiary: data["beneficiary"],
grantor: data["grantor"],
total_amount: data["total_amount"],
released_amount: data["released_amount"],
releasable_amount: data["releasable_amount"],
start_time: data["start_time"],
cliff_time: data["cliff_time"],
end_time: data["end_time"],
status: data["status"],
revocable: data["revocable"],
created_at: data["created_at"]
)
end
def parse_claim_receipt(data)
ClaimReceipt.new(
id: data["id"],
contract_id: data["contract_id"],
amount: data["amount"],
tx_hash: data["tx_hash"],
claimed_at: data["claimed_at"]
)
end
# Conversion methods
def proposal_draft_to_hash(draft)
hash = { title: draft.title, description: draft.description }
hash[:discussion_url] = draft.discussion_url if draft.discussion_url
hash[:voting_start_time] = draft.voting_start_time if draft.voting_start_time
hash[:voting_end_time] = draft.voting_end_time if draft.voting_end_time
hash[:dao_id] = draft.dao_id if draft.dao_id
hash[:actions] = draft.actions.map { |a| proposal_action_to_hash(a) } if draft.actions
hash
end
def proposal_action_to_hash(action)
{ target: action.target, method: action.method, data: action.data, value: action.value }
end
def dao_config_to_hash(config)
hash = {
name: config.name,
description: config.description,
type: config.type,
voting_period_days: config.voting_period_days,
timelock_days: config.timelock_days
}
hash[:token_address] = config.token_address if config.token_address
hash[:quorum_percent] = config.quorum_percent if config.quorum_percent
hash[:proposal_threshold] = config.proposal_threshold if config.proposal_threshold
hash[:multisig_members] = config.multisig_members if config.multisig_members
hash[:multisig_threshold] = config.multisig_threshold if config.multisig_threshold
hash
end
def vesting_schedule_to_hash(schedule)
{
beneficiary: schedule.beneficiary,
total_amount: schedule.total_amount,
start_time: schedule.start_time,
cliff_duration: schedule.cliff_duration,
vesting_duration: schedule.vesting_duration,
revocable: schedule.revocable
}
end
end
end

View file

@ -0,0 +1,74 @@
# frozen_string_literal: true
module SynorGovernance
# Proposal status
module ProposalStatus
PENDING = "pending"
ACTIVE = "active"
PASSED = "passed"
REJECTED = "rejected"
EXECUTED = "executed"
CANCELLED = "cancelled"
end
# Vote choice
module VoteChoice
FOR = "for"
AGAINST = "against"
ABSTAIN = "abstain"
end
# DAO type
module DaoType
TOKEN = "token"
MULTISIG = "multisig"
HYBRID = "hybrid"
end
# Vesting status
module VestingStatus
ACTIVE = "active"
COMPLETED = "completed"
REVOKED = "revoked"
end
# Configuration
Config = Struct.new(:api_key, :endpoint, :timeout, :retries, :debug, keyword_init: true) do
def initialize(api_key:, endpoint: "https://governance.synor.io/v1", timeout: 30, retries: 3, debug: false)
super
end
end
# Data types
ProposalAction = Struct.new(:target, :method, :data, :value, keyword_init: true)
ProposalDraft = Struct.new(:title, :description, :discussion_url, :voting_start_time, :voting_end_time, :dao_id, :actions, keyword_init: true)
VoteTally = Struct.new(:for_votes, :against_votes, :abstain_votes, :quorum, :quorum_required, :total_voters, keyword_init: true)
Proposal = Struct.new(:id, :title, :description, :discussion_url, :proposer, :status, :created_at, :voting_start_time, :voting_end_time, :execution_time, :vote_tally, :actions, :dao_id, keyword_init: true)
ProposalFilter = Struct.new(:status, :proposer, :dao_id, :limit, :offset, keyword_init: true)
Vote = Struct.new(:choice, :reason, keyword_init: true)
VoteReceipt = Struct.new(:id, :proposal_id, :voter, :choice, :weight, :reason, :voted_at, :tx_hash, keyword_init: true)
VotingPower = Struct.new(:address, :delegated_power, :own_power, :total_power, :delegators, keyword_init: true)
DelegationReceipt = Struct.new(:id, :from, :to, :amount, :delegated_at, :tx_hash, keyword_init: true)
DaoConfig = Struct.new(:name, :description, :type, :voting_period_days, :timelock_days, :token_address, :quorum_percent, :proposal_threshold, :multisig_members, :multisig_threshold, keyword_init: true)
Dao = Struct.new(:id, :name, :description, :type, :token_address, :voting_period_days, :timelock_days, :quorum_percent, :proposal_threshold, :total_proposals, :active_proposals, :treasury_value, :member_count, :created_at, keyword_init: true)
TreasuryToken = Struct.new(:address, :balance, :name, :symbol, keyword_init: true)
DaoTreasury = Struct.new(:dao_id, :total_value, :tokens, :last_updated, keyword_init: true)
VestingSchedule = Struct.new(:beneficiary, :total_amount, :start_time, :cliff_duration, :vesting_duration, :revocable, keyword_init: true)
VestingContract = Struct.new(:id, :beneficiary, :grantor, :total_amount, :released_amount, :releasable_amount, :start_time, :cliff_time, :end_time, :status, :revocable, :created_at, keyword_init: true)
ClaimReceipt = Struct.new(:id, :contract_id, :amount, :tx_hash, :claimed_at, keyword_init: true)
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
module SynorGovernance
VERSION = "0.1.0"
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative "synor_mining/version"
require_relative "synor_mining/types"
require_relative "synor_mining/client"
module SynorMining
class Error < StandardError; end
class ClientClosedError < Error; end
class HttpError < Error
attr_reader :status_code, :code
def initialize(message, status_code: nil, code: nil)
super(message)
@status_code = status_code
@code = code
end
end
end

View file

@ -0,0 +1,437 @@
# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorMining
# Synor Mining SDK client for Ruby.
# Pool connections, block templates, hashrate stats, and GPU management.
class Client
attr_reader :closed, :active_connection
def initialize(config)
@config = config
@closed = false
@active_connection = nil
@conn = Faraday.new(url: config.endpoint) do |f|
f.request :json
f.response :json
f.options.timeout = config.timeout
f.headers["Authorization"] = "Bearer #{config.api_key}"
f.headers["Content-Type"] = "application/json"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
end
# ==================== Pool Operations ====================
def connect(pool:)
body = { url: pool.url, user: pool.user }
body[:password] = pool.password if pool.password
body[:algorithm] = pool.algorithm if pool.algorithm
body[:difficulty] = pool.difficulty if pool.difficulty
response = post("/pool/connect", body)
@active_connection = parse_stratum_connection(response)
end
def disconnect
return unless @active_connection
post("/pool/disconnect", {})
@active_connection = nil
end
def get_connection
response = get("/pool/connection")
@active_connection = parse_stratum_connection(response)
end
def get_pool_stats
response = get("/pool/stats")
parse_pool_stats(response)
end
def connected?
@active_connection&.status == ConnectionStatus::CONNECTED
end
# ==================== Mining Operations ====================
def get_block_template
response = get("/mining/template")
parse_block_template(response)
end
def submit_work(work:)
body = {
template_id: work.template_id,
nonce: work.nonce,
extra_nonce: work.extra_nonce,
timestamp: work.timestamp,
hash: work.hash
}
response = post("/mining/submit", body)
parse_submit_result(response)
end
def start_mining(algorithm: nil)
body = algorithm ? { algorithm: algorithm } : {}
post("/mining/start", body)
end
def stop_mining
post("/mining/stop", {})
end
def mining?
response = get("/mining/status")
response["mining"] == true
end
# ==================== Stats Operations ====================
def get_hashrate
response = get("/stats/hashrate")
parse_hashrate(response)
end
def get_stats
response = get("/stats")
parse_mining_stats(response)
end
def get_earnings(period: nil)
path = period ? "/stats/earnings?period=#{period}" : "/stats/earnings"
response = get(path)
parse_earnings(response)
end
def get_share_stats
response = get("/stats/shares")
parse_share_stats(response)
end
# ==================== Device Operations ====================
def list_devices
response = get("/devices")
(response["devices"] || []).map { |d| parse_mining_device(d) }
end
def get_device(device_id)
response = get("/devices/#{encode(device_id)}")
parse_mining_device(response)
end
def set_device_config(device_id:, config:)
body = { enabled: config.enabled }
body[:intensity] = config.intensity if config.intensity
body[:power_limit] = config.power_limit if config.power_limit
body[:core_clock_offset] = config.core_clock_offset if config.core_clock_offset
body[:memory_clock_offset] = config.memory_clock_offset if config.memory_clock_offset
body[:fan_speed] = config.fan_speed if config.fan_speed
response = put("/devices/#{encode(device_id)}/config", body)
parse_mining_device(response)
end
def enable_device(device_id)
set_device_config(device_id: device_id, config: DeviceConfig.new(enabled: true))
end
def disable_device(device_id)
set_device_config(device_id: device_id, config: DeviceConfig.new(enabled: false))
end
# ==================== Worker Operations ====================
def list_workers
response = get("/workers")
(response["workers"] || []).map { |w| parse_worker_info(w) }
end
def get_worker(worker_id)
response = get("/workers/#{encode(worker_id)}")
parse_worker_info(response)
end
def remove_worker(worker_id)
delete("/workers/#{encode(worker_id)}")
end
# ==================== Algorithm Operations ====================
def list_algorithms
response = get("/algorithms")
(response["algorithms"] || []).map { |a| parse_mining_algorithm(a) }
end
def get_algorithm(name)
response = get("/algorithms/#{encode(name)}")
parse_mining_algorithm(response)
end
def switch_algorithm(algorithm)
post("/algorithms/switch", { algorithm: algorithm })
end
def get_most_profitable
response = get("/algorithms/profitable")
parse_mining_algorithm(response)
end
# ==================== Lifecycle ====================
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
def close
@closed = true
@active_connection = nil
@conn.close if @conn.respond_to?(:close)
end
private
def get(path, params = {})
execute { @conn.get(path, params).body }
end
def post(path, body)
execute { @conn.post(path, body).body }
end
def put(path, body)
execute { @conn.put(path, body).body }
end
def delete(path)
execute { @conn.delete(path).body }
end
def execute
raise ClientClosedError, "Client has been closed" if @closed
last_error = nil
@config.retries.times do |attempt|
begin
response = yield
check_error(response) if response.is_a?(Hash)
return response
rescue StandardError => e
last_error = e
sleep(2**attempt) if attempt < @config.retries - 1
end
end
raise last_error
end
def check_error(response)
return unless response["error"] || (response["code"] && response["message"])
message = response["message"] || response["error"] || "Unknown error"
code = response["code"]
status = response["status_code"] || 0
raise HttpError.new(message, status_code: status, code: code)
end
def encode(str)
URI.encode_www_form_component(str)
end
# Parsing methods
def parse_stratum_connection(data)
StratumConnection.new(
id: data["id"],
pool: data["pool"],
status: data["status"],
algorithm: data["algorithm"],
difficulty: data["difficulty"],
connected_at: data["connected_at"],
accepted_shares: data["accepted_shares"],
rejected_shares: data["rejected_shares"],
stale_shares: data["stale_shares"],
last_share_at: data["last_share_at"]
)
end
def parse_pool_stats(data)
PoolStats.new(
url: data["url"],
workers: data["workers"],
hashrate: data["hashrate"],
difficulty: data["difficulty"],
last_block: data["last_block"],
blocks_found_24h: data["blocks_found_24h"],
luck: data["luck"]
)
end
def parse_block_template(data)
BlockTemplate.new(
id: data["id"],
previous_block_hash: data["previous_block_hash"],
merkle_root: data["merkle_root"],
timestamp: data["timestamp"],
bits: data["bits"],
height: data["height"],
coinbase_value: data["coinbase_value"],
transactions: (data["transactions"] || []).map { |t| parse_template_transaction(t) },
target: data["target"],
algorithm: data["algorithm"],
extra_nonce: data["extra_nonce"]
)
end
def parse_template_transaction(data)
TemplateTransaction.new(
txid: data["txid"],
data: data["data"],
fee: data["fee"],
weight: data["weight"]
)
end
def parse_submit_result(data)
SubmitResult.new(
status: data["status"],
share: data["share"] ? parse_share_info(data["share"]) : nil,
block_found: data["block_found"],
reason: data["reason"],
block_hash: data["block_hash"],
reward: data["reward"]
)
end
def parse_share_info(data)
ShareInfo.new(
hash: data["hash"],
difficulty: data["difficulty"],
timestamp: data["timestamp"],
accepted: data["accepted"]
)
end
def parse_hashrate(data)
Hashrate.new(
current: data["current"],
average_1h: data["average_1h"],
average_24h: data["average_24h"],
peak: data["peak"],
unit: data["unit"]
)
end
def parse_share_stats(data)
ShareStats.new(
accepted: data["accepted"],
rejected: data["rejected"],
stale: data["stale"],
total: data["total"],
accept_rate: data["accept_rate"]
)
end
def parse_mining_stats(data)
MiningStats.new(
hashrate: data["hashrate"] ? parse_hashrate(data["hashrate"]) : nil,
shares: data["shares"] ? parse_share_stats(data["shares"]) : nil,
uptime: data["uptime"],
efficiency: data["efficiency"],
earnings: data["earnings"] ? parse_earnings_snapshot(data["earnings"]) : nil,
power_consumption: data["power_consumption"],
temperature: data["temperature"] ? parse_device_temperature(data["temperature"]) : nil
)
end
def parse_earnings_snapshot(data)
EarningsSnapshot.new(
today: data["today"],
yesterday: data["yesterday"],
this_week: data["this_week"],
this_month: data["this_month"],
total: data["total"],
currency: data["currency"]
)
end
def parse_device_temperature(data)
DeviceTemperature.new(
current: data["current"],
max: data["max"],
throttling: data["throttling"]
)
end
def parse_earnings(data)
Earnings.new(
period: data["period"],
start_date: data["start_date"],
end_date: data["end_date"],
amount: data["amount"],
blocks: data["blocks"],
shares: data["shares"],
average_hashrate: data["average_hashrate"],
currency: data["currency"],
breakdown: (data["breakdown"] || []).map { |b| parse_earnings_breakdown(b) }
)
end
def parse_earnings_breakdown(data)
EarningsBreakdown.new(
date: data["date"],
amount: data["amount"],
blocks: data["blocks"],
shares: data["shares"],
hashrate: data["hashrate"]
)
end
def parse_mining_device(data)
MiningDevice.new(
id: data["id"],
name: data["name"],
type: data["type"],
status: data["status"],
hashrate: data["hashrate"],
temperature: data["temperature"],
fan_speed: data["fan_speed"],
power_draw: data["power_draw"],
memory_used: data["memory_used"],
memory_total: data["memory_total"],
driver: data["driver"],
firmware: data["firmware"]
)
end
def parse_worker_info(data)
WorkerInfo.new(
id: data["id"],
name: data["name"],
status: data["status"],
hashrate: data["hashrate"] ? parse_hashrate(data["hashrate"]) : nil,
shares: data["shares"] ? parse_share_stats(data["shares"]) : nil,
devices: (data["devices"] || []).map { |d| parse_mining_device(d) },
last_seen: data["last_seen"],
uptime: data["uptime"]
)
end
def parse_mining_algorithm(data)
MiningAlgorithm.new(
name: data["name"],
display_name: data["display_name"],
hash_unit: data["hash_unit"],
profitability: data["profitability"],
difficulty: data["difficulty"],
block_reward: data["block_reward"],
block_time: data["block_time"]
)
end
end
end

View file

@ -0,0 +1,89 @@
# frozen_string_literal: true
module SynorMining
# Device types
module DeviceType
CPU = "cpu"
GPU_NVIDIA = "gpu_nvidia"
GPU_AMD = "gpu_amd"
ASIC = "asic"
end
# Device status
module DeviceStatus
IDLE = "idle"
MINING = "mining"
ERROR = "error"
OFFLINE = "offline"
end
# Connection status
module ConnectionStatus
DISCONNECTED = "disconnected"
CONNECTING = "connecting"
CONNECTED = "connected"
RECONNECTING = "reconnecting"
end
# Time periods
module TimePeriod
HOUR = "hour"
DAY = "day"
WEEK = "week"
MONTH = "month"
ALL = "all"
end
# Submit status
module SubmitStatus
ACCEPTED = "accepted"
REJECTED = "rejected"
STALE = "stale"
end
# Configuration
Config = Struct.new(:api_key, :endpoint, :timeout, :retries, :debug, keyword_init: true) do
def initialize(api_key:, endpoint: "https://mining.synor.io/v1", timeout: 60, retries: 3, debug: false)
super
end
end
# Data types
PoolConfig = Struct.new(:url, :user, :password, :algorithm, :difficulty, keyword_init: true)
StratumConnection = Struct.new(:id, :pool, :status, :algorithm, :difficulty, :connected_at, :accepted_shares, :rejected_shares, :stale_shares, :last_share_at, keyword_init: true)
PoolStats = Struct.new(:url, :workers, :hashrate, :difficulty, :last_block, :blocks_found_24h, :luck, keyword_init: true)
TemplateTransaction = Struct.new(:txid, :data, :fee, :weight, keyword_init: true)
BlockTemplate = Struct.new(:id, :previous_block_hash, :merkle_root, :timestamp, :bits, :height, :coinbase_value, :transactions, :target, :algorithm, :extra_nonce, keyword_init: true)
MinedWork = Struct.new(:template_id, :nonce, :extra_nonce, :timestamp, :hash, keyword_init: true)
ShareInfo = Struct.new(:hash, :difficulty, :timestamp, :accepted, keyword_init: true)
SubmitResult = Struct.new(:status, :share, :block_found, :reason, :block_hash, :reward, keyword_init: true)
Hashrate = Struct.new(:current, :average_1h, :average_24h, :peak, :unit, keyword_init: true)
ShareStats = Struct.new(:accepted, :rejected, :stale, :total, :accept_rate, keyword_init: true)
DeviceTemperature = Struct.new(:current, :max, :throttling, keyword_init: true)
EarningsSnapshot = Struct.new(:today, :yesterday, :this_week, :this_month, :total, :currency, keyword_init: true)
MiningStats = Struct.new(:hashrate, :shares, :uptime, :efficiency, :earnings, :power_consumption, :temperature, keyword_init: true)
EarningsBreakdown = Struct.new(:date, :amount, :blocks, :shares, :hashrate, keyword_init: true)
Earnings = Struct.new(:period, :start_date, :end_date, :amount, :blocks, :shares, :average_hashrate, :currency, :breakdown, keyword_init: true)
MiningDevice = Struct.new(:id, :name, :type, :status, :hashrate, :temperature, :fan_speed, :power_draw, :memory_used, :memory_total, :driver, :firmware, keyword_init: true)
DeviceConfig = Struct.new(:enabled, :intensity, :power_limit, :core_clock_offset, :memory_clock_offset, :fan_speed, keyword_init: true)
WorkerInfo = Struct.new(:id, :name, :status, :hashrate, :shares, :devices, :last_seen, :uptime, keyword_init: true)
MiningAlgorithm = Struct.new(:name, :display_name, :hash_unit, :profitability, :difficulty, :block_reward, :block_time, keyword_init: true)
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
module SynorMining
VERSION = "0.1.0"
end

View file

@ -0,0 +1,286 @@
import Foundation
/// Synor Economics SDK client for Swift.
///
/// Provides pricing, billing, staking, and discount operations.
///
/// Example:
/// ```swift
/// let economics = SynorEconomics(config: EconomicsConfig(apiKey: "your-api-key"))
///
/// // Get price for compute usage
/// let usage = UsageMetrics(computeHours: 100, gpuHours: 10)
/// let price = try await economics.getPrice(service: .compute, usage: usage)
///
/// // Stake tokens
/// let receipt = try await economics.stake(amount: "1000000000000000000")
/// ```
public class SynorEconomics {
private let config: EconomicsConfig
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private var closed = false
public init(config: EconomicsConfig) {
self.config = config
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = config.timeout
sessionConfig.timeoutIntervalForResource = config.timeout * 2
self.session = URLSession(configuration: sessionConfig)
self.decoder = JSONDecoder()
self.encoder = JSONEncoder()
}
// MARK: - Pricing Operations
/// Get price for service usage.
public func getPrice(service: ServiceType, usage: UsageMetrics) async throws -> Price {
let body: [String: Any] = [
"service": service.rawValue,
"usage": [
"computeHours": usage.computeHours,
"storageGb": usage.storageGb,
"requests": usage.requests,
"bandwidthGb": usage.bandwidthGb,
"gpuHours": usage.gpuHours
]
]
return try await post(path: "/pricing/calculate", body: body)
}
/// Estimate cost for a usage plan.
public func estimateCost(plan: UsagePlan) async throws -> CostEstimate {
let body: [String: Any] = [
"service": plan.service.rawValue,
"estimatedUsage": [
"computeHours": plan.estimatedUsage.computeHours,
"storageGb": plan.estimatedUsage.storageGb,
"requests": plan.estimatedUsage.requests,
"bandwidthGb": plan.estimatedUsage.bandwidthGb,
"gpuHours": plan.estimatedUsage.gpuHours
],
"period": plan.period.rawValue
]
return try await post(path: "/pricing/estimate", body: body)
}
/// Get pricing tiers for a service.
public func getPricingTiers(service: ServiceType) async throws -> [PricingTier] {
let response: TiersResponse = try await get(path: "/pricing/tiers/\(service.rawValue)")
return response.tiers ?? []
}
// MARK: - Billing Operations
/// Get usage for a billing period.
public func getUsage(period: BillingPeriod? = nil) async throws -> Usage {
var path = "/billing/usage"
if let period = period {
path += "?period=\(period.rawValue)"
}
return try await get(path: path)
}
/// Get usage by date range.
public func getUsageByDateRange(startDate: Int64, endDate: Int64) async throws -> Usage {
return try await get(path: "/billing/usage?startDate=\(startDate)&endDate=\(endDate)")
}
/// Get all invoices.
public func getInvoices() async throws -> [Invoice] {
let response: InvoicesResponse = try await get(path: "/billing/invoices")
return response.invoices ?? []
}
/// Get invoice by ID.
public func getInvoice(invoiceId: String) async throws -> Invoice {
return try await get(path: "/billing/invoices/\(invoiceId.urlEncoded)")
}
/// Download invoice as PDF data.
public func downloadInvoice(invoiceId: String) async throws -> Data {
let response: [String: String] = try await get(path: "/billing/invoices/\(invoiceId.urlEncoded)/pdf")
guard let base64 = response["data"],
let data = Data(base64Encoded: base64) else {
throw EconomicsError(message: "Invalid PDF data")
}
return data
}
/// Get account balance.
public func getBalance() async throws -> AccountBalance {
return try await get(path: "/billing/balance")
}
/// Add credits to account.
public func addCredits(amount: String, paymentMethod: String) async throws {
let body = ["amount": amount, "paymentMethod": paymentMethod]
let _: [String: Any] = try await post(path: "/billing/credits", body: body)
}
// MARK: - Staking Operations
/// Stake tokens.
public func stake(amount: String, durationDays: Int? = nil) async throws -> StakeReceipt {
var body: [String: Any] = ["amount": amount]
if let duration = durationDays { body["durationDays"] = duration }
return try await post(path: "/staking/stake", body: body)
}
/// Unstake tokens.
public func unstake(stakeId: String) async throws -> UnstakeReceipt {
return try await post(path: "/staking/unstake/\(stakeId.urlEncoded)", body: [:] as [String: String])
}
/// Get all stakes.
public func getStakes() async throws -> [StakeInfo] {
let response: StakesResponse = try await get(path: "/staking/stakes")
return response.stakes ?? []
}
/// Get stake by ID.
public func getStake(stakeId: String) async throws -> StakeInfo {
return try await get(path: "/staking/stakes/\(stakeId.urlEncoded)")
}
/// Get staking rewards.
public func getStakingRewards() async throws -> Rewards {
return try await get(path: "/staking/rewards")
}
/// Claim staking rewards.
public func claimRewards() async throws -> String {
let response: [String: String] = try await post(path: "/staking/rewards/claim", body: [:] as [String: String])
return response["txHash"] ?? ""
}
/// Get current APY.
public func getCurrentApy() async throws -> Double {
let response: [String: Double] = try await get(path: "/staking/apy")
return response["apy"] ?? 0
}
// MARK: - Discount Operations
/// Apply a discount code.
public func applyDiscount(code: String) async throws -> AppliedDiscount {
return try await post(path: "/discounts/apply", body: ["code": code])
}
/// Get available discounts.
public func getAvailableDiscounts() async throws -> [Discount] {
let response: DiscountsResponse = try await get(path: "/discounts/available")
return response.discounts ?? []
}
/// Validate a discount code.
public func validateDiscount(code: String) async throws -> Discount {
return try await get(path: "/discounts/validate/\(code.urlEncoded)")
}
/// Get applied discounts.
public func getAppliedDiscounts() async throws -> [AppliedDiscount] {
let response: AppliedDiscountsResponse = try await get(path: "/discounts/applied")
return response.discounts ?? []
}
/// Remove a discount.
public func removeDiscount(discountId: String) async throws {
let _: [String: Any] = try await delete(path: "/discounts/\(discountId.urlEncoded)")
}
// MARK: - Lifecycle
/// Close the client.
public func close() {
closed = true
}
/// Check if client is closed.
public var isClosed: Bool { closed }
/// Health check.
public func healthCheck() async -> Bool {
do {
let response: [String: String] = try await get(path: "/health")
return response["status"] == "healthy"
} catch {
return false
}
}
// MARK: - Private Methods
private func get<T: Decodable>(path: String) async throws -> T {
return try await request(method: "GET", path: path)
}
private func post<T: Decodable>(path: String, body: Any) async throws -> T {
return try await request(method: "POST", path: path, body: body)
}
private func delete<T: Decodable>(path: String) async throws -> T {
return try await request(method: "DELETE", path: path)
}
private func request<T: Decodable>(method: String, path: String, body: Any? = nil) async throws -> T {
guard !closed else { throw EconomicsError(message: "Client has been closed") }
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await doRequest(method: method, path: path, body: body)
} catch {
lastError = error
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000)
}
}
}
throw lastError ?? EconomicsError(message: "Unknown error")
}
private func doRequest<T: Decodable>(method: String, path: String, body: Any?) async throws -> T {
guard let url = URL(string: config.endpoint + path) else {
throw EconomicsError(message: "Invalid URL")
}
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
if let body = body {
request.httpBody = try JSONSerialization.data(withJSONObject: body)
}
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw EconomicsError(message: "Invalid response")
}
if httpResponse.statusCode >= 400 {
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
throw EconomicsError(
message: errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)",
code: errorInfo?["code"] as? String,
statusCode: httpResponse.statusCode
)
}
return try decoder.decode(T.self, from: data)
}
}
// MARK: - Extensions
extension String {
var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
}
}

View file

@ -0,0 +1,314 @@
import Foundation
// MARK: - Configuration
/// Configuration for the Synor Economics client.
public struct EconomicsConfig {
public let apiKey: String
public let endpoint: String
public let timeout: TimeInterval
public let retries: Int
public let debug: Bool
public init(
apiKey: String,
endpoint: String = "https://economics.synor.io/v1",
timeout: TimeInterval = 60,
retries: Int = 3,
debug: Bool = false
) {
self.apiKey = apiKey
self.endpoint = endpoint
self.timeout = timeout
self.retries = retries
self.debug = debug
}
}
// MARK: - Enums
/// Service types for pricing.
public enum ServiceType: String, Codable, Sendable {
case compute
case storage
case database
case hosting
case bridge
case mining
case rpc
}
/// Billing period.
public enum BillingPeriod: String, Codable, Sendable {
case daily
case weekly
case monthly
case yearly
}
/// Stake status.
public enum StakeStatus: String, Codable, Sendable {
case active
case unstaking
case unlocked
case slashed
}
/// Discount type.
public enum DiscountType: String, Codable, Sendable {
case percentage
case fixed
case volume
}
// MARK: - Pricing Types
/// Usage metrics for pricing calculation.
public struct UsageMetrics: Codable, Sendable {
public var computeHours: Double
public var storageGb: Double
public var requests: Int64
public var bandwidthGb: Double
public var gpuHours: Double
public init(
computeHours: Double = 0,
storageGb: Double = 0,
requests: Int64 = 0,
bandwidthGb: Double = 0,
gpuHours: Double = 0
) {
self.computeHours = computeHours
self.storageGb = storageGb
self.requests = requests
self.bandwidthGb = bandwidthGb
self.gpuHours = gpuHours
}
}
/// Pricing tier.
public struct PricingTier: Codable, Sendable {
public let name: String
public let upTo: Double
public let pricePerUnit: Double
public let unit: String
}
/// Price result.
public struct Price: Codable, Sendable {
public let service: ServiceType
public let amount: String
public let currency: String
public let amountUsd: String
public let breakdown: [PricingTier]?
public let discount: String?
public let finalAmount: String
}
/// Usage plan for cost estimation.
public struct UsagePlan: Codable, Sendable {
public let service: ServiceType
public let estimatedUsage: UsageMetrics
public let period: BillingPeriod
public init(service: ServiceType, estimatedUsage: UsageMetrics, period: BillingPeriod) {
self.service = service
self.estimatedUsage = estimatedUsage
self.period = period
}
}
/// Cost breakdown item.
public struct CostBreakdown: Codable, Sendable {
public let category: String
public let amount: String
public let percentage: Double
}
/// Cost estimate result.
public struct CostEstimate: Codable, Sendable {
public let service: ServiceType
public let period: BillingPeriod
public let estimatedCost: String
public let estimatedCostUsd: String
public let currency: String
public let confidenceLevel: String
public let breakdown: [CostBreakdown]?
public let savingsFromStaking: String?
}
// MARK: - Billing Types
/// Usage record.
public struct UsageRecord: Codable, Sendable {
public let service: ServiceType
public let resource: String
public let quantity: Double
public let unit: String
public let timestamp: Int64
public let cost: String
}
/// Usage summary.
public struct Usage: Codable, Sendable {
public let period: BillingPeriod
public let startDate: Int64
public let endDate: Int64
public let records: [UsageRecord]
public let totalCost: String
public let currency: String
}
/// Invoice line item.
public struct InvoiceLineItem: Codable, Sendable {
public let description: String
public let quantity: Double
public let unit: String
public let unitPrice: String
public let amount: String
}
/// Invoice.
public struct Invoice: Codable, Sendable {
public let id: String
public let number: String
public let periodStart: Int64
public let periodEnd: Int64
public let subtotal: String
public let tax: String
public let discount: String
public let total: String
public let currency: String
public let status: String
public let dueDate: Int64?
public let paidAt: Int64?
public let lineItems: [InvoiceLineItem]
public let pdfUrl: String?
}
/// Account balance.
public struct AccountBalance: Codable, Sendable {
public let available: String
public let pending: String
public let reserved: String
public let staked: String
public let currency: String
public let lastUpdated: Int64
}
// MARK: - Staking Types
/// Stake receipt.
public struct StakeReceipt: Codable, Sendable {
public let id: String
public let txHash: String
public let amount: String
public let startDate: Int64
public let endDate: Int64
public let apy: Double
public let status: StakeStatus
}
/// Stake info.
public struct StakeInfo: Codable, Sendable {
public let id: String
public let amount: String
public let status: StakeStatus
public let startDate: Int64
public let endDate: Int64
public let apy: Double
public let earnedRewards: String
public let pendingRewards: String
public let unlockDate: Int64?
public let discountPercent: Int
}
/// Unstake receipt.
public struct UnstakeReceipt: Codable, Sendable {
public let id: String
public let txHash: String
public let amount: String
public let initiatedAt: Int64
public let availableAt: Int64
public let penalty: String?
}
/// Reward record.
public struct RewardRecord: Codable, Sendable {
public let date: Int64
public let amount: String
public let type: String
public let txHash: String?
}
/// Staking rewards.
public struct Rewards: Codable, Sendable {
public let totalEarned: String
public let pendingClaim: String
public let lastClaimDate: String?
public let currentApy: Double
public let history: [RewardRecord]
}
// MARK: - Discount Types
/// Discount.
public struct Discount: Codable, Sendable {
public let id: String
public let code: String
public let type: DiscountType
public let value: String
public let validFrom: Int64?
public let validUntil: Int64?
public let usageLimit: Int?
public let usageCount: Int
public let applicableServices: [ServiceType]?
public let minimumSpend: String?
public let active: Bool
}
/// Applied discount.
public struct AppliedDiscount: Codable, Sendable {
public let discount: Discount
public let savedAmount: String
public let appliedAt: Int64
}
// MARK: - Response Types
struct TiersResponse: Codable {
let tiers: [PricingTier]?
}
struct InvoicesResponse: Codable {
let invoices: [Invoice]?
}
struct StakesResponse: Codable {
let stakes: [StakeInfo]?
}
struct DiscountsResponse: Codable {
let discounts: [Discount]?
}
struct AppliedDiscountsResponse: Codable {
let discounts: [AppliedDiscount]?
}
// MARK: - Error
/// Error thrown by Economics operations.
public struct EconomicsError: Error, LocalizedError {
public let message: String
public let code: String?
public let statusCode: Int
public init(message: String, code: String? = nil, statusCode: Int = 0) {
self.message = message
self.code = code
self.statusCode = statusCode
}
public var errorDescription: String? { message }
}

View file

@ -0,0 +1,286 @@
import Foundation
/// Synor Governance SDK client for Swift.
///
/// Provides proposals, voting, DAOs, and vesting operations.
public class SynorGovernance {
private let config: GovernanceConfig
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private var closed = false
private static let finalStatuses: Set<ProposalStatus> = [.passed, .rejected, .executed, .cancelled]
public init(config: GovernanceConfig) {
self.config = config
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = config.timeout
sessionConfig.timeoutIntervalForResource = config.timeout * 2
self.session = URLSession(configuration: sessionConfig)
self.decoder = JSONDecoder()
self.encoder = JSONEncoder()
}
// MARK: - Proposal Operations
/// Create a proposal.
public func createProposal(_ proposal: ProposalDraft) async throws -> Proposal {
var body: [String: Any] = [
"title": proposal.title,
"description": proposal.description
]
if let url = proposal.discussionUrl { body["discussionUrl"] = url }
if let start = proposal.votingStartTime { body["votingStartTime"] = start }
if let end = proposal.votingEndTime { body["votingEndTime"] = end }
if let daoId = proposal.daoId { body["daoId"] = daoId }
if let actions = proposal.actions {
body["actions"] = actions.map { ["target": $0.target, "method": $0.method, "data": $0.data, "value": $0.value as Any] }
}
return try await post(path: "/proposals", body: body)
}
/// Get proposal by ID.
public func getProposal(proposalId: String) async throws -> Proposal {
return try await get(path: "/proposals/\(proposalId.urlEncoded)")
}
/// List proposals.
public func listProposals(filter: ProposalFilter? = nil) async throws -> [Proposal] {
var params: [String] = []
if let status = filter?.status { params.append("status=\(status.rawValue)") }
if let proposer = filter?.proposer { params.append("proposer=\(proposer.urlEncoded)") }
if let daoId = filter?.daoId { params.append("daoId=\(daoId.urlEncoded)") }
if let limit = filter?.limit { params.append("limit=\(limit)") }
if let offset = filter?.offset { params.append("offset=\(offset)") }
let path = params.isEmpty ? "/proposals" : "/proposals?\(params.joined(separator: "&"))"
let response: ProposalsResponse = try await get(path: path)
return response.proposals ?? []
}
/// Cancel a proposal.
public func cancelProposal(proposalId: String) async throws -> Proposal {
return try await post(path: "/proposals/\(proposalId.urlEncoded)/cancel", body: [:] as [String: String])
}
/// Execute a passed proposal.
public func executeProposal(proposalId: String) async throws -> Proposal {
return try await post(path: "/proposals/\(proposalId.urlEncoded)/execute", body: [:] as [String: String])
}
/// Wait for proposal to reach a final state.
public func waitForProposal(proposalId: String, pollInterval: TimeInterval = 60, maxWait: TimeInterval = 604800) async throws -> Proposal {
let deadline = Date().addingTimeInterval(maxWait)
while Date() < deadline {
let proposal = try await getProposal(proposalId: proposalId)
if Self.finalStatuses.contains(proposal.status) { return proposal }
try await Task.sleep(nanoseconds: UInt64(pollInterval * 1_000_000_000))
}
throw GovernanceError(message: "Timeout waiting for proposal completion")
}
// MARK: - Voting Operations
/// Vote on a proposal.
public func vote(proposalId: String, vote: Vote, weight: String? = nil) async throws -> VoteReceipt {
var body: [String: Any] = ["choice": vote.choice.rawValue]
if let reason = vote.reason { body["reason"] = reason }
if let weight = weight { body["weight"] = weight }
return try await post(path: "/proposals/\(proposalId.urlEncoded)/vote", body: body)
}
/// Get votes for a proposal.
public func getVotes(proposalId: String) async throws -> [VoteReceipt] {
let response: VotesResponse = try await get(path: "/proposals/\(proposalId.urlEncoded)/votes")
return response.votes ?? []
}
/// Get my vote on a proposal.
public func getMyVote(proposalId: String) async throws -> VoteReceipt {
return try await get(path: "/proposals/\(proposalId.urlEncoded)/votes/me")
}
/// Delegate voting power.
public func delegate(delegatee: String, amount: String? = nil) async throws -> DelegationReceipt {
var body: [String: Any] = ["delegatee": delegatee]
if let amount = amount { body["amount"] = amount }
return try await post(path: "/voting/delegate", body: body)
}
/// Undelegate voting power.
public func undelegate(delegatee: String) async throws -> DelegationReceipt {
return try await post(path: "/voting/undelegate", body: ["delegatee": delegatee])
}
/// Get voting power for an address.
public func getVotingPower(address: String) async throws -> VotingPower {
return try await get(path: "/voting/power/\(address.urlEncoded)")
}
/// Get delegations for an address.
public func getDelegations(address: String) async throws -> [DelegationReceipt] {
let response: DelegationsResponse = try await get(path: "/voting/delegations/\(address.urlEncoded)")
return response.delegations ?? []
}
// MARK: - DAO Operations
/// Create a DAO.
public func createDao(_ daoConfig: DaoConfig) async throws -> Dao {
var body: [String: Any] = [
"name": daoConfig.name,
"description": daoConfig.description,
"type": daoConfig.type.rawValue,
"votingPeriodDays": daoConfig.votingPeriodDays,
"timelockDays": daoConfig.timelockDays
]
if let token = daoConfig.tokenAddress { body["tokenAddress"] = token }
if let quorum = daoConfig.quorumPercent { body["quorumPercent"] = quorum }
if let threshold = daoConfig.proposalThreshold { body["proposalThreshold"] = threshold }
if let members = daoConfig.multisigMembers { body["multisigMembers"] = members }
if let threshold = daoConfig.multisigThreshold { body["multisigThreshold"] = threshold }
return try await post(path: "/daos", body: body)
}
/// Get DAO by ID.
public func getDao(daoId: String) async throws -> Dao {
return try await get(path: "/daos/\(daoId.urlEncoded)")
}
/// List DAOs.
public func listDaos(limit: Int? = nil, offset: Int? = nil) async throws -> [Dao] {
var params: [String] = []
if let limit = limit { params.append("limit=\(limit)") }
if let offset = offset { params.append("offset=\(offset)") }
let path = params.isEmpty ? "/daos" : "/daos?\(params.joined(separator: "&"))"
let response: DaosResponse = try await get(path: path)
return response.daos ?? []
}
/// Get DAO treasury.
public func getDaoTreasury(daoId: String) async throws -> DaoTreasury {
return try await get(path: "/daos/\(daoId.urlEncoded)/treasury")
}
/// Get DAO members.
public func getDaoMembers(daoId: String) async throws -> [String] {
let response: MembersResponse = try await get(path: "/daos/\(daoId.urlEncoded)/members")
return response.members ?? []
}
// MARK: - Vesting Operations
/// Create a vesting schedule.
public func createVestingSchedule(_ schedule: VestingSchedule) async throws -> VestingContract {
let body: [String: Any] = [
"beneficiary": schedule.beneficiary,
"totalAmount": schedule.totalAmount,
"startTime": schedule.startTime,
"cliffDuration": schedule.cliffDuration,
"vestingDuration": schedule.vestingDuration,
"revocable": schedule.revocable
]
return try await post(path: "/vesting", body: body)
}
/// Get vesting contract.
public func getVestingContract(contractId: String) async throws -> VestingContract {
return try await get(path: "/vesting/\(contractId.urlEncoded)")
}
/// List vesting contracts.
public func listVestingContracts(beneficiary: String? = nil) async throws -> [VestingContract] {
let path = beneficiary != nil ? "/vesting?beneficiary=\(beneficiary!.urlEncoded)" : "/vesting"
let response: VestingContractsResponse = try await get(path: path)
return response.contracts ?? []
}
/// Claim vested tokens.
public func claimVested(contractId: String) async throws -> ClaimReceipt {
return try await post(path: "/vesting/\(contractId.urlEncoded)/claim", body: [:] as [String: String])
}
/// Revoke vesting.
public func revokeVesting(contractId: String) async throws -> VestingContract {
return try await post(path: "/vesting/\(contractId.urlEncoded)/revoke", body: [:] as [String: String])
}
/// Get releasable amount.
public func getReleasableAmount(contractId: String) async throws -> String {
let response: [String: String] = try await get(path: "/vesting/\(contractId.urlEncoded)/releasable")
return response["amount"] ?? "0"
}
// MARK: - Lifecycle
public func close() { closed = true }
public var isClosed: Bool { closed }
public func healthCheck() async -> Bool {
do {
let response: [String: String] = try await get(path: "/health")
return response["status"] == "healthy"
} catch { return false }
}
// MARK: - Private Methods
private func get<T: Decodable>(path: String) async throws -> T {
return try await request(method: "GET", path: path)
}
private func post<T: Decodable>(path: String, body: Any) async throws -> T {
return try await request(method: "POST", path: path, body: body)
}
private func request<T: Decodable>(method: String, path: String, body: Any? = nil) async throws -> T {
guard !closed else { throw GovernanceError(message: "Client has been closed") }
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await doRequest(method: method, path: path, body: body)
} catch {
lastError = error
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000)
}
}
}
throw lastError ?? GovernanceError(message: "Unknown error")
}
private func doRequest<T: Decodable>(method: String, path: String, body: Any?) async throws -> T {
guard let url = URL(string: config.endpoint + path) else { throw GovernanceError(message: "Invalid URL") }
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
if let body = body { request.httpBody = try JSONSerialization.data(withJSONObject: body) }
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else { throw GovernanceError(message: "Invalid response") }
if httpResponse.statusCode >= 400 {
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
throw GovernanceError(
message: errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)",
code: errorInfo?["code"] as? String,
statusCode: httpResponse.statusCode
)
}
return try decoder.decode(T.self, from: data)
}
}
extension String {
fileprivate var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
}
}

View file

@ -0,0 +1,374 @@
import Foundation
// MARK: - Configuration
/// Configuration for the Synor Governance client.
public struct GovernanceConfig {
public let apiKey: String
public let endpoint: String
public let timeout: TimeInterval
public let retries: Int
public let debug: Bool
public init(
apiKey: String,
endpoint: String = "https://governance.synor.io/v1",
timeout: TimeInterval = 60,
retries: Int = 3,
debug: Bool = false
) {
self.apiKey = apiKey
self.endpoint = endpoint
self.timeout = timeout
self.retries = retries
self.debug = debug
}
}
// MARK: - Enums
/// Proposal status.
public enum ProposalStatus: String, Codable, Sendable {
case draft
case active
case passed
case rejected
case executed
case cancelled
}
/// Vote choice.
public enum VoteChoice: String, Codable, Sendable {
case yes
case no
case abstain
}
/// DAO type.
public enum DaoType: String, Codable, Sendable {
case token
case multisig
case hybrid
}
/// Vesting status.
public enum VestingStatus: String, Codable, Sendable {
case pending
case active
case paused
case completed
case revoked
}
// MARK: - Proposal Types
/// Proposal action.
public struct ProposalAction: Codable, Sendable {
public var target: String
public var method: String
public var data: String
public var value: String?
public init(target: String, method: String, data: String, value: String? = nil) {
self.target = target
self.method = method
self.data = data
self.value = value
}
}
/// Proposal draft.
public struct ProposalDraft: Codable, Sendable {
public var title: String
public var description: String
public var discussionUrl: String?
public var votingStartTime: Int64?
public var votingEndTime: Int64?
public var actions: [ProposalAction]?
public var daoId: String?
public init(
title: String,
description: String,
discussionUrl: String? = nil,
votingStartTime: Int64? = nil,
votingEndTime: Int64? = nil,
actions: [ProposalAction]? = nil,
daoId: String? = nil
) {
self.title = title
self.description = description
self.discussionUrl = discussionUrl
self.votingStartTime = votingStartTime
self.votingEndTime = votingEndTime
self.actions = actions
self.daoId = daoId
}
}
/// Vote breakdown.
public struct VoteBreakdown: Codable, Sendable {
public let yes: String
public let no: String
public let abstain: String
public let quorum: String
public let quorumPercent: Double
public let quorumReached: Bool
}
/// Proposal.
public struct Proposal: Codable, Sendable {
public let id: String
public let title: String
public let description: String
public let proposer: String
public let status: ProposalStatus
public let createdAt: Int64
public let votingStartTime: Int64
public let votingEndTime: Int64
public let votes: VoteBreakdown
public let discussionUrl: String?
public let actions: [ProposalAction]?
public let daoId: String?
public let snapshotBlock: String?
public let executedAt: Int64?
public let executedTxHash: String?
}
/// Proposal filter.
public struct ProposalFilter {
public var status: ProposalStatus?
public var proposer: String?
public var daoId: String?
public var limit: Int?
public var offset: Int?
public init(
status: ProposalStatus? = nil,
proposer: String? = nil,
daoId: String? = nil,
limit: Int? = nil,
offset: Int? = nil
) {
self.status = status
self.proposer = proposer
self.daoId = daoId
self.limit = limit
self.offset = offset
}
}
// MARK: - Voting Types
/// Vote.
public struct Vote {
public var choice: VoteChoice
public var reason: String?
public init(choice: VoteChoice, reason: String? = nil) {
self.choice = choice
self.reason = reason
}
}
/// Vote receipt.
public struct VoteReceipt: Codable, Sendable {
public let id: String
public let proposalId: String
public let voter: String
public let choice: VoteChoice
public let weight: String
public let timestamp: Int64
public let txHash: String
public let reason: String?
}
/// Voting power.
public struct VotingPower: Codable, Sendable {
public let address: String
public let balance: String
public let delegatedTo: String?
public let delegatedFrom: String
public let totalPower: String
public let blockNumber: Int64
}
/// Delegation receipt.
public struct DelegationReceipt: Codable, Sendable {
public let id: String
public let delegator: String
public let delegatee: String
public let amount: String
public let timestamp: Int64
public let txHash: String
}
// MARK: - DAO Types
/// DAO configuration.
public struct DaoConfig {
public var name: String
public var description: String
public var type: DaoType
public var tokenAddress: String?
public var quorumPercent: String?
public var votingPeriodDays: Int
public var timelockDays: Int
public var proposalThreshold: Int?
public var multisigMembers: [String]?
public var multisigThreshold: Int?
public init(
name: String,
description: String,
type: DaoType,
tokenAddress: String? = nil,
quorumPercent: String? = nil,
votingPeriodDays: Int = 7,
timelockDays: Int = 2,
proposalThreshold: Int? = nil,
multisigMembers: [String]? = nil,
multisigThreshold: Int? = nil
) {
self.name = name
self.description = description
self.type = type
self.tokenAddress = tokenAddress
self.quorumPercent = quorumPercent
self.votingPeriodDays = votingPeriodDays
self.timelockDays = timelockDays
self.proposalThreshold = proposalThreshold
self.multisigMembers = multisigMembers
self.multisigThreshold = multisigThreshold
}
}
/// Treasury asset.
public struct TreasuryAsset: Codable, Sendable {
public let address: String
public let symbol: String
public let balance: String
public let valueUsd: String
}
/// DAO treasury.
public struct DaoTreasury: Codable, Sendable {
public let address: String
public let balance: String
public let currency: String
public let assets: [TreasuryAsset]
}
/// DAO.
public struct Dao: Codable, Sendable {
public let id: String
public let name: String
public let description: String
public let type: DaoType
public let tokenAddress: String?
public let governorAddress: String
public let timelockAddress: String
public let treasury: DaoTreasury
public let quorumPercent: String
public let votingPeriodDays: Int
public let timelockDays: Int
public let proposalCount: Int
public let memberCount: Int
public let createdAt: Int64
}
// MARK: - Vesting Types
/// Vesting schedule.
public struct VestingSchedule {
public var beneficiary: String
public var totalAmount: String
public var startTime: Int64
public var cliffDuration: Int64
public var vestingDuration: Int64
public var revocable: Bool
public init(
beneficiary: String,
totalAmount: String,
startTime: Int64,
cliffDuration: Int64,
vestingDuration: Int64,
revocable: Bool = false
) {
self.beneficiary = beneficiary
self.totalAmount = totalAmount
self.startTime = startTime
self.cliffDuration = cliffDuration
self.vestingDuration = vestingDuration
self.revocable = revocable
}
}
/// Vesting contract.
public struct VestingContract: Codable, Sendable {
public let id: String
public let contractAddress: String
public let beneficiary: String
public let totalAmount: String
public let releasedAmount: String
public let releasableAmount: String
public let startTime: Int64
public let cliffEnd: Int64
public let vestingEnd: Int64
public let status: VestingStatus
public let createdAt: Int64
public let txHash: String
}
/// Claim receipt.
public struct ClaimReceipt: Codable, Sendable {
public let vestingContractId: String
public let amount: String
public let txHash: String
public let timestamp: Int64
public let remainingAmount: String
}
// MARK: - Response Types
struct ProposalsResponse: Codable {
let proposals: [Proposal]?
}
struct VotesResponse: Codable {
let votes: [VoteReceipt]?
}
struct DelegationsResponse: Codable {
let delegations: [DelegationReceipt]?
}
struct DaosResponse: Codable {
let daos: [Dao]?
}
struct MembersResponse: Codable {
let members: [String]?
}
struct VestingContractsResponse: Codable {
let contracts: [VestingContract]?
}
// MARK: - Error
/// Error thrown by Governance operations.
public struct GovernanceError: Error, LocalizedError {
public let message: String
public let code: String?
public let statusCode: Int
public init(message: String, code: String? = nil, statusCode: Int = 0) {
self.message = message
self.code = code
self.statusCode = statusCode
}
public var errorDescription: String? { message }
}

View file

@ -0,0 +1,279 @@
import Foundation
/// Synor Mining SDK client for Swift.
///
/// Provides pool connections, block templates, hashrate stats, and GPU management.
public class SynorMining {
private let config: MiningConfig
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private var closed = false
private var activeConnection: StratumConnection?
public init(config: MiningConfig) {
self.config = config
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = config.timeout
sessionConfig.timeoutIntervalForResource = config.timeout * 2
self.session = URLSession(configuration: sessionConfig)
self.decoder = JSONDecoder()
self.encoder = JSONEncoder()
}
// MARK: - Pool Operations
/// Connect to a mining pool.
public func connect(pool: PoolConfig) async throws -> StratumConnection {
var body: [String: Any] = ["url": pool.url, "user": pool.user]
if let password = pool.password { body["password"] = password }
if let algorithm = pool.algorithm { body["algorithm"] = algorithm }
if let difficulty = pool.difficulty { body["difficulty"] = difficulty }
let conn: StratumConnection = try await post(path: "/pool/connect", body: body)
activeConnection = conn
return conn
}
/// Disconnect from the pool.
public func disconnect() async throws {
guard activeConnection != nil else { return }
let _: [String: Any] = try await post(path: "/pool/disconnect", body: [:] as [String: String])
activeConnection = nil
}
/// Get current connection.
public func getConnection() async throws -> StratumConnection {
let conn: StratumConnection = try await get(path: "/pool/connection")
activeConnection = conn
return conn
}
/// Get pool stats.
public func getPoolStats() async throws -> PoolStats {
return try await get(path: "/pool/stats")
}
/// Check if connected.
public var isConnected: Bool {
activeConnection?.status == .connected
}
// MARK: - Mining Operations
/// Get block template.
public func getBlockTemplate() async throws -> BlockTemplate {
return try await get(path: "/mining/template")
}
/// Submit mined work.
public func submitWork(_ work: MinedWork) async throws -> SubmitResult {
let body: [String: Any] = [
"templateId": work.templateId,
"nonce": work.nonce,
"extraNonce": work.extraNonce,
"timestamp": work.timestamp,
"hash": work.hash
]
return try await post(path: "/mining/submit", body: body)
}
/// Start mining.
public func startMining(algorithm: String? = nil) async throws {
var body: [String: Any] = [:]
if let algorithm = algorithm { body["algorithm"] = algorithm }
let _: [String: Any] = try await post(path: "/mining/start", body: body)
}
/// Stop mining.
public func stopMining() async throws {
let _: [String: Any] = try await post(path: "/mining/stop", body: [:] as [String: String])
}
/// Check if mining.
public func isMining() async throws -> Bool {
let response: [String: Bool] = try await get(path: "/mining/status")
return response["mining"] ?? false
}
// MARK: - Stats Operations
/// Get hashrate.
public func getHashrate() async throws -> Hashrate {
return try await get(path: "/stats/hashrate")
}
/// Get mining stats.
public func getStats() async throws -> MiningStats {
return try await get(path: "/stats")
}
/// Get earnings.
public func getEarnings(period: TimePeriod? = nil) async throws -> Earnings {
let path = period != nil ? "/stats/earnings?period=\(period!.rawValue)" : "/stats/earnings"
return try await get(path: path)
}
/// Get share stats.
public func getShareStats() async throws -> ShareStats {
return try await get(path: "/stats/shares")
}
// MARK: - Device Operations
/// List devices.
public func listDevices() async throws -> [MiningDevice] {
let response: DevicesResponse = try await get(path: "/devices")
return response.devices ?? []
}
/// Get device.
public func getDevice(deviceId: String) async throws -> MiningDevice {
return try await get(path: "/devices/\(deviceId.urlEncoded)")
}
/// Set device config.
public func setDeviceConfig(deviceId: String, config: DeviceConfig) async throws -> MiningDevice {
var body: [String: Any] = ["enabled": config.enabled]
if let intensity = config.intensity { body["intensity"] = intensity }
if let powerLimit = config.powerLimit { body["powerLimit"] = powerLimit }
if let coreClockOffset = config.coreClockOffset { body["coreClockOffset"] = coreClockOffset }
if let memoryClockOffset = config.memoryClockOffset { body["memoryClockOffset"] = memoryClockOffset }
if let fanSpeed = config.fanSpeed { body["fanSpeed"] = fanSpeed }
return try await put(path: "/devices/\(deviceId.urlEncoded)/config", body: body)
}
/// Enable device.
public func enableDevice(deviceId: String) async throws {
_ = try await setDeviceConfig(deviceId: deviceId, config: DeviceConfig(enabled: true))
}
/// Disable device.
public func disableDevice(deviceId: String) async throws {
_ = try await setDeviceConfig(deviceId: deviceId, config: DeviceConfig(enabled: false))
}
// MARK: - Worker Operations
/// List workers.
public func listWorkers() async throws -> [WorkerInfo] {
let response: WorkersResponse = try await get(path: "/workers")
return response.workers ?? []
}
/// Get worker.
public func getWorker(workerId: String) async throws -> WorkerInfo {
return try await get(path: "/workers/\(workerId.urlEncoded)")
}
/// Remove worker.
public func removeWorker(workerId: String) async throws {
let _: [String: Any] = try await delete(path: "/workers/\(workerId.urlEncoded)")
}
// MARK: - Algorithm Operations
/// List algorithms.
public func listAlgorithms() async throws -> [MiningAlgorithm] {
let response: AlgorithmsResponse = try await get(path: "/algorithms")
return response.algorithms ?? []
}
/// Get algorithm.
public func getAlgorithm(name: String) async throws -> MiningAlgorithm {
return try await get(path: "/algorithms/\(name.urlEncoded)")
}
/// Switch algorithm.
public func switchAlgorithm(_ algorithm: String) async throws {
let _: [String: Any] = try await post(path: "/algorithms/switch", body: ["algorithm": algorithm])
}
/// Get most profitable algorithm.
public func getMostProfitable() async throws -> MiningAlgorithm {
return try await get(path: "/algorithms/profitable")
}
// MARK: - Lifecycle
public func close() {
closed = true
activeConnection = nil
}
public var isClosed: Bool { closed }
public func healthCheck() async -> Bool {
do {
let response: [String: String] = try await get(path: "/health")
return response["status"] == "healthy"
} catch { return false }
}
// MARK: - Private Methods
private func get<T: Decodable>(path: String) async throws -> T {
return try await request(method: "GET", path: path)
}
private func post<T: Decodable>(path: String, body: Any) async throws -> T {
return try await request(method: "POST", path: path, body: body)
}
private func put<T: Decodable>(path: String, body: Any) async throws -> T {
return try await request(method: "PUT", path: path, body: body)
}
private func delete<T: Decodable>(path: String) async throws -> T {
return try await request(method: "DELETE", path: path)
}
private func request<T: Decodable>(method: String, path: String, body: Any? = nil) async throws -> T {
guard !closed else { throw MiningError(message: "Client has been closed") }
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await doRequest(method: method, path: path, body: body)
} catch {
lastError = error
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000)
}
}
}
throw lastError ?? MiningError(message: "Unknown error")
}
private func doRequest<T: Decodable>(method: String, path: String, body: Any?) async throws -> T {
guard let url = URL(string: config.endpoint + path) else { throw MiningError(message: "Invalid URL") }
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
if let body = body { request.httpBody = try JSONSerialization.data(withJSONObject: body) }
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else { throw MiningError(message: "Invalid response") }
if httpResponse.statusCode >= 400 {
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
throw MiningError(
message: errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)",
code: errorInfo?["code"] as? String,
statusCode: httpResponse.statusCode
)
}
return try decoder.decode(T.self, from: data)
}
}
extension String {
fileprivate var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
}
}

View file

@ -0,0 +1,352 @@
import Foundation
// MARK: - Configuration
/// Configuration for the Synor Mining client.
public struct MiningConfig {
public let apiKey: String
public let endpoint: String
public let timeout: TimeInterval
public let retries: Int
public let debug: Bool
public init(
apiKey: String,
endpoint: String = "https://mining.synor.io/v1",
timeout: TimeInterval = 60,
retries: Int = 3,
debug: Bool = false
) {
self.apiKey = apiKey
self.endpoint = endpoint
self.timeout = timeout
self.retries = retries
self.debug = debug
}
}
// MARK: - Enums
/// Device type.
public enum DeviceType: String, Codable, Sendable {
case cpu
case gpu_nvidia
case gpu_amd
case asic
}
/// Device status.
public enum DeviceStatus: String, Codable, Sendable {
case idle
case mining
case error
case offline
}
/// Connection status.
public enum ConnectionStatus: String, Codable, Sendable {
case disconnected
case connecting
case connected
case reconnecting
}
/// Time period.
public enum TimePeriod: String, Codable, Sendable {
case hour
case day
case week
case month
case all
}
/// Submit result status.
public enum SubmitResultStatus: String, Codable, Sendable {
case accepted
case rejected
case stale
}
// MARK: - Pool Types
/// Pool configuration.
public struct PoolConfig {
public var url: String
public var user: String
public var password: String?
public var algorithm: String?
public var difficulty: Double?
public init(
url: String,
user: String,
password: String? = nil,
algorithm: String? = nil,
difficulty: Double? = nil
) {
self.url = url
self.user = user
self.password = password
self.algorithm = algorithm
self.difficulty = difficulty
}
}
/// Stratum connection.
public struct StratumConnection: Codable, Sendable {
public let id: String
public let pool: String
public let status: ConnectionStatus
public let algorithm: String
public let difficulty: Double
public let connectedAt: Int64
public let acceptedShares: Int
public let rejectedShares: Int
public let staleShares: Int
public let lastShareAt: Int64?
}
/// Pool stats.
public struct PoolStats: Codable, Sendable {
public let url: String
public let workers: Int
public let hashrate: Double
public let difficulty: Double
public let lastBlock: Int64
public let blocksFound24h: Int
public let luck: Double
}
// MARK: - Mining Types
/// Template transaction.
public struct TemplateTransaction: Codable, Sendable {
public let txid: String
public let data: String
public let fee: String
public let weight: Int
}
/// Block template.
public struct BlockTemplate: Codable, Sendable {
public let id: String
public let previousBlockHash: String
public let merkleRoot: String
public let timestamp: Int64
public let bits: String
public let height: Int64
public let coinbaseValue: String
public let transactions: [TemplateTransaction]
public let target: String
public let algorithm: String
public let extraNonce: String
}
/// Mined work.
public struct MinedWork {
public var templateId: String
public var nonce: String
public var extraNonce: String
public var timestamp: Int64
public var hash: String
public init(
templateId: String,
nonce: String,
extraNonce: String,
timestamp: Int64,
hash: String
) {
self.templateId = templateId
self.nonce = nonce
self.extraNonce = extraNonce
self.timestamp = timestamp
self.hash = hash
}
}
/// Share info.
public struct ShareInfo: Codable, Sendable {
public let hash: String
public let difficulty: Double
public let timestamp: Int64
public let accepted: Bool
}
/// Submit result.
public struct SubmitResult: Codable, Sendable {
public let status: SubmitResultStatus
public let share: ShareInfo
public let blockFound: Bool
public let reason: String?
public let blockHash: String?
public let reward: String?
}
// MARK: - Stats Types
/// Hashrate.
public struct Hashrate: Codable, Sendable {
public let current: Double
public let average1h: Double
public let average24h: Double
public let peak: Double
public let unit: String
}
/// Share stats.
public struct ShareStats: Codable, Sendable {
public let accepted: Int
public let rejected: Int
public let stale: Int
public let total: Int
public let acceptRate: Double
}
/// Device temperature.
public struct DeviceTemperature: Codable, Sendable {
public let current: Double
public let max: Double
public let throttling: Bool
}
/// Earnings snapshot.
public struct EarningsSnapshot: Codable, Sendable {
public let today: String
public let yesterday: String
public let thisWeek: String
public let thisMonth: String
public let total: String
public let currency: String
}
/// Mining stats.
public struct MiningStats: Codable, Sendable {
public let hashrate: Hashrate
public let shares: ShareStats
public let uptime: Int64
public let efficiency: Double
public let earnings: EarningsSnapshot
public let powerConsumption: Double?
public let temperature: DeviceTemperature?
}
/// Earnings breakdown.
public struct EarningsBreakdown: Codable, Sendable {
public let date: Int64
public let amount: String
public let blocks: Int
public let shares: Int
public let hashrate: Double
}
/// Earnings.
public struct Earnings: Codable, Sendable {
public let period: TimePeriod
public let startDate: Int64
public let endDate: Int64
public let amount: String
public let blocks: Int
public let shares: Int
public let averageHashrate: Double
public let currency: String
public let breakdown: [EarningsBreakdown]
}
// MARK: - Device Types
/// Mining device.
public struct MiningDevice: Codable, Sendable {
public let id: String
public let name: String
public let type: DeviceType
public let status: DeviceStatus
public let hashrate: Double
public let temperature: Double
public let fanSpeed: Double
public let powerDraw: Double
public let memoryUsed: Int64
public let memoryTotal: Int64
public let driver: String?
public let firmware: String?
}
/// Device configuration.
public struct DeviceConfig {
public var enabled: Bool
public var intensity: Int?
public var powerLimit: Int?
public var coreClockOffset: Int?
public var memoryClockOffset: Int?
public var fanSpeed: Int?
public init(
enabled: Bool,
intensity: Int? = nil,
powerLimit: Int? = nil,
coreClockOffset: Int? = nil,
memoryClockOffset: Int? = nil,
fanSpeed: Int? = nil
) {
self.enabled = enabled
self.intensity = intensity
self.powerLimit = powerLimit
self.coreClockOffset = coreClockOffset
self.memoryClockOffset = memoryClockOffset
self.fanSpeed = fanSpeed
}
}
/// Worker info.
public struct WorkerInfo: Codable, Sendable {
public let id: String
public let name: String
public let status: ConnectionStatus
public let hashrate: Hashrate
public let shares: ShareStats
public let devices: [MiningDevice]
public let lastSeen: Int64
public let uptime: Int64
}
/// Mining algorithm.
public struct MiningAlgorithm: Codable, Sendable {
public let name: String
public let displayName: String
public let hashUnit: String
public let profitability: String
public let difficulty: Double
public let blockReward: String
public let blockTime: Int
}
// MARK: - Response Types
struct DevicesResponse: Codable {
let devices: [MiningDevice]?
}
struct WorkersResponse: Codable {
let workers: [WorkerInfo]?
}
struct AlgorithmsResponse: Codable {
let algorithms: [MiningAlgorithm]?
}
// MARK: - Error
/// Error thrown by Mining operations.
public struct MiningError: Error, LocalizedError {
public let message: String
public let code: String?
public let statusCode: Int
public init(message: String, code: String? = nil, statusCode: Int = 0) {
self.message = message
self.code = code
self.statusCode = statusCode
}
public var errorDescription: String? { message }
}