feat: complete Phase 3 SDKs for Swift, C, C++, C#, and Ruby

Implements Database, Hosting, and Bridge SDKs for remaining languages:

Swift SDKs:
- SynorDatabase with KV, Document, Vector, TimeSeries stores
- SynorHosting with domain, DNS, deployment, SSL operations
- SynorBridge with lock-mint and burn-unlock cross-chain flows

C SDKs:
- database.h/c - multi-model database client
- hosting.h/c - hosting and domain management
- bridge.h/c - cross-chain asset transfers

C++ SDKs:
- database.hpp - modern C++17 with std::future async
- hosting.hpp - domain and deployment operations
- bridge.hpp - cross-chain bridge with wait operations

C# SDKs:
- SynorDatabase.cs - async/await with inner store classes
- SynorHosting.cs - domain management and analytics
- SynorBridge.cs - cross-chain with BridgeException handling

Ruby SDKs:
- synor_database - Struct-based types with Faraday HTTP
- synor_hosting - domain, DNS, SSL, analytics
- synor_bridge - lock-mint/burn-unlock with retry logic

Phase 3 complete: Database/Hosting/Bridge now available in all 12 languages.
This commit is contained in:
Gulshan Yadav 2026-01-27 02:23:07 +05:30
parent 14cd439552
commit a874faef13
33 changed files with 8006 additions and 0 deletions

View file

@ -0,0 +1,397 @@
/**
* @file bridge.h
* @brief Synor Bridge SDK for C
*
* Cross-chain asset transfers with lock-mint and burn-unlock patterns.
*/
#ifndef SYNOR_BRIDGE_H
#define SYNOR_BRIDGE_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ==================== Error Types ==================== */
typedef enum {
SYNOR_BRIDGE_OK = 0,
SYNOR_BRIDGE_ERROR_CLIENT_CLOSED,
SYNOR_BRIDGE_ERROR_NETWORK,
SYNOR_BRIDGE_ERROR_HTTP,
SYNOR_BRIDGE_ERROR_ENCODING,
SYNOR_BRIDGE_ERROR_DECODING,
SYNOR_BRIDGE_ERROR_INVALID_RESPONSE,
SYNOR_BRIDGE_ERROR_MEMORY,
SYNOR_BRIDGE_ERROR_TIMEOUT,
SYNOR_BRIDGE_ERROR_CONFIRMATIONS_PENDING,
SYNOR_BRIDGE_ERROR_UNKNOWN
} synor_bridge_error_t;
/* ==================== Configuration ==================== */
typedef struct {
const char* api_key;
const char* endpoint; /* Default: https://bridge.synor.io/v1 */
uint32_t timeout_ms; /* Default: 60000 */
uint32_t retries; /* Default: 3 */
bool debug;
} synor_bridge_config_t;
/* ==================== Enums ==================== */
typedef enum {
SYNOR_CHAIN_SYNOR,
SYNOR_CHAIN_ETHEREUM,
SYNOR_CHAIN_POLYGON,
SYNOR_CHAIN_ARBITRUM,
SYNOR_CHAIN_OPTIMISM,
SYNOR_CHAIN_BSC,
SYNOR_CHAIN_AVALANCHE,
SYNOR_CHAIN_SOLANA,
SYNOR_CHAIN_COSMOS
} synor_chain_id_t;
typedef enum {
SYNOR_ASSET_NATIVE,
SYNOR_ASSET_ERC20,
SYNOR_ASSET_ERC721,
SYNOR_ASSET_ERC1155
} synor_asset_type_t;
typedef enum {
SYNOR_TRANSFER_PENDING,
SYNOR_TRANSFER_LOCKED,
SYNOR_TRANSFER_CONFIRMING,
SYNOR_TRANSFER_MINTING,
SYNOR_TRANSFER_COMPLETED,
SYNOR_TRANSFER_FAILED,
SYNOR_TRANSFER_REFUNDED
} synor_transfer_status_t;
typedef enum {
SYNOR_DIRECTION_LOCK_MINT,
SYNOR_DIRECTION_BURN_UNLOCK
} synor_transfer_direction_t;
/* ==================== Chain Types ==================== */
typedef struct {
char* name;
char* symbol;
int32_t decimals;
} synor_native_currency_t;
typedef struct {
synor_chain_id_t id;
char* name;
int64_t chain_id;
char* rpc_url;
char* explorer_url;
synor_native_currency_t native_currency;
int32_t confirmations;
int32_t estimated_block_time;
bool supported;
} synor_chain_t;
typedef struct {
synor_chain_t* chains;
size_t count;
} synor_chain_list_t;
/* ==================== Asset Types ==================== */
typedef struct {
char* id;
char* symbol;
char* name;
synor_asset_type_t type;
synor_chain_id_t chain;
char* contract_address;
int32_t decimals;
char* logo_url;
bool verified;
} synor_asset_t;
typedef struct {
synor_asset_t* assets;
size_t count;
} synor_asset_list_t;
typedef struct {
synor_asset_t original_asset;
synor_asset_t wrapped_asset;
synor_chain_id_t chain;
char* bridge_contract;
} synor_wrapped_asset_t;
/* ==================== Transfer Types ==================== */
typedef struct {
char* id;
synor_transfer_direction_t direction;
synor_transfer_status_t status;
synor_chain_id_t source_chain;
synor_chain_id_t target_chain;
synor_asset_t asset;
char* amount;
char* sender;
char* recipient;
char* source_tx_hash;
char* target_tx_hash;
char* fee;
synor_asset_t fee_asset;
int64_t created_at;
int64_t updated_at;
int64_t completed_at;
char* error_message;
} synor_transfer_t;
typedef struct {
synor_transfer_t* transfers;
size_t count;
} synor_transfer_list_t;
typedef struct {
synor_transfer_status_t* status; /* NULL for all */
synor_chain_id_t* source_chain; /* NULL for all */
synor_chain_id_t* target_chain; /* NULL for all */
int32_t limit;
int32_t offset;
} synor_transfer_filter_t;
/* ==================== Lock-Mint Types ==================== */
typedef struct {
char* validator;
char* signature;
int64_t timestamp;
} synor_validator_signature_t;
typedef struct {
char* id;
char* tx_hash;
synor_chain_id_t source_chain;
synor_chain_id_t target_chain;
synor_asset_t asset;
char* amount;
char* sender;
char* recipient;
int64_t lock_timestamp;
int32_t confirmations;
int32_t required_confirmations;
} synor_lock_receipt_t;
typedef struct {
synor_lock_receipt_t lock_receipt;
char** merkle_proof;
size_t merkle_proof_count;
char* block_header;
synor_validator_signature_t* signatures;
size_t signature_count;
} synor_lock_proof_t;
typedef struct {
const char* recipient;
int64_t deadline;
double slippage;
} synor_lock_options_t;
typedef struct {
const char* gas_limit;
const char* max_fee_per_gas;
const char* max_priority_fee_per_gas;
} synor_mint_options_t;
/* ==================== Burn-Unlock Types ==================== */
typedef struct {
char* id;
char* tx_hash;
synor_chain_id_t source_chain;
synor_chain_id_t target_chain;
synor_asset_t wrapped_asset;
synor_asset_t original_asset;
char* amount;
char* sender;
char* recipient;
int64_t burn_timestamp;
int32_t confirmations;
int32_t required_confirmations;
} synor_burn_receipt_t;
typedef struct {
synor_burn_receipt_t burn_receipt;
char** merkle_proof;
size_t merkle_proof_count;
char* block_header;
synor_validator_signature_t* signatures;
size_t signature_count;
} synor_burn_proof_t;
typedef struct {
const char* recipient;
int64_t deadline;
} synor_burn_options_t;
typedef struct {
const char* gas_limit;
const char* gas_price;
} synor_unlock_options_t;
/* ==================== Fee Types ==================== */
typedef struct {
char* bridge_fee;
char* gas_fee_source;
char* gas_fee_target;
char* total_fee;
synor_asset_t fee_asset;
int32_t estimated_time;
char* exchange_rate;
} synor_fee_estimate_t;
typedef struct {
synor_asset_t from_asset;
synor_asset_t to_asset;
char* rate;
char* inverse_rate;
int64_t last_updated;
char* source;
} synor_exchange_rate_t;
/* ==================== Transaction Types ==================== */
typedef struct {
char* tx_hash;
synor_chain_id_t chain;
char* from;
char* to;
char* value;
char* data;
char* gas_limit;
char* gas_price;
char* max_fee_per_gas;
char* max_priority_fee_per_gas;
int32_t nonce;
char* signature;
} synor_signed_tx_t;
/* ==================== Client Handle ==================== */
typedef struct synor_bridge synor_bridge_t;
/* ==================== Lifecycle Functions ==================== */
synor_bridge_t* synor_bridge_create(const synor_bridge_config_t* config);
void synor_bridge_destroy(synor_bridge_t* bridge);
bool synor_bridge_is_closed(synor_bridge_t* bridge);
bool synor_bridge_health_check(synor_bridge_t* bridge);
/* ==================== Chain Operations ==================== */
synor_bridge_error_t synor_bridge_get_supported_chains(synor_bridge_t* bridge,
synor_chain_list_t* result);
synor_bridge_error_t synor_bridge_get_chain(synor_bridge_t* bridge,
synor_chain_id_t chain_id, synor_chain_t* result);
bool synor_bridge_is_chain_supported(synor_bridge_t* bridge, synor_chain_id_t chain_id);
void synor_chain_free(synor_chain_t* chain);
void synor_chain_list_free(synor_chain_list_t* list);
/* ==================== Asset Operations ==================== */
synor_bridge_error_t synor_bridge_get_supported_assets(synor_bridge_t* bridge,
synor_chain_id_t chain_id, synor_asset_list_t* result);
synor_bridge_error_t synor_bridge_get_asset(synor_bridge_t* bridge,
const char* asset_id, synor_asset_t* result);
synor_bridge_error_t synor_bridge_get_wrapped_asset(synor_bridge_t* bridge,
const char* original_asset_id, synor_chain_id_t target_chain,
synor_wrapped_asset_t* result);
void synor_asset_free(synor_asset_t* asset);
void synor_asset_list_free(synor_asset_list_t* list);
void synor_wrapped_asset_free(synor_wrapped_asset_t* asset);
/* ==================== Fee Operations ==================== */
synor_bridge_error_t synor_bridge_estimate_fee(synor_bridge_t* bridge,
const char* asset, const char* amount,
synor_chain_id_t source_chain, synor_chain_id_t target_chain,
synor_fee_estimate_t* result);
synor_bridge_error_t synor_bridge_get_exchange_rate(synor_bridge_t* bridge,
const char* from_asset, const char* to_asset, synor_exchange_rate_t* result);
void synor_fee_estimate_free(synor_fee_estimate_t* fee);
void synor_exchange_rate_free(synor_exchange_rate_t* rate);
/* ==================== Lock-Mint Flow ==================== */
synor_bridge_error_t synor_bridge_lock(synor_bridge_t* bridge,
const char* asset, const char* amount, synor_chain_id_t target_chain,
const synor_lock_options_t* options, synor_lock_receipt_t* result);
synor_bridge_error_t synor_bridge_get_lock_proof(synor_bridge_t* bridge,
const char* lock_receipt_id, synor_lock_proof_t* result);
synor_bridge_error_t synor_bridge_wait_for_lock_proof(synor_bridge_t* bridge,
const char* lock_receipt_id, uint32_t poll_interval_ms, uint32_t max_wait_ms,
synor_lock_proof_t* result);
synor_bridge_error_t synor_bridge_mint(synor_bridge_t* bridge,
const synor_lock_proof_t* proof, const char* target_address,
const synor_mint_options_t* options, synor_signed_tx_t* result);
void synor_lock_receipt_free(synor_lock_receipt_t* receipt);
void synor_lock_proof_free(synor_lock_proof_t* proof);
/* ==================== Burn-Unlock Flow ==================== */
synor_bridge_error_t synor_bridge_burn(synor_bridge_t* bridge,
const char* wrapped_asset, const char* amount,
const synor_burn_options_t* options, synor_burn_receipt_t* result);
synor_bridge_error_t synor_bridge_get_burn_proof(synor_bridge_t* bridge,
const char* burn_receipt_id, synor_burn_proof_t* result);
synor_bridge_error_t synor_bridge_wait_for_burn_proof(synor_bridge_t* bridge,
const char* burn_receipt_id, uint32_t poll_interval_ms, uint32_t max_wait_ms,
synor_burn_proof_t* result);
synor_bridge_error_t synor_bridge_unlock(synor_bridge_t* bridge,
const synor_burn_proof_t* proof, const synor_unlock_options_t* options,
synor_signed_tx_t* result);
void synor_burn_receipt_free(synor_burn_receipt_t* receipt);
void synor_burn_proof_free(synor_burn_proof_t* proof);
/* ==================== Transfer Operations ==================== */
synor_bridge_error_t synor_bridge_get_transfer(synor_bridge_t* bridge,
const char* transfer_id, synor_transfer_t* result);
synor_bridge_error_t synor_bridge_get_transfer_status(synor_bridge_t* bridge,
const char* transfer_id, synor_transfer_status_t* status);
synor_bridge_error_t synor_bridge_list_transfers(synor_bridge_t* bridge,
const synor_transfer_filter_t* filter, synor_transfer_list_t* result);
synor_bridge_error_t synor_bridge_wait_for_transfer(synor_bridge_t* bridge,
const char* transfer_id, uint32_t poll_interval_ms, uint32_t max_wait_ms,
synor_transfer_t* result);
void synor_transfer_free(synor_transfer_t* transfer);
void synor_transfer_list_free(synor_transfer_list_t* list);
void synor_signed_tx_free(synor_signed_tx_t* tx);
/* ==================== Convenience Functions ==================== */
synor_bridge_error_t synor_bridge_bridge_to(synor_bridge_t* bridge,
const char* asset, const char* amount, synor_chain_id_t target_chain,
const char* target_address, const synor_lock_options_t* lock_options,
const synor_mint_options_t* mint_options, synor_transfer_t* result);
synor_bridge_error_t synor_bridge_bridge_back(synor_bridge_t* bridge,
const char* wrapped_asset, const char* amount,
const synor_burn_options_t* burn_options,
const synor_unlock_options_t* unlock_options, synor_transfer_t* result);
#ifdef __cplusplus
}
#endif
#endif /* SYNOR_BRIDGE_H */

View file

@ -0,0 +1,279 @@
/**
* @file database.h
* @brief Synor Database SDK for C
*
* Multi-model database with Key-Value, Document, Vector, and Time Series stores.
*/
#ifndef SYNOR_DATABASE_H
#define SYNOR_DATABASE_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ==================== Error Types ==================== */
typedef enum {
SYNOR_DB_OK = 0,
SYNOR_DB_ERROR_CLIENT_CLOSED,
SYNOR_DB_ERROR_NETWORK,
SYNOR_DB_ERROR_HTTP,
SYNOR_DB_ERROR_ENCODING,
SYNOR_DB_ERROR_DECODING,
SYNOR_DB_ERROR_INVALID_RESPONSE,
SYNOR_DB_ERROR_MEMORY,
SYNOR_DB_ERROR_UNKNOWN
} synor_db_error_t;
/* ==================== Configuration ==================== */
typedef struct {
const char* api_key;
const char* endpoint; /* Default: https://db.synor.io/v1 */
uint32_t timeout_ms; /* Default: 60000 */
uint32_t retries; /* Default: 3 */
bool debug;
} synor_db_config_t;
/* ==================== Key-Value Types ==================== */
typedef struct {
char* key;
char* value; /* JSON string */
int32_t ttl;
int64_t created_at;
int64_t updated_at;
} synor_kv_entry_t;
typedef struct {
synor_kv_entry_t* items;
size_t count;
} synor_kv_list_t;
/* ==================== Document Types ==================== */
typedef struct {
char* id;
char* collection;
char* data; /* JSON string */
int64_t created_at;
int64_t updated_at;
} synor_document_t;
typedef struct {
synor_document_t* documents;
size_t count;
} synor_document_list_t;
typedef struct {
const char* filter; /* JSON string */
const char* sort; /* JSON string */
int32_t limit;
int32_t offset;
} synor_document_query_t;
/* ==================== Vector Types ==================== */
typedef struct {
const char* id;
const double* vector;
size_t vector_size;
const char* metadata; /* JSON string, optional */
} synor_vector_entry_t;
typedef struct {
char* id;
double score;
double* vector;
size_t vector_size;
char* metadata;
} synor_search_result_t;
typedef struct {
synor_search_result_t* results;
size_t count;
} synor_search_result_list_t;
/* ==================== Time Series Types ==================== */
typedef struct {
int64_t timestamp;
double value;
const char* tags; /* JSON string, optional */
} synor_data_point_t;
typedef struct {
int64_t start;
int64_t end;
} synor_time_range_t;
typedef struct {
const char* function; /* avg, sum, min, max, count */
const char* interval; /* 1m, 5m, 1h, 1d */
} synor_aggregation_t;
typedef struct {
synor_data_point_t* points;
size_t count;
} synor_data_point_list_t;
/* ==================== Client Handle ==================== */
typedef struct synor_database synor_database_t;
/* ==================== Lifecycle Functions ==================== */
/**
* Create a new database client.
*/
synor_database_t* synor_database_create(const synor_db_config_t* config);
/**
* Destroy the database client and free resources.
*/
void synor_database_destroy(synor_database_t* db);
/**
* Check if the client is closed.
*/
bool synor_database_is_closed(synor_database_t* db);
/**
* Perform a health check.
*/
bool synor_database_health_check(synor_database_t* db);
/* ==================== Key-Value Operations ==================== */
/**
* Get a value by key.
* Caller must free result->value.
*/
synor_db_error_t synor_kv_get(synor_database_t* db, const char* key, char** value);
/**
* Set a value for a key.
*/
synor_db_error_t synor_kv_set(synor_database_t* db, const char* key,
const char* value, int32_t ttl);
/**
* Delete a key.
*/
synor_db_error_t synor_kv_delete(synor_database_t* db, const char* key);
/**
* List keys by prefix.
* Caller must free result with synor_kv_list_free.
*/
synor_db_error_t synor_kv_list(synor_database_t* db, const char* prefix,
synor_kv_list_t* result);
/**
* Free a key-value list.
*/
void synor_kv_list_free(synor_kv_list_t* list);
/* ==================== Document Operations ==================== */
/**
* Create a document.
* Caller must free returned id.
*/
synor_db_error_t synor_doc_create(synor_database_t* db, const char* collection,
const char* document, char** id);
/**
* Get a document by ID.
* Caller must free result with synor_document_free.
*/
synor_db_error_t synor_doc_get(synor_database_t* db, const char* collection,
const char* id, synor_document_t* result);
/**
* Update a document.
*/
synor_db_error_t synor_doc_update(synor_database_t* db, const char* collection,
const char* id, const char* update);
/**
* Delete a document.
*/
synor_db_error_t synor_doc_delete(synor_database_t* db, const char* collection,
const char* id);
/**
* Query documents.
* Caller must free result with synor_document_list_free.
*/
synor_db_error_t synor_doc_query(synor_database_t* db, const char* collection,
const synor_document_query_t* query, synor_document_list_t* result);
/**
* Free a document.
*/
void synor_document_free(synor_document_t* doc);
/**
* Free a document list.
*/
void synor_document_list_free(synor_document_list_t* list);
/* ==================== Vector Operations ==================== */
/**
* Upsert vectors.
*/
synor_db_error_t synor_vector_upsert(synor_database_t* db, const char* collection,
const synor_vector_entry_t* vectors, size_t count);
/**
* Search for similar vectors.
* Caller must free result with synor_search_result_list_free.
*/
synor_db_error_t synor_vector_search(synor_database_t* db, const char* collection,
const double* vector, size_t vector_size, int32_t k,
synor_search_result_list_t* result);
/**
* Delete vectors by IDs.
*/
synor_db_error_t synor_vector_delete(synor_database_t* db, const char* collection,
const char** ids, size_t count);
/**
* Free search results.
*/
void synor_search_result_list_free(synor_search_result_list_t* list);
/* ==================== Time Series Operations ==================== */
/**
* Write data points.
*/
synor_db_error_t synor_ts_write(synor_database_t* db, const char* series,
const synor_data_point_t* points, size_t count);
/**
* Query data points.
* Caller must free result with synor_data_point_list_free.
*/
synor_db_error_t synor_ts_query(synor_database_t* db, const char* series,
const synor_time_range_t* range, const synor_aggregation_t* aggregation,
synor_data_point_list_t* result);
/**
* Free data point list.
*/
void synor_data_point_list_free(synor_data_point_list_t* list);
#ifdef __cplusplus
}
#endif
#endif /* SYNOR_DATABASE_H */

View file

@ -0,0 +1,295 @@
/**
* @file hosting.h
* @brief Synor Hosting SDK for C
*
* Decentralized web hosting with domain management, DNS, deployments, and SSL.
*/
#ifndef SYNOR_HOSTING_H
#define SYNOR_HOSTING_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ==================== Error Types ==================== */
typedef enum {
SYNOR_HOSTING_OK = 0,
SYNOR_HOSTING_ERROR_CLIENT_CLOSED,
SYNOR_HOSTING_ERROR_NETWORK,
SYNOR_HOSTING_ERROR_HTTP,
SYNOR_HOSTING_ERROR_ENCODING,
SYNOR_HOSTING_ERROR_DECODING,
SYNOR_HOSTING_ERROR_INVALID_RESPONSE,
SYNOR_HOSTING_ERROR_MEMORY,
SYNOR_HOSTING_ERROR_UNKNOWN
} synor_hosting_error_t;
/* ==================== Configuration ==================== */
typedef struct {
const char* api_key;
const char* endpoint; /* Default: https://hosting.synor.io/v1 */
uint32_t timeout_ms; /* Default: 60000 */
uint32_t retries; /* Default: 3 */
bool debug;
} synor_hosting_config_t;
/* ==================== Enums ==================== */
typedef enum {
SYNOR_DOMAIN_PENDING,
SYNOR_DOMAIN_ACTIVE,
SYNOR_DOMAIN_EXPIRED,
SYNOR_DOMAIN_SUSPENDED
} synor_domain_status_t;
typedef enum {
SYNOR_DEPLOYMENT_PENDING,
SYNOR_DEPLOYMENT_BUILDING,
SYNOR_DEPLOYMENT_DEPLOYING,
SYNOR_DEPLOYMENT_ACTIVE,
SYNOR_DEPLOYMENT_FAILED,
SYNOR_DEPLOYMENT_INACTIVE
} synor_deployment_status_t;
typedef enum {
SYNOR_CERT_PENDING,
SYNOR_CERT_ISSUED,
SYNOR_CERT_EXPIRED,
SYNOR_CERT_REVOKED
} synor_cert_status_t;
typedef enum {
SYNOR_DNS_A,
SYNOR_DNS_AAAA,
SYNOR_DNS_CNAME,
SYNOR_DNS_TXT,
SYNOR_DNS_MX,
SYNOR_DNS_NS,
SYNOR_DNS_SRV,
SYNOR_DNS_CAA
} synor_dns_record_type_t;
/* ==================== Domain Types ==================== */
typedef struct {
char* cid;
char** ipv4;
size_t ipv4_count;
char** ipv6;
size_t ipv6_count;
char* cname;
char** txt;
size_t txt_count;
} synor_domain_record_t;
typedef struct {
char* name;
synor_domain_status_t status;
char* owner;
int64_t registered_at;
int64_t expires_at;
bool auto_renew;
synor_domain_record_t* records;
} synor_domain_t;
typedef struct {
synor_domain_t* domains;
size_t count;
} synor_domain_list_t;
typedef struct {
char* name;
bool available;
double price;
bool premium;
} synor_domain_availability_t;
typedef struct {
int32_t years;
bool auto_renew;
} synor_register_domain_options_t;
/* ==================== DNS Types ==================== */
typedef struct {
synor_dns_record_type_t type;
char* name;
char* value;
int32_t ttl;
int32_t priority;
} synor_dns_record_t;
typedef struct {
char* domain;
synor_dns_record_t* records;
size_t record_count;
int64_t updated_at;
} synor_dns_zone_t;
/* ==================== Deployment Types ==================== */
typedef struct {
char* id;
char* domain;
char* cid;
synor_deployment_status_t status;
char* url;
int64_t created_at;
int64_t updated_at;
char* build_logs;
char* error_message;
} synor_deployment_t;
typedef struct {
synor_deployment_t* deployments;
size_t count;
} synor_deployment_list_t;
typedef struct {
const char* domain;
const char* subdomain;
bool spa;
bool clean_urls;
bool trailing_slash;
} synor_deploy_options_t;
/* ==================== SSL Types ==================== */
typedef struct {
char* domain;
synor_cert_status_t status;
bool auto_renew;
char* issuer;
int64_t issued_at;
int64_t expires_at;
char* fingerprint;
} synor_certificate_t;
typedef struct {
bool include_www;
bool auto_renew;
} synor_provision_ssl_options_t;
/* ==================== Analytics Types ==================== */
typedef struct {
char* path;
int64_t views;
} synor_page_view_t;
typedef struct {
char* domain;
char* period;
int64_t page_views;
int64_t unique_visitors;
int64_t bandwidth;
synor_page_view_t* top_pages;
size_t top_pages_count;
} synor_analytics_data_t;
typedef struct {
const char* period;
const char* start;
const char* end;
} synor_analytics_options_t;
/* ==================== Client Handle ==================== */
typedef struct synor_hosting synor_hosting_t;
/* ==================== Lifecycle Functions ==================== */
synor_hosting_t* synor_hosting_create(const synor_hosting_config_t* config);
void synor_hosting_destroy(synor_hosting_t* hosting);
bool synor_hosting_is_closed(synor_hosting_t* hosting);
bool synor_hosting_health_check(synor_hosting_t* hosting);
/* ==================== Domain Operations ==================== */
synor_hosting_error_t synor_hosting_check_availability(synor_hosting_t* hosting,
const char* name, synor_domain_availability_t* result);
synor_hosting_error_t synor_hosting_register_domain(synor_hosting_t* hosting,
const char* name, const synor_register_domain_options_t* options,
synor_domain_t* result);
synor_hosting_error_t synor_hosting_get_domain(synor_hosting_t* hosting,
const char* name, synor_domain_t* result);
synor_hosting_error_t synor_hosting_list_domains(synor_hosting_t* hosting,
synor_domain_list_t* result);
synor_hosting_error_t synor_hosting_resolve_domain(synor_hosting_t* hosting,
const char* name, synor_domain_record_t* result);
synor_hosting_error_t synor_hosting_renew_domain(synor_hosting_t* hosting,
const char* name, int32_t years, synor_domain_t* result);
void synor_domain_free(synor_domain_t* domain);
void synor_domain_list_free(synor_domain_list_t* list);
void synor_domain_record_free(synor_domain_record_t* record);
void synor_domain_availability_free(synor_domain_availability_t* avail);
/* ==================== DNS Operations ==================== */
synor_hosting_error_t synor_hosting_get_dns_zone(synor_hosting_t* hosting,
const char* domain, synor_dns_zone_t* result);
synor_hosting_error_t synor_hosting_set_dns_records(synor_hosting_t* hosting,
const char* domain, const synor_dns_record_t* records, size_t count,
synor_dns_zone_t* result);
synor_hosting_error_t synor_hosting_add_dns_record(synor_hosting_t* hosting,
const char* domain, const synor_dns_record_t* record, synor_dns_zone_t* result);
synor_hosting_error_t synor_hosting_delete_dns_record(synor_hosting_t* hosting,
const char* domain, const char* record_type, const char* name,
synor_dns_zone_t* result);
void synor_dns_zone_free(synor_dns_zone_t* zone);
/* ==================== Deployment Operations ==================== */
synor_hosting_error_t synor_hosting_deploy(synor_hosting_t* hosting,
const char* cid, const synor_deploy_options_t* options,
synor_deployment_t* result);
synor_hosting_error_t synor_hosting_get_deployment(synor_hosting_t* hosting,
const char* id, synor_deployment_t* result);
synor_hosting_error_t synor_hosting_list_deployments(synor_hosting_t* hosting,
const char* domain, synor_deployment_list_t* result);
synor_hosting_error_t synor_hosting_rollback(synor_hosting_t* hosting,
const char* domain, const char* deployment_id, synor_deployment_t* result);
synor_hosting_error_t synor_hosting_delete_deployment(synor_hosting_t* hosting,
const char* id);
void synor_deployment_free(synor_deployment_t* deployment);
void synor_deployment_list_free(synor_deployment_list_t* list);
/* ==================== SSL Operations ==================== */
synor_hosting_error_t synor_hosting_provision_ssl(synor_hosting_t* hosting,
const char* domain, const synor_provision_ssl_options_t* options,
synor_certificate_t* result);
synor_hosting_error_t synor_hosting_get_certificate(synor_hosting_t* hosting,
const char* domain, synor_certificate_t* result);
synor_hosting_error_t synor_hosting_renew_certificate(synor_hosting_t* hosting,
const char* domain, synor_certificate_t* result);
synor_hosting_error_t synor_hosting_delete_certificate(synor_hosting_t* hosting,
const char* domain);
void synor_certificate_free(synor_certificate_t* cert);
/* ==================== Analytics Operations ==================== */
synor_hosting_error_t synor_hosting_get_analytics(synor_hosting_t* hosting,
const char* domain, const synor_analytics_options_t* options,
synor_analytics_data_t* result);
synor_hosting_error_t synor_hosting_purge_cache(synor_hosting_t* hosting,
const char* domain, const char** paths, size_t path_count, int64_t* purged);
void synor_analytics_data_free(synor_analytics_data_t* data);
#ifdef __cplusplus
}
#endif
#endif /* SYNOR_HOSTING_H */

441
sdk/c/src/bridge/bridge.c Normal file
View file

@ -0,0 +1,441 @@
/**
* @file bridge.c
* @brief Synor Bridge SDK implementation
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "synor/bridge.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);
synor_error_t synor_http_get(synor_http_client_t* client, const char* path,
char** response, size_t* response_size);
synor_error_t 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 bridge structure */
struct synor_bridge {
synor_http_client_t* http;
bool closed;
bool debug;
};
static const char* chain_id_to_string(synor_chain_id_t id) {
switch (id) {
case SYNOR_CHAIN_SYNOR: return "synor";
case SYNOR_CHAIN_ETHEREUM: return "ethereum";
case SYNOR_CHAIN_POLYGON: return "polygon";
case SYNOR_CHAIN_ARBITRUM: return "arbitrum";
case SYNOR_CHAIN_OPTIMISM: return "optimism";
case SYNOR_CHAIN_BSC: return "bsc";
case SYNOR_CHAIN_AVALANCHE: return "avalanche";
case SYNOR_CHAIN_SOLANA: return "solana";
case SYNOR_CHAIN_COSMOS: return "cosmos";
default: return "unknown";
}
}
synor_bridge_t* synor_bridge_create(const synor_bridge_config_t* config) {
if (!config || !config->api_key) {
return NULL;
}
synor_bridge_t* bridge = calloc(1, sizeof(synor_bridge_t));
if (!bridge) return NULL;
const char* endpoint = config->endpoint ?
config->endpoint : "https://bridge.synor.io/v1";
uint32_t timeout = config->timeout_ms > 0 ? config->timeout_ms : 60000;
uint32_t retries = config->retries > 0 ? config->retries : 3;
bridge->http = synor_http_client_create(
config->api_key, endpoint, timeout, retries, config->debug);
if (!bridge->http) {
free(bridge);
return NULL;
}
bridge->closed = false;
bridge->debug = config->debug;
return bridge;
}
void synor_bridge_destroy(synor_bridge_t* bridge) {
if (!bridge) return;
bridge->closed = true;
synor_http_client_destroy(bridge->http);
free(bridge);
}
bool synor_bridge_is_closed(synor_bridge_t* bridge) {
return bridge ? bridge->closed : true;
}
bool synor_bridge_health_check(synor_bridge_t* bridge) {
if (!bridge || bridge->closed) return false;
char* response = NULL;
size_t response_size = 0;
if (synor_http_get(bridge->http, "/health", &response, &response_size) != 0) {
return false;
}
bool healthy = response && strstr(response, "\"healthy\"") != NULL;
free(response);
return healthy;
}
/* ==================== Chain Operations ==================== */
synor_bridge_error_t synor_bridge_get_supported_chains(synor_bridge_t* bridge,
synor_chain_list_t* result) {
(void)bridge; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_get_chain(synor_bridge_t* bridge,
synor_chain_id_t chain_id, synor_chain_t* result) {
(void)bridge; (void)chain_id; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
bool synor_bridge_is_chain_supported(synor_bridge_t* bridge, synor_chain_id_t chain_id) {
synor_chain_t chain;
if (synor_bridge_get_chain(bridge, chain_id, &chain) != SYNOR_BRIDGE_OK) {
return false;
}
bool supported = chain.supported;
synor_chain_free(&chain);
return supported;
}
void synor_chain_free(synor_chain_t* chain) {
if (!chain) return;
free(chain->name);
free(chain->rpc_url);
free(chain->explorer_url);
free(chain->native_currency.name);
free(chain->native_currency.symbol);
}
void synor_chain_list_free(synor_chain_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_chain_free(&list->chains[i]);
}
free(list->chains);
list->chains = NULL;
list->count = 0;
}
/* ==================== Asset Operations ==================== */
synor_bridge_error_t synor_bridge_get_supported_assets(synor_bridge_t* bridge,
synor_chain_id_t chain_id, synor_asset_list_t* result) {
(void)bridge; (void)chain_id; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_get_asset(synor_bridge_t* bridge,
const char* asset_id, synor_asset_t* result) {
(void)bridge; (void)asset_id; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_get_wrapped_asset(synor_bridge_t* bridge,
const char* original_asset_id, synor_chain_id_t target_chain,
synor_wrapped_asset_t* result) {
(void)bridge; (void)original_asset_id; (void)target_chain; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
void synor_asset_free(synor_asset_t* asset) {
if (!asset) return;
free(asset->id);
free(asset->symbol);
free(asset->name);
free(asset->contract_address);
free(asset->logo_url);
}
void synor_asset_list_free(synor_asset_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_asset_free(&list->assets[i]);
}
free(list->assets);
list->assets = NULL;
list->count = 0;
}
void synor_wrapped_asset_free(synor_wrapped_asset_t* asset) {
if (!asset) return;
synor_asset_free(&asset->original_asset);
synor_asset_free(&asset->wrapped_asset);
free(asset->bridge_contract);
}
/* ==================== Fee Operations ==================== */
synor_bridge_error_t synor_bridge_estimate_fee(synor_bridge_t* bridge,
const char* asset, const char* amount,
synor_chain_id_t source_chain, synor_chain_id_t target_chain,
synor_fee_estimate_t* result) {
if (!bridge || bridge->closed) return SYNOR_BRIDGE_ERROR_CLIENT_CLOSED;
if (!asset || !amount || !result) return SYNOR_BRIDGE_ERROR_UNKNOWN;
char body[1024];
snprintf(body, sizeof(body),
"{\"asset\":\"%s\",\"amount\":\"%s\",\"sourceChain\":\"%s\",\"targetChain\":\"%s\"}",
asset, amount, chain_id_to_string(source_chain), chain_id_to_string(target_chain));
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(bridge->http, "/fees/estimate", body, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_BRIDGE_ERROR_HTTP;
}
/* TODO: Parse JSON response */
free(response);
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_get_exchange_rate(synor_bridge_t* bridge,
const char* from_asset, const char* to_asset, synor_exchange_rate_t* result) {
(void)bridge; (void)from_asset; (void)to_asset; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
void synor_fee_estimate_free(synor_fee_estimate_t* fee) {
if (!fee) return;
free(fee->bridge_fee);
free(fee->gas_fee_source);
free(fee->gas_fee_target);
free(fee->total_fee);
synor_asset_free(&fee->fee_asset);
free(fee->exchange_rate);
}
void synor_exchange_rate_free(synor_exchange_rate_t* rate) {
if (!rate) return;
synor_asset_free(&rate->from_asset);
synor_asset_free(&rate->to_asset);
free(rate->rate);
free(rate->inverse_rate);
free(rate->source);
}
/* ==================== Lock-Mint Flow ==================== */
synor_bridge_error_t synor_bridge_lock(synor_bridge_t* bridge,
const char* asset, const char* amount, synor_chain_id_t target_chain,
const synor_lock_options_t* options, synor_lock_receipt_t* result) {
(void)bridge; (void)asset; (void)amount; (void)target_chain;
(void)options; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_get_lock_proof(synor_bridge_t* bridge,
const char* lock_receipt_id, synor_lock_proof_t* result) {
(void)bridge; (void)lock_receipt_id; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_wait_for_lock_proof(synor_bridge_t* bridge,
const char* lock_receipt_id, uint32_t poll_interval_ms, uint32_t max_wait_ms,
synor_lock_proof_t* result) {
(void)bridge; (void)lock_receipt_id; (void)poll_interval_ms;
(void)max_wait_ms; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_mint(synor_bridge_t* bridge,
const synor_lock_proof_t* proof, const char* target_address,
const synor_mint_options_t* options, synor_signed_tx_t* result) {
(void)bridge; (void)proof; (void)target_address; (void)options; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
void synor_lock_receipt_free(synor_lock_receipt_t* receipt) {
if (!receipt) return;
free(receipt->id);
free(receipt->tx_hash);
synor_asset_free(&receipt->asset);
free(receipt->amount);
free(receipt->sender);
free(receipt->recipient);
}
void synor_lock_proof_free(synor_lock_proof_t* proof) {
if (!proof) return;
synor_lock_receipt_free(&proof->lock_receipt);
for (size_t i = 0; i < proof->merkle_proof_count; i++) {
free(proof->merkle_proof[i]);
}
free(proof->merkle_proof);
free(proof->block_header);
for (size_t i = 0; i < proof->signature_count; i++) {
free(proof->signatures[i].validator);
free(proof->signatures[i].signature);
}
free(proof->signatures);
}
/* ==================== Burn-Unlock Flow ==================== */
synor_bridge_error_t synor_bridge_burn(synor_bridge_t* bridge,
const char* wrapped_asset, const char* amount,
const synor_burn_options_t* options, synor_burn_receipt_t* result) {
(void)bridge; (void)wrapped_asset; (void)amount; (void)options; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_get_burn_proof(synor_bridge_t* bridge,
const char* burn_receipt_id, synor_burn_proof_t* result) {
(void)bridge; (void)burn_receipt_id; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_wait_for_burn_proof(synor_bridge_t* bridge,
const char* burn_receipt_id, uint32_t poll_interval_ms, uint32_t max_wait_ms,
synor_burn_proof_t* result) {
(void)bridge; (void)burn_receipt_id; (void)poll_interval_ms;
(void)max_wait_ms; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_unlock(synor_bridge_t* bridge,
const synor_burn_proof_t* proof, const synor_unlock_options_t* options,
synor_signed_tx_t* result) {
(void)bridge; (void)proof; (void)options; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
void synor_burn_receipt_free(synor_burn_receipt_t* receipt) {
if (!receipt) return;
free(receipt->id);
free(receipt->tx_hash);
synor_asset_free(&receipt->wrapped_asset);
synor_asset_free(&receipt->original_asset);
free(receipt->amount);
free(receipt->sender);
free(receipt->recipient);
}
void synor_burn_proof_free(synor_burn_proof_t* proof) {
if (!proof) return;
synor_burn_receipt_free(&proof->burn_receipt);
for (size_t i = 0; i < proof->merkle_proof_count; i++) {
free(proof->merkle_proof[i]);
}
free(proof->merkle_proof);
free(proof->block_header);
for (size_t i = 0; i < proof->signature_count; i++) {
free(proof->signatures[i].validator);
free(proof->signatures[i].signature);
}
free(proof->signatures);
}
/* ==================== Transfer Operations ==================== */
synor_bridge_error_t synor_bridge_get_transfer(synor_bridge_t* bridge,
const char* transfer_id, synor_transfer_t* result) {
(void)bridge; (void)transfer_id; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_get_transfer_status(synor_bridge_t* bridge,
const char* transfer_id, synor_transfer_status_t* status) {
synor_transfer_t transfer;
synor_bridge_error_t err = synor_bridge_get_transfer(bridge, transfer_id, &transfer);
if (err != SYNOR_BRIDGE_OK) return err;
*status = transfer.status;
synor_transfer_free(&transfer);
return SYNOR_BRIDGE_OK;
}
synor_bridge_error_t synor_bridge_list_transfers(synor_bridge_t* bridge,
const synor_transfer_filter_t* filter, synor_transfer_list_t* result) {
(void)bridge; (void)filter; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_wait_for_transfer(synor_bridge_t* bridge,
const char* transfer_id, uint32_t poll_interval_ms, uint32_t max_wait_ms,
synor_transfer_t* result) {
(void)bridge; (void)transfer_id; (void)poll_interval_ms;
(void)max_wait_ms; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
void synor_transfer_free(synor_transfer_t* transfer) {
if (!transfer) return;
free(transfer->id);
synor_asset_free(&transfer->asset);
free(transfer->amount);
free(transfer->sender);
free(transfer->recipient);
free(transfer->source_tx_hash);
free(transfer->target_tx_hash);
free(transfer->fee);
synor_asset_free(&transfer->fee_asset);
free(transfer->error_message);
}
void synor_transfer_list_free(synor_transfer_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_transfer_free(&list->transfers[i]);
}
free(list->transfers);
list->transfers = NULL;
list->count = 0;
}
void synor_signed_tx_free(synor_signed_tx_t* tx) {
if (!tx) return;
free(tx->tx_hash);
free(tx->from);
free(tx->to);
free(tx->value);
free(tx->data);
free(tx->gas_limit);
free(tx->gas_price);
free(tx->max_fee_per_gas);
free(tx->max_priority_fee_per_gas);
free(tx->signature);
}
/* ==================== Convenience Functions ==================== */
synor_bridge_error_t synor_bridge_bridge_to(synor_bridge_t* bridge,
const char* asset, const char* amount, synor_chain_id_t target_chain,
const char* target_address, const synor_lock_options_t* lock_options,
const synor_mint_options_t* mint_options, synor_transfer_t* result) {
(void)bridge; (void)asset; (void)amount; (void)target_chain;
(void)target_address; (void)lock_options; (void)mint_options; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}
synor_bridge_error_t synor_bridge_bridge_back(synor_bridge_t* bridge,
const char* wrapped_asset, const char* amount,
const synor_burn_options_t* burn_options,
const synor_unlock_options_t* unlock_options, synor_transfer_t* result) {
(void)bridge; (void)wrapped_asset; (void)amount;
(void)burn_options; (void)unlock_options; (void)result;
return SYNOR_BRIDGE_ERROR_UNKNOWN;
}

View file

@ -0,0 +1,303 @@
/**
* @file database.c
* @brief Synor Database SDK implementation
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "synor/database.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);
synor_error_t synor_http_get(synor_http_client_t* client, const char* path,
char** response, size_t* response_size);
synor_error_t synor_http_post(synor_http_client_t* client, const char* path,
const char* body, char** response, size_t* response_size);
synor_error_t synor_http_put(synor_http_client_t* client, const char* path,
const char* body, char** response, size_t* response_size);
synor_error_t 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 database structure */
struct synor_database {
synor_http_client_t* http;
bool closed;
};
synor_database_t* synor_database_create(const synor_db_config_t* config) {
if (!config || !config->api_key) {
return NULL;
}
synor_database_t* db = calloc(1, sizeof(synor_database_t));
if (!db) return NULL;
const char* endpoint = config->endpoint ?
config->endpoint : "https://db.synor.io/v1";
uint32_t timeout = config->timeout_ms > 0 ? config->timeout_ms : 60000;
uint32_t retries = config->retries > 0 ? config->retries : 3;
db->http = synor_http_client_create(
config->api_key, endpoint, timeout, retries, config->debug);
if (!db->http) {
free(db);
return NULL;
}
db->closed = false;
return db;
}
void synor_database_destroy(synor_database_t* db) {
if (!db) return;
db->closed = true;
synor_http_client_destroy(db->http);
free(db);
}
bool synor_database_is_closed(synor_database_t* db) {
return db ? db->closed : true;
}
bool synor_database_health_check(synor_database_t* db) {
if (!db || db->closed) return false;
char* response = NULL;
size_t response_size = 0;
if (synor_http_get(db->http, "/health", &response, &response_size) != 0) {
return false;
}
bool healthy = response && strstr(response, "\"healthy\"") != NULL;
free(response);
return healthy;
}
/* ==================== Key-Value Operations ==================== */
synor_db_error_t synor_kv_get(synor_database_t* db, const char* key, char** value) {
if (!db || db->closed) return SYNOR_DB_ERROR_CLIENT_CLOSED;
if (!key || !value) return SYNOR_DB_ERROR_UNKNOWN;
char* encoded_key = synor_url_encode(key);
if (!encoded_key) return SYNOR_DB_ERROR_MEMORY;
char path[512];
snprintf(path, sizeof(path), "/kv/%s", encoded_key);
free(encoded_key);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_get(db->http, path, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_DB_ERROR_HTTP;
}
/* Parse JSON response to extract value - simplified */
*value = response;
return SYNOR_DB_OK;
}
synor_db_error_t synor_kv_set(synor_database_t* db, const char* key,
const char* value, int32_t ttl) {
if (!db || db->closed) return SYNOR_DB_ERROR_CLIENT_CLOSED;
if (!key || !value) return SYNOR_DB_ERROR_UNKNOWN;
char* encoded_key = synor_url_encode(key);
if (!encoded_key) return SYNOR_DB_ERROR_MEMORY;
char path[512];
snprintf(path, sizeof(path), "/kv/%s", encoded_key);
free(encoded_key);
char body[4096];
if (ttl > 0) {
snprintf(body, sizeof(body), "{\"key\":\"%s\",\"value\":%s,\"ttl\":%d}",
key, value, ttl);
} else {
snprintf(body, sizeof(body), "{\"key\":\"%s\",\"value\":%s}", key, value);
}
char* response = NULL;
size_t response_size = 0;
int err = synor_http_put(db->http, path, body, &response, &response_size);
free(response);
return err == 0 ? SYNOR_DB_OK : SYNOR_DB_ERROR_HTTP;
}
synor_db_error_t synor_kv_delete(synor_database_t* db, const char* key) {
if (!db || db->closed) return SYNOR_DB_ERROR_CLIENT_CLOSED;
if (!key) return SYNOR_DB_ERROR_UNKNOWN;
char* encoded_key = synor_url_encode(key);
if (!encoded_key) return SYNOR_DB_ERROR_MEMORY;
char path[512];
snprintf(path, sizeof(path), "/kv/%s", encoded_key);
free(encoded_key);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_delete(db->http, path, &response, &response_size);
free(response);
return err == 0 ? SYNOR_DB_OK : SYNOR_DB_ERROR_HTTP;
}
synor_db_error_t synor_kv_list(synor_database_t* db, const char* prefix,
synor_kv_list_t* result) {
(void)db; (void)prefix; (void)result;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
void synor_kv_list_free(synor_kv_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
free(list->items[i].key);
free(list->items[i].value);
}
free(list->items);
list->items = NULL;
list->count = 0;
}
/* ==================== Document Operations ==================== */
synor_db_error_t synor_doc_create(synor_database_t* db, const char* collection,
const char* document, char** id) {
if (!db || db->closed) return SYNOR_DB_ERROR_CLIENT_CLOSED;
if (!collection || !document || !id) return SYNOR_DB_ERROR_UNKNOWN;
char* encoded = synor_url_encode(collection);
if (!encoded) return SYNOR_DB_ERROR_MEMORY;
char path[512];
snprintf(path, sizeof(path), "/collections/%s/documents", encoded);
free(encoded);
char* response = NULL;
size_t response_size = 0;
int err = synor_http_post(db->http, path, document, &response, &response_size);
if (err != 0) {
free(response);
return SYNOR_DB_ERROR_HTTP;
}
/* Parse response to extract id - simplified */
*id = response;
return SYNOR_DB_OK;
}
synor_db_error_t synor_doc_get(synor_database_t* db, const char* collection,
const char* id, synor_document_t* result) {
(void)db; (void)collection; (void)id; (void)result;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_db_error_t synor_doc_update(synor_database_t* db, const char* collection,
const char* id, const char* update) {
(void)db; (void)collection; (void)id; (void)update;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_db_error_t synor_doc_delete(synor_database_t* db, const char* collection,
const char* id) {
(void)db; (void)collection; (void)id;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_db_error_t synor_doc_query(synor_database_t* db, const char* collection,
const synor_document_query_t* query, synor_document_list_t* result) {
(void)db; (void)collection; (void)query; (void)result;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
void synor_document_free(synor_document_t* doc) {
if (!doc) return;
free(doc->id);
free(doc->collection);
free(doc->data);
doc->id = NULL;
doc->collection = NULL;
doc->data = NULL;
}
void synor_document_list_free(synor_document_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_document_free(&list->documents[i]);
}
free(list->documents);
list->documents = NULL;
list->count = 0;
}
/* ==================== Vector Operations ==================== */
synor_db_error_t synor_vector_upsert(synor_database_t* db, const char* collection,
const synor_vector_entry_t* vectors, size_t count) {
(void)db; (void)collection; (void)vectors; (void)count;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_db_error_t synor_vector_search(synor_database_t* db, const char* collection,
const double* vector, size_t vector_size, int32_t k,
synor_search_result_list_t* result) {
(void)db; (void)collection; (void)vector; (void)vector_size; (void)k; (void)result;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_db_error_t synor_vector_delete(synor_database_t* db, const char* collection,
const char** ids, size_t count) {
(void)db; (void)collection; (void)ids; (void)count;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
void synor_search_result_list_free(synor_search_result_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
free(list->results[i].id);
free(list->results[i].vector);
free(list->results[i].metadata);
}
free(list->results);
list->results = NULL;
list->count = 0;
}
/* ==================== Time Series Operations ==================== */
synor_db_error_t synor_ts_write(synor_database_t* db, const char* series,
const synor_data_point_t* points, size_t count) {
(void)db; (void)series; (void)points; (void)count;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_db_error_t synor_ts_query(synor_database_t* db, const char* series,
const synor_time_range_t* range, const synor_aggregation_t* aggregation,
synor_data_point_list_t* result) {
(void)db; (void)series; (void)range; (void)aggregation; (void)result;
return SYNOR_DB_ERROR_UNKNOWN; /* TODO: Implement */
}
void synor_data_point_list_free(synor_data_point_list_t* list) {
if (!list) return;
free(list->points);
list->points = NULL;
list->count = 0;
}

309
sdk/c/src/hosting/hosting.c Normal file
View file

@ -0,0 +1,309 @@
/**
* @file hosting.c
* @brief Synor Hosting SDK implementation
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "synor/hosting.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);
synor_error_t synor_http_get(synor_http_client_t* client, const char* path,
char** response, size_t* response_size);
synor_error_t synor_http_post(synor_http_client_t* client, const char* path,
const char* body, char** response, size_t* response_size);
synor_error_t synor_http_put(synor_http_client_t* client, const char* path,
const char* body, char** response, size_t* response_size);
synor_error_t 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 hosting structure */
struct synor_hosting {
synor_http_client_t* http;
bool closed;
};
synor_hosting_t* synor_hosting_create(const synor_hosting_config_t* config) {
if (!config || !config->api_key) {
return NULL;
}
synor_hosting_t* hosting = calloc(1, sizeof(synor_hosting_t));
if (!hosting) return NULL;
const char* endpoint = config->endpoint ?
config->endpoint : "https://hosting.synor.io/v1";
uint32_t timeout = config->timeout_ms > 0 ? config->timeout_ms : 60000;
uint32_t retries = config->retries > 0 ? config->retries : 3;
hosting->http = synor_http_client_create(
config->api_key, endpoint, timeout, retries, config->debug);
if (!hosting->http) {
free(hosting);
return NULL;
}
hosting->closed = false;
return hosting;
}
void synor_hosting_destroy(synor_hosting_t* hosting) {
if (!hosting) return;
hosting->closed = true;
synor_http_client_destroy(hosting->http);
free(hosting);
}
bool synor_hosting_is_closed(synor_hosting_t* hosting) {
return hosting ? hosting->closed : true;
}
bool synor_hosting_health_check(synor_hosting_t* hosting) {
if (!hosting || hosting->closed) return false;
char* response = NULL;
size_t response_size = 0;
if (synor_http_get(hosting->http, "/health", &response, &response_size) != 0) {
return false;
}
bool healthy = response && strstr(response, "\"healthy\"") != NULL;
free(response);
return healthy;
}
/* ==================== Domain Operations ==================== */
synor_hosting_error_t synor_hosting_check_availability(synor_hosting_t* hosting,
const char* name, synor_domain_availability_t* result) {
(void)hosting; (void)name; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_hosting_error_t synor_hosting_register_domain(synor_hosting_t* hosting,
const char* name, const synor_register_domain_options_t* options,
synor_domain_t* result) {
(void)hosting; (void)name; (void)options; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_hosting_error_t synor_hosting_get_domain(synor_hosting_t* hosting,
const char* name, synor_domain_t* result) {
(void)hosting; (void)name; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_hosting_error_t synor_hosting_list_domains(synor_hosting_t* hosting,
synor_domain_list_t* result) {
(void)hosting; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_hosting_error_t synor_hosting_resolve_domain(synor_hosting_t* hosting,
const char* name, synor_domain_record_t* result) {
(void)hosting; (void)name; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN; /* TODO: Implement */
}
synor_hosting_error_t synor_hosting_renew_domain(synor_hosting_t* hosting,
const char* name, int32_t years, synor_domain_t* result) {
(void)hosting; (void)name; (void)years; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN; /* TODO: Implement */
}
void synor_domain_free(synor_domain_t* domain) {
if (!domain) return;
free(domain->name);
free(domain->owner);
if (domain->records) {
synor_domain_record_free(domain->records);
free(domain->records);
}
}
void synor_domain_list_free(synor_domain_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_domain_free(&list->domains[i]);
}
free(list->domains);
list->domains = NULL;
list->count = 0;
}
void synor_domain_record_free(synor_domain_record_t* record) {
if (!record) return;
free(record->cid);
for (size_t i = 0; i < record->ipv4_count; i++) free(record->ipv4[i]);
free(record->ipv4);
for (size_t i = 0; i < record->ipv6_count; i++) free(record->ipv6[i]);
free(record->ipv6);
free(record->cname);
for (size_t i = 0; i < record->txt_count; i++) free(record->txt[i]);
free(record->txt);
}
void synor_domain_availability_free(synor_domain_availability_t* avail) {
if (!avail) return;
free(avail->name);
}
/* ==================== DNS Operations ==================== */
synor_hosting_error_t synor_hosting_get_dns_zone(synor_hosting_t* hosting,
const char* domain, synor_dns_zone_t* result) {
(void)hosting; (void)domain; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_set_dns_records(synor_hosting_t* hosting,
const char* domain, const synor_dns_record_t* records, size_t count,
synor_dns_zone_t* result) {
(void)hosting; (void)domain; (void)records; (void)count; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_add_dns_record(synor_hosting_t* hosting,
const char* domain, const synor_dns_record_t* record, synor_dns_zone_t* result) {
(void)hosting; (void)domain; (void)record; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_delete_dns_record(synor_hosting_t* hosting,
const char* domain, const char* record_type, const char* name,
synor_dns_zone_t* result) {
(void)hosting; (void)domain; (void)record_type; (void)name; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
void synor_dns_zone_free(synor_dns_zone_t* zone) {
if (!zone) return;
free(zone->domain);
for (size_t i = 0; i < zone->record_count; i++) {
free(zone->records[i].name);
free(zone->records[i].value);
}
free(zone->records);
}
/* ==================== Deployment Operations ==================== */
synor_hosting_error_t synor_hosting_deploy(synor_hosting_t* hosting,
const char* cid, const synor_deploy_options_t* options,
synor_deployment_t* result) {
(void)hosting; (void)cid; (void)options; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_get_deployment(synor_hosting_t* hosting,
const char* id, synor_deployment_t* result) {
(void)hosting; (void)id; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_list_deployments(synor_hosting_t* hosting,
const char* domain, synor_deployment_list_t* result) {
(void)hosting; (void)domain; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_rollback(synor_hosting_t* hosting,
const char* domain, const char* deployment_id, synor_deployment_t* result) {
(void)hosting; (void)domain; (void)deployment_id; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_delete_deployment(synor_hosting_t* hosting,
const char* id) {
(void)hosting; (void)id;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
void synor_deployment_free(synor_deployment_t* deployment) {
if (!deployment) return;
free(deployment->id);
free(deployment->domain);
free(deployment->cid);
free(deployment->url);
free(deployment->build_logs);
free(deployment->error_message);
}
void synor_deployment_list_free(synor_deployment_list_t* list) {
if (!list) return;
for (size_t i = 0; i < list->count; i++) {
synor_deployment_free(&list->deployments[i]);
}
free(list->deployments);
list->deployments = NULL;
list->count = 0;
}
/* ==================== SSL Operations ==================== */
synor_hosting_error_t synor_hosting_provision_ssl(synor_hosting_t* hosting,
const char* domain, const synor_provision_ssl_options_t* options,
synor_certificate_t* result) {
(void)hosting; (void)domain; (void)options; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_get_certificate(synor_hosting_t* hosting,
const char* domain, synor_certificate_t* result) {
(void)hosting; (void)domain; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_renew_certificate(synor_hosting_t* hosting,
const char* domain, synor_certificate_t* result) {
(void)hosting; (void)domain; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_delete_certificate(synor_hosting_t* hosting,
const char* domain) {
(void)hosting; (void)domain;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
void synor_certificate_free(synor_certificate_t* cert) {
if (!cert) return;
free(cert->domain);
free(cert->issuer);
free(cert->fingerprint);
}
/* ==================== Analytics Operations ==================== */
synor_hosting_error_t synor_hosting_get_analytics(synor_hosting_t* hosting,
const char* domain, const synor_analytics_options_t* options,
synor_analytics_data_t* result) {
(void)hosting; (void)domain; (void)options; (void)result;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
synor_hosting_error_t synor_hosting_purge_cache(synor_hosting_t* hosting,
const char* domain, const char** paths, size_t path_count, int64_t* purged) {
(void)hosting; (void)domain; (void)paths; (void)path_count; (void)purged;
return SYNOR_HOSTING_ERROR_UNKNOWN;
}
void synor_analytics_data_free(synor_analytics_data_t* data) {
if (!data) return;
free(data->domain);
free(data->period);
for (size_t i = 0; i < data->top_pages_count; i++) {
free(data->top_pages[i].path);
}
free(data->top_pages);
}

View file

@ -0,0 +1,340 @@
/**
* @file bridge.hpp
* @brief Synor Bridge SDK for C++
*
* Modern C++17 SDK for cross-chain asset transfers with lock-mint and burn-unlock patterns.
*/
#pragma once
#include <string>
#include <vector>
#include <optional>
#include <future>
#include <memory>
#include <cstdint>
#include <chrono>
#include "wallet.hpp" // For SynorException
namespace synor {
namespace bridge {
/// Chain identifier
enum class ChainId {
Synor,
Ethereum,
Polygon,
Arbitrum,
Optimism,
Bsc,
Avalanche,
Solana,
Cosmos
};
/// Asset type
enum class AssetType {
Native,
Erc20,
Erc721,
Erc1155
};
/// Transfer status
enum class TransferStatus {
Pending,
Locked,
Confirming,
Minting,
Completed,
Failed,
Refunded
};
/// Transfer direction
enum class TransferDirection {
LockMint,
BurnUnlock
};
/// Bridge configuration
struct Config {
std::string api_key;
std::string endpoint = "https://bridge.synor.io/v1";
uint32_t timeout_ms = 60000;
uint32_t retries = 3;
bool debug = false;
};
/// Native currency
struct NativeCurrency {
std::string name;
std::string symbol;
int32_t decimals;
};
/// A blockchain network
struct Chain {
ChainId id;
std::string name;
int64_t chain_id;
std::string rpc_url;
std::string explorer_url;
NativeCurrency native_currency;
int32_t confirmations;
int32_t estimated_block_time;
bool supported;
};
/// An asset
struct Asset {
std::string id;
std::string symbol;
std::string name;
AssetType type;
ChainId chain;
std::optional<std::string> contract_address;
int32_t decimals;
std::optional<std::string> logo_url;
bool verified = false;
};
/// A wrapped asset
struct WrappedAsset {
Asset original_asset;
Asset wrapped_asset;
ChainId chain;
std::string bridge_contract;
};
/// Validator signature
struct ValidatorSignature {
std::string validator;
std::string signature;
int64_t timestamp;
};
/// Lock receipt
struct LockReceipt {
std::string id;
std::string tx_hash;
ChainId source_chain;
ChainId target_chain;
Asset asset;
std::string amount;
std::string sender;
std::string recipient;
int64_t lock_timestamp;
int32_t confirmations;
int32_t required_confirmations;
};
/// Lock proof
struct LockProof {
LockReceipt lock_receipt;
std::vector<std::string> merkle_proof;
std::string block_header;
std::vector<ValidatorSignature> signatures;
};
/// Burn receipt
struct BurnReceipt {
std::string id;
std::string tx_hash;
ChainId source_chain;
ChainId target_chain;
Asset wrapped_asset;
Asset original_asset;
std::string amount;
std::string sender;
std::string recipient;
int64_t burn_timestamp;
int32_t confirmations;
int32_t required_confirmations;
};
/// Burn proof
struct BurnProof {
BurnReceipt burn_receipt;
std::vector<std::string> merkle_proof;
std::string block_header;
std::vector<ValidatorSignature> signatures;
};
/// A transfer
struct Transfer {
std::string id;
TransferDirection direction;
TransferStatus status;
ChainId source_chain;
ChainId target_chain;
Asset asset;
std::string amount;
std::string sender;
std::string recipient;
std::optional<std::string> source_tx_hash;
std::optional<std::string> target_tx_hash;
std::string fee;
Asset fee_asset;
int64_t created_at;
int64_t updated_at;
std::optional<int64_t> completed_at;
std::optional<std::string> error_message;
};
/// Transfer filter
struct TransferFilter {
std::optional<TransferStatus> status;
std::optional<ChainId> source_chain;
std::optional<ChainId> target_chain;
std::optional<std::string> asset;
std::optional<std::string> sender;
std::optional<std::string> recipient;
std::optional<int32_t> limit;
std::optional<int32_t> offset;
};
/// Fee estimate
struct FeeEstimate {
std::string bridge_fee;
std::string gas_fee_source;
std::string gas_fee_target;
std::string total_fee;
Asset fee_asset;
int32_t estimated_time;
std::optional<std::string> exchange_rate;
};
/// Exchange rate
struct ExchangeRate {
Asset from_asset;
Asset to_asset;
std::string rate;
std::string inverse_rate;
int64_t last_updated;
std::string source;
};
/// Signed transaction
struct SignedTransaction {
std::string tx_hash;
ChainId chain;
std::string from;
std::string to;
std::string value;
std::string data;
std::string gas_limit;
std::optional<std::string> gas_price;
std::optional<std::string> max_fee_per_gas;
std::optional<std::string> max_priority_fee_per_gas;
int32_t nonce;
std::string signature;
};
/// Lock options
struct LockOptions {
std::optional<std::string> recipient;
std::optional<int64_t> deadline;
std::optional<double> slippage;
};
/// Mint options
struct MintOptions {
std::optional<std::string> gas_limit;
std::optional<std::string> max_fee_per_gas;
std::optional<std::string> max_priority_fee_per_gas;
};
/// Burn options
struct BurnOptions {
std::optional<std::string> recipient;
std::optional<int64_t> deadline;
};
/// Unlock options
struct UnlockOptions {
std::optional<std::string> gas_limit;
std::optional<std::string> gas_price;
};
// Forward declaration
class SynorBridgeImpl;
/// Synor Bridge client
class SynorBridge {
public:
explicit SynorBridge(const Config& config);
~SynorBridge();
SynorBridge(const SynorBridge&) = delete;
SynorBridge& operator=(const SynorBridge&) = delete;
SynorBridge(SynorBridge&&) noexcept;
SynorBridge& operator=(SynorBridge&&) noexcept;
// Chain operations
std::future<std::vector<Chain>> get_supported_chains();
std::future<Chain> get_chain(ChainId chain_id);
std::future<bool> is_chain_supported(ChainId chain_id);
// Asset operations
std::future<std::vector<Asset>> get_supported_assets(ChainId chain_id);
std::future<Asset> get_asset(const std::string& asset_id);
std::future<WrappedAsset> get_wrapped_asset(const std::string& original_asset_id,
ChainId target_chain);
// Fee & rate operations
std::future<FeeEstimate> estimate_fee(const std::string& asset, const std::string& amount,
ChainId source_chain, ChainId target_chain);
std::future<ExchangeRate> get_exchange_rate(const std::string& from_asset,
const std::string& to_asset);
// Lock-Mint flow
std::future<LockReceipt> lock(const std::string& asset, const std::string& amount,
ChainId target_chain, const LockOptions& options = {});
std::future<LockProof> get_lock_proof(const std::string& lock_receipt_id);
std::future<LockProof> wait_for_lock_proof(
const std::string& lock_receipt_id,
std::chrono::milliseconds poll_interval = std::chrono::seconds(5),
std::chrono::milliseconds max_wait = std::chrono::minutes(10));
std::future<SignedTransaction> mint(const LockProof& proof, const std::string& target_address,
const MintOptions& options = {});
// Burn-Unlock flow
std::future<BurnReceipt> burn(const std::string& wrapped_asset, const std::string& amount,
const BurnOptions& options = {});
std::future<BurnProof> get_burn_proof(const std::string& burn_receipt_id);
std::future<BurnProof> wait_for_burn_proof(
const std::string& burn_receipt_id,
std::chrono::milliseconds poll_interval = std::chrono::seconds(5),
std::chrono::milliseconds max_wait = std::chrono::minutes(10));
std::future<SignedTransaction> unlock(const BurnProof& proof, const UnlockOptions& options = {});
// Transfer management
std::future<Transfer> get_transfer(const std::string& transfer_id);
std::future<TransferStatus> get_transfer_status(const std::string& transfer_id);
std::future<std::vector<Transfer>> list_transfers(const TransferFilter& filter = {});
std::future<Transfer> wait_for_transfer(
const std::string& transfer_id,
std::chrono::milliseconds poll_interval = std::chrono::seconds(10),
std::chrono::milliseconds max_wait = std::chrono::minutes(30));
// Convenience methods
std::future<Transfer> bridge_to(const std::string& asset, const std::string& amount,
ChainId target_chain, const std::string& target_address,
const LockOptions& lock_options = {},
const MintOptions& mint_options = {});
std::future<Transfer> bridge_back(const std::string& wrapped_asset, const std::string& amount,
const BurnOptions& burn_options = {},
const UnlockOptions& unlock_options = {});
// Lifecycle
void close();
bool is_closed() const;
std::future<bool> health_check();
private:
std::unique_ptr<SynorBridgeImpl> impl_;
};
} // namespace bridge
} // namespace synor

View file

@ -0,0 +1,194 @@
/**
* @file database.hpp
* @brief Synor Database SDK for C++
*
* Modern C++17 SDK for multi-model database with Key-Value, Document, Vector, and Time Series stores.
*/
#pragma once
#include <string>
#include <vector>
#include <optional>
#include <future>
#include <memory>
#include <map>
#include <cstdint>
#include "wallet.hpp" // For SynorException
namespace synor {
namespace database {
/// Database configuration
struct Config {
std::string api_key;
std::string endpoint = "https://db.synor.io/v1";
uint32_t timeout_ms = 60000;
uint32_t retries = 3;
bool debug = false;
};
/// A key-value entry
struct KeyValue {
std::string key;
std::string value; // JSON string
std::optional<int32_t> ttl;
std::optional<int64_t> created_at;
std::optional<int64_t> updated_at;
};
/// A document
struct Document {
std::string id;
std::string collection;
std::map<std::string, std::string> data; // JSON data
int64_t created_at;
int64_t updated_at;
};
/// Query for documents
struct Query {
std::optional<std::string> filter; // JSON
std::optional<std::string> sort; // JSON
std::optional<int32_t> limit;
std::optional<int32_t> offset;
std::optional<std::vector<std::string>> projection;
};
/// A vector entry
struct VectorEntry {
std::string id;
std::vector<double> vector;
std::optional<std::map<std::string, std::string>> metadata;
};
/// A search result
struct SearchResult {
std::string id;
double score;
std::optional<std::vector<double>> vector;
std::optional<std::map<std::string, std::string>> metadata;
};
/// A time series data point
struct DataPoint {
int64_t timestamp;
double value;
std::optional<std::map<std::string, std::string>> tags;
};
/// Time range for queries
struct TimeRange {
int64_t start;
int64_t end;
};
/// Aggregation settings
struct Aggregation {
std::string function; // avg, sum, min, max, count
std::string interval; // 1m, 5m, 1h, 1d
};
// Forward declaration
class SynorDatabaseImpl;
/// Key-Value store interface
class KeyValueStore {
public:
explicit KeyValueStore(std::shared_ptr<SynorDatabaseImpl> impl);
std::future<std::optional<std::string>> get(const std::string& key);
std::future<void> set(const std::string& key, const std::string& value,
std::optional<int32_t> ttl = std::nullopt);
std::future<void> remove(const std::string& key);
std::future<std::vector<KeyValue>> list(const std::string& prefix);
private:
std::shared_ptr<SynorDatabaseImpl> impl_;
};
/// Document store interface
class DocumentStore {
public:
explicit DocumentStore(std::shared_ptr<SynorDatabaseImpl> impl);
std::future<std::string> create(const std::string& collection, const std::string& document);
std::future<Document> get(const std::string& collection, const std::string& id);
std::future<void> update(const std::string& collection, const std::string& id,
const std::string& update);
std::future<void> remove(const std::string& collection, const std::string& id);
std::future<std::vector<Document>> query(const std::string& collection, const Query& query);
private:
std::shared_ptr<SynorDatabaseImpl> impl_;
};
/// Vector store interface
class VectorStore {
public:
explicit VectorStore(std::shared_ptr<SynorDatabaseImpl> impl);
std::future<void> upsert(const std::string& collection, const std::vector<VectorEntry>& vectors);
std::future<std::vector<SearchResult>> search(const std::string& collection,
const std::vector<double>& vector, int32_t k);
std::future<void> remove(const std::string& collection, const std::vector<std::string>& ids);
private:
std::shared_ptr<SynorDatabaseImpl> impl_;
};
/// Time series store interface
class TimeSeriesStore {
public:
explicit TimeSeriesStore(std::shared_ptr<SynorDatabaseImpl> impl);
std::future<void> write(const std::string& series, const std::vector<DataPoint>& points);
std::future<std::vector<DataPoint>> query(const std::string& series, const TimeRange& range,
std::optional<Aggregation> aggregation = std::nullopt);
private:
std::shared_ptr<SynorDatabaseImpl> impl_;
};
/// Synor Database client
class SynorDatabase {
public:
explicit SynorDatabase(const Config& config);
~SynorDatabase();
SynorDatabase(const SynorDatabase&) = delete;
SynorDatabase& operator=(const SynorDatabase&) = delete;
SynorDatabase(SynorDatabase&&) noexcept;
SynorDatabase& operator=(SynorDatabase&&) noexcept;
/// Key-Value store operations
KeyValueStore& kv();
/// Document store operations
DocumentStore& documents();
/// Vector store operations
VectorStore& vectors();
/// Time series store operations
TimeSeriesStore& timeseries();
/// Close the client
void close();
/// Check if the client is closed
bool is_closed() const;
/// Perform a health check
std::future<bool> health_check();
private:
std::shared_ptr<SynorDatabaseImpl> impl_;
std::unique_ptr<KeyValueStore> kv_;
std::unique_ptr<DocumentStore> documents_;
std::unique_ptr<VectorStore> vectors_;
std::unique_ptr<TimeSeriesStore> timeseries_;
};
} // namespace database
} // namespace synor

View file

@ -0,0 +1,271 @@
/**
* @file hosting.hpp
* @brief Synor Hosting SDK for C++
*
* Modern C++17 SDK for decentralized web hosting with domain management, DNS, deployments, and SSL.
*/
#pragma once
#include <string>
#include <vector>
#include <optional>
#include <future>
#include <memory>
#include <map>
#include <cstdint>
#include "wallet.hpp" // For SynorException
namespace synor {
namespace hosting {
/// Domain status
enum class DomainStatus {
Pending,
Active,
Expired,
Suspended
};
/// Deployment status
enum class DeploymentStatus {
Pending,
Building,
Deploying,
Active,
Failed,
Inactive
};
/// Certificate status
enum class CertificateStatus {
Pending,
Issued,
Expired,
Revoked
};
/// DNS record type
enum class DnsRecordType {
A,
AAAA,
CNAME,
TXT,
MX,
NS,
SRV,
CAA
};
/// Hosting configuration
struct Config {
std::string api_key;
std::string endpoint = "https://hosting.synor.io/v1";
uint32_t timeout_ms = 60000;
uint32_t retries = 3;
bool debug = false;
};
/// Domain record
struct DomainRecord {
std::optional<std::string> cid;
std::optional<std::vector<std::string>> ipv4;
std::optional<std::vector<std::string>> ipv6;
std::optional<std::string> cname;
std::optional<std::vector<std::string>> txt;
std::optional<std::map<std::string, std::string>> metadata;
};
/// A domain
struct Domain {
std::string name;
DomainStatus status;
std::string owner;
int64_t registered_at;
int64_t expires_at;
bool auto_renew;
std::optional<DomainRecord> records;
};
/// Domain availability
struct DomainAvailability {
std::string name;
bool available;
std::optional<double> price;
bool premium = false;
};
/// Domain registration options
struct RegisterDomainOptions {
std::optional<int32_t> years;
std::optional<bool> auto_renew;
};
/// DNS record
struct DnsRecord {
DnsRecordType type;
std::string name;
std::string value;
int32_t ttl = 3600;
std::optional<int32_t> priority;
};
/// DNS zone
struct DnsZone {
std::string domain;
std::vector<DnsRecord> records;
int64_t updated_at;
};
/// A deployment
struct Deployment {
std::string id;
std::string domain;
std::string cid;
DeploymentStatus status;
std::string url;
int64_t created_at;
int64_t updated_at;
std::optional<std::string> build_logs;
std::optional<std::string> error_message;
};
/// Deploy options
struct DeployOptions {
std::optional<std::string> domain;
std::optional<std::string> subdomain;
std::optional<bool> spa;
std::optional<bool> clean_urls;
std::optional<bool> trailing_slash;
};
/// SSL certificate
struct Certificate {
std::string domain;
CertificateStatus status;
bool auto_renew;
std::string issuer;
std::optional<int64_t> issued_at;
std::optional<int64_t> expires_at;
std::optional<std::string> fingerprint;
};
/// SSL provisioning options
struct ProvisionSslOptions {
std::optional<bool> include_www;
std::optional<bool> auto_renew;
};
/// Redirect rule
struct RedirectRule {
std::string source;
std::string destination;
int32_t status_code = 301;
bool permanent = true;
};
/// Site configuration
struct SiteConfig {
std::string domain;
std::optional<std::string> cid;
std::optional<std::map<std::string, std::string>> headers;
std::optional<std::vector<RedirectRule>> redirects;
std::optional<std::map<std::string, std::string>> error_pages;
bool spa = false;
bool clean_urls = true;
bool trailing_slash = false;
};
/// Page view stats
struct PageView {
std::string path;
int64_t views;
};
/// Analytics data
struct AnalyticsData {
std::string domain;
std::string period;
int64_t page_views;
int64_t unique_visitors;
int64_t bandwidth;
std::vector<PageView> top_pages;
};
/// Analytics options
struct AnalyticsOptions {
std::optional<std::string> period;
std::optional<std::string> start;
std::optional<std::string> end;
};
// Forward declaration
class SynorHostingImpl;
/// Synor Hosting client
class SynorHosting {
public:
explicit SynorHosting(const Config& config);
~SynorHosting();
SynorHosting(const SynorHosting&) = delete;
SynorHosting& operator=(const SynorHosting&) = delete;
SynorHosting(SynorHosting&&) noexcept;
SynorHosting& operator=(SynorHosting&&) noexcept;
// Domain operations
std::future<DomainAvailability> check_availability(const std::string& name);
std::future<Domain> register_domain(const std::string& name,
const RegisterDomainOptions& options = {});
std::future<Domain> get_domain(const std::string& name);
std::future<std::vector<Domain>> list_domains();
std::future<Domain> update_domain_record(const std::string& name, const DomainRecord& record);
std::future<DomainRecord> resolve_domain(const std::string& name);
std::future<Domain> renew_domain(const std::string& name, int32_t years);
// DNS operations
std::future<DnsZone> get_dns_zone(const std::string& domain);
std::future<DnsZone> set_dns_records(const std::string& domain,
const std::vector<DnsRecord>& records);
std::future<DnsZone> add_dns_record(const std::string& domain, const DnsRecord& record);
std::future<DnsZone> delete_dns_record(const std::string& domain,
const std::string& record_type,
const std::string& name);
// Deployment operations
std::future<Deployment> deploy(const std::string& cid, const DeployOptions& options = {});
std::future<Deployment> get_deployment(const std::string& id);
std::future<std::vector<Deployment>> list_deployments(
std::optional<std::string> domain = std::nullopt);
std::future<Deployment> rollback(const std::string& domain, const std::string& deployment_id);
std::future<void> delete_deployment(const std::string& id);
// SSL operations
std::future<Certificate> provision_ssl(const std::string& domain,
const ProvisionSslOptions& options = {});
std::future<Certificate> get_certificate(const std::string& domain);
std::future<Certificate> renew_certificate(const std::string& domain);
std::future<void> delete_certificate(const std::string& domain);
// Site configuration
std::future<SiteConfig> get_site_config(const std::string& domain);
std::future<SiteConfig> update_site_config(const std::string& domain,
const std::map<std::string, std::string>& config);
std::future<int64_t> purge_cache(const std::string& domain,
std::optional<std::vector<std::string>> paths = std::nullopt);
// Analytics
std::future<AnalyticsData> get_analytics(const std::string& domain,
const AnalyticsOptions& options = {});
// Lifecycle
void close();
bool is_closed() const;
std::future<bool> health_check();
private:
std::unique_ptr<SynorHostingImpl> impl_;
};
} // namespace hosting
} // namespace synor

View file

@ -0,0 +1,237 @@
using System.Net.Http.Json;
using System.Text.Json;
namespace Synor.Sdk.Bridge;
/// <summary>
/// Synor Bridge SDK client for C#.
/// Cross-chain asset transfers with lock-mint and burn-unlock patterns.
/// </summary>
public class SynorBridge : IDisposable
{
private readonly BridgeConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
private static readonly HashSet<TransferStatus> FinalStatuses = new() { TransferStatus.Completed, TransferStatus.Failed, TransferStatus.Refunded };
public SynorBridge(BridgeConfig 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.SnakeCaseLower, PropertyNameCaseInsensitive = true };
}
// Chain Operations
public async Task<List<Chain>> GetSupportedChainsAsync(CancellationToken ct = default)
{
var r = await GetAsync<ChainsResponse>("/chains", ct);
return r.Chains ?? new List<Chain>();
}
public async Task<Chain> GetChainAsync(ChainId chainId, CancellationToken ct = default)
=> await GetAsync<Chain>($"/chains/{chainId.ToString().ToLower()}", ct);
public async Task<bool> IsChainSupportedAsync(ChainId chainId, CancellationToken ct = default)
{
try { var c = await GetChainAsync(chainId, ct); return c.Supported; }
catch { return false; }
}
// Asset Operations
public async Task<List<Asset>> GetSupportedAssetsAsync(ChainId chainId, CancellationToken ct = default)
{
var r = await GetAsync<AssetsResponse>($"/chains/{chainId.ToString().ToLower()}/assets", ct);
return r.Assets ?? new List<Asset>();
}
public async Task<Asset> GetAssetAsync(string assetId, CancellationToken ct = default)
=> await GetAsync<Asset>($"/assets/{Uri.EscapeDataString(assetId)}", ct);
public async Task<WrappedAsset> GetWrappedAssetAsync(string originalAssetId, ChainId targetChain, CancellationToken ct = default)
=> await GetAsync<WrappedAsset>($"/assets/{Uri.EscapeDataString(originalAssetId)}/wrapped/{targetChain.ToString().ToLower()}", ct);
// Fee Operations
public async Task<FeeEstimate> EstimateFeeAsync(string asset, string amount, ChainId sourceChain, ChainId targetChain, CancellationToken ct = default)
=> await PostAsync<FeeEstimate>("/fees/estimate", new { asset, amount, sourceChain = sourceChain.ToString().ToLower(), targetChain = targetChain.ToString().ToLower() }, ct);
public async Task<ExchangeRate> GetExchangeRateAsync(string fromAsset, string toAsset, CancellationToken ct = default)
=> await GetAsync<ExchangeRate>($"/rates/{Uri.EscapeDataString(fromAsset)}/{Uri.EscapeDataString(toAsset)}", ct);
// Lock-Mint Flow
public async Task<LockReceipt> LockAsync(string asset, string amount, ChainId targetChain, LockOptions? options = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["asset"] = asset, ["amount"] = amount, ["targetChain"] = targetChain.ToString().ToLower() };
if (options?.Recipient != null) body["recipient"] = options.Recipient;
if (options?.Deadline != null) body["deadline"] = options.Deadline;
if (options?.Slippage != null) body["slippage"] = options.Slippage;
return await PostAsync<LockReceipt>("/transfers/lock", body, ct);
}
public async Task<LockProof> GetLockProofAsync(string lockReceiptId, CancellationToken ct = default)
=> await GetAsync<LockProof>($"/transfers/lock/{Uri.EscapeDataString(lockReceiptId)}/proof", ct);
public async Task<LockProof> WaitForLockProofAsync(string lockReceiptId, TimeSpan? pollInterval = null, TimeSpan? maxWait = null, CancellationToken ct = default)
{
var interval = pollInterval ?? TimeSpan.FromSeconds(5);
var deadline = DateTime.UtcNow + (maxWait ?? TimeSpan.FromMinutes(10));
while (DateTime.UtcNow < deadline)
{
try { return await GetLockProofAsync(lockReceiptId, ct); }
catch (BridgeException ex) when (ex.IsConfirmationsPending) { await Task.Delay(interval, ct); }
}
throw new BridgeException("Timeout waiting for lock proof", "CONFIRMATIONS_PENDING");
}
public async Task<SignedTransaction> MintAsync(LockProof proof, string targetAddress, MintOptions? options = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["proof"] = proof, ["targetAddress"] = targetAddress };
if (options?.GasLimit != null) body["gasLimit"] = options.GasLimit;
if (options?.MaxFeePerGas != null) body["maxFeePerGas"] = options.MaxFeePerGas;
if (options?.MaxPriorityFeePerGas != null) body["maxPriorityFeePerGas"] = options.MaxPriorityFeePerGas;
return await PostAsync<SignedTransaction>("/transfers/mint", body, ct);
}
// Burn-Unlock Flow
public async Task<BurnReceipt> BurnAsync(string wrappedAsset, string amount, BurnOptions? options = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["wrappedAsset"] = wrappedAsset, ["amount"] = amount };
if (options?.Recipient != null) body["recipient"] = options.Recipient;
if (options?.Deadline != null) body["deadline"] = options.Deadline;
return await PostAsync<BurnReceipt>("/transfers/burn", body, ct);
}
public async Task<BurnProof> GetBurnProofAsync(string burnReceiptId, CancellationToken ct = default)
=> await GetAsync<BurnProof>($"/transfers/burn/{Uri.EscapeDataString(burnReceiptId)}/proof", ct);
public async Task<BurnProof> WaitForBurnProofAsync(string burnReceiptId, TimeSpan? pollInterval = null, TimeSpan? maxWait = null, CancellationToken ct = default)
{
var interval = pollInterval ?? TimeSpan.FromSeconds(5);
var deadline = DateTime.UtcNow + (maxWait ?? TimeSpan.FromMinutes(10));
while (DateTime.UtcNow < deadline)
{
try { return await GetBurnProofAsync(burnReceiptId, ct); }
catch (BridgeException ex) when (ex.IsConfirmationsPending) { await Task.Delay(interval, ct); }
}
throw new BridgeException("Timeout waiting for burn proof", "CONFIRMATIONS_PENDING");
}
public async Task<SignedTransaction> UnlockAsync(BurnProof proof, UnlockOptions? options = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["proof"] = proof };
if (options?.GasLimit != null) body["gasLimit"] = options.GasLimit;
if (options?.GasPrice != null) body["gasPrice"] = options.GasPrice;
return await PostAsync<SignedTransaction>("/transfers/unlock", body, ct);
}
// Transfer Management
public async Task<Transfer> GetTransferAsync(string transferId, CancellationToken ct = default)
=> await GetAsync<Transfer>($"/transfers/{Uri.EscapeDataString(transferId)}", ct);
public async Task<TransferStatus> GetTransferStatusAsync(string transferId, CancellationToken ct = default)
{
var t = await GetTransferAsync(transferId, ct);
return t.Status;
}
public async Task<List<Transfer>> ListTransfersAsync(TransferFilter? filter = null, CancellationToken ct = default)
{
var q = new List<string>();
if (filter?.Status != null) q.Add($"status={filter.Status.ToString()!.ToLower()}");
if (filter?.SourceChain != null) q.Add($"sourceChain={filter.SourceChain.ToString()!.ToLower()}");
if (filter?.TargetChain != null) q.Add($"targetChain={filter.TargetChain.ToString()!.ToLower()}");
if (filter?.Limit != null) q.Add($"limit={filter.Limit}");
if (filter?.Offset != null) q.Add($"offset={filter.Offset}");
var path = q.Count > 0 ? $"/transfers?{string.Join("&", q)}" : "/transfers";
var r = await GetAsync<TransfersResponse>(path, ct);
return r.Transfers ?? new List<Transfer>();
}
public async Task<Transfer> WaitForTransferAsync(string transferId, TimeSpan? pollInterval = null, TimeSpan? maxWait = null, CancellationToken ct = default)
{
var interval = pollInterval ?? TimeSpan.FromSeconds(10);
var deadline = DateTime.UtcNow + (maxWait ?? TimeSpan.FromMinutes(30));
while (DateTime.UtcNow < deadline)
{
var t = await GetTransferAsync(transferId, ct);
if (FinalStatuses.Contains(t.Status)) return t;
await Task.Delay(interval, ct);
}
throw new BridgeException("Timeout waiting for transfer completion");
}
// Convenience Methods
public async Task<Transfer> BridgeToAsync(string asset, string amount, ChainId targetChain, string targetAddress, LockOptions? lockOptions = null, MintOptions? mintOptions = null, CancellationToken ct = default)
{
var receipt = await LockAsync(asset, amount, targetChain, lockOptions, ct);
if (_config.Debug) Console.WriteLine($"Locked: {receipt.Id}");
var proof = await WaitForLockProofAsync(receipt.Id, ct: ct);
if (_config.Debug) Console.WriteLine($"Proof ready, minting on {targetChain}");
await MintAsync(proof, targetAddress, mintOptions, ct);
return await WaitForTransferAsync(receipt.Id, ct: ct);
}
public async Task<Transfer> BridgeBackAsync(string wrappedAsset, string amount, BurnOptions? burnOptions = null, UnlockOptions? unlockOptions = null, CancellationToken ct = default)
{
var receipt = await BurnAsync(wrappedAsset, amount, burnOptions, ct);
if (_config.Debug) Console.WriteLine($"Burned: {receipt.Id}");
var proof = await WaitForBurnProofAsync(receipt.Id, ct: ct);
if (_config.Debug) Console.WriteLine($"Proof ready, unlocking on {receipt.TargetChain}");
await UnlockAsync(proof, unlockOptions, ct);
return await WaitForTransferAsync(receipt.Id, ct: ct);
}
// Lifecycle
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
try { var r = await GetAsync<BridgeHealthResponse>("/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 BridgeException(e?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)r.StatusCode}",
e?.GetValueOrDefault("code")?.ToString(), (int)r.StatusCode);
}
}
}

View file

@ -0,0 +1,185 @@
namespace Synor.Sdk.Bridge;
public record BridgeConfig
{
public required string ApiKey { get; init; }
public string Endpoint { get; init; } = "https://bridge.synor.io/v1";
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(60);
public int Retries { get; init; } = 3;
public bool Debug { get; init; } = false;
}
public enum ChainId { Synor, Ethereum, Polygon, Arbitrum, Optimism, Bsc, Avalanche, Solana, Cosmos }
public enum AssetType { Native, Erc20, Erc721, Erc1155 }
public enum TransferStatus { Pending, Locked, Confirming, Minting, Completed, Failed, Refunded }
public enum TransferDirection { LockMint, BurnUnlock }
public record NativeCurrency { public required string Name { get; init; } public required string Symbol { get; init; } public int Decimals { get; init; } }
public record Chain
{
public ChainId Id { get; init; }
public required string Name { get; init; }
public long ChainIdNumber { get; init; }
public required string RpcUrl { get; init; }
public required string ExplorerUrl { get; init; }
public required NativeCurrency NativeCurrency { get; init; }
public int Confirmations { get; init; }
public int EstimatedBlockTime { get; init; }
public bool Supported { get; init; }
}
public record Asset
{
public required string Id { get; init; }
public required string Symbol { get; init; }
public required string Name { get; init; }
public AssetType Type { get; init; }
public ChainId Chain { get; init; }
public string? ContractAddress { get; init; }
public int Decimals { get; init; }
public string? LogoUrl { get; init; }
public bool Verified { get; init; }
}
public record WrappedAsset
{
public required Asset OriginalAsset { get; init; }
public required Asset WrappedAssetData { get; init; }
public ChainId Chain { get; init; }
public required string BridgeContract { get; init; }
}
public record ValidatorSignature { public required string Validator { get; init; } public required string Signature { get; init; } public long Timestamp { get; init; } }
public record LockReceipt
{
public required string Id { get; init; }
public required string TxHash { get; init; }
public ChainId SourceChain { get; init; }
public ChainId TargetChain { get; init; }
public required Asset Asset { get; init; }
public required string Amount { get; init; }
public required string Sender { get; init; }
public required string Recipient { get; init; }
public long LockTimestamp { get; init; }
public int Confirmations { get; init; }
public int RequiredConfirmations { get; init; }
}
public record LockProof
{
public required LockReceipt LockReceipt { get; init; }
public required List<string> MerkleProof { get; init; }
public required string BlockHeader { get; init; }
public required List<ValidatorSignature> Signatures { get; init; }
}
public record BurnReceipt
{
public required string Id { get; init; }
public required string TxHash { get; init; }
public ChainId SourceChain { get; init; }
public ChainId TargetChain { get; init; }
public required Asset WrappedAsset { get; init; }
public required Asset OriginalAsset { get; init; }
public required string Amount { get; init; }
public required string Sender { get; init; }
public required string Recipient { get; init; }
public long BurnTimestamp { get; init; }
public int Confirmations { get; init; }
public int RequiredConfirmations { get; init; }
}
public record BurnProof
{
public required BurnReceipt BurnReceipt { get; init; }
public required List<string> MerkleProof { get; init; }
public required string BlockHeader { get; init; }
public required List<ValidatorSignature> Signatures { get; init; }
}
public record Transfer
{
public required string Id { get; init; }
public TransferDirection Direction { get; init; }
public TransferStatus Status { get; init; }
public ChainId SourceChain { get; init; }
public ChainId TargetChain { get; init; }
public required Asset Asset { get; init; }
public required string Amount { get; init; }
public required string Sender { get; init; }
public required string Recipient { get; init; }
public string? SourceTxHash { get; init; }
public string? TargetTxHash { get; init; }
public required string Fee { get; init; }
public required Asset FeeAsset { get; init; }
public long CreatedAt { get; init; }
public long UpdatedAt { get; init; }
public long? CompletedAt { get; init; }
public string? ErrorMessage { get; init; }
}
public record TransferFilter
{
public TransferStatus? Status { get; init; }
public ChainId? SourceChain { get; init; }
public ChainId? TargetChain { get; init; }
public int? Limit { get; init; }
public int? Offset { get; init; }
}
public record FeeEstimate
{
public required string BridgeFee { get; init; }
public required string GasFeeSource { get; init; }
public required string GasFeeTarget { get; init; }
public required string TotalFee { get; init; }
public required Asset FeeAsset { get; init; }
public int EstimatedTime { get; init; }
public string? ExchangeRate { get; init; }
}
public record ExchangeRate
{
public required Asset FromAsset { get; init; }
public required Asset ToAsset { get; init; }
public required string Rate { get; init; }
public required string InverseRate { get; init; }
public long LastUpdated { get; init; }
public required string Source { get; init; }
}
public record SignedTransaction
{
public required string TxHash { get; init; }
public ChainId Chain { get; init; }
public required string From { get; init; }
public required string To { get; init; }
public required string Value { get; init; }
public required string Data { get; init; }
public required string GasLimit { get; init; }
public string? GasPrice { get; init; }
public string? MaxFeePerGas { get; init; }
public string? MaxPriorityFeePerGas { get; init; }
public int Nonce { get; init; }
public required string Signature { get; init; }
}
public record LockOptions { public string? Recipient { get; init; } public long? Deadline { get; init; } public double? Slippage { get; init; } }
public record MintOptions { public string? GasLimit { get; init; } public string? MaxFeePerGas { get; init; } public string? MaxPriorityFeePerGas { get; init; } }
public record BurnOptions { public string? Recipient { get; init; } public long? Deadline { get; init; } }
public record UnlockOptions { public string? GasLimit { get; init; } public string? GasPrice { get; init; } }
internal record ChainsResponse { public List<Chain>? Chains { get; init; } }
internal record AssetsResponse { public List<Asset>? Assets { get; init; } }
internal record TransfersResponse { public List<Transfer>? Transfers { get; init; } }
internal record BridgeHealthResponse { public required string Status { get; init; } }
public class BridgeException : Exception
{
public string? Code { get; }
public int StatusCode { get; }
public bool IsConfirmationsPending => Code == "CONFIRMATIONS_PENDING";
public BridgeException(string message, string? code = null, int statusCode = 0) : base(message) { Code = code; StatusCode = statusCode; }
}

View file

@ -0,0 +1,335 @@
using System.Net.Http.Json;
using System.Text.Json;
using System.Web;
namespace Synor.Sdk.Database;
/// <summary>
/// Synor Database SDK client for C#.
///
/// Provides multi-model database with Key-Value, Document, Vector, and Time Series stores.
/// </summary>
/// <example>
/// <code>
/// var db = new SynorDatabase(new DatabaseConfig { ApiKey = "your-api-key" });
///
/// // Key-Value operations
/// await db.Kv.SetAsync("mykey", "myvalue");
/// var value = await db.Kv.GetAsync("mykey");
///
/// // Document operations
/// var id = await db.Documents.CreateAsync("users", new { name = "Alice", age = 30 });
/// var doc = await db.Documents.GetAsync("users", id);
///
/// db.Dispose();
/// </code>
/// </example>
public class SynorDatabase : IDisposable
{
private readonly DatabaseConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
public KeyValueStore Kv { get; }
public DocumentStore Documents { get; }
public VectorStore Vectors { get; }
public TimeSeriesStore TimeSeries { get; }
public SynorDatabase(DatabaseConfig 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.SnakeCaseLower,
PropertyNameCaseInsensitive = true
};
Kv = new KeyValueStore(this);
Documents = new DocumentStore(this);
Vectors = new VectorStore(this);
TimeSeries = new TimeSeriesStore(this);
}
/// <summary>
/// Key-Value store operations.
/// </summary>
public class KeyValueStore
{
private readonly SynorDatabase _db;
internal KeyValueStore(SynorDatabase db) => _db = db;
public async Task<object?> GetAsync(string key, CancellationToken ct = default)
{
var response = await _db.GetAsync<KvGetResponse>($"/kv/{Uri.EscapeDataString(key)}", ct);
return response.Value;
}
public async Task SetAsync(string key, object value, int? ttl = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["key"] = key, ["value"] = value };
if (ttl.HasValue) body["ttl"] = ttl.Value;
await _db.PutAsync<object>($"/kv/{Uri.EscapeDataString(key)}", body, ct);
}
public async Task DeleteAsync(string key, CancellationToken ct = default)
{
await _db.DeleteAsync($"/kv/{Uri.EscapeDataString(key)}", ct);
}
public async Task<List<KeyValue>> ListAsync(string prefix, CancellationToken ct = default)
{
var response = await _db.GetAsync<KvListResponse>($"/kv?prefix={Uri.EscapeDataString(prefix)}", ct);
return response.Items ?? new List<KeyValue>();
}
}
/// <summary>
/// Document store operations.
/// </summary>
public class DocumentStore
{
private readonly SynorDatabase _db;
internal DocumentStore(SynorDatabase db) => _db = db;
public async Task<string> CreateAsync(string collection, object document, CancellationToken ct = default)
{
var response = await _db.PostAsync<CreateDocumentResponse>(
$"/collections/{Uri.EscapeDataString(collection)}/documents", document, ct);
return response.Id;
}
public async Task<Document> GetAsync(string collection, string id, CancellationToken ct = default)
{
return await _db.GetAsync<Document>(
$"/collections/{Uri.EscapeDataString(collection)}/documents/{Uri.EscapeDataString(id)}", ct);
}
public async Task UpdateAsync(string collection, string id, object update, CancellationToken ct = default)
{
await _db.PatchAsync<object>(
$"/collections/{Uri.EscapeDataString(collection)}/documents/{Uri.EscapeDataString(id)}", update, ct);
}
public async Task DeleteAsync(string collection, string id, CancellationToken ct = default)
{
await _db.DeleteAsync(
$"/collections/{Uri.EscapeDataString(collection)}/documents/{Uri.EscapeDataString(id)}", ct);
}
public async Task<List<Document>> QueryAsync(string collection, DocumentQuery query, CancellationToken ct = default)
{
var response = await _db.PostAsync<DocumentListResponse>(
$"/collections/{Uri.EscapeDataString(collection)}/query", query, ct);
return response.Documents ?? new List<Document>();
}
}
/// <summary>
/// Vector store operations.
/// </summary>
public class VectorStore
{
private readonly SynorDatabase _db;
internal VectorStore(SynorDatabase db) => _db = db;
public async Task UpsertAsync(string collection, IEnumerable<VectorEntry> vectors, CancellationToken ct = default)
{
await _db.PostAsync<object>(
$"/vectors/{Uri.EscapeDataString(collection)}/upsert",
new { vectors = vectors }, ct);
}
public async Task<List<SearchResult>> SearchAsync(string collection, double[] vector, int k, CancellationToken ct = default)
{
var response = await _db.PostAsync<SearchListResponse>(
$"/vectors/{Uri.EscapeDataString(collection)}/search",
new { vector, k }, ct);
return response.Results ?? new List<SearchResult>();
}
public async Task DeleteAsync(string collection, IEnumerable<string> ids, CancellationToken ct = default)
{
await _db.DeleteAsync($"/vectors/{Uri.EscapeDataString(collection)}", new { ids = ids }, ct);
}
}
/// <summary>
/// Time series store operations.
/// </summary>
public class TimeSeriesStore
{
private readonly SynorDatabase _db;
internal TimeSeriesStore(SynorDatabase db) => _db = db;
public async Task WriteAsync(string series, IEnumerable<DataPoint> points, CancellationToken ct = default)
{
await _db.PostAsync<object>(
$"/timeseries/{Uri.EscapeDataString(series)}/write",
new { points = points }, ct);
}
public async Task<List<DataPoint>> QueryAsync(
string series, TimeRange range, Aggregation? aggregation = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["range"] = range };
if (aggregation != null) body["aggregation"] = aggregation;
var response = await _db.PostAsync<DataPointListResponse>(
$"/timeseries/{Uri.EscapeDataString(series)}/query", body, ct);
return response.Points ?? new List<DataPoint>();
}
}
/// <summary>
/// Perform a health check.
/// </summary>
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
try
{
var response = await GetAsync<HealthResponse>("/health", ct);
return response.Status == "healthy";
}
catch
{
return false;
}
}
public bool IsClosed => _disposed;
public void Dispose()
{
if (!_disposed)
{
_httpClient.Dispose();
_disposed = true;
}
GC.SuppressFinalize(this);
}
// Private HTTP methods
internal async Task<T> GetAsync<T>(string path, CancellationToken ct)
{
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.GetAsync(path, ct);
await EnsureSuccessAsync(response);
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)
?? throw new DatabaseException("Failed to deserialize response");
});
}
internal async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
{
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
await EnsureSuccessAsync(response);
var content = await response.Content.ReadAsStringAsync(ct);
if (string.IsNullOrEmpty(content)) return default!;
return JsonSerializer.Deserialize<T>(content, _jsonOptions)!;
});
}
internal async Task<T> PutAsync<T>(string path, object body, CancellationToken ct)
{
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.PutAsJsonAsync(path, body, _jsonOptions, ct);
await EnsureSuccessAsync(response);
var content = await response.Content.ReadAsStringAsync(ct);
if (string.IsNullOrEmpty(content)) return default!;
return JsonSerializer.Deserialize<T>(content, _jsonOptions)!;
});
}
internal async Task<T> PatchAsync<T>(string path, object body, CancellationToken ct)
{
return await ExecuteWithRetryAsync(async () =>
{
var request = new HttpRequestMessage(HttpMethod.Patch, path)
{
Content = JsonContent.Create(body, options: _jsonOptions)
};
var response = await _httpClient.SendAsync(request, ct);
await EnsureSuccessAsync(response);
var content = await response.Content.ReadAsStringAsync(ct);
if (string.IsNullOrEmpty(content)) return default!;
return JsonSerializer.Deserialize<T>(content, _jsonOptions)!;
});
}
internal async Task DeleteAsync(string path, CancellationToken ct)
{
await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.DeleteAsync(path, ct);
await EnsureSuccessAsync(response);
return true;
});
}
internal async Task DeleteAsync(string path, object body, CancellationToken ct)
{
await ExecuteWithRetryAsync(async () =>
{
var request = new HttpRequestMessage(HttpMethod.Delete, path)
{
Content = JsonContent.Create(body, options: _jsonOptions)
};
var response = await _httpClient.SendAsync(request, ct);
await EnsureSuccessAsync(response);
return true;
});
}
private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation)
{
Exception? lastError = null;
for (int attempt = 0; attempt < _config.Retries; attempt++)
{
try
{
return await operation();
}
catch (Exception ex)
{
lastError = ex;
if (_config.Debug) Console.WriteLine($"Attempt {attempt + 1} failed: {ex.Message}");
if (attempt < _config.Retries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(1 << attempt));
}
}
}
throw lastError ?? new DatabaseException("Unknown error");
}
private async Task EnsureSuccessAsync(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var error = JsonSerializer.Deserialize<Dictionary<string, object>>(content, _jsonOptions);
var message = error?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)response.StatusCode}";
var code = error?.GetValueOrDefault("code")?.ToString();
throw new DatabaseException(message, code, (int)response.StatusCode);
}
}
}

View file

@ -0,0 +1,152 @@
using System.Text.Json.Serialization;
namespace Synor.Sdk.Database;
/// <summary>
/// Database configuration.
/// </summary>
public record DatabaseConfig
{
public required string ApiKey { get; init; }
public string Endpoint { get; init; } = "https://db.synor.io/v1";
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(60);
public int Retries { get; init; } = 3;
public bool Debug { get; init; } = false;
}
/// <summary>
/// A key-value entry.
/// </summary>
public record KeyValue
{
public required string Key { get; init; }
public object? Value { get; init; }
public int? Ttl { get; init; }
public long? CreatedAt { get; init; }
public long? UpdatedAt { get; init; }
}
/// <summary>
/// A document.
/// </summary>
public record Document
{
public required string Id { get; init; }
public required string Collection { get; init; }
public required Dictionary<string, object> Data { get; init; }
public long CreatedAt { get; init; }
public long UpdatedAt { get; init; }
}
/// <summary>
/// Query parameters for documents.
/// </summary>
public record DocumentQuery
{
public Dictionary<string, object>? Filter { get; init; }
public Dictionary<string, int>? Sort { get; init; }
public int? Limit { get; init; }
public int? Offset { get; init; }
public List<string>? Projection { get; init; }
}
/// <summary>
/// A vector entry.
/// </summary>
public record VectorEntry
{
public required string Id { get; init; }
public required double[] Vector { get; init; }
public Dictionary<string, object>? Metadata { get; init; }
}
/// <summary>
/// A search result.
/// </summary>
public record SearchResult
{
public required string Id { get; init; }
public double Score { get; init; }
public double[]? Vector { get; init; }
public Dictionary<string, object>? Metadata { get; init; }
}
/// <summary>
/// A time series data point.
/// </summary>
public record DataPoint
{
public long Timestamp { get; init; }
public double Value { get; init; }
public Dictionary<string, string>? Tags { get; init; }
}
/// <summary>
/// Time range for queries.
/// </summary>
public record TimeRange
{
public long Start { get; init; }
public long End { get; init; }
}
/// <summary>
/// Aggregation settings.
/// </summary>
public record Aggregation
{
public required string Function { get; init; } // avg, sum, min, max, count
public required string Interval { get; init; } // 1m, 5m, 1h, 1d
}
// Internal response types
internal record KvGetResponse
{
public object? Value { get; init; }
}
internal record KvListResponse
{
public List<KeyValue>? Items { get; init; }
}
internal record CreateDocumentResponse
{
public required string Id { get; init; }
}
internal record DocumentListResponse
{
public List<Document>? Documents { get; init; }
}
internal record SearchListResponse
{
public List<SearchResult>? Results { get; init; }
}
internal record DataPointListResponse
{
public List<DataPoint>? Points { get; init; }
}
internal record HealthResponse
{
public required string Status { get; init; }
}
/// <summary>
/// Database exception.
/// </summary>
public class DatabaseException : Exception
{
public string? Code { get; }
public int StatusCode { get; }
public DatabaseException(string message, string? code = null, int statusCode = 0)
: base(message)
{
Code = code;
StatusCode = statusCode;
}
}

View file

@ -0,0 +1,204 @@
using System.Net.Http.Json;
using System.Text.Json;
namespace Synor.Sdk.Hosting;
/// <summary>
/// Synor Hosting SDK client for C#.
/// Provides decentralized web hosting with domain management, DNS, deployments, and SSL.
/// </summary>
public class SynorHosting : IDisposable
{
private readonly HostingConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
public SynorHosting(HostingConfig 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.SnakeCaseLower,
PropertyNameCaseInsensitive = true
};
}
// Domain Operations
public async Task<DomainAvailability> CheckAvailabilityAsync(string name, CancellationToken ct = default)
=> await GetAsync<DomainAvailability>($"/domains/check/{Uri.EscapeDataString(name)}", ct);
public async Task<Domain> RegisterDomainAsync(string name, RegisterDomainOptions? options = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["name"] = name };
if (options?.Years != null) body["years"] = options.Years;
if (options?.AutoRenew != null) body["autoRenew"] = options.AutoRenew;
return await PostAsync<Domain>("/domains", body, ct);
}
public async Task<Domain> GetDomainAsync(string name, CancellationToken ct = default)
=> await GetAsync<Domain>($"/domains/{Uri.EscapeDataString(name)}", ct);
public async Task<List<Domain>> ListDomainsAsync(CancellationToken ct = default)
{
var response = await GetAsync<DomainsResponse>("/domains", ct);
return response.Domains ?? new List<Domain>();
}
public async Task<DomainRecord> ResolveDomainAsync(string name, CancellationToken ct = default)
=> await GetAsync<DomainRecord>($"/domains/{Uri.EscapeDataString(name)}/resolve", ct);
public async Task<Domain> RenewDomainAsync(string name, int years, CancellationToken ct = default)
=> await PostAsync<Domain>($"/domains/{Uri.EscapeDataString(name)}/renew", new { years }, ct);
// DNS Operations
public async Task<DnsZone> GetDnsZoneAsync(string domain, CancellationToken ct = default)
=> await GetAsync<DnsZone>($"/dns/{Uri.EscapeDataString(domain)}", ct);
public async Task<DnsZone> SetDnsRecordsAsync(string domain, IEnumerable<DnsRecord> records, CancellationToken ct = default)
=> await PutAsync<DnsZone>($"/dns/{Uri.EscapeDataString(domain)}", new { records }, ct);
public async Task<DnsZone> AddDnsRecordAsync(string domain, DnsRecord record, CancellationToken ct = default)
=> await PostAsync<DnsZone>($"/dns/{Uri.EscapeDataString(domain)}/records", record, ct);
// Deployment Operations
public async Task<Deployment> DeployAsync(string cid, DeployOptions? options = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["cid"] = cid };
if (options?.Domain != null) body["domain"] = options.Domain;
if (options?.Subdomain != null) body["subdomain"] = options.Subdomain;
if (options?.Spa != null) body["spa"] = options.Spa;
if (options?.CleanUrls != null) body["cleanUrls"] = options.CleanUrls;
return await PostAsync<Deployment>("/deployments", body, ct);
}
public async Task<Deployment> GetDeploymentAsync(string id, CancellationToken ct = default)
=> await GetAsync<Deployment>($"/deployments/{Uri.EscapeDataString(id)}", ct);
public async Task<List<Deployment>> ListDeploymentsAsync(string? domain = null, CancellationToken ct = default)
{
var path = domain != null ? $"/deployments?domain={Uri.EscapeDataString(domain)}" : "/deployments";
var response = await GetAsync<DeploymentsResponse>(path, ct);
return response.Deployments ?? new List<Deployment>();
}
public async Task<Deployment> RollbackAsync(string domain, string deploymentId, CancellationToken ct = default)
=> await PostAsync<Deployment>($"/deployments/{Uri.EscapeDataString(deploymentId)}/rollback", new { domain }, ct);
public async Task DeleteDeploymentAsync(string id, CancellationToken ct = default)
=> await DeleteAsync($"/deployments/{Uri.EscapeDataString(id)}", ct);
// SSL Operations
public async Task<Certificate> ProvisionSslAsync(string domain, ProvisionSslOptions? options = null, CancellationToken ct = default)
{
var body = options ?? new ProvisionSslOptions();
return await PostAsync<Certificate>($"/ssl/{Uri.EscapeDataString(domain)}", body, ct);
}
public async Task<Certificate> GetCertificateAsync(string domain, CancellationToken ct = default)
=> await GetAsync<Certificate>($"/ssl/{Uri.EscapeDataString(domain)}", ct);
public async Task<Certificate> RenewCertificateAsync(string domain, CancellationToken ct = default)
=> await PostAsync<Certificate>($"/ssl/{Uri.EscapeDataString(domain)}/renew", new { }, ct);
// Analytics
public async Task<AnalyticsData> GetAnalyticsAsync(string domain, AnalyticsOptions? options = null, CancellationToken ct = default)
{
var path = $"/sites/{Uri.EscapeDataString(domain)}/analytics";
var query = new List<string>();
if (options?.Period != null) query.Add($"period={Uri.EscapeDataString(options.Period)}");
if (options?.Start != null) query.Add($"start={Uri.EscapeDataString(options.Start)}");
if (options?.End != null) query.Add($"end={Uri.EscapeDataString(options.End)}");
if (query.Count > 0) path += "?" + string.Join("&", query);
return await GetAsync<AnalyticsData>(path, ct);
}
public async Task<long> PurgeCacheAsync(string domain, IEnumerable<string>? paths = null, CancellationToken ct = default)
{
var body = paths != null ? new { paths } : null;
var response = await DeleteAsync<PurgeResponse>($"/sites/{Uri.EscapeDataString(domain)}/cache", body, ct);
return response.Purged;
}
// Lifecycle
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
try { var r = await GetAsync<HostingHealthResponse>("/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> 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> DeleteAsync<T>(string path, object? body, CancellationToken ct)
=> await ExecuteAsync(async () => {
var req = new HttpRequestMessage(HttpMethod.Delete, path);
if (body != null) req.Content = JsonContent.Create(body, options: _jsonOptions);
var r = await _httpClient.SendAsync(req, 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 HostingException(e?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)r.StatusCode}",
e?.GetValueOrDefault("code")?.ToString(), (int)r.StatusCode);
}
}
}

View file

@ -0,0 +1,152 @@
namespace Synor.Sdk.Hosting;
public record HostingConfig
{
public required string ApiKey { get; init; }
public string Endpoint { get; init; } = "https://hosting.synor.io/v1";
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(60);
public int Retries { get; init; } = 3;
public bool Debug { get; init; } = false;
}
public enum DomainStatus { Pending, Active, Expired, Suspended }
public enum DeploymentStatus { Pending, Building, Deploying, Active, Failed, Inactive }
public enum CertificateStatus { Pending, Issued, Expired, Revoked }
public enum DnsRecordType { A, AAAA, CNAME, TXT, MX, NS, SRV, CAA }
public record DomainRecord
{
public string? Cid { get; init; }
public List<string>? Ipv4 { get; init; }
public List<string>? Ipv6 { get; init; }
public string? Cname { get; init; }
public List<string>? Txt { get; init; }
public Dictionary<string, string>? Metadata { get; init; }
}
public record Domain
{
public required string Name { get; init; }
public DomainStatus Status { get; init; }
public required string Owner { get; init; }
public long RegisteredAt { get; init; }
public long ExpiresAt { get; init; }
public bool AutoRenew { get; init; }
public DomainRecord? Records { get; init; }
}
public record DomainAvailability
{
public required string Name { get; init; }
public bool Available { get; init; }
public double? Price { get; init; }
public bool Premium { get; init; }
}
public record RegisterDomainOptions
{
public int? Years { get; init; }
public bool? AutoRenew { get; init; }
}
public record DnsRecord
{
public DnsRecordType Type { get; init; }
public required string Name { get; init; }
public required string Value { get; init; }
public int Ttl { get; init; } = 3600;
public int? Priority { get; init; }
}
public record DnsZone
{
public required string Domain { get; init; }
public required List<DnsRecord> Records { get; init; }
public long UpdatedAt { get; init; }
}
public record Deployment
{
public required string Id { get; init; }
public required string Domain { get; init; }
public required string Cid { get; init; }
public DeploymentStatus Status { get; init; }
public required string Url { get; init; }
public long CreatedAt { get; init; }
public long UpdatedAt { get; init; }
public string? BuildLogs { get; init; }
public string? ErrorMessage { get; init; }
}
public record DeployOptions
{
public string? Domain { get; init; }
public string? Subdomain { get; init; }
public bool? Spa { get; init; }
public bool? CleanUrls { get; init; }
public bool? TrailingSlash { get; init; }
}
public record Certificate
{
public required string Domain { get; init; }
public CertificateStatus Status { get; init; }
public bool AutoRenew { get; init; }
public required string Issuer { get; init; }
public long? IssuedAt { get; init; }
public long? ExpiresAt { get; init; }
public string? Fingerprint { get; init; }
}
public record ProvisionSslOptions
{
public bool? IncludeWww { get; init; }
public bool? AutoRenew { get; init; }
}
public record SiteConfig
{
public required string Domain { get; init; }
public string? Cid { get; init; }
public Dictionary<string, string>? Headers { get; init; }
public bool Spa { get; init; }
public bool CleanUrls { get; init; } = true;
public bool TrailingSlash { get; init; }
}
public record PageView { public required string Path { get; init; } public long Views { get; init; } }
public record Referrer { public required string ReferrerUrl { get; init; } public long Count { get; init; } }
public record Country { public required string CountryCode { get; init; } public long Count { get; init; } }
public record AnalyticsData
{
public required string Domain { get; init; }
public required string Period { get; init; }
public long PageViews { get; init; }
public long UniqueVisitors { get; init; }
public long Bandwidth { get; init; }
public required List<PageView> TopPages { get; init; }
}
public record AnalyticsOptions
{
public string? Period { get; init; }
public string? Start { get; init; }
public string? End { get; init; }
}
internal record DomainsResponse { public List<Domain>? Domains { get; init; } }
internal record DeploymentsResponse { public List<Deployment>? Deployments { get; init; } }
internal record PurgeResponse { public long Purged { get; init; } }
internal record HostingHealthResponse { public required string Status { get; init; } }
public class HostingException : Exception
{
public string? Code { get; }
public int StatusCode { get; }
public HostingException(string message, string? code = null, int statusCode = 0) : base(message)
{
Code = code;
StatusCode = statusCode;
}
}

View file

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

View file

@ -0,0 +1,579 @@
# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorBridge
# Synor Bridge SDK client for Ruby.
# Cross-chain asset transfers with lock-mint and burn-unlock patterns.
class Client
FINAL_STATUSES = [
TransferStatus::COMPLETED,
TransferStatus::FAILED,
TransferStatus::REFUNDED
].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
# ==================== Chain Operations ====================
# Get supported chains
def get_supported_chains
response = get("/chains")
(response["chains"] || []).map { |c| parse_chain(c) }
end
# Get chain details
def get_chain(chain_id)
response = get("/chains/#{chain_id.downcase}")
parse_chain(response)
end
# Check if chain is supported
def chain_supported?(chain_id)
chain = get_chain(chain_id)
chain.supported
rescue StandardError
false
end
# ==================== Asset Operations ====================
# Get supported assets for a chain
def get_supported_assets(chain_id)
response = get("/chains/#{chain_id.downcase}/assets")
(response["assets"] || []).map { |a| parse_asset(a) }
end
# Get asset details
def get_asset(asset_id)
response = get("/assets/#{encode(asset_id)}")
parse_asset(response)
end
# Get wrapped asset info
def get_wrapped_asset(original_asset_id, target_chain)
response = get("/assets/#{encode(original_asset_id)}/wrapped/#{target_chain.downcase}")
parse_wrapped_asset(response)
end
# ==================== Fee Operations ====================
# Estimate transfer fee
def estimate_fee(asset:, amount:, source_chain:, target_chain:)
body = {
asset: asset,
amount: amount,
source_chain: source_chain.downcase,
target_chain: target_chain.downcase
}
response = post("/fees/estimate", body)
parse_fee_estimate(response)
end
# Get exchange rate
def get_exchange_rate(from_asset, to_asset)
response = get("/rates/#{encode(from_asset)}/#{encode(to_asset)}")
parse_exchange_rate(response)
end
# ==================== Lock-Mint Flow ====================
# Lock assets for cross-chain transfer
def lock(asset:, amount:, target_chain:, options: nil)
body = {
asset: asset,
amount: amount,
target_chain: target_chain.downcase
}
if options
body[:recipient] = options.recipient if options.recipient
body[:deadline] = options.deadline if options.deadline
body[:slippage] = options.slippage if options.slippage
end
response = post("/transfers/lock", body)
parse_lock_receipt(response)
end
# Get lock proof
def get_lock_proof(lock_receipt_id)
response = get("/transfers/lock/#{encode(lock_receipt_id)}/proof")
parse_lock_proof(response)
end
# Wait for lock proof
def wait_for_lock_proof(lock_receipt_id, poll_interval: 5, max_wait: 600)
deadline = Time.now + max_wait
while Time.now < deadline
begin
return get_lock_proof(lock_receipt_id)
rescue HttpError => e
raise e unless e.confirmations_pending?
sleep(poll_interval)
end
end
raise HttpError.new("Timeout waiting for lock proof", code: "CONFIRMATIONS_PENDING")
end
# Mint wrapped assets
def mint(proof:, target_address:, options: nil)
body = { proof: proof_to_hash(proof), target_address: target_address }
if options
body[:gas_limit] = options.gas_limit if options.gas_limit
body[:max_fee_per_gas] = options.max_fee_per_gas if options.max_fee_per_gas
body[:max_priority_fee_per_gas] = options.max_priority_fee_per_gas if options.max_priority_fee_per_gas
end
response = post("/transfers/mint", body)
parse_signed_transaction(response)
end
# ==================== Burn-Unlock Flow ====================
# Burn wrapped assets
def burn(wrapped_asset:, amount:, options: nil)
body = { wrapped_asset: wrapped_asset, amount: amount }
if options
body[:recipient] = options.recipient if options.recipient
body[:deadline] = options.deadline if options.deadline
end
response = post("/transfers/burn", body)
parse_burn_receipt(response)
end
# Get burn proof
def get_burn_proof(burn_receipt_id)
response = get("/transfers/burn/#{encode(burn_receipt_id)}/proof")
parse_burn_proof(response)
end
# Wait for burn proof
def wait_for_burn_proof(burn_receipt_id, poll_interval: 5, max_wait: 600)
deadline = Time.now + max_wait
while Time.now < deadline
begin
return get_burn_proof(burn_receipt_id)
rescue HttpError => e
raise e unless e.confirmations_pending?
sleep(poll_interval)
end
end
raise HttpError.new("Timeout waiting for burn proof", code: "CONFIRMATIONS_PENDING")
end
# Unlock original assets
def unlock(proof:, options: nil)
body = { proof: burn_proof_to_hash(proof) }
if options
body[:gas_limit] = options.gas_limit if options.gas_limit
body[:gas_price] = options.gas_price if options.gas_price
end
response = post("/transfers/unlock", body)
parse_signed_transaction(response)
end
# ==================== Transfer Management ====================
# Get transfer details
def get_transfer(transfer_id)
response = get("/transfers/#{encode(transfer_id)}")
parse_transfer(response)
end
# Get transfer status
def get_transfer_status(transfer_id)
transfer = get_transfer(transfer_id)
transfer.status
end
# List transfers
def list_transfers(filter: nil)
params = {}
if filter
params[:status] = filter.status.downcase if filter.status
params[:source_chain] = filter.source_chain.downcase if filter.source_chain
params[:target_chain] = filter.target_chain.downcase if filter.target_chain
params[:limit] = filter.limit if filter.limit
params[:offset] = filter.offset if filter.offset
end
response = get("/transfers", params)
(response["transfers"] || []).map { |t| parse_transfer(t) }
end
# Wait for transfer completion
def wait_for_transfer(transfer_id, poll_interval: 10, max_wait: 1800)
deadline = Time.now + max_wait
while Time.now < deadline
transfer = get_transfer(transfer_id)
return transfer if FINAL_STATUSES.include?(transfer.status)
sleep(poll_interval)
end
raise Error, "Timeout waiting for transfer completion"
end
# ==================== Convenience Methods ====================
# Bridge assets to another chain (lock-mint flow)
def bridge_to(asset:, amount:, target_chain:, target_address:, lock_options: nil, mint_options: nil)
receipt = lock(asset: asset, amount: amount, target_chain: target_chain, options: lock_options)
puts "Locked: #{receipt.id}, waiting for confirmations..." if @config.debug
proof = wait_for_lock_proof(receipt.id)
puts "Proof ready, minting on #{target_chain}..." if @config.debug
mint(proof: proof, target_address: target_address, options: mint_options)
wait_for_transfer(receipt.id)
end
# Bridge assets back to original chain (burn-unlock flow)
def bridge_back(wrapped_asset:, amount:, burn_options: nil, unlock_options: nil)
receipt = burn(wrapped_asset: wrapped_asset, amount: amount, options: burn_options)
puts "Burned: #{receipt.id}, waiting for confirmations..." if @config.debug
proof = wait_for_burn_proof(receipt.id)
puts "Proof ready, unlocking on #{receipt.target_chain}..." if @config.debug
unlock(proof: proof, options: unlock_options)
wait_for_transfer(receipt.id)
end
# ==================== Lifecycle ====================
# Health check
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
# Close the client
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_chain(data)
return nil unless data
Chain.new(
id: data["id"],
name: data["name"],
chain_id: data["chain_id"],
rpc_url: data["rpc_url"],
explorer_url: data["explorer_url"],
native_currency: data["native_currency"] ? NativeCurrency.new(
name: data["native_currency"]["name"],
symbol: data["native_currency"]["symbol"],
decimals: data["native_currency"]["decimals"]
) : nil,
confirmations: data["confirmations"],
estimated_block_time: data["estimated_block_time"],
supported: data["supported"]
)
end
def parse_asset(data)
return nil unless data
Asset.new(
id: data["id"],
symbol: data["symbol"],
name: data["name"],
type: data["type"],
chain: data["chain"],
contract_address: data["contract_address"],
decimals: data["decimals"],
logo_url: data["logo_url"],
verified: data["verified"]
)
end
def parse_wrapped_asset(data)
return nil unless data
WrappedAsset.new(
original_asset: parse_asset(data["original_asset"]),
wrapped_asset: parse_asset(data["wrapped_asset"]),
chain: data["chain"],
bridge_contract: data["bridge_contract"]
)
end
def parse_lock_receipt(data)
return nil unless data
LockReceipt.new(
id: data["id"],
tx_hash: data["tx_hash"],
source_chain: data["source_chain"],
target_chain: data["target_chain"],
asset: parse_asset(data["asset"]),
amount: data["amount"],
sender: data["sender"],
recipient: data["recipient"],
lock_timestamp: data["lock_timestamp"],
confirmations: data["confirmations"],
required_confirmations: data["required_confirmations"]
)
end
def parse_lock_proof(data)
return nil unless data
LockProof.new(
lock_receipt: parse_lock_receipt(data["lock_receipt"]),
merkle_proof: data["merkle_proof"],
block_header: data["block_header"],
signatures: (data["signatures"] || []).map do |s|
ValidatorSignature.new(
validator: s["validator"],
signature: s["signature"],
timestamp: s["timestamp"]
)
end
)
end
def parse_burn_receipt(data)
return nil unless data
BurnReceipt.new(
id: data["id"],
tx_hash: data["tx_hash"],
source_chain: data["source_chain"],
target_chain: data["target_chain"],
wrapped_asset: parse_asset(data["wrapped_asset"]),
original_asset: parse_asset(data["original_asset"]),
amount: data["amount"],
sender: data["sender"],
recipient: data["recipient"],
burn_timestamp: data["burn_timestamp"],
confirmations: data["confirmations"],
required_confirmations: data["required_confirmations"]
)
end
def parse_burn_proof(data)
return nil unless data
BurnProof.new(
burn_receipt: parse_burn_receipt(data["burn_receipt"]),
merkle_proof: data["merkle_proof"],
block_header: data["block_header"],
signatures: (data["signatures"] || []).map do |s|
ValidatorSignature.new(
validator: s["validator"],
signature: s["signature"],
timestamp: s["timestamp"]
)
end
)
end
def parse_transfer(data)
return nil unless data
Transfer.new(
id: data["id"],
direction: data["direction"],
status: data["status"],
source_chain: data["source_chain"],
target_chain: data["target_chain"],
asset: parse_asset(data["asset"]),
amount: data["amount"],
sender: data["sender"],
recipient: data["recipient"],
source_tx_hash: data["source_tx_hash"],
target_tx_hash: data["target_tx_hash"],
fee: data["fee"],
fee_asset: parse_asset(data["fee_asset"]),
created_at: data["created_at"],
updated_at: data["updated_at"],
completed_at: data["completed_at"],
error_message: data["error_message"]
)
end
def parse_fee_estimate(data)
return nil unless data
FeeEstimate.new(
bridge_fee: data["bridge_fee"],
gas_fee_source: data["gas_fee_source"],
gas_fee_target: data["gas_fee_target"],
total_fee: data["total_fee"],
fee_asset: parse_asset(data["fee_asset"]),
estimated_time: data["estimated_time"],
exchange_rate: data["exchange_rate"]
)
end
def parse_exchange_rate(data)
return nil unless data
ExchangeRate.new(
from_asset: parse_asset(data["from_asset"]),
to_asset: parse_asset(data["to_asset"]),
rate: data["rate"],
inverse_rate: data["inverse_rate"],
last_updated: data["last_updated"],
source: data["source"]
)
end
def parse_signed_transaction(data)
return nil unless data
SignedTransaction.new(
tx_hash: data["tx_hash"],
chain: data["chain"],
from: data["from"],
to: data["to"],
value: data["value"],
data: data["data"],
gas_limit: data["gas_limit"],
gas_price: data["gas_price"],
max_fee_per_gas: data["max_fee_per_gas"],
max_priority_fee_per_gas: data["max_priority_fee_per_gas"],
nonce: data["nonce"],
signature: data["signature"]
)
end
# Conversion methods
def proof_to_hash(proof)
{
lock_receipt: lock_receipt_to_hash(proof.lock_receipt),
merkle_proof: proof.merkle_proof,
block_header: proof.block_header,
signatures: proof.signatures.map { |s| signature_to_hash(s) }
}
end
def lock_receipt_to_hash(receipt)
{
id: receipt.id,
tx_hash: receipt.tx_hash,
source_chain: receipt.source_chain,
target_chain: receipt.target_chain,
asset: asset_to_hash(receipt.asset),
amount: receipt.amount,
sender: receipt.sender,
recipient: receipt.recipient,
lock_timestamp: receipt.lock_timestamp,
confirmations: receipt.confirmations,
required_confirmations: receipt.required_confirmations
}
end
def burn_proof_to_hash(proof)
{
burn_receipt: burn_receipt_to_hash(proof.burn_receipt),
merkle_proof: proof.merkle_proof,
block_header: proof.block_header,
signatures: proof.signatures.map { |s| signature_to_hash(s) }
}
end
def burn_receipt_to_hash(receipt)
{
id: receipt.id,
tx_hash: receipt.tx_hash,
source_chain: receipt.source_chain,
target_chain: receipt.target_chain,
wrapped_asset: asset_to_hash(receipt.wrapped_asset),
original_asset: asset_to_hash(receipt.original_asset),
amount: receipt.amount,
sender: receipt.sender,
recipient: receipt.recipient,
burn_timestamp: receipt.burn_timestamp,
confirmations: receipt.confirmations,
required_confirmations: receipt.required_confirmations
}
end
def asset_to_hash(asset)
return nil unless asset
{
id: asset.id,
symbol: asset.symbol,
name: asset.name,
type: asset.type,
chain: asset.chain,
contract_address: asset.contract_address,
decimals: asset.decimals,
logo_url: asset.logo_url,
verified: asset.verified
}
end
def signature_to_hash(sig)
{
validator: sig.validator,
signature: sig.signature,
timestamp: sig.timestamp
}
end
end
end

View file

@ -0,0 +1,264 @@
# frozen_string_literal: true
module SynorBridge
# Chain identifiers
module ChainId
SYNOR = "synor"
ETHEREUM = "ethereum"
POLYGON = "polygon"
ARBITRUM = "arbitrum"
OPTIMISM = "optimism"
BSC = "bsc"
AVALANCHE = "avalanche"
SOLANA = "solana"
COSMOS = "cosmos"
end
# Asset types
module AssetType
NATIVE = "native"
ERC20 = "erc20"
ERC721 = "erc721"
ERC1155 = "erc1155"
end
# Transfer status
module TransferStatus
PENDING = "pending"
LOCKED = "locked"
CONFIRMING = "confirming"
MINTING = "minting"
COMPLETED = "completed"
FAILED = "failed"
REFUNDED = "refunded"
end
# Transfer direction
module TransferDirection
LOCK_MINT = "lock_mint"
BURN_UNLOCK = "burn_unlock"
end
# Native currency
NativeCurrency = Struct.new(
:name,
:symbol,
:decimals,
keyword_init: true
)
# Chain
Chain = Struct.new(
:id,
:name,
:chain_id,
:rpc_url,
:explorer_url,
:native_currency,
:confirmations,
:estimated_block_time,
:supported,
keyword_init: true
)
# Asset
Asset = Struct.new(
:id,
:symbol,
:name,
:type,
:chain,
:contract_address,
:decimals,
:logo_url,
:verified,
keyword_init: true
)
# Wrapped asset
WrappedAsset = Struct.new(
:original_asset,
:wrapped_asset,
:chain,
:bridge_contract,
keyword_init: true
)
# Validator signature
ValidatorSignature = Struct.new(
:validator,
:signature,
:timestamp,
keyword_init: true
)
# Lock receipt
LockReceipt = Struct.new(
:id,
:tx_hash,
:source_chain,
:target_chain,
:asset,
:amount,
:sender,
:recipient,
:lock_timestamp,
:confirmations,
:required_confirmations,
keyword_init: true
)
# Lock proof
LockProof = Struct.new(
:lock_receipt,
:merkle_proof,
:block_header,
:signatures,
keyword_init: true
)
# Burn receipt
BurnReceipt = Struct.new(
:id,
:tx_hash,
:source_chain,
:target_chain,
:wrapped_asset,
:original_asset,
:amount,
:sender,
:recipient,
:burn_timestamp,
:confirmations,
:required_confirmations,
keyword_init: true
)
# Burn proof
BurnProof = Struct.new(
:burn_receipt,
:merkle_proof,
:block_header,
:signatures,
keyword_init: true
)
# Transfer
Transfer = Struct.new(
:id,
:direction,
:status,
:source_chain,
:target_chain,
:asset,
:amount,
:sender,
:recipient,
:source_tx_hash,
:target_tx_hash,
:fee,
:fee_asset,
:created_at,
:updated_at,
:completed_at,
:error_message,
keyword_init: true
)
# Fee estimate
FeeEstimate = Struct.new(
:bridge_fee,
:gas_fee_source,
:gas_fee_target,
:total_fee,
:fee_asset,
:estimated_time,
:exchange_rate,
keyword_init: true
)
# Exchange rate
ExchangeRate = Struct.new(
:from_asset,
:to_asset,
:rate,
:inverse_rate,
:last_updated,
:source,
keyword_init: true
)
# Signed transaction
SignedTransaction = Struct.new(
:tx_hash,
:chain,
:from,
:to,
:value,
:data,
:gas_limit,
:gas_price,
:max_fee_per_gas,
:max_priority_fee_per_gas,
:nonce,
:signature,
keyword_init: true
)
# Lock options
LockOptions = Struct.new(
:recipient,
:deadline,
:slippage,
keyword_init: true
)
# Mint options
MintOptions = Struct.new(
:gas_limit,
:max_fee_per_gas,
:max_priority_fee_per_gas,
keyword_init: true
)
# Burn options
BurnOptions = Struct.new(
:recipient,
:deadline,
keyword_init: true
)
# Unlock options
UnlockOptions = Struct.new(
:gas_limit,
:gas_price,
keyword_init: true
)
# Transfer filter
TransferFilter = Struct.new(
:status,
:source_chain,
:target_chain,
:asset,
:sender,
:recipient,
:limit,
:offset,
keyword_init: true
)
# Bridge config
BridgeConfig = Struct.new(
:api_key,
:endpoint,
:timeout,
:retries,
:debug,
keyword_init: true
) do
def initialize(api_key:, endpoint: "https://bridge.synor.io/v1", timeout: 60, retries: 3, debug: false)
super(api_key: api_key, endpoint: endpoint, timeout: timeout, retries: retries, debug: debug)
end
end
end

View file

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

View file

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

View file

@ -0,0 +1,206 @@
# frozen_string_literal: true
require "faraday"
require "json"
require "cgi"
module SynorDatabase
# Synor Database SDK Client
#
# @example
# client = SynorDatabase::Client.new(api_key: 'your-api-key')
#
# # Key-Value operations
# client.kv.set("key", "value")
# value = client.kv.get("key")
#
# # Document operations
# id = client.documents.create("users", { name: "Alice" })
# doc = client.documents.get("users", id)
#
class Client
attr_reader :config, :kv, :documents, :vectors, :timeseries
def initialize(api_key: nil, **options)
@config = Config.new(api_key: api_key, **options)
raise ArgumentError, "API key is required" unless @config.api_key
@conn = Faraday.new(url: @config.base_url) do |f|
f.request :json
f.response :json
f.options.timeout = @config.timeout
f.headers["Authorization"] = "Bearer #{@config.api_key}"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
@closed = false
@kv = KeyValueStore.new(self)
@documents = DocumentStore.new(self)
@vectors = VectorStore.new(self)
@timeseries = TimeSeriesStore.new(self)
end
def closed?
@closed
end
def close
@closed = true
end
def health_check
response = request(:get, "/health")
response["status"] == "healthy"
rescue StandardError
false
end
# Key-Value Store
class KeyValueStore
def initialize(client)
@client = client
end
def get(key)
response = @client.request(:get, "/kv/#{CGI.escape(key)}")
response["value"]
end
def set(key, value, ttl: nil)
body = { key: key, value: value }
body[:ttl] = ttl if ttl
@client.request(:put, "/kv/#{CGI.escape(key)}", body)
end
def delete(key)
@client.request(:delete, "/kv/#{CGI.escape(key)}")
end
def list(prefix)
response = @client.request(:get, "/kv?prefix=#{CGI.escape(prefix)}")
(response["items"] || []).map { |h| KeyValue.from_hash(h) }
end
end
# Document Store
class DocumentStore
def initialize(client)
@client = client
end
def create(collection, document)
response = @client.request(:post, "/collections/#{CGI.escape(collection)}/documents", document)
response["id"]
end
def get(collection, id)
response = @client.request(:get, "/collections/#{CGI.escape(collection)}/documents/#{CGI.escape(id)}")
Document.from_hash(response)
end
def update(collection, id, update)
@client.request(:patch, "/collections/#{CGI.escape(collection)}/documents/#{CGI.escape(id)}", update)
end
def delete(collection, id)
@client.request(:delete, "/collections/#{CGI.escape(collection)}/documents/#{CGI.escape(id)}")
end
def query(collection, query)
body = query.to_h.compact
response = @client.request(:post, "/collections/#{CGI.escape(collection)}/query", body)
(response["documents"] || []).map { |h| Document.from_hash(h) }
end
end
# Vector Store
class VectorStore
def initialize(client)
@client = client
end
def upsert(collection, vectors)
entries = vectors.map { |v| v.is_a?(Hash) ? v : v.to_h }
@client.request(:post, "/vectors/#{CGI.escape(collection)}/upsert", { vectors: entries })
end
def search(collection, vector, k)
response = @client.request(:post, "/vectors/#{CGI.escape(collection)}/search", { vector: vector, k: k })
(response["results"] || []).map { |h| SearchResult.from_hash(h) }
end
def delete(collection, ids)
@client.request(:delete, "/vectors/#{CGI.escape(collection)}", { ids: ids })
end
end
# Time Series Store
class TimeSeriesStore
def initialize(client)
@client = client
end
def write(series, points)
entries = points.map { |p| p.is_a?(Hash) ? p : p.to_h }
@client.request(:post, "/timeseries/#{CGI.escape(series)}/write", { points: entries })
end
def query(series, range, aggregation: nil)
body = { range: range.to_h }
body[:aggregation] = aggregation.to_h if aggregation
response = @client.request(:post, "/timeseries/#{CGI.escape(series)}/query", body)
(response["points"] || []).map { |h| DataPoint.from_hash(h) }
end
end
# Internal request method
def request(method, path, body = nil)
check_closed!
with_retry do
response = case method
when :get then @conn.get(path)
when :post then @conn.post(path, body)
when :put then @conn.put(path, body)
when :patch then @conn.patch(path, body)
when :delete
if body
@conn.delete(path) { |req| req.body = body.to_json }
else
@conn.delete(path)
end
end
raise_on_error(response) unless response.success?
response.body || {}
end
end
private
def check_closed!
raise ClientClosedError, "Client has been closed" if @closed
end
def with_retry
last_error = nil
@config.retries.times do |attempt|
begin
return yield
rescue StandardError => e
last_error = e
puts "Attempt #{attempt + 1} failed: #{e.message}" if @config.debug
sleep(2**attempt) if attempt < @config.retries - 1
end
end
raise last_error
end
def raise_on_error(response)
body = response.body || {}
message = body["message"] || "HTTP #{response.status}"
code = body["code"]
raise HttpError.new(message, status_code: response.status, code: code)
end
end
end

View file

@ -0,0 +1,71 @@
# frozen_string_literal: true
module SynorDatabase
# Configuration for the database client
Config = Struct.new(:api_key, :base_url, :timeout, :retries, :debug, keyword_init: true) do
def initialize(api_key:, base_url: "https://db.synor.io/v1", timeout: 60, retries: 3, debug: false)
super(api_key: api_key, base_url: base_url, timeout: timeout, retries: retries, debug: debug)
end
end
# A key-value entry
KeyValue = Struct.new(:key, :value, :ttl, :created_at, :updated_at, keyword_init: true) do
def self.from_hash(hash)
new(
key: hash["key"],
value: hash["value"],
ttl: hash["ttl"],
created_at: hash["created_at"],
updated_at: hash["updated_at"]
)
end
end
# A document
Document = Struct.new(:id, :collection, :data, :created_at, :updated_at, keyword_init: true) do
def self.from_hash(hash)
new(
id: hash["id"],
collection: hash["collection"],
data: hash["data"],
created_at: hash["created_at"],
updated_at: hash["updated_at"]
)
end
end
# Query parameters for documents
Query = Struct.new(:filter, :sort, :limit, :offset, :projection, keyword_init: true)
# A vector entry
VectorEntry = Struct.new(:id, :vector, :metadata, keyword_init: true)
# A search result
SearchResult = Struct.new(:id, :score, :vector, :metadata, keyword_init: true) do
def self.from_hash(hash)
new(
id: hash["id"],
score: hash["score"],
vector: hash["vector"],
metadata: hash["metadata"]
)
end
end
# A time series data point
DataPoint = Struct.new(:timestamp, :value, :tags, keyword_init: true) do
def self.from_hash(hash)
new(
timestamp: hash["timestamp"],
value: hash["value"],
tags: hash["tags"]
)
end
end
# Time range for queries
TimeRange = Struct.new(:start, :end, keyword_init: true)
# Aggregation settings
Aggregation = Struct.new(:function, :interval, keyword_init: true)
end

View file

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

View file

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

View file

@ -0,0 +1,357 @@
# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorHosting
# Synor Hosting SDK client for Ruby.
# Provides domain management, DNS, deployments, SSL, and analytics.
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
# ==================== Domain Operations ====================
# Register a new domain
def register_domain(name, auto_renew: true)
body = { name: name, auto_renew: auto_renew }
response = post("/domains", body)
parse_domain(response)
end
# Get domain details
def get_domain(name)
response = get("/domains/#{encode(name)}")
parse_domain(response)
end
# Resolve domain to its record
def resolve_domain(name)
response = get("/domains/#{encode(name)}/resolve")
DomainRecord.new(
cid: response["cid"],
ttl: response["ttl"],
updated_at: response["updated_at"]
)
end
# Update domain record
def update_domain(name, cid:, ttl: nil)
body = { cid: cid }
body[:ttl] = ttl if ttl
response = put("/domains/#{encode(name)}", body)
parse_domain(response)
end
# Transfer domain ownership
def transfer_domain(name, new_owner)
body = { new_owner: new_owner }
response = post("/domains/#{encode(name)}/transfer", body)
parse_domain(response)
end
# List domains
def list_domains(limit: nil, offset: nil)
params = {}
params[:limit] = limit if limit
params[:offset] = offset if offset
response = get("/domains", params)
(response["domains"] || []).map { |d| parse_domain(d) }
end
# ==================== DNS Operations ====================
# Set DNS records for a domain
def set_dns_records(domain, records)
body = { records: records.map { |r| record_to_hash(r) } }
response = put("/domains/#{encode(domain)}/dns", body)
(response["records"] || []).map { |r| parse_dns_record(r) }
end
# Get DNS records for a domain
def get_dns_records(domain)
response = get("/domains/#{encode(domain)}/dns")
(response["records"] || []).map { |r| parse_dns_record(r) }
end
# Add a single DNS record
def add_dns_record(domain, record)
body = record_to_hash(record)
response = post("/domains/#{encode(domain)}/dns", body)
parse_dns_record(response)
end
# Delete a DNS record
def delete_dns_record(domain, record_type, name)
delete("/domains/#{encode(domain)}/dns/#{record_type}/#{encode(name)}")
nil
end
# ==================== Deployment Operations ====================
# Deploy content to a domain
def deploy(cid, domain, options = {})
body = { cid: cid, domain: domain }
body[:build_command] = options[:build_command] if options[:build_command]
body[:env_vars] = options[:env_vars] if options[:env_vars]
response = post("/deployments", body)
parse_deployment(response)
end
# Get deployment details
def get_deployment(id)
response = get("/deployments/#{encode(id)}")
parse_deployment(response)
end
# List deployments
def list_deployments(domain: nil, limit: nil, offset: nil)
params = {}
params[:domain] = domain if domain
params[:limit] = limit if limit
params[:offset] = offset if offset
response = get("/deployments", params)
(response["deployments"] || []).map { |d| parse_deployment(d) }
end
# Wait for deployment to complete
def wait_for_deployment(id, poll_interval: 5, max_wait: 600)
deadline = Time.now + max_wait
final_statuses = [DeploymentStatus::ACTIVE, DeploymentStatus::FAILED, DeploymentStatus::ROLLED_BACK]
while Time.now < deadline
deployment = get_deployment(id)
return deployment if final_statuses.include?(deployment.status)
sleep(poll_interval)
end
raise Error, "Timeout waiting for deployment"
end
# Rollback deployment
def rollback(domain, deployment_id)
body = { deployment_id: deployment_id }
response = post("/domains/#{encode(domain)}/rollback", body)
parse_deployment(response)
end
# ==================== SSL Operations ====================
# Provision SSL certificate for a domain
def provision_ssl(domain)
response = post("/domains/#{encode(domain)}/ssl", {})
parse_certificate(response)
end
# Get SSL certificate for a domain
def get_certificate(domain)
response = get("/domains/#{encode(domain)}/ssl")
parse_certificate(response)
end
# Renew SSL certificate
def renew_certificate(domain)
response = post("/domains/#{encode(domain)}/ssl/renew", {})
parse_certificate(response)
end
# ==================== Analytics Operations ====================
# Get analytics for a domain
def get_analytics(domain, start_time:, end_time:)
params = { start: start_time.to_i, end: end_time.to_i }
response = get("/domains/#{encode(domain)}/analytics", params)
parse_analytics(response)
end
# Get bandwidth stats
def get_bandwidth(domain, start_time:, end_time:)
params = { start: start_time.to_i, end: end_time.to_i }
response = get("/domains/#{encode(domain)}/analytics/bandwidth", params)
BandwidthStats.new(
total_bytes: response["total_bytes"],
cached_bytes: response["cached_bytes"],
uncached_bytes: response["uncached_bytes"]
)
end
# ==================== Lifecycle ====================
# Health check
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
# Close the client
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 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"]
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
def record_to_hash(record)
hash = {
type: record.type,
name: record.name,
value: record.value,
ttl: record.ttl
}
hash[:priority] = record.priority if record.priority
hash
end
def parse_domain(data)
return nil unless data
Domain.new(
name: data["name"],
owner: data["owner"],
status: data["status"],
record: data["record"] ? DomainRecord.new(
cid: data["record"]["cid"],
ttl: data["record"]["ttl"],
updated_at: data["record"]["updated_at"]
) : nil,
expires_at: data["expires_at"],
created_at: data["created_at"],
auto_renew: data["auto_renew"]
)
end
def parse_dns_record(data)
return nil unless data
DnsRecord.new(
type: data["type"],
name: data["name"],
value: data["value"],
ttl: data["ttl"],
priority: data["priority"]
)
end
def parse_deployment(data)
return nil unless data
Deployment.new(
id: data["id"],
domain: data["domain"],
cid: data["cid"],
status: data["status"],
url: data["url"],
created_at: data["created_at"],
deployed_at: data["deployed_at"],
build_logs: data["build_logs"],
error_message: data["error_message"]
)
end
def parse_certificate(data)
return nil unless data
Certificate.new(
domain: data["domain"],
status: data["status"],
issuer: data["issuer"],
issued_at: data["issued_at"],
expires_at: data["expires_at"],
auto_renew: data["auto_renew"],
fingerprint: data["fingerprint"]
)
end
def parse_analytics(data)
return nil unless data
Analytics.new(
domain: data["domain"],
time_range: AnalyticsTimeRange.new(
start_time: data["time_range"]&.dig("start"),
end_time: data["time_range"]&.dig("end")
),
requests: data["requests"] ? RequestStats.new(
total: data["requests"]["total"],
success: data["requests"]["success"],
error: data["requests"]["error"],
cached: data["requests"]["cached"]
) : nil,
bandwidth: data["bandwidth"] ? BandwidthStats.new(
total_bytes: data["bandwidth"]["total_bytes"],
cached_bytes: data["bandwidth"]["cached_bytes"],
uncached_bytes: data["bandwidth"]["uncached_bytes"]
) : nil,
visitors: data["visitors"],
page_views: data["page_views"],
top_paths: (data["top_paths"] || []).map do |p|
PathStats.new(path: p["path"], requests: p["requests"], bandwidth: p["bandwidth"])
end,
geo_distribution: (data["geo_distribution"] || []).map do |g|
GeoStats.new(country: g["country"], requests: g["requests"], bandwidth: g["bandwidth"])
end
)
end
end
end

View file

@ -0,0 +1,170 @@
# frozen_string_literal: true
module SynorHosting
# Domain status
module DomainStatus
PENDING = "pending"
ACTIVE = "active"
SUSPENDED = "suspended"
EXPIRED = "expired"
end
# DNS record types
module DnsRecordType
A = "A"
AAAA = "AAAA"
CNAME = "CNAME"
TXT = "TXT"
MX = "MX"
NS = "NS"
SRV = "SRV"
end
# Deployment status
module DeploymentStatus
PENDING = "pending"
BUILDING = "building"
DEPLOYING = "deploying"
ACTIVE = "active"
FAILED = "failed"
ROLLED_BACK = "rolled_back"
end
# Certificate status
module CertificateStatus
PENDING = "pending"
ISSUED = "issued"
EXPIRED = "expired"
REVOKED = "revoked"
FAILED = "failed"
end
# Domain record
DomainRecord = Struct.new(
:cid,
:ttl,
:updated_at,
keyword_init: true
)
# Domain
Domain = Struct.new(
:name,
:owner,
:status,
:record,
:expires_at,
:created_at,
:auto_renew,
keyword_init: true
)
# DNS record
DnsRecord = Struct.new(
:type,
:name,
:value,
:ttl,
:priority,
keyword_init: true
)
# Deployment
Deployment = Struct.new(
:id,
:domain,
:cid,
:status,
:url,
:created_at,
:deployed_at,
:build_logs,
:error_message,
keyword_init: true
)
# Certificate
Certificate = Struct.new(
:domain,
:status,
:issuer,
:issued_at,
:expires_at,
:auto_renew,
:fingerprint,
keyword_init: true
)
# Analytics time range
AnalyticsTimeRange = Struct.new(
:start_time,
:end_time,
keyword_init: true
)
# Bandwidth stats
BandwidthStats = Struct.new(
:total_bytes,
:cached_bytes,
:uncached_bytes,
keyword_init: true
)
# Request stats
RequestStats = Struct.new(
:total,
:success,
:error,
:cached,
keyword_init: true
)
# Geographic stats
GeoStats = Struct.new(
:country,
:requests,
:bandwidth,
keyword_init: true
)
# Analytics
Analytics = Struct.new(
:domain,
:time_range,
:requests,
:bandwidth,
:visitors,
:page_views,
:top_paths,
:geo_distribution,
keyword_init: true
)
# Path stats
PathStats = Struct.new(
:path,
:requests,
:bandwidth,
keyword_init: true
)
# Hosting config
HostingConfig = Struct.new(
:api_key,
:endpoint,
:timeout,
:retries,
:debug,
keyword_init: true
) do
def initialize(api_key:, endpoint: "https://hosting.synor.io/v1", timeout: 30, retries: 3, debug: false)
super(api_key: api_key, endpoint: endpoint, timeout: timeout, retries: retries, debug: debug)
end
end
# Health response
HealthResponse = Struct.new(
:status,
keyword_init: true
)
end

View file

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

View file

@ -0,0 +1,467 @@
import Foundation
/// Synor Bridge SDK client for Swift.
///
/// Provides cross-chain asset transfers with lock-mint and burn-unlock patterns.
///
/// Example:
/// ```swift
/// let bridge = SynorBridge(config: BridgeConfig(apiKey: "your-api-key"))
///
/// // Get supported chains
/// let chains = try await bridge.getSupportedChains()
///
/// // Estimate fee
/// let fee = try await bridge.estimateFee(
/// asset: "SYNR",
/// amount: "1000000000000000000",
/// sourceChain: .synor,
/// targetChain: .ethereum
/// )
///
/// // Full bridge flow
/// let transfer = try await bridge.bridgeTo(
/// asset: "SYNR",
/// amount: "1000000000000000000",
/// targetChain: .ethereum,
/// targetAddress: "0x..."
/// )
/// print("Transfer completed: \(transfer.id)")
/// ```
public class SynorBridge {
private let config: BridgeConfig
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private var closed = false
private static let finalStatuses: Set<TransferStatus> = [.completed, .failed, .refunded]
public init(config: BridgeConfig) {
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: - Chain Operations
/// Get all supported chains.
public func getSupportedChains() async throws -> [Chain] {
let response: ChainsResponse = try await get(path: "/chains")
return response.chains ?? []
}
/// Get chain details.
public func getChain(chainId: ChainId) async throws -> Chain {
return try await get(path: "/chains/\(chainId.rawValue)")
}
/// Check if a chain is supported.
public func isChainSupported(chainId: ChainId) async -> Bool {
do {
let chain = try await getChain(chainId: chainId)
return chain.supported
} catch {
return false
}
}
// MARK: - Asset Operations
/// Get supported assets for a chain.
public func getSupportedAssets(chainId: ChainId) async throws -> [Asset] {
let response: AssetsResponse = try await get(path: "/chains/\(chainId.rawValue)/assets")
return response.assets ?? []
}
/// Get asset details.
public func getAsset(assetId: String) async throws -> Asset {
return try await get(path: "/assets/\(assetId.urlEncoded)")
}
/// Get wrapped asset on a target chain.
public func getWrappedAsset(originalAssetId: String, targetChain: ChainId) async throws -> WrappedAsset {
return try await get(path: "/assets/\(originalAssetId.urlEncoded)/wrapped/\(targetChain.rawValue)")
}
// MARK: - Fee & Rate Operations
/// Estimate transfer fee.
public func estimateFee(
asset: String,
amount: String,
sourceChain: ChainId,
targetChain: ChainId
) async throws -> BridgeFeeEstimate {
let body: [String: String] = [
"asset": asset,
"amount": amount,
"sourceChain": sourceChain.rawValue,
"targetChain": targetChain.rawValue
]
return try await post(path: "/fees/estimate", body: body)
}
/// Get exchange rate between assets.
public func getExchangeRate(fromAsset: String, toAsset: String) async throws -> ExchangeRate {
return try await get(path: "/rates/\(fromAsset.urlEncoded)/\(toAsset.urlEncoded)")
}
// MARK: - Lock-Mint Flow
/// Lock assets on the source chain.
public func lock(
asset: String,
amount: String,
targetChain: ChainId,
options: LockOptions? = nil
) async throws -> LockReceipt {
var body: [String: Any] = [
"asset": asset,
"amount": amount,
"targetChain": targetChain.rawValue
]
if let recipient = options?.recipient { body["recipient"] = recipient }
if let deadline = options?.deadline { body["deadline"] = deadline }
if let slippage = options?.slippage { body["slippage"] = slippage }
return try await post(path: "/transfers/lock", body: body)
}
/// Get lock proof.
public func getLockProof(lockReceiptId: String) async throws -> LockProof {
return try await get(path: "/transfers/lock/\(lockReceiptId.urlEncoded)/proof")
}
/// Wait for lock proof with confirmations.
public func waitForLockProof(
lockReceiptId: String,
pollInterval: TimeInterval = 5,
maxWait: TimeInterval = 600
) async throws -> LockProof {
let deadline = Date().addingTimeInterval(maxWait)
while Date() < deadline {
do {
return try await getLockProof(lockReceiptId: lockReceiptId)
} catch let error as BridgeError {
if error.isConfirmationsPending {
try await Task.sleep(nanoseconds: UInt64(pollInterval * 1_000_000_000))
continue
}
throw error
}
}
throw BridgeError.timeout("Waiting for lock proof")
}
/// Mint assets on the target chain.
public func mint(
proof: LockProof,
targetAddress: String,
options: MintOptions? = nil
) async throws -> BridgeSignedTransaction {
var body: [String: Any] = [
"proof": try proofToDictionary(proof),
"targetAddress": targetAddress
]
if let gasLimit = options?.gasLimit { body["gasLimit"] = gasLimit }
if let maxFeePerGas = options?.maxFeePerGas { body["maxFeePerGas"] = maxFeePerGas }
if let maxPriorityFeePerGas = options?.maxPriorityFeePerGas { body["maxPriorityFeePerGas"] = maxPriorityFeePerGas }
return try await post(path: "/transfers/mint", body: body)
}
// MARK: - Burn-Unlock Flow
/// Burn wrapped assets.
public func burn(
wrappedAsset: String,
amount: String,
options: BurnOptions? = nil
) async throws -> BurnReceipt {
var body: [String: Any] = [
"wrappedAsset": wrappedAsset,
"amount": amount
]
if let recipient = options?.recipient { body["recipient"] = recipient }
if let deadline = options?.deadline { body["deadline"] = deadline }
return try await post(path: "/transfers/burn", body: body)
}
/// Get burn proof.
public func getBurnProof(burnReceiptId: String) async throws -> BurnProof {
return try await get(path: "/transfers/burn/\(burnReceiptId.urlEncoded)/proof")
}
/// Wait for burn proof with confirmations.
public func waitForBurnProof(
burnReceiptId: String,
pollInterval: TimeInterval = 5,
maxWait: TimeInterval = 600
) async throws -> BurnProof {
let deadline = Date().addingTimeInterval(maxWait)
while Date() < deadline {
do {
return try await getBurnProof(burnReceiptId: burnReceiptId)
} catch let error as BridgeError {
if error.isConfirmationsPending {
try await Task.sleep(nanoseconds: UInt64(pollInterval * 1_000_000_000))
continue
}
throw error
}
}
throw BridgeError.timeout("Waiting for burn proof")
}
/// Unlock original assets.
public func unlock(
proof: BurnProof,
options: UnlockOptions? = nil
) async throws -> BridgeSignedTransaction {
var body: [String: Any] = [
"proof": try burnProofToDictionary(proof)
]
if let gasLimit = options?.gasLimit { body["gasLimit"] = gasLimit }
if let gasPrice = options?.gasPrice { body["gasPrice"] = gasPrice }
return try await post(path: "/transfers/unlock", body: body)
}
// MARK: - Transfer Management
/// Get transfer details.
public func getTransfer(transferId: String) async throws -> Transfer {
return try await get(path: "/transfers/\(transferId.urlEncoded)")
}
/// Get transfer status.
public func getTransferStatus(transferId: String) async throws -> TransferStatus {
let transfer = try await getTransfer(transferId: transferId)
return transfer.status
}
/// List transfers with optional filter.
public func listTransfers(filter: TransferFilter? = nil) async throws -> [Transfer] {
var params: [String] = []
if let status = filter?.status { params.append("status=\(status.rawValue)") }
if let sourceChain = filter?.sourceChain { params.append("sourceChain=\(sourceChain.rawValue)") }
if let targetChain = filter?.targetChain { params.append("targetChain=\(targetChain.rawValue)") }
if let limit = filter?.limit { params.append("limit=\(limit)") }
if let offset = filter?.offset { params.append("offset=\(offset)") }
var path = "/transfers"
if !params.isEmpty {
path += "?\(params.joined(separator: "&"))"
}
let response: TransfersResponse = try await get(path: path)
return response.transfers ?? []
}
/// Wait for transfer to complete.
public func waitForTransfer(
transferId: String,
pollInterval: TimeInterval = 10,
maxWait: TimeInterval = 1800
) async throws -> Transfer {
let deadline = Date().addingTimeInterval(maxWait)
while Date() < deadline {
let transfer = try await getTransfer(transferId: transferId)
if Self.finalStatuses.contains(transfer.status) {
return transfer
}
try await Task.sleep(nanoseconds: UInt64(pollInterval * 1_000_000_000))
}
throw BridgeError.timeout("Waiting for transfer completion")
}
// MARK: - Convenience Methods
/// Complete lock-mint bridge flow.
public func bridgeTo(
asset: String,
amount: String,
targetChain: ChainId,
targetAddress: String,
lockOptions: LockOptions? = nil,
mintOptions: MintOptions? = nil
) async throws -> Transfer {
let lockReceipt = try await lock(asset: asset, amount: amount, targetChain: targetChain, options: lockOptions)
if config.debug {
print("Locked: \(lockReceipt.id), waiting for confirmations...")
}
let proof = try await waitForLockProof(lockReceiptId: lockReceipt.id)
if config.debug {
print("Proof ready, minting on \(targetChain.rawValue)...")
}
_ = try await mint(proof: proof, targetAddress: targetAddress, options: mintOptions)
return try await waitForTransfer(transferId: lockReceipt.id)
}
/// Complete burn-unlock bridge flow.
public func bridgeBack(
wrappedAsset: String,
amount: String,
burnOptions: BurnOptions? = nil,
unlockOptions: UnlockOptions? = nil
) async throws -> Transfer {
let burnReceipt = try await burn(wrappedAsset: wrappedAsset, amount: amount, options: burnOptions)
if config.debug {
print("Burned: \(burnReceipt.id), waiting for confirmations...")
}
let proof = try await waitForBurnProof(burnReceiptId: burnReceipt.id)
if config.debug {
print("Proof ready, unlocking on \(burnReceipt.targetChain.rawValue)...")
}
_ = try await unlock(proof: proof, options: unlockOptions)
return try await waitForTransfer(transferId: burnReceipt.id)
}
// MARK: - Lifecycle
/// Close the client.
public func close() {
closed = true
session.invalidateAndCancel()
}
/// Check if the client is closed.
public var isClosed: Bool { closed }
/// Perform a health check.
public func healthCheck() async -> Bool {
do {
let response: BridgeHealthResponse = try await get(path: "/health")
return response.status == "healthy"
} catch {
return false
}
}
// MARK: - Private HTTP Methods
private func get<T: Decodable>(path: String) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "GET", path: path, body: nil as [String: Any]?)
}
}
private func post<T: Decodable>(path: String, body: [String: Any]) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "POST", path: path, body: body)
}
}
private func performRequest<T: Decodable>(
method: String,
path: String,
body: [String: Any]?
) async throws -> T {
if closed { throw BridgeError.clientClosed }
guard let url = URL(string: "\(config.endpoint)\(path)") else {
throw BridgeError.invalidResponse
}
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 {
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body)
} catch {
throw BridgeError.encodingError(error)
}
}
let (data, response): (Data, URLResponse)
do {
(data, response) = try await session.data(for: request)
} catch {
throw BridgeError.networkError(error)
}
guard let httpResponse = response as? HTTPURLResponse else {
throw BridgeError.invalidResponse
}
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
if data.isEmpty {
throw BridgeError.invalidResponse
}
do {
return try decoder.decode(T.self, from: data)
} catch {
throw BridgeError.decodingError(error)
}
}
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
let message = errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)"
let code = errorInfo?["code"] as? String
throw BridgeError.httpError(statusCode: httpResponse.statusCode, message: message, code: code)
}
private func executeWithRetry<T>(_ operation: () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await operation()
} catch {
lastError = error
if config.debug {
print("Attempt \(attempt + 1) failed: \(error)")
}
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * (1 << attempt)))
}
}
}
throw lastError ?? BridgeError.invalidResponse
}
// MARK: - Private Helpers
private func proofToDictionary(_ proof: LockProof) throws -> [String: Any] {
let data = try encoder.encode(proof)
guard let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw BridgeError.encodingError(NSError(domain: "BridgeError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to encode proof"]))
}
return dict
}
private func burnProofToDictionary(_ proof: BurnProof) throws -> [String: Any] {
let data = try encoder.encode(proof)
guard let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw BridgeError.encodingError(NSError(domain: "BridgeError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to encode proof"]))
}
return dict
}
}
// MARK: - Private Helpers
private extension String {
var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
}
}

View file

@ -0,0 +1,373 @@
import Foundation
// MARK: - Configuration
/// Configuration for the Synor Bridge client.
public struct BridgeConfig {
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://bridge.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: - Chain Types
/// Supported blockchain identifiers.
public enum ChainId: String, Codable, Sendable {
case synor
case ethereum
case polygon
case arbitrum
case optimism
case bsc
case avalanche
case solana
case cosmos
}
/// A blockchain network.
public struct Chain: Codable, Sendable {
public let id: ChainId
public let name: String
public let chainId: Int64
public let rpcUrl: String
public let explorerUrl: String
public let nativeCurrency: NativeCurrency
public let confirmations: Int
public let estimatedBlockTime: Int
public let supported: Bool
}
/// Native currency of a chain.
public struct NativeCurrency: Codable, Sendable {
public let name: String
public let symbol: String
public let decimals: Int
}
/// Response for chain list operations.
public struct ChainsResponse: Codable {
public let chains: [Chain]?
}
// MARK: - Asset Types
/// Type of asset.
public enum AssetType: String, Codable, Sendable {
case native
case erc20
case erc721
case erc1155
}
/// An asset on a blockchain.
public struct Asset: Codable, Sendable {
public let id: String
public let symbol: String
public let name: String
public let type: AssetType
public let chain: ChainId
public let contractAddress: String?
public let decimals: Int
public let logoUrl: String?
public let verified: Bool
}
/// A wrapped asset on a target chain.
public struct WrappedAsset: Codable, Sendable {
public let originalAsset: Asset
public let wrappedAsset: Asset
public let chain: ChainId
public let bridgeContract: String
}
/// Response for asset list operations.
public struct AssetsResponse: Codable {
public let assets: [Asset]?
}
// MARK: - Transfer Types
/// Status of a transfer.
public enum TransferStatus: String, Codable, Sendable {
case pending
case locked
case confirming
case minting
case completed
case failed
case refunded
}
/// Direction of a transfer.
public enum TransferDirection: String, Codable, Sendable {
case lock_mint
case burn_unlock
}
/// A cross-chain transfer.
public struct Transfer: Codable, Sendable {
public let id: String
public let direction: TransferDirection
public let status: TransferStatus
public let sourceChain: ChainId
public let targetChain: ChainId
public let asset: Asset
public let amount: String
public let sender: String
public let recipient: String
public let sourceTxHash: String?
public let targetTxHash: String?
public let fee: String
public let feeAsset: Asset
public let createdAt: Int64
public let updatedAt: Int64
public let completedAt: Int64?
public let errorMessage: String?
}
/// Filter for listing transfers.
public struct TransferFilter {
public let status: TransferStatus?
public let sourceChain: ChainId?
public let targetChain: ChainId?
public let asset: String?
public let sender: String?
public let recipient: String?
public let limit: Int?
public let offset: Int?
public init(
status: TransferStatus? = nil,
sourceChain: ChainId? = nil,
targetChain: ChainId? = nil,
asset: String? = nil,
sender: String? = nil,
recipient: String? = nil,
limit: Int? = nil,
offset: Int? = nil
) {
self.status = status
self.sourceChain = sourceChain
self.targetChain = targetChain
self.asset = asset
self.sender = sender
self.recipient = recipient
self.limit = limit
self.offset = offset
}
}
/// Response for transfer list operations.
public struct TransfersResponse: Codable {
public let transfers: [Transfer]?
}
// MARK: - Lock-Mint Types
/// Validator signature for proofs.
public struct ValidatorSignature: Codable, Sendable {
public let validator: String
public let signature: String
public let timestamp: Int64
}
/// Receipt from locking assets.
public struct LockReceipt: Codable, Sendable {
public let id: String
public let txHash: String
public let sourceChain: ChainId
public let targetChain: ChainId
public let asset: Asset
public let amount: String
public let sender: String
public let recipient: String
public let lockTimestamp: Int64
public let confirmations: Int
public let requiredConfirmations: Int
}
/// Proof of lock for minting.
public struct LockProof: Codable, Sendable {
public let lockReceipt: LockReceipt
public let merkleProof: [String]
public let blockHeader: String
public let signatures: [ValidatorSignature]
}
/// Options for locking assets.
public struct LockOptions: Codable, Sendable {
public let recipient: String?
public let deadline: Int64?
public let slippage: Double?
public init(recipient: String? = nil, deadline: Int64? = nil, slippage: Double? = nil) {
self.recipient = recipient
self.deadline = deadline
self.slippage = slippage
}
}
/// Options for minting assets.
public struct MintOptions: Codable, Sendable {
public let gasLimit: String?
public let maxFeePerGas: String?
public let maxPriorityFeePerGas: String?
public init(gasLimit: String? = nil, maxFeePerGas: String? = nil, maxPriorityFeePerGas: String? = nil) {
self.gasLimit = gasLimit
self.maxFeePerGas = maxFeePerGas
self.maxPriorityFeePerGas = maxPriorityFeePerGas
}
}
// MARK: - Burn-Unlock Types
/// Receipt from burning wrapped assets.
public struct BurnReceipt: Codable, Sendable {
public let id: String
public let txHash: String
public let sourceChain: ChainId
public let targetChain: ChainId
public let wrappedAsset: Asset
public let originalAsset: Asset
public let amount: String
public let sender: String
public let recipient: String
public let burnTimestamp: Int64
public let confirmations: Int
public let requiredConfirmations: Int
}
/// Proof of burn for unlocking.
public struct BurnProof: Codable, Sendable {
public let burnReceipt: BurnReceipt
public let merkleProof: [String]
public let blockHeader: String
public let signatures: [ValidatorSignature]
}
/// Options for burning wrapped assets.
public struct BurnOptions: Codable, Sendable {
public let recipient: String?
public let deadline: Int64?
public init(recipient: String? = nil, deadline: Int64? = nil) {
self.recipient = recipient
self.deadline = deadline
}
}
/// Options for unlocking original assets.
public struct UnlockOptions: Codable, Sendable {
public let gasLimit: String?
public let gasPrice: String?
public init(gasLimit: String? = nil, gasPrice: String? = nil) {
self.gasLimit = gasLimit
self.gasPrice = gasPrice
}
}
// MARK: - Fee & Rate Types
/// Fee estimate for a transfer.
public struct BridgeFeeEstimate: Codable, Sendable {
public let bridgeFee: String
public let gasFeeSource: String
public let gasFeeTarget: String
public let totalFee: String
public let feeAsset: Asset
public let estimatedTime: Int
public let exchangeRate: String?
}
/// Exchange rate between assets.
public struct ExchangeRate: Codable, Sendable {
public let fromAsset: Asset
public let toAsset: Asset
public let rate: String
public let inverseRate: String
public let lastUpdated: Int64
public let source: String
}
// MARK: - Transaction Types
/// A signed transaction.
public struct BridgeSignedTransaction: Codable, Sendable {
public let txHash: String
public let chain: ChainId
public let from: String
public let to: String
public let value: String
public let data: String
public let gasLimit: String
public let gasPrice: String?
public let maxFeePerGas: String?
public let maxPriorityFeePerGas: String?
public let nonce: Int
public let signature: String
}
// MARK: - Error Types
/// Errors that can occur during bridge operations.
public enum BridgeError: Error, LocalizedError {
case clientClosed
case httpError(statusCode: Int, message: String, code: String?)
case networkError(Error)
case encodingError(Error)
case decodingError(Error)
case invalidResponse
case timeout(String)
public var errorDescription: String? {
switch self {
case .clientClosed:
return "Client has been closed"
case .httpError(let statusCode, let message, _):
return "HTTP \(statusCode): \(message)"
case .networkError(let error):
return "Network error: \(error.localizedDescription)"
case .encodingError(let error):
return "Encoding error: \(error.localizedDescription)"
case .decodingError(let error):
return "Decoding error: \(error.localizedDescription)"
case .invalidResponse:
return "Invalid response"
case .timeout(let message):
return "Timeout: \(message)"
}
}
/// Whether this error indicates confirmations are still pending.
public var isConfirmationsPending: Bool {
if case .httpError(_, _, let code) = self {
return code == "CONFIRMATIONS_PENDING"
}
return false
}
}
// MARK: - Health Check
/// Health check response.
public struct BridgeHealthResponse: Codable {
public let status: String
}

View file

@ -0,0 +1,369 @@
import Foundation
/// Synor Database SDK client for Swift.
///
/// Provides multi-model database with Key-Value, Document, Vector, and Time Series stores.
///
/// Example:
/// ```swift
/// let db = SynorDatabase(config: DatabaseConfig(apiKey: "your-api-key"))
///
/// // Key-Value operations
/// try await db.kv.set(key: "mykey", value: "myvalue", ttl: 3600)
/// let value = try await db.kv.get(key: "mykey")
///
/// // Document operations
/// let docId = try await db.documents.create(collection: "users", document: ["name": "Alice", "age": 30])
/// let doc = try await db.documents.get(collection: "users", id: docId)
///
/// // Vector operations
/// try await db.vectors.upsert(collection: "embeddings", vectors: [
/// VectorEntry(id: "vec1", vector: [0.1, 0.2, 0.3], metadata: ["label": "test"])
/// ])
///
/// // Time series operations
/// try await db.timeseries.write(series: "metrics", points: [
/// DataPoint(timestamp: Date().timeIntervalSince1970, value: 42.0)
/// ])
/// ```
public class SynorDatabase {
private let config: DatabaseConfig
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private var closed = false
/// Key-Value store operations.
public private(set) lazy var kv = KeyValueStore(db: self)
/// Document store operations.
public private(set) lazy var documents = DocumentStore(db: self)
/// Vector store operations.
public private(set) lazy var vectors = VectorStore(db: self)
/// Time series store operations.
public private(set) lazy var timeseries = TimeSeriesStore(db: self)
public init(config: DatabaseConfig) {
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: - Key-Value Store
/// Key-Value store for simple key-value operations.
public class KeyValueStore {
private let db: SynorDatabase
internal init(db: SynorDatabase) {
self.db = db
}
/// Get a value by key.
public func get(key: String) async throws -> Any? {
let response: KVGetResponse = try await db.request(
method: "GET",
path: "/kv/\(key.urlEncoded)"
)
return response.value?.value
}
/// Set a value for a key with optional TTL.
public func set(key: String, value: Any, ttl: Int? = nil) async throws {
var body: [String: AnyCodable] = [
"key": AnyCodable(key),
"value": AnyCodable(value)
]
if let ttl = ttl {
body["ttl"] = AnyCodable(ttl)
}
let _: EmptyResponse = try await db.request(
method: "PUT",
path: "/kv/\(key.urlEncoded)",
body: body
)
}
/// Delete a key.
public func delete(key: String) async throws {
let _: EmptyResponse = try await db.request(
method: "DELETE",
path: "/kv/\(key.urlEncoded)"
)
}
/// List keys by prefix.
public func list(prefix: String) async throws -> [KeyValue] {
let response: KVListResponse = try await db.request(
method: "GET",
path: "/kv?prefix=\(prefix.urlEncoded)"
)
return response.items ?? []
}
}
// MARK: - Document Store
/// Document store for document-oriented operations.
public class DocumentStore {
private let db: SynorDatabase
internal init(db: SynorDatabase) {
self.db = db
}
/// Create a new document.
public func create(collection: String, document: [String: Any]) async throws -> String {
let body = document.mapValues { AnyCodable($0) }
let response: CreateDocumentResponse = try await db.request(
method: "POST",
path: "/collections/\(collection.urlEncoded)/documents",
body: body
)
return response.id
}
/// Get a document by ID.
public func get(collection: String, id: String) async throws -> Document {
return try await db.request(
method: "GET",
path: "/collections/\(collection.urlEncoded)/documents/\(id.urlEncoded)"
)
}
/// Update a document.
public func update(collection: String, id: String, update: [String: Any]) async throws {
let body = update.mapValues { AnyCodable($0) }
let _: EmptyResponse = try await db.request(
method: "PATCH",
path: "/collections/\(collection.urlEncoded)/documents/\(id.urlEncoded)",
body: body
)
}
/// Delete a document.
public func delete(collection: String, id: String) async throws {
let _: EmptyResponse = try await db.request(
method: "DELETE",
path: "/collections/\(collection.urlEncoded)/documents/\(id.urlEncoded)"
)
}
/// Query documents.
public func query(collection: String, query: DocumentQuery) async throws -> [Document] {
let response: DocumentListResponse = try await db.request(
method: "POST",
path: "/collections/\(collection.urlEncoded)/query",
body: query
)
return response.documents ?? []
}
}
// MARK: - Vector Store
/// Vector store for similarity search operations.
public class VectorStore {
private let db: SynorDatabase
internal init(db: SynorDatabase) {
self.db = db
}
/// Upsert vectors into a collection.
public func upsert(collection: String, vectors: [VectorEntry]) async throws {
let body: [String: [VectorEntry]] = ["vectors": vectors]
let _: EmptyResponse = try await db.request(
method: "POST",
path: "/vectors/\(collection.urlEncoded)/upsert",
body: body
)
}
/// Search for similar vectors.
public func search(collection: String, vector: [Double], k: Int) async throws -> [SearchResult] {
let body: [String: AnyCodable] = [
"vector": AnyCodable(vector),
"k": AnyCodable(k)
]
let response: SearchListResponse = try await db.request(
method: "POST",
path: "/vectors/\(collection.urlEncoded)/search",
body: body
)
return response.results ?? []
}
/// Delete vectors by IDs.
public func delete(collection: String, ids: [String]) async throws {
let body: [String: [String]] = ["ids": ids]
let _: EmptyResponse = try await db.request(
method: "DELETE",
path: "/vectors/\(collection.urlEncoded)",
body: body
)
}
}
// MARK: - Time Series Store
/// Time series store for time-based data operations.
public class TimeSeriesStore {
private let db: SynorDatabase
internal init(db: SynorDatabase) {
self.db = db
}
/// Write data points to a series.
public func write(series: String, points: [DataPoint]) async throws {
let body: [String: [DataPoint]] = ["points": points]
let _: EmptyResponse = try await db.request(
method: "POST",
path: "/timeseries/\(series.urlEncoded)/write",
body: body
)
}
/// Query data points from a series.
public func query(series: String, range: TimeRange, aggregation: Aggregation? = nil) async throws -> [DataPoint] {
var body: [String: AnyCodable] = ["range": AnyCodable(["start": range.start, "end": range.end])]
if let aggregation = aggregation {
body["aggregation"] = AnyCodable(["function": aggregation.function, "interval": aggregation.interval])
}
let response: DataPointListResponse = try await db.request(
method: "POST",
path: "/timeseries/\(series.urlEncoded)/query",
body: body
)
return response.points ?? []
}
}
// MARK: - Lifecycle
/// Close the client.
public func close() {
closed = true
session.invalidateAndCancel()
}
/// Check if the client is closed.
public var isClosed: Bool { closed }
/// Perform a health check.
public func healthCheck() async -> Bool {
do {
let response: HealthResponse = try await request(method: "GET", path: "/health")
return response.status == "healthy"
} catch {
return false
}
}
// MARK: - Internal HTTP Methods
internal func request<T: Decodable>(method: String, path: String) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: method, path: path, body: nil as EmptyBody?)
}
}
internal func request<T: Decodable, R: Encodable>(method: String, path: String, body: R) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: method, path: path, body: body)
}
}
private func performRequest<T: Decodable, R: Encodable>(
method: String,
path: String,
body: R?
) async throws -> T {
if closed { throw DatabaseError.clientClosed }
guard let url = URL(string: "\(config.endpoint)\(path)") else {
throw DatabaseError.invalidResponse
}
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 {
do {
request.httpBody = try encoder.encode(body)
} catch {
throw DatabaseError.encodingError(error)
}
}
let (data, response): (Data, URLResponse)
do {
(data, response) = try await session.data(for: request)
} catch {
throw DatabaseError.networkError(error)
}
guard let httpResponse = response as? HTTPURLResponse else {
throw DatabaseError.invalidResponse
}
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
if data.isEmpty && T.self == EmptyResponse.self {
return EmptyResponse() as! T
}
do {
return try decoder.decode(T.self, from: data)
} catch {
throw DatabaseError.decodingError(error)
}
}
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
let message = errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)"
let code = errorInfo?["code"] as? String
throw DatabaseError.httpError(statusCode: httpResponse.statusCode, message: message, code: code)
}
private func executeWithRetry<T>(_ operation: () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await operation()
} catch {
lastError = error
if config.debug {
print("Attempt \(attempt + 1) failed: \(error)")
}
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * (1 << attempt)))
}
}
}
throw lastError ?? DatabaseError.invalidResponse
}
}
// MARK: - Private Helpers
private struct EmptyBody: Encodable {}
private struct EmptyResponse: Decodable {}
private extension String {
var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
}
}

View file

@ -0,0 +1,251 @@
import Foundation
// MARK: - Configuration
/// Configuration for the Synor Database client.
public struct DatabaseConfig {
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://db.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: - Key-Value Store Types
/// A key-value pair with metadata.
public struct KeyValue: Codable, Sendable {
public let key: String
public let value: AnyCodable
public let ttl: Int?
public let createdAt: Int64?
public let updatedAt: Int64?
}
/// Response for key-value get operations.
public struct KVGetResponse: Codable {
public let value: AnyCodable?
}
/// Response for key-value list operations.
public struct KVListResponse: Codable {
public let items: [KeyValue]?
}
// MARK: - Document Store Types
/// A document with metadata.
public struct Document: Codable, Sendable {
public let id: String
public let collection: String
public let data: [String: AnyCodable]
public let createdAt: Int64
public let updatedAt: Int64
}
/// Response for document creation.
public struct CreateDocumentResponse: Codable {
public let id: String
}
/// Response for document queries.
public struct DocumentListResponse: Codable {
public let documents: [Document]?
}
/// Query parameters for document store.
public struct DocumentQuery: Codable {
public let filter: [String: AnyCodable]?
public let sort: [String: Int]?
public let limit: Int?
public let offset: Int?
public let projection: [String]?
public init(
filter: [String: AnyCodable]? = nil,
sort: [String: Int]? = nil,
limit: Int? = nil,
offset: Int? = nil,
projection: [String]? = nil
) {
self.filter = filter
self.sort = sort
self.limit = limit
self.offset = offset
self.projection = projection
}
}
// MARK: - Vector Store Types
/// A vector entry with metadata.
public struct VectorEntry: Codable, Sendable {
public let id: String
public let vector: [Double]
public let metadata: [String: AnyCodable]?
public init(id: String, vector: [Double], metadata: [String: AnyCodable]? = nil) {
self.id = id
self.vector = vector
self.metadata = metadata
}
}
/// Result from a vector search.
public struct SearchResult: Codable, Sendable {
public let id: String
public let score: Double
public let vector: [Double]?
public let metadata: [String: AnyCodable]?
}
/// Response for vector search operations.
public struct SearchListResponse: Codable {
public let results: [SearchResult]?
}
// MARK: - Time Series Types
/// A time series data point.
public struct DataPoint: Codable, Sendable {
public let timestamp: Int64
public let value: Double
public let tags: [String: String]?
public init(timestamp: Int64, value: Double, tags: [String: String]? = nil) {
self.timestamp = timestamp
self.value = value
self.tags = tags
}
}
/// Time range for queries.
public struct TimeRange: Codable, Sendable {
public let start: Int64
public let end: Int64
public init(start: Int64, end: Int64) {
self.start = start
self.end = end
}
}
/// Aggregation settings for time series queries.
public struct Aggregation: Codable, Sendable {
public let function: String
public let interval: String
public init(function: String, interval: String) {
self.function = function
self.interval = interval
}
}
/// Response for time series queries.
public struct DataPointListResponse: Codable {
public let points: [DataPoint]?
}
// MARK: - Error Types
/// Errors that can occur during database operations.
public enum DatabaseError: Error, LocalizedError {
case clientClosed
case httpError(statusCode: Int, message: String, code: String?)
case networkError(Error)
case encodingError(Error)
case decodingError(Error)
case invalidResponse
public var errorDescription: String? {
switch self {
case .clientClosed:
return "Client has been closed"
case .httpError(let statusCode, let message, _):
return "HTTP \(statusCode): \(message)"
case .networkError(let error):
return "Network error: \(error.localizedDescription)"
case .encodingError(let error):
return "Encoding error: \(error.localizedDescription)"
case .decodingError(let error):
return "Decoding error: \(error.localizedDescription)"
case .invalidResponse:
return "Invalid response"
}
}
}
// MARK: - Helper Types
/// Type-erased codable value for dynamic JSON.
public struct AnyCodable: Codable, Sendable {
public let value: Any
public init(_ value: Any) {
self.value = value
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
value = NSNull()
} else if let bool = try? container.decode(Bool.self) {
value = bool
} else if let int = try? container.decode(Int.self) {
value = int
} else if let double = try? container.decode(Double.self) {
value = double
} else if let string = try? container.decode(String.self) {
value = string
} else if let array = try? container.decode([AnyCodable].self) {
value = array.map { $0.value }
} else if let dict = try? container.decode([String: AnyCodable].self) {
value = dict.mapValues { $0.value }
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unable to decode value")
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch value {
case is NSNull:
try container.encodeNil()
case let bool as Bool:
try container.encode(bool)
case let int as Int:
try container.encode(int)
case let double as Double:
try container.encode(double)
case let string as String:
try container.encode(string)
case let array as [Any]:
try container.encode(array.map { AnyCodable($0) })
case let dict as [String: Any]:
try container.encode(dict.mapValues { AnyCodable($0) })
default:
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Unable to encode value"))
}
}
}
/// Health check response.
public struct HealthResponse: Codable {
public let status: String
}

View file

@ -0,0 +1,402 @@
import Foundation
/// Synor Hosting SDK client for Swift.
///
/// Provides decentralized web hosting with domain management, DNS, deployments, SSL, and analytics.
///
/// Example:
/// ```swift
/// let hosting = SynorHosting(config: HostingConfig(apiKey: "your-api-key"))
///
/// // Check domain availability
/// let availability = try await hosting.checkAvailability(name: "mydomain.synor")
/// if availability.available {
/// // Register domain
/// let domain = try await hosting.registerDomain(name: "mydomain.synor")
/// print("Registered: \(domain.name)")
/// }
///
/// // Deploy content
/// let deployment = try await hosting.deploy(cid: "Qm...", options: DeployOptions(domain: "mydomain.synor"))
/// print("Deployed to: \(deployment.url)")
///
/// // Provision SSL
/// let cert = try await hosting.provisionSsl(domain: "mydomain.synor")
/// print("SSL status: \(cert.status)")
/// ```
public class SynorHosting {
private let config: HostingConfig
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private var closed = false
public init(config: HostingConfig) {
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: - Domain Operations
/// Check domain availability.
public func checkAvailability(name: String) async throws -> DomainAvailability {
return try await get(path: "/domains/check/\(name.urlEncoded)")
}
/// Register a new domain.
public func registerDomain(name: String, options: RegisterDomainOptions? = nil) async throws -> Domain {
var body: [String: Any] = ["name": name]
if let options = options {
if let years = options.years { body["years"] = years }
if let autoRenew = options.autoRenew { body["autoRenew"] = autoRenew }
}
return try await post(path: "/domains", body: body)
}
/// Get domain details.
public func getDomain(name: String) async throws -> Domain {
return try await get(path: "/domains/\(name.urlEncoded)")
}
/// List all domains.
public func listDomains() async throws -> [Domain] {
let response: DomainsResponse = try await get(path: "/domains")
return response.domains ?? []
}
/// Update domain record.
public func updateDomainRecord(name: String, record: DomainRecord) async throws -> Domain {
return try await put(path: "/domains/\(name.urlEncoded)/record", body: record)
}
/// Resolve domain to its record.
public func resolveDomain(name: String) async throws -> DomainRecord {
return try await get(path: "/domains/\(name.urlEncoded)/resolve")
}
/// Renew a domain.
public func renewDomain(name: String, years: Int) async throws -> Domain {
return try await post(path: "/domains/\(name.urlEncoded)/renew", body: ["years": years])
}
// MARK: - DNS Operations
/// Get DNS zone for a domain.
public func getDnsZone(domain: String) async throws -> DnsZone {
return try await get(path: "/dns/\(domain.urlEncoded)")
}
/// Set DNS records for a domain.
public func setDnsRecords(domain: String, records: [DnsRecord]) async throws -> DnsZone {
return try await put(path: "/dns/\(domain.urlEncoded)", body: ["records": records])
}
/// Add a DNS record.
public func addDnsRecord(domain: String, record: DnsRecord) async throws -> DnsZone {
return try await post(path: "/dns/\(domain.urlEncoded)/records", body: record)
}
/// Delete a DNS record.
public func deleteDnsRecord(domain: String, recordType: String, name: String) async throws -> DnsZone {
return try await delete(path: "/dns/\(domain.urlEncoded)/records/\(recordType)/\(name.urlEncoded)")
}
// MARK: - Deployment Operations
/// Deploy content to a domain.
public func deploy(cid: String, options: DeployOptions? = nil) async throws -> Deployment {
var body: [String: Any] = ["cid": cid]
if let options = options {
if let domain = options.domain { body["domain"] = domain }
if let subdomain = options.subdomain { body["subdomain"] = subdomain }
if let spa = options.spa { body["spa"] = spa }
if let cleanUrls = options.cleanUrls { body["cleanUrls"] = cleanUrls }
if let trailingSlash = options.trailingSlash { body["trailingSlash"] = trailingSlash }
}
return try await post(path: "/deployments", body: body)
}
/// Get deployment details.
public func getDeployment(id: String) async throws -> Deployment {
return try await get(path: "/deployments/\(id.urlEncoded)")
}
/// List deployments.
public func listDeployments(domain: String? = nil) async throws -> [Deployment] {
var path = "/deployments"
if let domain = domain {
path += "?domain=\(domain.urlEncoded)"
}
let response: DeploymentsResponse = try await get(path: path)
return response.deployments ?? []
}
/// Rollback to a previous deployment.
public func rollback(domain: String, deploymentId: String) async throws -> Deployment {
return try await post(
path: "/deployments/\(deploymentId.urlEncoded)/rollback",
body: ["domain": domain]
)
}
/// Delete a deployment.
public func deleteDeployment(id: String) async throws {
let _: EmptyHostingResponse = try await deleteRequest(path: "/deployments/\(id.urlEncoded)")
}
// MARK: - SSL Operations
/// Provision SSL certificate for a domain.
public func provisionSsl(domain: String, options: ProvisionSslOptions? = nil) async throws -> Certificate {
var body: [String: Any] = [:]
if let options = options {
if let includeWww = options.includeWww { body["includeWww"] = includeWww }
if let autoRenew = options.autoRenew { body["autoRenew"] = autoRenew }
}
return try await post(path: "/ssl/\(domain.urlEncoded)", body: body.isEmpty ? nil : body)
}
/// Get certificate details.
public func getCertificate(domain: String) async throws -> Certificate {
return try await get(path: "/ssl/\(domain.urlEncoded)")
}
/// Renew SSL certificate.
public func renewCertificate(domain: String) async throws -> Certificate {
return try await post(path: "/ssl/\(domain.urlEncoded)/renew", body: nil as [String: Any]?)
}
/// Delete SSL certificate.
public func deleteCertificate(domain: String) async throws {
let _: EmptyHostingResponse = try await deleteRequest(path: "/ssl/\(domain.urlEncoded)")
}
// MARK: - Site Configuration
/// Get site configuration.
public func getSiteConfig(domain: String) async throws -> SiteConfig {
return try await get(path: "/sites/\(domain.urlEncoded)/config")
}
/// Update site configuration.
public func updateSiteConfig(domain: String, config: [String: Any]) async throws -> SiteConfig {
return try await patch(path: "/sites/\(domain.urlEncoded)/config", body: config)
}
/// Purge cache.
public func purgeCache(domain: String, paths: [String]? = nil) async throws -> Int64 {
let body: [String: Any]? = paths.map { ["paths": $0] }
let response: PurgeResponse = try await deleteRequest(
path: "/sites/\(domain.urlEncoded)/cache",
body: body
)
return response.purged
}
// MARK: - Analytics
/// Get analytics data for a domain.
public func getAnalytics(domain: String, options: AnalyticsOptions? = nil) async throws -> AnalyticsData {
var params: [String] = []
if let period = options?.period { params.append("period=\(period.urlEncoded)") }
if let start = options?.start { params.append("start=\(start.urlEncoded)") }
if let end = options?.end { params.append("end=\(end.urlEncoded)") }
var path = "/sites/\(domain.urlEncoded)/analytics"
if !params.isEmpty {
path += "?\(params.joined(separator: "&"))"
}
return try await get(path: path)
}
// MARK: - Lifecycle
/// Close the client.
public func close() {
closed = true
session.invalidateAndCancel()
}
/// Check if the client is closed.
public var isClosed: Bool { closed }
/// Perform a health check.
public func healthCheck() async -> Bool {
do {
let response: HostingHealthResponse = try await get(path: "/health")
return response.status == "healthy"
} catch {
return false
}
}
// MARK: - Private HTTP Methods
private func get<T: Decodable>(path: String) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "GET", path: path, body: nil as [String: Any]?)
}
}
private func post<T: Decodable>(path: String, body: [String: Any]?) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "POST", path: path, body: body)
}
}
private func post<T: Decodable, R: Encodable>(path: String, body: R) async throws -> T {
return try await executeWithRetry {
try await self.performEncodableRequest(method: "POST", path: path, body: body)
}
}
private func put<T: Decodable>(path: String, body: [String: Any]) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "PUT", path: path, body: body)
}
}
private func put<T: Decodable, R: Encodable>(path: String, body: R) async throws -> T {
return try await executeWithRetry {
try await self.performEncodableRequest(method: "PUT", path: path, body: body)
}
}
private func patch<T: Decodable>(path: String, body: [String: Any]) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "PATCH", path: path, body: body)
}
}
private func delete<T: Decodable>(path: String) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "DELETE", path: path, body: nil as [String: Any]?)
}
}
private func deleteRequest<T: Decodable>(path: String, body: [String: Any]? = nil) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: "DELETE", path: path, body: body)
}
}
private func performRequest<T: Decodable>(
method: String,
path: String,
body: [String: Any]?
) async throws -> T {
if closed { throw HostingError.clientClosed }
guard let url = URL(string: "\(config.endpoint)\(path)") else {
throw HostingError.invalidResponse
}
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 {
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body)
} catch {
throw HostingError.encodingError(error)
}
}
return try await executeRequest(request)
}
private func performEncodableRequest<T: Decodable, R: Encodable>(
method: String,
path: String,
body: R
) async throws -> T {
if closed { throw HostingError.clientClosed }
guard let url = URL(string: "\(config.endpoint)\(path)") else {
throw HostingError.invalidResponse
}
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")
do {
request.httpBody = try encoder.encode(body)
} catch {
throw HostingError.encodingError(error)
}
return try await executeRequest(request)
}
private func executeRequest<T: Decodable>(_ request: URLRequest) async throws -> T {
let (data, response): (Data, URLResponse)
do {
(data, response) = try await session.data(for: request)
} catch {
throw HostingError.networkError(error)
}
guard let httpResponse = response as? HTTPURLResponse else {
throw HostingError.invalidResponse
}
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
if data.isEmpty && T.self == EmptyHostingResponse.self {
return EmptyHostingResponse() as! T
}
do {
return try decoder.decode(T.self, from: data)
} catch {
throw HostingError.decodingError(error)
}
}
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
let message = errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)"
let code = errorInfo?["code"] as? String
throw HostingError.httpError(statusCode: httpResponse.statusCode, message: message, code: code)
}
private func executeWithRetry<T>(_ operation: () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await operation()
} catch {
lastError = error
if config.debug {
print("Attempt \(attempt + 1) failed: \(error)")
}
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * (1 << attempt)))
}
}
}
throw lastError ?? HostingError.invalidResponse
}
}
// MARK: - Private Helpers
private struct EmptyHostingResponse: Decodable {}
private extension String {
var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
}
}

View file

@ -0,0 +1,328 @@
import Foundation
// MARK: - Configuration
/// Configuration for the Synor Hosting client.
public struct HostingConfig {
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://hosting.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: - Domain Types
/// Status of a domain.
public enum DomainStatus: String, Codable, Sendable {
case pending
case active
case expired
case suspended
}
/// A domain registration.
public struct Domain: Codable, Sendable {
public let name: String
public let status: DomainStatus
public let owner: String
public let registeredAt: Int64
public let expiresAt: Int64
public let autoRenew: Bool
public let records: DomainRecord?
}
/// DNS record for a domain.
public struct DomainRecord: Codable, Sendable {
public let cid: String?
public let ipv4: [String]?
public let ipv6: [String]?
public let cname: String?
public let txt: [String]?
public let metadata: [String: String]?
public init(
cid: String? = nil,
ipv4: [String]? = nil,
ipv6: [String]? = nil,
cname: String? = nil,
txt: [String]? = nil,
metadata: [String: String]? = nil
) {
self.cid = cid
self.ipv4 = ipv4
self.ipv6 = ipv6
self.cname = cname
self.txt = txt
self.metadata = metadata
}
}
/// Domain availability check result.
public struct DomainAvailability: Codable, Sendable {
public let name: String
public let available: Bool
public let price: Double?
public let premium: Bool
}
/// Options for domain registration.
public struct RegisterDomainOptions: Codable, Sendable {
public let years: Int?
public let autoRenew: Bool?
public init(years: Int? = nil, autoRenew: Bool? = nil) {
self.years = years
self.autoRenew = autoRenew
}
}
/// Response for domain list operations.
public struct DomainsResponse: Codable {
public let domains: [Domain]?
}
// MARK: - DNS Types
/// Type of DNS record.
public enum DnsRecordType: String, Codable, Sendable {
case A, AAAA, CNAME, TXT, MX, NS, SRV, CAA
}
/// A DNS record.
public struct DnsRecord: Codable, Sendable {
public let type: DnsRecordType
public let name: String
public let value: String
public let ttl: Int
public let priority: Int?
public init(type: DnsRecordType, name: String, value: String, ttl: Int = 3600, priority: Int? = nil) {
self.type = type
self.name = name
self.value = value
self.ttl = ttl
self.priority = priority
}
}
/// A DNS zone containing records.
public struct DnsZone: Codable, Sendable {
public let domain: String
public let records: [DnsRecord]
public let updatedAt: Int64
}
// MARK: - Deployment Types
/// Status of a deployment.
public enum DeploymentStatus: String, Codable, Sendable {
case pending
case building
case deploying
case active
case failed
case inactive
}
/// A deployment.
public struct Deployment: Codable, Sendable {
public let id: String
public let domain: String
public let cid: String
public let status: DeploymentStatus
public let url: String
public let createdAt: Int64
public let updatedAt: Int64
public let buildLogs: String?
public let errorMessage: String?
}
/// Options for deploying content.
public struct DeployOptions: Codable, Sendable {
public let domain: String?
public let subdomain: String?
public let spa: Bool?
public let cleanUrls: Bool?
public let trailingSlash: Bool?
public init(
domain: String? = nil,
subdomain: String? = nil,
spa: Bool? = nil,
cleanUrls: Bool? = nil,
trailingSlash: Bool? = nil
) {
self.domain = domain
self.subdomain = subdomain
self.spa = spa
self.cleanUrls = cleanUrls
self.trailingSlash = trailingSlash
}
}
/// Response for deployment list operations.
public struct DeploymentsResponse: Codable {
public let deployments: [Deployment]?
}
// MARK: - SSL Types
/// Status of an SSL certificate.
public enum CertificateStatus: String, Codable, Sendable {
case pending
case issued
case expired
case revoked
}
/// An SSL certificate.
public struct Certificate: Codable, Sendable {
public let domain: String
public let status: CertificateStatus
public let autoRenew: Bool
public let issuer: String
public let issuedAt: Int64?
public let expiresAt: Int64?
public let fingerprint: String?
}
/// Options for SSL provisioning.
public struct ProvisionSslOptions: Codable, Sendable {
public let includeWww: Bool?
public let autoRenew: Bool?
public init(includeWww: Bool? = nil, autoRenew: Bool? = nil) {
self.includeWww = includeWww
self.autoRenew = autoRenew
}
}
// MARK: - Site Configuration Types
/// Site configuration.
public struct SiteConfig: Codable, Sendable {
public let domain: String
public let cid: String?
public let headers: [String: String]?
public let redirects: [RedirectRule]?
public let errorPages: [String: String]?
public let spa: Bool
public let cleanUrls: Bool
public let trailingSlash: Bool
}
/// A redirect rule.
public struct RedirectRule: Codable, Sendable {
public let source: String
public let destination: String
public let statusCode: Int
public let permanent: Bool
public init(source: String, destination: String, statusCode: Int = 301, permanent: Bool = true) {
self.source = source
self.destination = destination
self.statusCode = statusCode
self.permanent = permanent
}
}
// MARK: - Analytics Types
/// Analytics data for a site.
public struct AnalyticsData: Codable, Sendable {
public let domain: String
public let period: String
public let pageViews: Int64
public let uniqueVisitors: Int64
public let bandwidth: Int64
public let topPages: [PageView]
public let topReferrers: [Referrer]
public let topCountries: [Country]
}
/// Page view statistics.
public struct PageView: Codable, Sendable {
public let path: String
public let views: Int64
}
/// Referrer statistics.
public struct Referrer: Codable, Sendable {
public let referrer: String
public let count: Int64
}
/// Country statistics.
public struct Country: Codable, Sendable {
public let country: String
public let count: Int64
}
/// Options for analytics queries.
public struct AnalyticsOptions: Codable, Sendable {
public let period: String?
public let start: String?
public let end: String?
public init(period: String? = nil, start: String? = nil, end: String? = nil) {
self.period = period
self.start = start
self.end = end
}
}
/// Response for cache purge operations.
public struct PurgeResponse: Codable {
public let purged: Int64
}
// MARK: - Error Types
/// Errors that can occur during hosting operations.
public enum HostingError: Error, LocalizedError {
case clientClosed
case httpError(statusCode: Int, message: String, code: String?)
case networkError(Error)
case encodingError(Error)
case decodingError(Error)
case invalidResponse
public var errorDescription: String? {
switch self {
case .clientClosed:
return "Client has been closed"
case .httpError(let statusCode, let message, _):
return "HTTP \(statusCode): \(message)"
case .networkError(let error):
return "Network error: \(error.localizedDescription)"
case .encodingError(let error):
return "Encoding error: \(error.localizedDescription)"
case .decodingError(let error):
return "Decoding error: \(error.localizedDescription)"
case .invalidResponse:
return "Invalid response"
}
}
}
// MARK: - Health Check
/// Health check response.
public struct HostingHealthResponse: Codable {
public let status: String
}