feat(sdk): implement DEX SDK with perpetual futures for all 12 languages

Complete decentralized exchange client implementation featuring:
- AMM swaps (constant product, stable, concentrated liquidity)
- Liquidity provision with impermanent loss tracking
- Perpetual futures (up to 100x leverage, funding rates, liquidation)
- Order books with limit orders (GTC, IOC, FOK, GTD)
- Yield farming & staking with reward claiming
- Real-time WebSocket subscriptions
- Analytics (OHLCV, trade history, volume, TVL)

Languages: JS/TS, Python, Go, Rust, Java, Kotlin, Swift, Flutter,
C, C++, C#, Ruby
This commit is contained in:
Gulshan Yadav 2026-01-28 12:32:04 +05:30
parent 9478bc24e3
commit e7dc8f70a0
32 changed files with 10295 additions and 0 deletions

359
sdk/c/include/synor/dex.h Normal file
View file

@ -0,0 +1,359 @@
/**
* @file dex.h
* @brief Synor DEX SDK for C
*
* Complete decentralized exchange client with support for:
* - AMM swaps (constant product, stable, concentrated)
* - Liquidity provision
* - Perpetual futures (up to 100x leverage)
* - Order books (limit orders)
* - Farming & staking
*/
#ifndef SYNOR_DEX_H
#define SYNOR_DEX_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Error codes */
typedef enum {
SYNOR_DEX_OK = 0,
SYNOR_DEX_ERROR_CLIENT_CLOSED = -1,
SYNOR_DEX_ERROR_HTTP = -2,
SYNOR_DEX_ERROR_NETWORK = -3,
SYNOR_DEX_ERROR_INVALID_PARAM = -4,
SYNOR_DEX_ERROR_TIMEOUT = -5,
SYNOR_DEX_ERROR_JSON = -6,
SYNOR_DEX_ERROR_ALLOCATION = -7
} synor_dex_error_t;
/* Enums */
typedef enum {
SYNOR_DEX_POOL_TYPE_CONSTANT_PRODUCT,
SYNOR_DEX_POOL_TYPE_STABLE,
SYNOR_DEX_POOL_TYPE_CONCENTRATED
} synor_dex_pool_type_t;
typedef enum {
SYNOR_DEX_POSITION_SIDE_LONG,
SYNOR_DEX_POSITION_SIDE_SHORT
} synor_dex_position_side_t;
typedef enum {
SYNOR_DEX_ORDER_TYPE_MARKET,
SYNOR_DEX_ORDER_TYPE_LIMIT,
SYNOR_DEX_ORDER_TYPE_STOP_MARKET,
SYNOR_DEX_ORDER_TYPE_STOP_LIMIT,
SYNOR_DEX_ORDER_TYPE_TAKE_PROFIT,
SYNOR_DEX_ORDER_TYPE_TAKE_PROFIT_LIMIT
} synor_dex_order_type_t;
typedef enum {
SYNOR_DEX_MARGIN_TYPE_CROSS,
SYNOR_DEX_MARGIN_TYPE_ISOLATED
} synor_dex_margin_type_t;
typedef enum {
SYNOR_DEX_TIME_IN_FORCE_GTC,
SYNOR_DEX_TIME_IN_FORCE_IOC,
SYNOR_DEX_TIME_IN_FORCE_FOK,
SYNOR_DEX_TIME_IN_FORCE_GTD
} synor_dex_time_in_force_t;
typedef enum {
SYNOR_DEX_ORDER_STATUS_PENDING,
SYNOR_DEX_ORDER_STATUS_OPEN,
SYNOR_DEX_ORDER_STATUS_PARTIALLY_FILLED,
SYNOR_DEX_ORDER_STATUS_FILLED,
SYNOR_DEX_ORDER_STATUS_CANCELLED,
SYNOR_DEX_ORDER_STATUS_EXPIRED
} synor_dex_order_status_t;
/* Opaque handle types */
typedef struct synor_dex_client synor_dex_client_t;
typedef struct synor_dex_perps_client synor_dex_perps_client_t;
typedef struct synor_dex_orderbook_client synor_dex_orderbook_client_t;
typedef struct synor_dex_farms_client synor_dex_farms_client_t;
/* Configuration */
typedef struct {
const char* api_key;
const char* endpoint; /* Default: "https://dex.synor.io/v1" */
const char* ws_endpoint; /* Default: "wss://dex.synor.io/v1/ws" */
uint32_t timeout_ms; /* Default: 30000 */
uint32_t retries; /* Default: 3 */
bool debug; /* Default: false */
} synor_dex_config_t;
/* Data types */
typedef struct {
char* address;
char* symbol;
char* name;
int decimals;
char* total_supply;
double* price_usd; /* NULL if not available */
char* logo_url; /* NULL if not available */
bool verified;
} synor_dex_token_t;
typedef struct {
char* id;
synor_dex_token_t token_a;
synor_dex_token_t token_b;
synor_dex_pool_type_t pool_type;
char* reserve_a;
char* reserve_b;
double fee;
double tvl_usd;
double volume_24h;
double apr;
char* lp_token_address;
} synor_dex_pool_t;
typedef struct {
char* token_in;
char* token_out;
char* amount_in;
char* amount_out;
double price_impact;
char** route;
size_t route_count;
char* fee;
char* minimum_received;
int64_t expires_at;
} synor_dex_quote_t;
typedef struct {
const char* token_in;
const char* token_out;
const char* amount_in;
double slippage; /* Default: 0.005 */
} synor_dex_quote_params_t;
typedef struct {
const char* token_in;
const char* token_out;
const char* amount_in;
const char* min_amount_out;
int64_t deadline; /* 0 for default */
const char* recipient; /* NULL for self */
} synor_dex_swap_params_t;
typedef struct {
char* transaction_hash;
char* amount_in;
char* amount_out;
double effective_price;
char* fee_paid;
char** route;
size_t route_count;
} synor_dex_swap_result_t;
typedef struct {
const char* token_a;
const char* token_b;
const char* amount_a;
const char* amount_b;
const char* min_amount_a; /* NULL for no minimum */
const char* min_amount_b; /* NULL for no minimum */
int64_t deadline; /* 0 for default */
} synor_dex_add_liquidity_params_t;
typedef struct {
const char* pool;
const char* lp_amount;
const char* min_amount_a; /* NULL for no minimum */
const char* min_amount_b; /* NULL for no minimum */
int64_t deadline; /* 0 for default */
} synor_dex_remove_liquidity_params_t;
typedef struct {
char* transaction_hash;
char* amount_a;
char* amount_b;
char* lp_tokens;
double pool_share;
} synor_dex_liquidity_result_t;
typedef struct {
char* pool_id;
char* lp_tokens;
char* token_a_amount;
char* token_b_amount;
double value_usd;
char* unclaimed_fees_a;
char* unclaimed_fees_b;
double impermanent_loss;
} synor_dex_lp_position_t;
typedef struct {
char* symbol;
char* base_asset;
char* quote_asset;
double index_price;
double mark_price;
double funding_rate;
int64_t next_funding_time;
char* open_interest;
double volume_24h;
int max_leverage;
} synor_dex_perp_market_t;
typedef struct {
const char* market;
synor_dex_position_side_t side;
const char* size;
int leverage;
synor_dex_order_type_t order_type;
double* limit_price; /* NULL for market orders */
double* stop_loss; /* NULL if not set */
double* take_profit; /* NULL if not set */
synor_dex_margin_type_t margin_type;
bool reduce_only;
} synor_dex_open_position_params_t;
typedef struct {
const char* market;
const char* size; /* NULL for entire position */
synor_dex_order_type_t order_type;
double* limit_price; /* NULL for market orders */
} synor_dex_close_position_params_t;
typedef struct {
char* id;
char* market;
synor_dex_position_side_t side;
char* size;
double entry_price;
double mark_price;
double liquidation_price;
char* margin;
int leverage;
char* unrealized_pnl;
} synor_dex_perp_position_t;
typedef struct {
int64_t timestamp;
double open;
double high;
double low;
double close;
double volume;
} synor_dex_ohlcv_t;
typedef struct {
double volume_24h;
double volume_7d;
double volume_30d;
int trades_24h;
} synor_dex_volume_stats_t;
typedef struct {
double total_tvl;
double pools_tvl;
double farms_tvl;
double perps_tvl;
} synor_dex_tvl_stats_t;
/* Callbacks */
typedef void (*synor_dex_callback_t)(synor_dex_error_t error, void* result, void* user_data);
/* Client lifecycle */
synor_dex_error_t synor_dex_create(synor_dex_client_t** client, const synor_dex_config_t* config);
void synor_dex_destroy(synor_dex_client_t* client);
bool synor_dex_is_closed(const synor_dex_client_t* client);
void synor_dex_close(synor_dex_client_t* client);
/* Token operations */
synor_dex_error_t synor_dex_get_token(synor_dex_client_t* client, const char* address,
synor_dex_token_t** token);
synor_dex_error_t synor_dex_list_tokens(synor_dex_client_t* client,
synor_dex_token_t** tokens, size_t* count);
synor_dex_error_t synor_dex_search_tokens(synor_dex_client_t* client, const char* query,
synor_dex_token_t** tokens, size_t* count);
/* Pool operations */
synor_dex_error_t synor_dex_get_pool(synor_dex_client_t* client,
const char* token_a, const char* token_b,
synor_dex_pool_t** pool);
synor_dex_error_t synor_dex_list_pools(synor_dex_client_t* client,
synor_dex_pool_t** pools, size_t* count);
/* Swap operations */
synor_dex_error_t synor_dex_get_quote(synor_dex_client_t* client,
const synor_dex_quote_params_t* params,
synor_dex_quote_t** quote);
synor_dex_error_t synor_dex_swap(synor_dex_client_t* client,
const synor_dex_swap_params_t* params,
synor_dex_swap_result_t** result);
/* Liquidity operations */
synor_dex_error_t synor_dex_add_liquidity(synor_dex_client_t* client,
const synor_dex_add_liquidity_params_t* params,
synor_dex_liquidity_result_t** result);
synor_dex_error_t synor_dex_remove_liquidity(synor_dex_client_t* client,
const synor_dex_remove_liquidity_params_t* params,
synor_dex_liquidity_result_t** result);
synor_dex_error_t synor_dex_get_my_positions(synor_dex_client_t* client,
synor_dex_lp_position_t** positions, size_t* count);
/* Analytics */
synor_dex_error_t synor_dex_get_price_history(synor_dex_client_t* client,
const char* pair, const char* interval, int limit,
synor_dex_ohlcv_t** candles, size_t* count);
synor_dex_error_t synor_dex_get_volume_stats(synor_dex_client_t* client,
synor_dex_volume_stats_t** stats);
synor_dex_error_t synor_dex_get_tvl(synor_dex_client_t* client, synor_dex_tvl_stats_t** stats);
/* Health check */
synor_dex_error_t synor_dex_health_check(synor_dex_client_t* client, bool* healthy);
/* Sub-clients */
synor_dex_perps_client_t* synor_dex_perps(synor_dex_client_t* client);
synor_dex_orderbook_client_t* synor_dex_orderbook(synor_dex_client_t* client);
synor_dex_farms_client_t* synor_dex_farms(synor_dex_client_t* client);
/* Perps operations */
synor_dex_error_t synor_dex_perps_list_markets(synor_dex_perps_client_t* client,
synor_dex_perp_market_t** markets, size_t* count);
synor_dex_error_t synor_dex_perps_get_market(synor_dex_perps_client_t* client,
const char* symbol,
synor_dex_perp_market_t** market);
synor_dex_error_t synor_dex_perps_open_position(synor_dex_perps_client_t* client,
const synor_dex_open_position_params_t* params,
synor_dex_perp_position_t** position);
synor_dex_error_t synor_dex_perps_close_position(synor_dex_perps_client_t* client,
const synor_dex_close_position_params_t* params,
synor_dex_perp_position_t** position);
synor_dex_error_t synor_dex_perps_get_positions(synor_dex_perps_client_t* client,
synor_dex_perp_position_t** positions, size_t* count);
/* Memory management */
void synor_dex_free_token(synor_dex_token_t* token);
void synor_dex_free_tokens(synor_dex_token_t* tokens, size_t count);
void synor_dex_free_pool(synor_dex_pool_t* pool);
void synor_dex_free_pools(synor_dex_pool_t* pools, size_t count);
void synor_dex_free_quote(synor_dex_quote_t* quote);
void synor_dex_free_swap_result(synor_dex_swap_result_t* result);
void synor_dex_free_liquidity_result(synor_dex_liquidity_result_t* result);
void synor_dex_free_lp_positions(synor_dex_lp_position_t* positions, size_t count);
void synor_dex_free_perp_market(synor_dex_perp_market_t* market);
void synor_dex_free_perp_markets(synor_dex_perp_market_t* markets, size_t count);
void synor_dex_free_perp_position(synor_dex_perp_position_t* position);
void synor_dex_free_perp_positions(synor_dex_perp_position_t* positions, size_t count);
void synor_dex_free_ohlcv(synor_dex_ohlcv_t* candles, size_t count);
void synor_dex_free_volume_stats(synor_dex_volume_stats_t* stats);
void synor_dex_free_tvl_stats(synor_dex_tvl_stats_t* stats);
#ifdef __cplusplus
}
#endif
#endif /* SYNOR_DEX_H */

View file

@ -0,0 +1,484 @@
/**
* @file dex.hpp
* @brief Synor DEX SDK for C++
*
* Complete decentralized exchange client with support for:
* - AMM swaps (constant product, stable, concentrated)
* - Liquidity provision
* - Perpetual futures (up to 100x leverage)
* - Order books (limit orders)
* - Farming & staking
*/
#ifndef SYNOR_DEX_HPP
#define SYNOR_DEX_HPP
#include <string>
#include <vector>
#include <optional>
#include <memory>
#include <future>
#include <functional>
#include <cstdint>
namespace synor {
namespace dex {
// Forward declarations
class SynorDexImpl;
class PerpsClient;
class OrderBookClient;
class FarmsClient;
// Enums
enum class PoolType {
ConstantProduct,
Stable,
Concentrated
};
enum class PositionSide {
Long,
Short
};
enum class OrderType {
Market,
Limit,
StopMarket,
StopLimit,
TakeProfit,
TakeProfitLimit
};
enum class MarginType {
Cross,
Isolated
};
enum class TimeInForce {
GTC,
IOC,
FOK,
GTD
};
enum class OrderStatus {
Pending,
Open,
PartiallyFilled,
Filled,
Cancelled,
Expired
};
// Configuration
struct Config {
std::string api_key;
std::string endpoint = "https://dex.synor.io/v1";
std::string ws_endpoint = "wss://dex.synor.io/v1/ws";
uint32_t timeout_ms = 30000;
uint32_t retries = 3;
bool debug = false;
};
// Types
struct Token {
std::string address;
std::string symbol;
std::string name;
int decimals;
std::string total_supply;
std::optional<double> price_usd;
std::optional<std::string> logo_url;
bool verified = false;
};
struct Pool {
std::string id;
Token token_a;
Token token_b;
PoolType pool_type;
std::string reserve_a;
std::string reserve_b;
double fee;
double tvl_usd;
double volume_24h;
double apr;
std::string lp_token_address;
};
struct PoolFilter {
std::optional<std::vector<std::string>> tokens;
std::optional<double> min_tvl;
std::optional<double> min_volume_24h;
std::optional<bool> verified;
std::optional<int> limit;
std::optional<int> offset;
};
struct Quote {
std::string token_in;
std::string token_out;
std::string amount_in;
std::string amount_out;
double price_impact;
std::vector<std::string> route;
std::string fee;
std::string minimum_received;
int64_t expires_at;
};
struct QuoteParams {
std::string token_in;
std::string token_out;
std::string amount_in;
std::optional<double> slippage;
};
struct SwapParams {
std::string token_in;
std::string token_out;
std::string amount_in;
std::string min_amount_out;
std::optional<int64_t> deadline;
std::optional<std::string> recipient;
};
struct SwapResult {
std::string transaction_hash;
std::string amount_in;
std::string amount_out;
double effective_price;
std::string fee_paid;
std::vector<std::string> route;
};
struct AddLiquidityParams {
std::string token_a;
std::string token_b;
std::string amount_a;
std::string amount_b;
std::optional<std::string> min_amount_a;
std::optional<std::string> min_amount_b;
std::optional<int64_t> deadline;
};
struct RemoveLiquidityParams {
std::string pool;
std::string lp_amount;
std::optional<std::string> min_amount_a;
std::optional<std::string> min_amount_b;
std::optional<int64_t> deadline;
};
struct LiquidityResult {
std::string transaction_hash;
std::string amount_a;
std::string amount_b;
std::string lp_tokens;
double pool_share;
};
struct LPPosition {
std::string pool_id;
std::string lp_tokens;
std::string token_a_amount;
std::string token_b_amount;
double value_usd;
std::string unclaimed_fees_a;
std::string unclaimed_fees_b;
double impermanent_loss;
};
struct PerpMarket {
std::string symbol;
std::string base_asset;
std::string quote_asset;
double index_price;
double mark_price;
double funding_rate;
int64_t next_funding_time;
std::string open_interest;
double volume_24h;
int max_leverage;
};
struct OpenPositionParams {
std::string market;
PositionSide side;
std::string size;
int leverage;
OrderType order_type;
std::optional<double> limit_price;
std::optional<double> stop_loss;
std::optional<double> take_profit;
MarginType margin_type = MarginType::Cross;
bool reduce_only = false;
};
struct ClosePositionParams {
std::string market;
std::optional<std::string> size;
OrderType order_type = OrderType::Market;
std::optional<double> limit_price;
};
struct PerpPosition {
std::string id;
std::string market;
PositionSide side;
std::string size;
double entry_price;
double mark_price;
double liquidation_price;
std::string margin;
int leverage;
std::string unrealized_pnl;
};
struct PerpOrder {
std::string id;
std::string market;
PositionSide side;
OrderType order_type;
std::string size;
std::optional<double> price;
OrderStatus status;
};
struct FundingPayment {
std::string market;
std::string amount;
double rate;
std::string position_size;
int64_t timestamp;
};
struct OrderBookEntry {
double price;
std::string size;
int orders;
};
struct OrderBook {
std::string market;
std::vector<OrderBookEntry> bids;
std::vector<OrderBookEntry> asks;
int64_t timestamp;
};
struct LimitOrderParams {
std::string market;
std::string side;
double price;
std::string size;
TimeInForce time_in_force = TimeInForce::GTC;
bool post_only = false;
};
struct Order {
std::string id;
std::string market;
std::string side;
double price;
std::string size;
std::string filled_size;
OrderStatus status;
TimeInForce time_in_force;
bool post_only;
};
struct Farm {
std::string id;
std::string name;
Token stake_token;
std::vector<Token> reward_tokens;
double tvl_usd;
double apr;
};
struct StakeParams {
std::string farm;
std::string amount;
};
struct FarmPosition {
std::string farm_id;
std::string staked_amount;
std::vector<std::string> pending_rewards;
int64_t staked_at;
};
struct OHLCV {
int64_t timestamp;
double open;
double high;
double low;
double close;
double volume;
};
struct TradeHistory {
std::string id;
std::string market;
std::string side;
double price;
std::string size;
int64_t timestamp;
std::string maker;
std::string taker;
};
struct VolumeStats {
double volume_24h;
double volume_7d;
double volume_30d;
int trades_24h;
};
struct TVLStats {
double total_tvl;
double pools_tvl;
double farms_tvl;
double perps_tvl;
};
struct ClaimRewardsResult {
std::string amount;
std::string transaction_hash;
};
struct FundingRateInfo {
double rate;
int64_t next_time;
};
// Exception
class DexException : public std::runtime_error {
public:
DexException(const std::string& message,
const std::string& code = "",
int status = 0)
: std::runtime_error(message), code_(code), status_(status) {}
const std::string& code() const { return code_; }
int status() const { return status_; }
private:
std::string code_;
int status_;
};
// Main Client
class SynorDex {
public:
explicit SynorDex(const Config& config);
~SynorDex();
SynorDex(const SynorDex&) = delete;
SynorDex& operator=(const SynorDex&) = delete;
SynorDex(SynorDex&&) noexcept;
SynorDex& operator=(SynorDex&&) noexcept;
// Token Operations
std::future<Token> get_token(const std::string& address);
std::future<std::vector<Token>> list_tokens();
std::future<std::vector<Token>> search_tokens(const std::string& query);
// Pool Operations
std::future<Pool> get_pool(const std::string& token_a, const std::string& token_b);
std::future<Pool> get_pool_by_id(const std::string& pool_id);
std::future<std::vector<Pool>> list_pools(const std::optional<PoolFilter>& filter = std::nullopt);
// Swap Operations
std::future<Quote> get_quote(const QuoteParams& params);
std::future<SwapResult> swap(const SwapParams& params);
// Liquidity Operations
std::future<LiquidityResult> add_liquidity(const AddLiquidityParams& params);
std::future<LiquidityResult> remove_liquidity(const RemoveLiquidityParams& params);
std::future<std::vector<LPPosition>> get_my_positions();
// Analytics
std::future<std::vector<OHLCV>> get_price_history(const std::string& pair,
const std::string& interval,
int limit = 100);
std::future<std::vector<TradeHistory>> get_trade_history(const std::string& pair,
int limit = 50);
std::future<VolumeStats> get_volume_stats();
std::future<TVLStats> get_tvl();
// Lifecycle
std::future<bool> health_check();
void close();
bool is_closed() const;
// Sub-clients
PerpsClient& perps();
OrderBookClient& orderbook();
FarmsClient& farms();
private:
std::unique_ptr<SynorDexImpl> impl_;
};
// Perpetuals Client
class PerpsClient {
public:
explicit PerpsClient(SynorDex& dex);
~PerpsClient();
std::future<std::vector<PerpMarket>> list_markets();
std::future<PerpMarket> get_market(const std::string& symbol);
std::future<PerpPosition> open_position(const OpenPositionParams& params);
std::future<PerpPosition> close_position(const ClosePositionParams& params);
std::future<std::vector<PerpPosition>> get_positions();
std::future<std::vector<PerpOrder>> get_orders();
std::future<void> cancel_order(const std::string& order_id);
std::future<int> cancel_all_orders(const std::optional<std::string>& market = std::nullopt);
std::future<std::vector<FundingPayment>> get_funding_history(const std::string& market,
int limit = 100);
std::future<FundingRateInfo> get_funding_rate(const std::string& market);
private:
SynorDex& dex_;
};
// OrderBook Client
class OrderBookClient {
public:
explicit OrderBookClient(SynorDex& dex);
~OrderBookClient();
std::future<OrderBook> get_order_book(const std::string& market, int depth = 20);
std::future<Order> place_limit_order(const LimitOrderParams& params);
std::future<void> cancel_order(const std::string& order_id);
std::future<std::vector<Order>> get_open_orders(const std::optional<std::string>& market = std::nullopt);
std::future<std::vector<Order>> get_order_history(int limit = 50);
private:
SynorDex& dex_;
};
// Farms Client
class FarmsClient {
public:
explicit FarmsClient(SynorDex& dex);
~FarmsClient();
std::future<std::vector<Farm>> list_farms();
std::future<Farm> get_farm(const std::string& farm_id);
std::future<FarmPosition> stake(const StakeParams& params);
std::future<FarmPosition> unstake(const std::string& farm, const std::string& amount);
std::future<ClaimRewardsResult> claim_rewards(const std::string& farm);
std::future<std::vector<FarmPosition>> get_my_farm_positions();
private:
SynorDex& dex_;
};
} // namespace dex
} // namespace synor
#endif // SYNOR_DEX_HPP

View file

@ -0,0 +1,387 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;
namespace Synor.Dex
{
/// <summary>
/// Synor DEX SDK Client
///
/// Complete decentralized exchange client with support for:
/// - AMM swaps (constant product, stable, concentrated)
/// - Liquidity provision
/// - Perpetual futures (up to 100x leverage)
/// - Order books (limit orders)
/// - Farming & staking
/// </summary>
public class SynorDex : IDisposable
{
private readonly DexConfig _config;
private readonly HttpClient _client;
private readonly JsonSerializerOptions _jsonOptions;
private bool _closed;
public PerpsClient Perps { get; }
public OrderBookClient OrderBook { get; }
public FarmsClient Farms { get; }
public SynorDex(DexConfig config)
{
_config = config;
_client = new HttpClient
{
BaseAddress = new Uri(config.Endpoint),
Timeout = TimeSpan.FromMilliseconds(config.Timeout)
};
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", config.ApiKey);
_client.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
PropertyNameCaseInsensitive = true
};
Perps = new PerpsClient(this);
OrderBook = new OrderBookClient(this);
Farms = new FarmsClient(this);
}
// Token Operations
public Task<Token> GetTokenAsync(string address)
=> GetAsync<Token>($"/tokens/{address}");
public Task<List<Token>> ListTokensAsync()
=> GetAsync<List<Token>>("/tokens");
public Task<List<Token>> SearchTokensAsync(string query)
=> GetAsync<List<Token>>($"/tokens/search?q={HttpUtility.UrlEncode(query)}");
// Pool Operations
public Task<Pool> GetPoolAsync(string tokenA, string tokenB)
=> GetAsync<Pool>($"/pools/{tokenA}/{tokenB}");
public Task<Pool> GetPoolByIdAsync(string poolId)
=> GetAsync<Pool>($"/pools/{poolId}");
public Task<List<Pool>> ListPoolsAsync(PoolFilter filter = null)
{
var parameters = new List<string>();
if (filter != null)
{
if (filter.Tokens != null) parameters.Add($"tokens={string.Join(",", filter.Tokens)}");
if (filter.MinTvl.HasValue) parameters.Add($"min_tvl={filter.MinTvl}");
if (filter.MinVolume24h.HasValue) parameters.Add($"min_volume={filter.MinVolume24h}");
if (filter.Verified.HasValue) parameters.Add($"verified={filter.Verified.Value.ToString().ToLower()}");
if (filter.Limit.HasValue) parameters.Add($"limit={filter.Limit}");
if (filter.Offset.HasValue) parameters.Add($"offset={filter.Offset}");
}
var path = parameters.Count > 0 ? $"/pools?{string.Join("&", parameters)}" : "/pools";
return GetAsync<List<Pool>>(path);
}
// Swap Operations
public Task<Quote> GetQuoteAsync(QuoteParams prms)
=> PostAsync<Quote>("/swap/quote", new
{
token_in = prms.TokenIn,
token_out = prms.TokenOut,
amount_in = prms.AmountIn.ToString(),
slippage = prms.Slippage ?? 0.005
});
public Task<SwapResult> SwapAsync(SwapParams prms)
{
var deadline = prms.Deadline ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 1200;
return PostAsync<SwapResult>("/swap", new
{
token_in = prms.TokenIn,
token_out = prms.TokenOut,
amount_in = prms.AmountIn.ToString(),
min_amount_out = prms.MinAmountOut.ToString(),
deadline,
recipient = prms.Recipient
});
}
// Liquidity Operations
public Task<LiquidityResult> AddLiquidityAsync(AddLiquidityParams prms)
{
var deadline = prms.Deadline ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 1200;
return PostAsync<LiquidityResult>("/liquidity/add", new
{
token_a = prms.TokenA,
token_b = prms.TokenB,
amount_a = prms.AmountA.ToString(),
amount_b = prms.AmountB.ToString(),
deadline,
min_amount_a = prms.MinAmountA?.ToString(),
min_amount_b = prms.MinAmountB?.ToString()
});
}
public Task<LiquidityResult> RemoveLiquidityAsync(RemoveLiquidityParams prms)
{
var deadline = prms.Deadline ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 1200;
return PostAsync<LiquidityResult>("/liquidity/remove", new
{
pool = prms.Pool,
lp_amount = prms.LpAmount.ToString(),
deadline,
min_amount_a = prms.MinAmountA?.ToString(),
min_amount_b = prms.MinAmountB?.ToString()
});
}
public Task<List<LPPosition>> GetMyPositionsAsync()
=> GetAsync<List<LPPosition>>("/liquidity/positions");
// Analytics
public Task<List<OHLCV>> GetPriceHistoryAsync(string pair, string interval, int limit = 100)
=> GetAsync<List<OHLCV>>($"/analytics/candles/{pair}?interval={interval}&limit={limit}");
public Task<List<TradeHistory>> GetTradeHistoryAsync(string pair, int limit = 50)
=> GetAsync<List<TradeHistory>>($"/analytics/trades/{pair}?limit={limit}");
public Task<VolumeStats> GetVolumeStatsAsync()
=> GetAsync<VolumeStats>("/analytics/volume");
public Task<TVLStats> GetTVLAsync()
=> GetAsync<TVLStats>("/analytics/tvl");
// Lifecycle
public async Task<bool> HealthCheckAsync()
{
try
{
var response = await GetAsync<HealthResponse>("/health");
return response.Status == "healthy";
}
catch
{
return false;
}
}
public void Close()
{
_closed = true;
_client.Dispose();
}
public bool IsClosed => _closed;
public void Dispose()
{
Close();
}
// Internal methods
internal async Task<T> GetAsync<T>(string path)
{
CheckClosed();
var response = await _client.GetAsync(path);
return await HandleResponseAsync<T>(response);
}
internal async Task<T> PostAsync<T>(string path, object body)
{
CheckClosed();
var json = JsonSerializer.Serialize(body, _jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _client.PostAsync(path, content);
return await HandleResponseAsync<T>(response);
}
internal async Task<T> DeleteAsync<T>(string path)
{
CheckClosed();
var response = await _client.DeleteAsync(path);
return await HandleResponseAsync<T>(response);
}
private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response)
{
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
ErrorResponse error = null;
try { error = JsonSerializer.Deserialize<ErrorResponse>(body, _jsonOptions); } catch { }
throw new DexException(
error?.Message ?? $"HTTP {(int)response.StatusCode}",
error?.Code,
(int)response.StatusCode);
}
return JsonSerializer.Deserialize<T>(body, _jsonOptions);
}
private void CheckClosed()
{
if (_closed)
throw new DexException("Client has been closed", "CLIENT_CLOSED", 0);
}
private class HealthResponse { public string Status { get; set; } }
private class ErrorResponse { public string Message { get; set; } public string Code { get; set; } }
}
/// <summary>
/// DEX Configuration
/// </summary>
public class DexConfig
{
public string ApiKey { get; set; }
public string Endpoint { get; set; } = "https://dex.synor.io/v1";
public string WsEndpoint { get; set; } = "wss://dex.synor.io/v1/ws";
public int Timeout { get; set; } = 30000;
public int Retries { get; set; } = 3;
public bool Debug { get; set; } = false;
}
/// <summary>
/// DEX Exception
/// </summary>
public class DexException : Exception
{
public string Code { get; }
public int Status { get; }
public DexException(string message, string code = null, int status = 0) : base(message)
{
Code = code;
Status = status;
}
}
/// <summary>
/// Perpetual futures sub-client
/// </summary>
public class PerpsClient
{
private readonly SynorDex _dex;
internal PerpsClient(SynorDex dex) => _dex = dex;
public Task<List<PerpMarket>> ListMarketsAsync()
=> _dex.GetAsync<List<PerpMarket>>("/perps/markets");
public Task<PerpMarket> GetMarketAsync(string symbol)
=> _dex.GetAsync<PerpMarket>($"/perps/markets/{symbol}");
public Task<PerpPosition> OpenPositionAsync(OpenPositionParams prms)
=> _dex.PostAsync<PerpPosition>("/perps/positions", new
{
market = prms.Market,
side = prms.Side.ToString().ToLower(),
size = prms.Size.ToString(),
leverage = prms.Leverage,
order_type = prms.OrderType.ToString().ToLower(),
margin_type = prms.MarginType.ToString().ToLower(),
reduce_only = prms.ReduceOnly,
limit_price = prms.LimitPrice,
stop_loss = prms.StopLoss,
take_profit = prms.TakeProfit
});
public Task<PerpPosition> ClosePositionAsync(ClosePositionParams prms)
=> _dex.PostAsync<PerpPosition>("/perps/positions/close", new
{
market = prms.Market,
size = prms.Size?.ToString(),
order_type = prms.OrderType.ToString().ToLower(),
limit_price = prms.LimitPrice
});
public Task<List<PerpPosition>> GetPositionsAsync()
=> _dex.GetAsync<List<PerpPosition>>("/perps/positions");
public Task<List<PerpOrder>> GetOrdersAsync()
=> _dex.GetAsync<List<PerpOrder>>("/perps/orders");
public Task<List<FundingPayment>> GetFundingHistoryAsync(string market, int limit = 100)
=> _dex.GetAsync<List<FundingPayment>>($"/perps/funding/{market}?limit={limit}");
public Task<FundingRateInfo> GetFundingRateAsync(string market)
=> _dex.GetAsync<FundingRateInfo>($"/perps/funding/{market}/current");
}
/// <summary>
/// Order book sub-client
/// </summary>
public class OrderBookClient
{
private readonly SynorDex _dex;
internal OrderBookClient(SynorDex dex) => _dex = dex;
public Task<OrderBook> GetOrderBookAsync(string market, int depth = 20)
=> _dex.GetAsync<OrderBook>($"/orderbook/{market}?depth={depth}");
public Task<Order> PlaceLimitOrderAsync(LimitOrderParams prms)
=> _dex.PostAsync<Order>("/orderbook/orders", new
{
market = prms.Market,
side = prms.Side,
price = prms.Price,
size = prms.Size.ToString(),
time_in_force = prms.TimeInForce.ToString(),
post_only = prms.PostOnly
});
public Task<List<Order>> GetOpenOrdersAsync(string market = null)
{
var path = market != null ? $"/orderbook/orders?market={market}" : "/orderbook/orders";
return _dex.GetAsync<List<Order>>(path);
}
public Task<List<Order>> GetOrderHistoryAsync(int limit = 50)
=> _dex.GetAsync<List<Order>>($"/orderbook/orders/history?limit={limit}");
}
/// <summary>
/// Farms sub-client
/// </summary>
public class FarmsClient
{
private readonly SynorDex _dex;
internal FarmsClient(SynorDex dex) => _dex = dex;
public Task<List<Farm>> ListFarmsAsync()
=> _dex.GetAsync<List<Farm>>("/farms");
public Task<Farm> GetFarmAsync(string farmId)
=> _dex.GetAsync<Farm>($"/farms/{farmId}");
public Task<FarmPosition> StakeAsync(StakeParams prms)
=> _dex.PostAsync<FarmPosition>("/farms/stake", new
{
farm = prms.Farm,
amount = prms.Amount.ToString()
});
public Task<FarmPosition> UnstakeAsync(string farm, System.Numerics.BigInteger amount)
=> _dex.PostAsync<FarmPosition>("/farms/unstake", new
{
farm,
amount = amount.ToString()
});
public Task<ClaimRewardsResult> ClaimRewardsAsync(string farm)
=> _dex.PostAsync<ClaimRewardsResult>("/farms/claim", new { farm });
public Task<List<FarmPosition>> GetMyFarmPositionsAsync()
=> _dex.GetAsync<List<FarmPosition>>("/farms/positions");
}
}

View file

@ -0,0 +1,315 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text.Json.Serialization;
namespace Synor.Dex
{
public enum PoolType { ConstantProduct, Stable, Concentrated }
public enum PositionSide { Long, Short }
public enum OrderType { Market, Limit, StopMarket, StopLimit, TakeProfit, TakeProfitLimit }
public enum MarginType { Cross, Isolated }
public enum TimeInForce { GTC, IOC, FOK, GTD }
public enum OrderStatus { Pending, Open, PartiallyFilled, Filled, Cancelled, Expired }
public class Token
{
public string Address { get; set; }
public string Symbol { get; set; }
public string Name { get; set; }
public int Decimals { get; set; }
public string TotalSupply { get; set; }
public double? PriceUsd { get; set; }
public string LogoUrl { get; set; }
public bool Verified { get; set; }
}
public class Pool
{
public string Id { get; set; }
public Token TokenA { get; set; }
public Token TokenB { get; set; }
public PoolType PoolType { get; set; }
public string ReserveA { get; set; }
public string ReserveB { get; set; }
public double Fee { get; set; }
public double TvlUsd { get; set; }
public double Volume24h { get; set; }
public double Apr { get; set; }
public string LpTokenAddress { get; set; }
}
public class PoolFilter
{
public List<string> Tokens { get; set; }
public double? MinTvl { get; set; }
public double? MinVolume24h { get; set; }
public bool? Verified { get; set; }
public int? Limit { get; set; }
public int? Offset { get; set; }
}
public class Quote
{
public string TokenIn { get; set; }
public string TokenOut { get; set; }
public string AmountIn { get; set; }
public string AmountOut { get; set; }
public double PriceImpact { get; set; }
public List<string> Route { get; set; }
public string Fee { get; set; }
public string MinimumReceived { get; set; }
public long ExpiresAt { get; set; }
}
public class QuoteParams
{
public string TokenIn { get; set; }
public string TokenOut { get; set; }
public BigInteger AmountIn { get; set; }
public double? Slippage { get; set; }
}
public class SwapParams
{
public string TokenIn { get; set; }
public string TokenOut { get; set; }
public BigInteger AmountIn { get; set; }
public BigInteger MinAmountOut { get; set; }
public long? Deadline { get; set; }
public string Recipient { get; set; }
}
public class SwapResult
{
public string TransactionHash { get; set; }
public string AmountIn { get; set; }
public string AmountOut { get; set; }
public double EffectivePrice { get; set; }
public string FeePaid { get; set; }
public List<string> Route { get; set; }
}
public class AddLiquidityParams
{
public string TokenA { get; set; }
public string TokenB { get; set; }
public BigInteger AmountA { get; set; }
public BigInteger AmountB { get; set; }
public BigInteger? MinAmountA { get; set; }
public BigInteger? MinAmountB { get; set; }
public long? Deadline { get; set; }
}
public class RemoveLiquidityParams
{
public string Pool { get; set; }
public BigInteger LpAmount { get; set; }
public BigInteger? MinAmountA { get; set; }
public BigInteger? MinAmountB { get; set; }
public long? Deadline { get; set; }
}
public class LiquidityResult
{
public string TransactionHash { get; set; }
public string AmountA { get; set; }
public string AmountB { get; set; }
public string LpTokens { get; set; }
public double PoolShare { get; set; }
}
public class LPPosition
{
public string PoolId { get; set; }
public string LpTokens { get; set; }
public string TokenAAmount { get; set; }
public string TokenBAmount { get; set; }
public double ValueUsd { get; set; }
public string UnclaimedFeesA { get; set; }
public string UnclaimedFeesB { get; set; }
public double ImpermanentLoss { get; set; }
}
public class PerpMarket
{
public string Symbol { get; set; }
public string BaseAsset { get; set; }
public string QuoteAsset { get; set; }
public double IndexPrice { get; set; }
public double MarkPrice { get; set; }
public double FundingRate { get; set; }
public long NextFundingTime { get; set; }
public string OpenInterest { get; set; }
public double Volume24h { get; set; }
public int MaxLeverage { get; set; }
}
public class OpenPositionParams
{
public string Market { get; set; }
public PositionSide Side { get; set; }
public BigInteger Size { get; set; }
public int Leverage { get; set; }
public OrderType OrderType { get; set; }
public double? LimitPrice { get; set; }
public double? StopLoss { get; set; }
public double? TakeProfit { get; set; }
public MarginType MarginType { get; set; } = MarginType.Cross;
public bool ReduceOnly { get; set; }
}
public class ClosePositionParams
{
public string Market { get; set; }
public BigInteger? Size { get; set; }
public OrderType OrderType { get; set; } = OrderType.Market;
public double? LimitPrice { get; set; }
}
public class PerpPosition
{
public string Id { get; set; }
public string Market { get; set; }
public PositionSide Side { get; set; }
public string Size { get; set; }
public double EntryPrice { get; set; }
public double MarkPrice { get; set; }
public double LiquidationPrice { get; set; }
public string Margin { get; set; }
public int Leverage { get; set; }
public string UnrealizedPnl { get; set; }
}
public class PerpOrder
{
public string Id { get; set; }
public string Market { get; set; }
public PositionSide Side { get; set; }
public OrderType OrderType { get; set; }
public string Size { get; set; }
public double? Price { get; set; }
public OrderStatus Status { get; set; }
}
public class FundingPayment
{
public string Market { get; set; }
public string Amount { get; set; }
public double Rate { get; set; }
public string PositionSize { get; set; }
public long Timestamp { get; set; }
}
public class FundingRateInfo
{
public double Rate { get; set; }
public long NextTime { get; set; }
}
public class OrderBookEntry
{
public double Price { get; set; }
public string Size { get; set; }
public int Orders { get; set; }
}
public class OrderBook
{
public string Market { get; set; }
public List<OrderBookEntry> Bids { get; set; }
public List<OrderBookEntry> Asks { get; set; }
public long Timestamp { get; set; }
}
public class LimitOrderParams
{
public string Market { get; set; }
public string Side { get; set; }
public double Price { get; set; }
public BigInteger Size { get; set; }
public TimeInForce TimeInForce { get; set; } = TimeInForce.GTC;
public bool PostOnly { get; set; }
}
public class Order
{
public string Id { get; set; }
public string Market { get; set; }
public string Side { get; set; }
public double Price { get; set; }
public string Size { get; set; }
public string FilledSize { get; set; }
public OrderStatus Status { get; set; }
public TimeInForce TimeInForce { get; set; }
public bool PostOnly { get; set; }
}
public class Farm
{
public string Id { get; set; }
public string Name { get; set; }
public Token StakeToken { get; set; }
public List<Token> RewardTokens { get; set; }
public double TvlUsd { get; set; }
public double Apr { get; set; }
}
public class StakeParams
{
public string Farm { get; set; }
public BigInteger Amount { get; set; }
}
public class FarmPosition
{
public string FarmId { get; set; }
public string StakedAmount { get; set; }
public List<string> PendingRewards { get; set; }
public long StakedAt { get; set; }
}
public class ClaimRewardsResult
{
public string Amount { get; set; }
public string TransactionHash { get; set; }
}
public class OHLCV
{
public long Timestamp { get; set; }
public double Open { get; set; }
public double High { get; set; }
public double Low { get; set; }
public double Close { get; set; }
public double Volume { get; set; }
}
public class TradeHistory
{
public string Id { get; set; }
public string Market { get; set; }
public string Side { get; set; }
public double Price { get; set; }
public string Size { get; set; }
public long Timestamp { get; set; }
public string Maker { get; set; }
public string Taker { get; set; }
}
public class VolumeStats
{
public double Volume24h { get; set; }
public double Volume7d { get; set; }
public double Volume30d { get; set; }
public int Trades24h { get; set; }
}
public class TVLStats
{
public double TotalTvl { get; set; }
public double PoolsTvl { get; set; }
public double FarmsTvl { get; set; }
public double PerpsTvl { get; set; }
}
}

View file

@ -0,0 +1,355 @@
/// Synor DEX SDK for Flutter/Dart
///
/// Complete decentralized exchange client with support for:
/// - AMM swaps (constant product, stable, concentrated)
/// - Liquidity provision
/// - Perpetual futures (up to 100x leverage)
/// - Order books (limit orders)
/// - Farming & staking
library synor_dex;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'types.dart';
export 'types.dart';
/// Main DEX client
class SynorDex {
final DexConfig config;
final http.Client _client;
bool _closed = false;
late final PerpsClient perps;
late final OrderBookClient orderbook;
late final FarmsClient farms;
SynorDex(this.config) : _client = http.Client() {
perps = PerpsClient(this);
orderbook = OrderBookClient(this);
farms = FarmsClient(this);
}
// Token Operations
Future<Token> getToken(String address) async {
return _get('/tokens/$address', Token.fromJson);
}
Future<List<Token>> listTokens() async {
return _getList('/tokens', Token.fromJson);
}
Future<List<Token>> searchTokens(String query) async {
return _getList('/tokens/search?q=${Uri.encodeComponent(query)}', Token.fromJson);
}
// Pool Operations
Future<Pool> getPool(String tokenA, String tokenB) async {
return _get('/pools/$tokenA/$tokenB', Pool.fromJson);
}
Future<Pool> getPoolById(String poolId) async {
return _get('/pools/$poolId', Pool.fromJson);
}
Future<List<Pool>> listPools({PoolFilter? filter}) async {
final params = <String>[];
if (filter != null) {
if (filter.tokens != null) params.add('tokens=${filter.tokens!.join(",")}');
if (filter.minTvl != null) params.add('min_tvl=${filter.minTvl}');
if (filter.minVolume24h != null) params.add('min_volume=${filter.minVolume24h}');
if (filter.verified != null) params.add('verified=${filter.verified}');
if (filter.limit != null) params.add('limit=${filter.limit}');
if (filter.offset != null) params.add('offset=${filter.offset}');
}
final path = params.isEmpty ? '/pools' : '/pools?${params.join("&")}';
return _getList(path, Pool.fromJson);
}
// Swap Operations
Future<Quote> getQuote(QuoteParams params) async {
return _post('/swap/quote', {
'token_in': params.tokenIn,
'token_out': params.tokenOut,
'amount_in': params.amountIn.toString(),
'slippage': params.slippage ?? 0.005,
}, Quote.fromJson);
}
Future<SwapResult> swap(SwapParams params) async {
final deadline = params.deadline ??
(DateTime.now().millisecondsSinceEpoch ~/ 1000) + 1200;
return _post('/swap', {
'token_in': params.tokenIn,
'token_out': params.tokenOut,
'amount_in': params.amountIn.toString(),
'min_amount_out': params.minAmountOut.toString(),
'deadline': deadline,
if (params.recipient != null) 'recipient': params.recipient,
}, SwapResult.fromJson);
}
// Liquidity Operations
Future<LiquidityResult> addLiquidity(AddLiquidityParams params) async {
final deadline = params.deadline ??
(DateTime.now().millisecondsSinceEpoch ~/ 1000) + 1200;
return _post('/liquidity/add', {
'token_a': params.tokenA,
'token_b': params.tokenB,
'amount_a': params.amountA.toString(),
'amount_b': params.amountB.toString(),
'deadline': deadline,
if (params.minAmountA != null) 'min_amount_a': params.minAmountA.toString(),
if (params.minAmountB != null) 'min_amount_b': params.minAmountB.toString(),
}, LiquidityResult.fromJson);
}
Future<LiquidityResult> removeLiquidity(RemoveLiquidityParams params) async {
final deadline = params.deadline ??
(DateTime.now().millisecondsSinceEpoch ~/ 1000) + 1200;
return _post('/liquidity/remove', {
'pool': params.pool,
'lp_amount': params.lpAmount.toString(),
'deadline': deadline,
if (params.minAmountA != null) 'min_amount_a': params.minAmountA.toString(),
if (params.minAmountB != null) 'min_amount_b': params.minAmountB.toString(),
}, LiquidityResult.fromJson);
}
Future<List<LPPosition>> getMyPositions() async {
return _getList('/liquidity/positions', LPPosition.fromJson);
}
// Analytics
Future<List<OHLCV>> getPriceHistory(String pair, String interval, {int limit = 100}) async {
return _getList('/analytics/candles/$pair?interval=$interval&limit=$limit', OHLCV.fromJson);
}
Future<List<TradeHistory>> getTradeHistory(String pair, {int limit = 50}) async {
return _getList('/analytics/trades/$pair?limit=$limit', TradeHistory.fromJson);
}
Future<VolumeStats> getVolumeStats() async {
return _get('/analytics/volume', VolumeStats.fromJson);
}
Future<TVLStats> getTVL() async {
return _get('/analytics/tvl', TVLStats.fromJson);
}
// Lifecycle
Future<bool> healthCheck() async {
try {
final response = await _get('/health', (json) => json['status'] as String);
return response == 'healthy';
} catch (_) {
return false;
}
}
void close() {
_closed = true;
_client.close();
}
bool get isClosed => _closed;
// Internal methods
Future<T> _get<T>(String path, T Function(Map<String, dynamic>) fromJson) async {
_checkClosed();
final response = await _client.get(
Uri.parse('${config.endpoint}$path'),
headers: _headers,
);
return _handleResponse(response, fromJson);
}
Future<List<T>> _getList<T>(String path, T Function(Map<String, dynamic>) fromJson) async {
_checkClosed();
final response = await _client.get(
Uri.parse('${config.endpoint}$path'),
headers: _headers,
);
_checkResponse(response);
final list = jsonDecode(response.body) as List;
return list.map((e) => fromJson(e as Map<String, dynamic>)).toList();
}
Future<T> _post<T>(String path, Map<String, dynamic> body, T Function(Map<String, dynamic>) fromJson) async {
_checkClosed();
final response = await _client.post(
Uri.parse('${config.endpoint}$path'),
headers: _headers,
body: jsonEncode(body),
);
return _handleResponse(response, fromJson);
}
Future<T> _delete<T>(String path, T Function(Map<String, dynamic>) fromJson) async {
_checkClosed();
final response = await _client.delete(
Uri.parse('${config.endpoint}$path'),
headers: _headers,
);
return _handleResponse(response, fromJson);
}
Map<String, String> get _headers => {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${config.apiKey}',
'X-SDK-Version': 'flutter/0.1.0',
};
T _handleResponse<T>(http.Response response, T Function(Map<String, dynamic>) fromJson) {
_checkResponse(response);
final json = jsonDecode(response.body) as Map<String, dynamic>;
return fromJson(json);
}
void _checkResponse(http.Response response) {
if (response.statusCode >= 400) {
Map<String, dynamic>? error;
try {
error = jsonDecode(response.body) as Map<String, dynamic>;
} catch (_) {}
throw DexException(
error?['message'] ?? 'HTTP ${response.statusCode}',
error?['code'],
response.statusCode,
);
}
}
void _checkClosed() {
if (_closed) {
throw DexException('Client has been closed', 'CLIENT_CLOSED', 0);
}
}
}
/// Perpetual futures sub-client
class PerpsClient {
final SynorDex _dex;
PerpsClient(this._dex);
Future<List<PerpMarket>> listMarkets() async {
return _dex._getList('/perps/markets', PerpMarket.fromJson);
}
Future<PerpMarket> getMarket(String symbol) async {
return _dex._get('/perps/markets/$symbol', PerpMarket.fromJson);
}
Future<PerpPosition> openPosition(OpenPositionParams params) async {
return _dex._post('/perps/positions', {
'market': params.market,
'side': params.side.name,
'size': params.size.toString(),
'leverage': params.leverage,
'order_type': params.orderType.name,
'margin_type': params.marginType.name,
'reduce_only': params.reduceOnly,
if (params.limitPrice != null) 'limit_price': params.limitPrice,
if (params.stopLoss != null) 'stop_loss': params.stopLoss,
if (params.takeProfit != null) 'take_profit': params.takeProfit,
}, PerpPosition.fromJson);
}
Future<PerpPosition> closePosition(ClosePositionParams params) async {
return _dex._post('/perps/positions/close', {
'market': params.market,
'order_type': params.orderType.name,
if (params.size != null) 'size': params.size.toString(),
if (params.limitPrice != null) 'limit_price': params.limitPrice,
}, PerpPosition.fromJson);
}
Future<List<PerpPosition>> getPositions() async {
return _dex._getList('/perps/positions', PerpPosition.fromJson);
}
Future<List<PerpOrder>> getOrders() async {
return _dex._getList('/perps/orders', PerpOrder.fromJson);
}
Future<List<FundingPayment>> getFundingHistory(String market, {int limit = 100}) async {
return _dex._getList('/perps/funding/$market?limit=$limit', FundingPayment.fromJson);
}
Future<FundingRateInfo> getFundingRate(String market) async {
return _dex._get('/perps/funding/$market/current', FundingRateInfo.fromJson);
}
}
/// Order book sub-client
class OrderBookClient {
final SynorDex _dex;
OrderBookClient(this._dex);
Future<OrderBook> getOrderBook(String market, {int depth = 20}) async {
return _dex._get('/orderbook/$market?depth=$depth', OrderBook.fromJson);
}
Future<Order> placeLimitOrder(LimitOrderParams params) async {
return _dex._post('/orderbook/orders', {
'market': params.market,
'side': params.side,
'price': params.price,
'size': params.size.toString(),
'time_in_force': params.timeInForce.name,
'post_only': params.postOnly,
}, Order.fromJson);
}
Future<List<Order>> getOpenOrders({String? market}) async {
final path = market != null ? '/orderbook/orders?market=$market' : '/orderbook/orders';
return _dex._getList(path, Order.fromJson);
}
Future<List<Order>> getOrderHistory({int limit = 50}) async {
return _dex._getList('/orderbook/orders/history?limit=$limit', Order.fromJson);
}
}
/// Farms sub-client
class FarmsClient {
final SynorDex _dex;
FarmsClient(this._dex);
Future<List<Farm>> listFarms() async {
return _dex._getList('/farms', Farm.fromJson);
}
Future<Farm> getFarm(String farmId) async {
return _dex._get('/farms/$farmId', Farm.fromJson);
}
Future<FarmPosition> stake(StakeParams params) async {
return _dex._post('/farms/stake', {
'farm': params.farm,
'amount': params.amount.toString(),
}, FarmPosition.fromJson);
}
Future<FarmPosition> unstake(String farm, BigInt amount) async {
return _dex._post('/farms/unstake', {
'farm': farm,
'amount': amount.toString(),
}, FarmPosition.fromJson);
}
Future<ClaimRewardsResult> claimRewards(String farm) async {
return _dex._post('/farms/claim', {'farm': farm}, ClaimRewardsResult.fromJson);
}
Future<List<FarmPosition>> getMyFarmPositions() async {
return _dex._getList('/farms/positions', FarmPosition.fromJson);
}
}

View file

@ -0,0 +1,776 @@
/// Synor DEX SDK Types for Flutter/Dart
/// Pool type
enum PoolType { constantProduct, stable, concentrated }
/// Position side for perpetuals
enum PositionSide { long, short }
/// Order type
enum OrderType { market, limit, stopMarket, stopLimit, takeProfit, takeProfitLimit }
/// Margin type
enum MarginType { cross, isolated }
/// Time in force
enum TimeInForce { GTC, IOC, FOK, GTD }
/// Order status
enum OrderStatus { pending, open, partiallyFilled, filled, cancelled, expired }
/// DEX exception
class DexException implements Exception {
final String message;
final String? code;
final int status;
DexException(this.message, this.code, this.status);
@override
String toString() => 'DexException: $message (code: $code, status: $status)';
}
/// DEX configuration
class DexConfig {
final String apiKey;
final String endpoint;
final String wsEndpoint;
final int timeout;
final int retries;
final bool debug;
DexConfig({
required this.apiKey,
this.endpoint = 'https://dex.synor.io/v1',
this.wsEndpoint = 'wss://dex.synor.io/v1/ws',
this.timeout = 30000,
this.retries = 3,
this.debug = false,
});
}
/// Token
class Token {
final String address;
final String symbol;
final String name;
final int decimals;
final BigInt totalSupply;
final double? priceUsd;
final String? logoUrl;
final bool verified;
Token({
required this.address,
required this.symbol,
required this.name,
required this.decimals,
required this.totalSupply,
this.priceUsd,
this.logoUrl,
this.verified = false,
});
factory Token.fromJson(Map<String, dynamic> json) => Token(
address: json['address'] as String,
symbol: json['symbol'] as String,
name: json['name'] as String,
decimals: json['decimals'] as int,
totalSupply: BigInt.parse(json['total_supply'] as String),
priceUsd: json['price_usd'] as double?,
logoUrl: json['logo_url'] as String?,
verified: json['verified'] as bool? ?? false,
);
}
/// Pool
class Pool {
final String id;
final Token tokenA;
final Token tokenB;
final PoolType poolType;
final BigInt reserveA;
final BigInt reserveB;
final double fee;
final double tvlUsd;
final double volume24h;
final double apr;
final String lpTokenAddress;
Pool({
required this.id,
required this.tokenA,
required this.tokenB,
required this.poolType,
required this.reserveA,
required this.reserveB,
required this.fee,
required this.tvlUsd,
required this.volume24h,
required this.apr,
required this.lpTokenAddress,
});
factory Pool.fromJson(Map<String, dynamic> json) => Pool(
id: json['id'] as String,
tokenA: Token.fromJson(json['token_a'] as Map<String, dynamic>),
tokenB: Token.fromJson(json['token_b'] as Map<String, dynamic>),
poolType: PoolType.values.firstWhere((e) => e.name == json['pool_type']),
reserveA: BigInt.parse(json['reserve_a'] as String),
reserveB: BigInt.parse(json['reserve_b'] as String),
fee: json['fee'] as double,
tvlUsd: json['tvl_usd'] as double,
volume24h: json['volume_24h'] as double,
apr: json['apr'] as double,
lpTokenAddress: json['lp_token_address'] as String,
);
}
/// Pool filter
class PoolFilter {
final List<String>? tokens;
final double? minTvl;
final double? minVolume24h;
final bool? verified;
final int? limit;
final int? offset;
PoolFilter({this.tokens, this.minTvl, this.minVolume24h, this.verified, this.limit, this.offset});
}
/// Quote
class Quote {
final String tokenIn;
final String tokenOut;
final BigInt amountIn;
final BigInt amountOut;
final double priceImpact;
final List<String> route;
final BigInt fee;
final BigInt minimumReceived;
final int expiresAt;
Quote({
required this.tokenIn,
required this.tokenOut,
required this.amountIn,
required this.amountOut,
required this.priceImpact,
required this.route,
required this.fee,
required this.minimumReceived,
required this.expiresAt,
});
factory Quote.fromJson(Map<String, dynamic> json) => Quote(
tokenIn: json['token_in'] as String,
tokenOut: json['token_out'] as String,
amountIn: BigInt.parse(json['amount_in'] as String),
amountOut: BigInt.parse(json['amount_out'] as String),
priceImpact: json['price_impact'] as double,
route: (json['route'] as List).cast<String>(),
fee: BigInt.parse(json['fee'] as String),
minimumReceived: BigInt.parse(json['minimum_received'] as String),
expiresAt: json['expires_at'] as int,
);
}
/// Quote params
class QuoteParams {
final String tokenIn;
final String tokenOut;
final BigInt amountIn;
final double? slippage;
QuoteParams({required this.tokenIn, required this.tokenOut, required this.amountIn, this.slippage});
}
/// Swap params
class SwapParams {
final String tokenIn;
final String tokenOut;
final BigInt amountIn;
final BigInt minAmountOut;
final int? deadline;
final String? recipient;
SwapParams({
required this.tokenIn,
required this.tokenOut,
required this.amountIn,
required this.minAmountOut,
this.deadline,
this.recipient,
});
}
/// Swap result
class SwapResult {
final String transactionHash;
final BigInt amountIn;
final BigInt amountOut;
final double effectivePrice;
final BigInt feePaid;
final List<String> route;
SwapResult({
required this.transactionHash,
required this.amountIn,
required this.amountOut,
required this.effectivePrice,
required this.feePaid,
required this.route,
});
factory SwapResult.fromJson(Map<String, dynamic> json) => SwapResult(
transactionHash: json['transaction_hash'] as String,
amountIn: BigInt.parse(json['amount_in'] as String),
amountOut: BigInt.parse(json['amount_out'] as String),
effectivePrice: json['effective_price'] as double,
feePaid: BigInt.parse(json['fee_paid'] as String),
route: (json['route'] as List).cast<String>(),
);
}
/// Add liquidity params
class AddLiquidityParams {
final String tokenA;
final String tokenB;
final BigInt amountA;
final BigInt amountB;
final BigInt? minAmountA;
final BigInt? minAmountB;
final int? deadline;
AddLiquidityParams({
required this.tokenA,
required this.tokenB,
required this.amountA,
required this.amountB,
this.minAmountA,
this.minAmountB,
this.deadline,
});
}
/// Remove liquidity params
class RemoveLiquidityParams {
final String pool;
final BigInt lpAmount;
final BigInt? minAmountA;
final BigInt? minAmountB;
final int? deadline;
RemoveLiquidityParams({
required this.pool,
required this.lpAmount,
this.minAmountA,
this.minAmountB,
this.deadline,
});
}
/// Liquidity result
class LiquidityResult {
final String transactionHash;
final BigInt amountA;
final BigInt amountB;
final BigInt lpTokens;
final double poolShare;
LiquidityResult({
required this.transactionHash,
required this.amountA,
required this.amountB,
required this.lpTokens,
required this.poolShare,
});
factory LiquidityResult.fromJson(Map<String, dynamic> json) => LiquidityResult(
transactionHash: json['transaction_hash'] as String,
amountA: BigInt.parse(json['amount_a'] as String),
amountB: BigInt.parse(json['amount_b'] as String),
lpTokens: BigInt.parse(json['lp_tokens'] as String),
poolShare: json['pool_share'] as double,
);
}
/// LP position
class LPPosition {
final String poolId;
final BigInt lpTokens;
final BigInt tokenAAmount;
final BigInt tokenBAmount;
final double valueUsd;
final BigInt unclaimedFeesA;
final BigInt unclaimedFeesB;
final double impermanentLoss;
LPPosition({
required this.poolId,
required this.lpTokens,
required this.tokenAAmount,
required this.tokenBAmount,
required this.valueUsd,
required this.unclaimedFeesA,
required this.unclaimedFeesB,
required this.impermanentLoss,
});
factory LPPosition.fromJson(Map<String, dynamic> json) => LPPosition(
poolId: json['pool_id'] as String,
lpTokens: BigInt.parse(json['lp_tokens'] as String),
tokenAAmount: BigInt.parse(json['token_a_amount'] as String),
tokenBAmount: BigInt.parse(json['token_b_amount'] as String),
valueUsd: json['value_usd'] as double,
unclaimedFeesA: BigInt.parse(json['unclaimed_fees_a'] as String),
unclaimedFeesB: BigInt.parse(json['unclaimed_fees_b'] as String),
impermanentLoss: json['impermanent_loss'] as double,
);
}
/// Perp market
class PerpMarket {
final String symbol;
final String baseAsset;
final String quoteAsset;
final double indexPrice;
final double markPrice;
final double fundingRate;
final int nextFundingTime;
final BigInt openInterest;
final double volume24h;
final int maxLeverage;
PerpMarket({
required this.symbol,
required this.baseAsset,
required this.quoteAsset,
required this.indexPrice,
required this.markPrice,
required this.fundingRate,
required this.nextFundingTime,
required this.openInterest,
required this.volume24h,
required this.maxLeverage,
});
factory PerpMarket.fromJson(Map<String, dynamic> json) => PerpMarket(
symbol: json['symbol'] as String,
baseAsset: json['base_asset'] as String,
quoteAsset: json['quote_asset'] as String,
indexPrice: json['index_price'] as double,
markPrice: json['mark_price'] as double,
fundingRate: json['funding_rate'] as double,
nextFundingTime: json['next_funding_time'] as int,
openInterest: BigInt.parse(json['open_interest'] as String),
volume24h: json['volume_24h'] as double,
maxLeverage: json['max_leverage'] as int,
);
}
/// Open position params
class OpenPositionParams {
final String market;
final PositionSide side;
final BigInt size;
final int leverage;
final OrderType orderType;
final double? limitPrice;
final double? stopLoss;
final double? takeProfit;
final MarginType marginType;
final bool reduceOnly;
OpenPositionParams({
required this.market,
required this.side,
required this.size,
required this.leverage,
required this.orderType,
this.limitPrice,
this.stopLoss,
this.takeProfit,
this.marginType = MarginType.cross,
this.reduceOnly = false,
});
}
/// Close position params
class ClosePositionParams {
final String market;
final BigInt? size;
final OrderType orderType;
final double? limitPrice;
ClosePositionParams({
required this.market,
this.size,
this.orderType = OrderType.market,
this.limitPrice,
});
}
/// Perp position
class PerpPosition {
final String id;
final String market;
final PositionSide side;
final BigInt size;
final double entryPrice;
final double markPrice;
final double liquidationPrice;
final BigInt margin;
final int leverage;
final BigInt unrealizedPnl;
PerpPosition({
required this.id,
required this.market,
required this.side,
required this.size,
required this.entryPrice,
required this.markPrice,
required this.liquidationPrice,
required this.margin,
required this.leverage,
required this.unrealizedPnl,
});
factory PerpPosition.fromJson(Map<String, dynamic> json) => PerpPosition(
id: json['id'] as String,
market: json['market'] as String,
side: PositionSide.values.firstWhere((e) => e.name == json['side']),
size: BigInt.parse(json['size'] as String),
entryPrice: json['entry_price'] as double,
markPrice: json['mark_price'] as double,
liquidationPrice: json['liquidation_price'] as double,
margin: BigInt.parse(json['margin'] as String),
leverage: json['leverage'] as int,
unrealizedPnl: BigInt.parse(json['unrealized_pnl'] as String),
);
}
/// Perp order
class PerpOrder {
final String id;
final String market;
final PositionSide side;
final OrderType orderType;
final BigInt size;
final double? price;
final OrderStatus status;
PerpOrder({
required this.id,
required this.market,
required this.side,
required this.orderType,
required this.size,
this.price,
required this.status,
});
factory PerpOrder.fromJson(Map<String, dynamic> json) => PerpOrder(
id: json['id'] as String,
market: json['market'] as String,
side: PositionSide.values.firstWhere((e) => e.name == json['side']),
orderType: OrderType.values.firstWhere((e) => e.name == json['order_type']),
size: BigInt.parse(json['size'] as String),
price: json['price'] as double?,
status: OrderStatus.values.firstWhere((e) => e.name == json['status']),
);
}
/// Funding payment
class FundingPayment {
final String market;
final BigInt amount;
final double rate;
final BigInt positionSize;
final int timestamp;
FundingPayment({
required this.market,
required this.amount,
required this.rate,
required this.positionSize,
required this.timestamp,
});
factory FundingPayment.fromJson(Map<String, dynamic> json) => FundingPayment(
market: json['market'] as String,
amount: BigInt.parse(json['amount'] as String),
rate: json['rate'] as double,
positionSize: BigInt.parse(json['position_size'] as String),
timestamp: json['timestamp'] as int,
);
}
/// Funding rate info
class FundingRateInfo {
final double rate;
final int nextTime;
FundingRateInfo({required this.rate, required this.nextTime});
factory FundingRateInfo.fromJson(Map<String, dynamic> json) => FundingRateInfo(
rate: json['rate'] as double,
nextTime: json['next_time'] as int,
);
}
/// Order book entry
class OrderBookEntry {
final double price;
final BigInt size;
final int orders;
OrderBookEntry({required this.price, required this.size, required this.orders});
factory OrderBookEntry.fromJson(Map<String, dynamic> json) => OrderBookEntry(
price: json['price'] as double,
size: BigInt.parse(json['size'] as String),
orders: json['orders'] as int,
);
}
/// Order book
class OrderBook {
final String market;
final List<OrderBookEntry> bids;
final List<OrderBookEntry> asks;
final int timestamp;
OrderBook({required this.market, required this.bids, required this.asks, required this.timestamp});
factory OrderBook.fromJson(Map<String, dynamic> json) => OrderBook(
market: json['market'] as String,
bids: (json['bids'] as List).map((e) => OrderBookEntry.fromJson(e as Map<String, dynamic>)).toList(),
asks: (json['asks'] as List).map((e) => OrderBookEntry.fromJson(e as Map<String, dynamic>)).toList(),
timestamp: json['timestamp'] as int,
);
}
/// Limit order params
class LimitOrderParams {
final String market;
final String side;
final double price;
final BigInt size;
final TimeInForce timeInForce;
final bool postOnly;
LimitOrderParams({
required this.market,
required this.side,
required this.price,
required this.size,
this.timeInForce = TimeInForce.GTC,
this.postOnly = false,
});
}
/// Order
class Order {
final String id;
final String market;
final String side;
final double price;
final BigInt size;
final BigInt filledSize;
final OrderStatus status;
final TimeInForce timeInForce;
final bool postOnly;
Order({
required this.id,
required this.market,
required this.side,
required this.price,
required this.size,
required this.filledSize,
required this.status,
required this.timeInForce,
required this.postOnly,
});
factory Order.fromJson(Map<String, dynamic> json) => Order(
id: json['id'] as String,
market: json['market'] as String,
side: json['side'] as String,
price: json['price'] as double,
size: BigInt.parse(json['size'] as String),
filledSize: BigInt.parse(json['filled_size'] as String),
status: OrderStatus.values.firstWhere((e) => e.name == json['status']),
timeInForce: TimeInForce.values.firstWhere((e) => e.name == json['time_in_force']),
postOnly: json['post_only'] as bool,
);
}
/// Farm
class Farm {
final String id;
final String name;
final Token stakeToken;
final List<Token> rewardTokens;
final double tvlUsd;
final double apr;
Farm({
required this.id,
required this.name,
required this.stakeToken,
required this.rewardTokens,
required this.tvlUsd,
required this.apr,
});
factory Farm.fromJson(Map<String, dynamic> json) => Farm(
id: json['id'] as String,
name: json['name'] as String,
stakeToken: Token.fromJson(json['stake_token'] as Map<String, dynamic>),
rewardTokens: (json['reward_tokens'] as List).map((e) => Token.fromJson(e as Map<String, dynamic>)).toList(),
tvlUsd: json['tvl_usd'] as double,
apr: json['apr'] as double,
);
}
/// Stake params
class StakeParams {
final String farm;
final BigInt amount;
StakeParams({required this.farm, required this.amount});
}
/// Farm position
class FarmPosition {
final String farmId;
final BigInt stakedAmount;
final List<BigInt> pendingRewards;
final int stakedAt;
FarmPosition({
required this.farmId,
required this.stakedAmount,
required this.pendingRewards,
required this.stakedAt,
});
factory FarmPosition.fromJson(Map<String, dynamic> json) => FarmPosition(
farmId: json['farm_id'] as String,
stakedAmount: BigInt.parse(json['staked_amount'] as String),
pendingRewards: (json['pending_rewards'] as List).map((e) => BigInt.parse(e as String)).toList(),
stakedAt: json['staked_at'] as int,
);
}
/// Claim rewards result
class ClaimRewardsResult {
final BigInt amount;
final String transactionHash;
ClaimRewardsResult({required this.amount, required this.transactionHash});
factory ClaimRewardsResult.fromJson(Map<String, dynamic> json) => ClaimRewardsResult(
amount: BigInt.parse(json['amount'] as String),
transactionHash: json['transaction_hash'] as String,
);
}
/// OHLCV
class OHLCV {
final int timestamp;
final double open;
final double high;
final double low;
final double close;
final double volume;
OHLCV({
required this.timestamp,
required this.open,
required this.high,
required this.low,
required this.close,
required this.volume,
});
factory OHLCV.fromJson(Map<String, dynamic> json) => OHLCV(
timestamp: json['timestamp'] as int,
open: json['open'] as double,
high: json['high'] as double,
low: json['low'] as double,
close: json['close'] as double,
volume: json['volume'] as double,
);
}
/// Trade history
class TradeHistory {
final String id;
final String market;
final String side;
final double price;
final BigInt size;
final int timestamp;
final String maker;
final String taker;
TradeHistory({
required this.id,
required this.market,
required this.side,
required this.price,
required this.size,
required this.timestamp,
required this.maker,
required this.taker,
});
factory TradeHistory.fromJson(Map<String, dynamic> json) => TradeHistory(
id: json['id'] as String,
market: json['market'] as String,
side: json['side'] as String,
price: json['price'] as double,
size: BigInt.parse(json['size'] as String),
timestamp: json['timestamp'] as int,
maker: json['maker'] as String,
taker: json['taker'] as String,
);
}
/// Volume stats
class VolumeStats {
final double volume24h;
final double volume7d;
final double volume30d;
final int trades24h;
VolumeStats({required this.volume24h, required this.volume7d, required this.volume30d, required this.trades24h});
factory VolumeStats.fromJson(Map<String, dynamic> json) => VolumeStats(
volume24h: json['volume_24h'] as double,
volume7d: json['volume_7d'] as double,
volume30d: json['volume_30d'] as double,
trades24h: json['trades_24h'] as int,
);
}
/// TVL stats
class TVLStats {
final double totalTvl;
final double poolsTvl;
final double farmsTvl;
final double perpsTvl;
TVLStats({required this.totalTvl, required this.poolsTvl, required this.farmsTvl, required this.perpsTvl});
factory TVLStats.fromJson(Map<String, dynamic> json) => TVLStats(
totalTvl: json['total_tvl'] as double,
poolsTvl: json['pools_tvl'] as double,
farmsTvl: json['farms_tvl'] as double,
perpsTvl: json['perps_tvl'] as double,
);
}

805
sdk/go/dex/client.go Normal file
View file

@ -0,0 +1,805 @@
// Package dex provides the Synor DEX SDK for Go
package dex
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
"net/url"
"strconv"
"sync"
"time"
"github.com/google/uuid"
"github.com/gorilla/websocket"
)
// SynorDex is the main DEX client
type SynorDex struct {
config Config
client *http.Client
closed bool
ws *websocket.Conn
wsMu sync.Mutex
subscriptions map[string]func([]byte)
subMu sync.RWMutex
// Sub-clients
Perps *PerpsClient
OrderBook *OrderBookClient
Farms *FarmsClient
}
// New creates a new SynorDex client
func New(config Config) *SynorDex {
if config.Endpoint == "" {
config.Endpoint = "https://dex.synor.io/v1"
}
if config.WSEndpoint == "" {
config.WSEndpoint = "wss://dex.synor.io/v1/ws"
}
if config.Timeout == 0 {
config.Timeout = 30 * time.Second
}
if config.Retries == 0 {
config.Retries = 3
}
dex := &SynorDex{
config: config,
client: &http.Client{
Timeout: config.Timeout,
},
subscriptions: make(map[string]func([]byte)),
}
dex.Perps = &PerpsClient{dex: dex}
dex.OrderBook = &OrderBookClient{dex: dex}
dex.Farms = &FarmsClient{dex: dex}
return dex
}
// Token Operations
// GetToken gets token information
func (d *SynorDex) GetToken(ctx context.Context, address string) (*Token, error) {
var token Token
err := d.get(ctx, "/tokens/"+address, &token)
return &token, err
}
// ListTokens lists all tokens
func (d *SynorDex) ListTokens(ctx context.Context) ([]Token, error) {
var tokens []Token
err := d.get(ctx, "/tokens", &tokens)
return tokens, err
}
// SearchTokens searches for tokens
func (d *SynorDex) SearchTokens(ctx context.Context, query string) ([]Token, error) {
var tokens []Token
err := d.get(ctx, "/tokens/search?q="+url.QueryEscape(query), &tokens)
return tokens, err
}
// Pool Operations
// GetPool gets a pool by token pair
func (d *SynorDex) GetPool(ctx context.Context, tokenA, tokenB string) (*Pool, error) {
var pool Pool
err := d.get(ctx, fmt.Sprintf("/pools/%s/%s", tokenA, tokenB), &pool)
return &pool, err
}
// GetPoolByID gets a pool by ID
func (d *SynorDex) GetPoolByID(ctx context.Context, poolID string) (*Pool, error) {
var pool Pool
err := d.get(ctx, "/pools/"+poolID, &pool)
return &pool, err
}
// ListPools lists pools with optional filtering
func (d *SynorDex) ListPools(ctx context.Context, filter *PoolFilter) ([]Pool, error) {
params := url.Values{}
if filter != nil {
if len(filter.Tokens) > 0 {
for _, t := range filter.Tokens {
params.Add("tokens", t)
}
}
if filter.MinTVL != nil {
params.Set("min_tvl", strconv.FormatFloat(*filter.MinTVL, 'f', -1, 64))
}
if filter.MinVolume24h != nil {
params.Set("min_volume", strconv.FormatFloat(*filter.MinVolume24h, 'f', -1, 64))
}
if filter.Verified != nil {
params.Set("verified", strconv.FormatBool(*filter.Verified))
}
if filter.Limit != nil {
params.Set("limit", strconv.Itoa(*filter.Limit))
}
if filter.Offset != nil {
params.Set("offset", strconv.Itoa(*filter.Offset))
}
}
var pools []Pool
path := "/pools"
if len(params) > 0 {
path += "?" + params.Encode()
}
err := d.get(ctx, path, &pools)
return pools, err
}
// Swap Operations
// GetQuote gets a swap quote
func (d *SynorDex) GetQuote(ctx context.Context, params QuoteParams) (*Quote, error) {
slippage := params.Slippage
if slippage == 0 {
slippage = 0.005
}
body := map[string]interface{}{
"token_in": params.TokenIn,
"token_out": params.TokenOut,
"amount_in": params.AmountIn.String(),
"slippage": slippage,
}
var quote Quote
err := d.post(ctx, "/swap/quote", body, &quote)
return &quote, err
}
// Swap executes a swap
func (d *SynorDex) Swap(ctx context.Context, params SwapParams) (*SwapResult, error) {
deadline := time.Now().Unix() + 1200
if params.Deadline != nil {
deadline = *params.Deadline
}
body := map[string]interface{}{
"token_in": params.TokenIn,
"token_out": params.TokenOut,
"amount_in": params.AmountIn.String(),
"min_amount_out": params.MinAmountOut.String(),
"deadline": deadline,
}
if params.Recipient != nil {
body["recipient"] = *params.Recipient
}
var result SwapResult
err := d.post(ctx, "/swap", body, &result)
return &result, err
}
// Liquidity Operations
// AddLiquidity adds liquidity to a pool
func (d *SynorDex) AddLiquidity(ctx context.Context, params AddLiquidityParams) (*LiquidityResult, error) {
deadline := time.Now().Unix() + 1200
if params.Deadline != nil {
deadline = *params.Deadline
}
body := map[string]interface{}{
"token_a": params.TokenA,
"token_b": params.TokenB,
"amount_a": params.AmountA.String(),
"amount_b": params.AmountB.String(),
"deadline": deadline,
}
if params.MinAmountA != nil {
body["min_amount_a"] = params.MinAmountA.String()
}
if params.MinAmountB != nil {
body["min_amount_b"] = params.MinAmountB.String()
}
var result LiquidityResult
err := d.post(ctx, "/liquidity/add", body, &result)
return &result, err
}
// RemoveLiquidity removes liquidity from a pool
func (d *SynorDex) RemoveLiquidity(ctx context.Context, params RemoveLiquidityParams) (*LiquidityResult, error) {
deadline := time.Now().Unix() + 1200
if params.Deadline != nil {
deadline = *params.Deadline
}
body := map[string]interface{}{
"pool": params.Pool,
"lp_amount": params.LPAmount.String(),
"deadline": deadline,
}
if params.MinAmountA != nil {
body["min_amount_a"] = params.MinAmountA.String()
}
if params.MinAmountB != nil {
body["min_amount_b"] = params.MinAmountB.String()
}
var result LiquidityResult
err := d.post(ctx, "/liquidity/remove", body, &result)
return &result, err
}
// GetMyPositions gets all LP positions
func (d *SynorDex) GetMyPositions(ctx context.Context) ([]LPPosition, error) {
var positions []LPPosition
err := d.get(ctx, "/liquidity/positions", &positions)
return positions, err
}
// Analytics
// GetPriceHistory gets price history (OHLCV candles)
func (d *SynorDex) GetPriceHistory(ctx context.Context, pair, interval string, limit int) ([]OHLCV, error) {
var candles []OHLCV
path := fmt.Sprintf("/analytics/candles/%s?interval=%s&limit=%d", pair, interval, limit)
err := d.get(ctx, path, &candles)
return candles, err
}
// GetTradeHistory gets trade history
func (d *SynorDex) GetTradeHistory(ctx context.Context, pair string, limit int) ([]TradeHistory, error) {
var trades []TradeHistory
path := fmt.Sprintf("/analytics/trades/%s?limit=%d", pair, limit)
err := d.get(ctx, path, &trades)
return trades, err
}
// GetVolumeStats gets volume statistics
func (d *SynorDex) GetVolumeStats(ctx context.Context) (*VolumeStats, error) {
var stats VolumeStats
err := d.get(ctx, "/analytics/volume", &stats)
return &stats, err
}
// GetTVL gets TVL statistics
func (d *SynorDex) GetTVL(ctx context.Context) (*TVLStats, error) {
var stats TVLStats
err := d.get(ctx, "/analytics/tvl", &stats)
return &stats, err
}
// Subscriptions
// SubscribePrice subscribes to price updates
func (d *SynorDex) SubscribePrice(market string, callback PriceCallback) (*Subscription, error) {
return d.subscribe("price", map[string]interface{}{"market": market}, func(data []byte) {
var price float64
if err := json.Unmarshal(data, &price); err == nil {
callback(price)
}
})
}
// SubscribeTrades subscribes to trade updates
func (d *SynorDex) SubscribeTrades(market string, callback TradeCallback) (*Subscription, error) {
return d.subscribe("trades", map[string]interface{}{"market": market}, func(data []byte) {
var trade TradeHistory
if err := json.Unmarshal(data, &trade); err == nil {
callback(trade)
}
})
}
// SubscribeOrderBook subscribes to order book updates
func (d *SynorDex) SubscribeOrderBook(market string, callback OrderBookCallback) (*Subscription, error) {
return d.subscribe("orderbook", map[string]interface{}{"market": market}, func(data []byte) {
var book OrderBook
if err := json.Unmarshal(data, &book); err == nil {
callback(book)
}
})
}
// Lifecycle
// HealthCheck checks if the service is healthy
func (d *SynorDex) HealthCheck(ctx context.Context) bool {
var result struct {
Status string `json:"status"`
}
if err := d.get(ctx, "/health", &result); err != nil {
return false
}
return result.Status == "healthy"
}
// Close closes the client
func (d *SynorDex) Close() error {
d.closed = true
d.wsMu.Lock()
defer d.wsMu.Unlock()
if d.ws != nil {
err := d.ws.Close()
d.ws = nil
return err
}
return nil
}
// Internal methods
func (d *SynorDex) get(ctx context.Context, path string, result interface{}) error {
return d.request(ctx, "GET", path, nil, result)
}
func (d *SynorDex) post(ctx context.Context, path string, body interface{}, result interface{}) error {
return d.request(ctx, "POST", path, body, result)
}
func (d *SynorDex) delete(ctx context.Context, path string, result interface{}) error {
return d.request(ctx, "DELETE", path, nil, result)
}
func (d *SynorDex) request(ctx context.Context, method, path string, body interface{}, result interface{}) error {
if d.closed {
return NewDexError("Client has been closed", "CLIENT_CLOSED", 0)
}
var lastErr error
for attempt := 0; attempt < d.config.Retries; attempt++ {
var bodyReader io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return err
}
bodyReader = bytes.NewReader(jsonBody)
}
req, err := http.NewRequestWithContext(ctx, method, d.config.Endpoint+path, bodyReader)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+d.config.APIKey)
req.Header.Set("X-SDK-Version", "go/0.1.0")
resp, err := d.client.Do(req)
if err != nil {
lastErr = err
if d.config.Debug {
fmt.Printf("Attempt %d failed: %v\n", attempt+1, err)
}
time.Sleep(time.Duration(1<<attempt) * time.Second)
continue
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
lastErr = err
continue
}
if resp.StatusCode >= 400 {
var errResp struct {
Message string `json:"message"`
Code string `json:"code"`
}
json.Unmarshal(respBody, &errResp)
return NewDexError(
errResp.Message,
errResp.Code,
resp.StatusCode,
)
}
if result != nil {
return json.Unmarshal(respBody, result)
}
return nil
}
if lastErr != nil {
return lastErr
}
return NewDexError("Unknown error", "", 0)
}
func (d *SynorDex) subscribe(channel string, params map[string]interface{}, callback func([]byte)) (*Subscription, error) {
if err := d.ensureWebSocket(); err != nil {
return nil, err
}
subscriptionID := uuid.New().String()[:8]
d.subMu.Lock()
d.subscriptions[subscriptionID] = callback
d.subMu.Unlock()
msg := map[string]interface{}{
"type": "subscribe",
"channel": channel,
"subscription_id": subscriptionID,
}
for k, v := range params {
msg[k] = v
}
d.wsMu.Lock()
err := d.ws.WriteJSON(msg)
d.wsMu.Unlock()
if err != nil {
return nil, err
}
return &Subscription{
ID: subscriptionID,
Channel: channel,
Cancel: func() {
d.subMu.Lock()
delete(d.subscriptions, subscriptionID)
d.subMu.Unlock()
d.wsMu.Lock()
if d.ws != nil {
d.ws.WriteJSON(map[string]interface{}{
"type": "unsubscribe",
"subscription_id": subscriptionID,
})
}
d.wsMu.Unlock()
},
}, nil
}
func (d *SynorDex) ensureWebSocket() error {
d.wsMu.Lock()
defer d.wsMu.Unlock()
if d.ws != nil {
return nil
}
conn, _, err := websocket.DefaultDialer.Dial(d.config.WSEndpoint, nil)
if err != nil {
return err
}
d.ws = conn
// Authenticate
err = d.ws.WriteJSON(map[string]interface{}{
"type": "auth",
"api_key": d.config.APIKey,
})
if err != nil {
return err
}
// Start message handler
go d.handleMessages()
return nil
}
func (d *SynorDex) handleMessages() {
for {
d.wsMu.Lock()
ws := d.ws
d.wsMu.Unlock()
if ws == nil {
return
}
_, message, err := ws.ReadMessage()
if err != nil {
return
}
var msg struct {
SubscriptionID string `json:"subscription_id"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(message, &msg); err != nil {
continue
}
if msg.SubscriptionID != "" {
d.subMu.RLock()
callback, ok := d.subscriptions[msg.SubscriptionID]
d.subMu.RUnlock()
if ok {
callback(msg.Data)
}
}
}
}
// PerpsClient handles perpetual futures operations
type PerpsClient struct {
dex *SynorDex
}
// ListMarkets lists all perpetual markets
func (p *PerpsClient) ListMarkets(ctx context.Context) ([]PerpMarket, error) {
var markets []PerpMarket
err := p.dex.get(ctx, "/perps/markets", &markets)
return markets, err
}
// GetMarket gets a specific perpetual market
func (p *PerpsClient) GetMarket(ctx context.Context, symbol string) (*PerpMarket, error) {
var market PerpMarket
err := p.dex.get(ctx, "/perps/markets/"+symbol, &market)
return &market, err
}
// OpenPosition opens a perpetual position
func (p *PerpsClient) OpenPosition(ctx context.Context, params OpenPositionParams) (*PerpPosition, error) {
marginType := params.MarginType
if marginType == "" {
marginType = MarginTypeCross
}
body := map[string]interface{}{
"market": params.Market,
"side": string(params.Side),
"size": params.Size.String(),
"leverage": params.Leverage,
"order_type": string(params.OrderType),
"margin_type": string(marginType),
"reduce_only": params.ReduceOnly,
}
if params.LimitPrice != nil {
body["limit_price"] = *params.LimitPrice
}
if params.StopLoss != nil {
body["stop_loss"] = *params.StopLoss
}
if params.TakeProfit != nil {
body["take_profit"] = *params.TakeProfit
}
var position PerpPosition
err := p.dex.post(ctx, "/perps/positions", body, &position)
return &position, err
}
// ClosePosition closes a perpetual position
func (p *PerpsClient) ClosePosition(ctx context.Context, params ClosePositionParams) (*PerpPosition, error) {
body := map[string]interface{}{
"market": params.Market,
"order_type": string(params.OrderType),
}
if params.Size != nil {
body["size"] = params.Size.String()
}
if params.LimitPrice != nil {
body["limit_price"] = *params.LimitPrice
}
var position PerpPosition
err := p.dex.post(ctx, "/perps/positions/close", body, &position)
return &position, err
}
// ModifyPosition modifies a perpetual position
func (p *PerpsClient) ModifyPosition(ctx context.Context, params ModifyPositionParams) (*PerpPosition, error) {
body := map[string]interface{}{}
if params.NewLeverage != nil {
body["new_leverage"] = *params.NewLeverage
}
if params.NewMargin != nil {
body["new_margin"] = params.NewMargin.String()
}
if params.NewStopLoss != nil {
body["new_stop_loss"] = *params.NewStopLoss
}
if params.NewTakeProfit != nil {
body["new_take_profit"] = *params.NewTakeProfit
}
var position PerpPosition
err := p.dex.post(ctx, "/perps/positions/"+params.PositionID+"/modify", body, &position)
return &position, err
}
// GetPositions gets all open positions
func (p *PerpsClient) GetPositions(ctx context.Context) ([]PerpPosition, error) {
var positions []PerpPosition
err := p.dex.get(ctx, "/perps/positions", &positions)
return positions, err
}
// GetPosition gets position for a specific market
func (p *PerpsClient) GetPosition(ctx context.Context, market string) (*PerpPosition, error) {
var position PerpPosition
err := p.dex.get(ctx, "/perps/positions/"+market, &position)
return &position, err
}
// GetOrders gets all open orders
func (p *PerpsClient) GetOrders(ctx context.Context) ([]PerpOrder, error) {
var orders []PerpOrder
err := p.dex.get(ctx, "/perps/orders", &orders)
return orders, err
}
// CancelOrder cancels an order
func (p *PerpsClient) CancelOrder(ctx context.Context, orderID string) error {
return p.dex.delete(ctx, "/perps/orders/"+orderID, nil)
}
// CancelAllOrders cancels all orders
func (p *PerpsClient) CancelAllOrders(ctx context.Context, market *string) (int, error) {
path := "/perps/orders"
if market != nil {
path += "?market=" + *market
}
var result struct {
Cancelled int `json:"cancelled"`
}
err := p.dex.delete(ctx, path, &result)
return result.Cancelled, err
}
// GetFundingHistory gets funding payment history
func (p *PerpsClient) GetFundingHistory(ctx context.Context, market string, limit int) ([]FundingPayment, error) {
var payments []FundingPayment
path := fmt.Sprintf("/perps/funding/%s?limit=%d", market, limit)
err := p.dex.get(ctx, path, &payments)
return payments, err
}
// GetFundingRate gets current funding rate
func (p *PerpsClient) GetFundingRate(ctx context.Context, market string) (*struct {
Rate float64 `json:"rate"`
NextTime int64 `json:"next_time"`
}, error) {
var result struct {
Rate float64 `json:"rate"`
NextTime int64 `json:"next_time"`
}
err := p.dex.get(ctx, "/perps/funding/"+market+"/current", &result)
return &result, err
}
// SubscribePosition subscribes to position updates
func (p *PerpsClient) SubscribePosition(callback PositionCallback) (*Subscription, error) {
return p.dex.subscribe("position", map[string]interface{}{}, func(data []byte) {
var position PerpPosition
if err := json.Unmarshal(data, &position); err == nil {
callback(position)
}
})
}
// OrderBookClient handles order book operations
type OrderBookClient struct {
dex *SynorDex
}
// GetOrderBook gets order book for a market
func (o *OrderBookClient) GetOrderBook(ctx context.Context, market string, depth int) (*OrderBook, error) {
var book OrderBook
path := fmt.Sprintf("/orderbook/%s?depth=%d", market, depth)
err := o.dex.get(ctx, path, &book)
return &book, err
}
// PlaceLimitOrder places a limit order
func (o *OrderBookClient) PlaceLimitOrder(ctx context.Context, params LimitOrderParams) (*Order, error) {
tif := params.TimeInForce
if tif == "" {
tif = TimeInForceGTC
}
body := map[string]interface{}{
"market": params.Market,
"side": params.Side,
"price": params.Price,
"size": params.Size.String(),
"time_in_force": string(tif),
"post_only": params.PostOnly,
}
var order Order
err := o.dex.post(ctx, "/orderbook/orders", body, &order)
return &order, err
}
// CancelOrder cancels an order
func (o *OrderBookClient) CancelOrder(ctx context.Context, orderID string) error {
return o.dex.delete(ctx, "/orderbook/orders/"+orderID, nil)
}
// GetOpenOrders gets all open orders
func (o *OrderBookClient) GetOpenOrders(ctx context.Context, market *string) ([]Order, error) {
path := "/orderbook/orders"
if market != nil {
path += "?market=" + *market
}
var orders []Order
err := o.dex.get(ctx, path, &orders)
return orders, err
}
// GetOrderHistory gets order history
func (o *OrderBookClient) GetOrderHistory(ctx context.Context, limit int) ([]Order, error) {
var orders []Order
path := fmt.Sprintf("/orderbook/orders/history?limit=%d", limit)
err := o.dex.get(ctx, path, &orders)
return orders, err
}
// FarmsClient handles farming operations
type FarmsClient struct {
dex *SynorDex
}
// ListFarms lists all farms
func (f *FarmsClient) ListFarms(ctx context.Context) ([]Farm, error) {
var farms []Farm
err := f.dex.get(ctx, "/farms", &farms)
return farms, err
}
// GetFarm gets a specific farm
func (f *FarmsClient) GetFarm(ctx context.Context, farmID string) (*Farm, error) {
var farm Farm
err := f.dex.get(ctx, "/farms/"+farmID, &farm)
return &farm, err
}
// Stake stakes tokens in a farm
func (f *FarmsClient) Stake(ctx context.Context, params StakeParams) (*FarmPosition, error) {
body := map[string]interface{}{
"farm": params.Farm,
"amount": params.Amount.String(),
}
var position FarmPosition
err := f.dex.post(ctx, "/farms/stake", body, &position)
return &position, err
}
// Unstake unstakes tokens from a farm
func (f *FarmsClient) Unstake(ctx context.Context, farm string, amount *big.Int) (*FarmPosition, error) {
body := map[string]interface{}{
"farm": farm,
"amount": amount.String(),
}
var position FarmPosition
err := f.dex.post(ctx, "/farms/unstake", body, &position)
return &position, err
}
// ClaimRewards claims rewards from a farm
func (f *FarmsClient) ClaimRewards(ctx context.Context, farm string) (*struct {
Amount *big.Int `json:"amount"`
TransactionHash string `json:"transaction_hash"`
}, error) {
body := map[string]interface{}{
"farm": farm,
}
var result struct {
Amount *big.Int `json:"amount"`
TransactionHash string `json:"transaction_hash"`
}
err := f.dex.post(ctx, "/farms/claim", body, &result)
return &result, err
}
// GetMyFarmPositions gets all farm positions
func (f *FarmsClient) GetMyFarmPositions(ctx context.Context) ([]FarmPosition, error) {
var positions []FarmPosition
err := f.dex.get(ctx, "/farms/positions", &positions)
return positions, err
}

454
sdk/go/dex/types.go Normal file
View file

@ -0,0 +1,454 @@
// Package dex provides the Synor DEX SDK for Go
//
// Complete decentralized exchange client with support for:
// - AMM swaps (constant product, stable, concentrated)
// - Liquidity provision
// - Perpetual futures (up to 100x leverage)
// - Order books (limit orders)
// - Farming & staking
package dex
import (
"math/big"
"time"
)
// PoolType represents the type of liquidity pool
type PoolType string
const (
PoolTypeConstantProduct PoolType = "constant_product" // x * y = k
PoolTypeStable PoolType = "stable" // Optimized for stablecoins
PoolTypeConcentrated PoolType = "concentrated" // Uniswap v3 style
)
// PositionSide represents the side of a perpetual position
type PositionSide string
const (
PositionSideLong PositionSide = "long"
PositionSideShort PositionSide = "short"
)
// OrderType represents the type of order
type OrderType string
const (
OrderTypeMarket OrderType = "market"
OrderTypeLimit OrderType = "limit"
OrderTypeStopMarket OrderType = "stop_market"
OrderTypeStopLimit OrderType = "stop_limit"
OrderTypeTakeProfit OrderType = "take_profit"
OrderTypeTakeProfitLimit OrderType = "take_profit_limit"
)
// MarginType represents the margin type for perpetuals
type MarginType string
const (
MarginTypeCross MarginType = "cross"
MarginTypeIsolated MarginType = "isolated"
)
// TimeInForce represents the time in force for limit orders
type TimeInForce string
const (
TimeInForceGTC TimeInForce = "GTC" // Good Till Cancel
TimeInForceIOC TimeInForce = "IOC" // Immediate Or Cancel
TimeInForceFOK TimeInForce = "FOK" // Fill Or Kill
TimeInForceGTD TimeInForce = "GTD" // Good Till Date
)
// OrderStatus represents the status of an order
type OrderStatus string
const (
OrderStatusPending OrderStatus = "pending"
OrderStatusOpen OrderStatus = "open"
OrderStatusPartiallyFilled OrderStatus = "partially_filled"
OrderStatusFilled OrderStatus = "filled"
OrderStatusCancelled OrderStatus = "cancelled"
OrderStatusExpired OrderStatus = "expired"
)
// Config holds the DEX client configuration
type Config struct {
APIKey string
Endpoint string
WSEndpoint string
Timeout time.Duration
Retries int
Debug bool
}
// DefaultConfig returns a Config with default values
func DefaultConfig(apiKey string) Config {
return Config{
APIKey: apiKey,
Endpoint: "https://dex.synor.io/v1",
WSEndpoint: "wss://dex.synor.io/v1/ws",
Timeout: 30 * time.Second,
Retries: 3,
Debug: false,
}
}
// Token represents token information
type Token struct {
Address string `json:"address"`
Symbol string `json:"symbol"`
Name string `json:"name"`
Decimals int `json:"decimals"`
TotalSupply *big.Int `json:"total_supply"`
PriceUSD *float64 `json:"price_usd,omitempty"`
LogoURL *string `json:"logo_url,omitempty"`
Verified bool `json:"verified"`
}
// Pool represents a liquidity pool
type Pool struct {
ID string `json:"id"`
TokenA Token `json:"token_a"`
TokenB Token `json:"token_b"`
PoolType PoolType `json:"pool_type"`
ReserveA *big.Int `json:"reserve_a"`
ReserveB *big.Int `json:"reserve_b"`
Fee float64 `json:"fee"`
TVLUSD float64 `json:"tvl_usd"`
Volume24h float64 `json:"volume_24h"`
APR float64 `json:"apr"`
LPTokenAddress string `json:"lp_token_address"`
TickSpacing *int `json:"tick_spacing,omitempty"` // For concentrated liquidity
SqrtPrice *string `json:"sqrt_price,omitempty"`
}
// PoolFilter holds pool listing filters
type PoolFilter struct {
Tokens []string
MinTVL *float64
MinVolume24h *float64
Verified *bool
Limit *int
Offset *int
}
// Quote represents a swap quote
type Quote struct {
TokenIn string `json:"token_in"`
TokenOut string `json:"token_out"`
AmountIn *big.Int `json:"amount_in"`
AmountOut *big.Int `json:"amount_out"`
PriceImpact float64 `json:"price_impact"`
Route []string `json:"route"`
Fee *big.Int `json:"fee"`
MinimumReceived *big.Int `json:"minimum_received"`
ExpiresAt int64 `json:"expires_at"`
}
// QuoteParams holds parameters for getting a quote
type QuoteParams struct {
TokenIn string
TokenOut string
AmountIn *big.Int
Slippage float64 // Default: 0.005
}
// SwapParams holds parameters for executing a swap
type SwapParams struct {
TokenIn string
TokenOut string
AmountIn *big.Int
MinAmountOut *big.Int
Deadline *int64
Recipient *string
}
// SwapResult represents the result of a swap
type SwapResult struct {
TransactionHash string `json:"transaction_hash"`
AmountIn *big.Int `json:"amount_in"`
AmountOut *big.Int `json:"amount_out"`
EffectivePrice float64 `json:"effective_price"`
FeePaid *big.Int `json:"fee_paid"`
Route []string `json:"route"`
}
// AddLiquidityParams holds parameters for adding liquidity
type AddLiquidityParams struct {
TokenA string
TokenB string
AmountA *big.Int
AmountB *big.Int
MinAmountA *big.Int
MinAmountB *big.Int
Deadline *int64
TickLower *int // For concentrated liquidity
TickUpper *int
}
// RemoveLiquidityParams holds parameters for removing liquidity
type RemoveLiquidityParams struct {
Pool string
LPAmount *big.Int
MinAmountA *big.Int
MinAmountB *big.Int
Deadline *int64
}
// LiquidityResult represents the result of a liquidity operation
type LiquidityResult struct {
TransactionHash string `json:"transaction_hash"`
AmountA *big.Int `json:"amount_a"`
AmountB *big.Int `json:"amount_b"`
LPTokens *big.Int `json:"lp_tokens"`
PoolShare float64 `json:"pool_share"`
}
// LPPosition represents an LP position
type LPPosition struct {
PoolID string `json:"pool_id"`
LPTokens *big.Int `json:"lp_tokens"`
TokenAAmount *big.Int `json:"token_a_amount"`
TokenBAmount *big.Int `json:"token_b_amount"`
ValueUSD float64 `json:"value_usd"`
UnclaimedFeesA *big.Int `json:"unclaimed_fees_a"`
UnclaimedFeesB *big.Int `json:"unclaimed_fees_b"`
ImpermanentLoss float64 `json:"impermanent_loss"`
TickLower *int `json:"tick_lower,omitempty"`
TickUpper *int `json:"tick_upper,omitempty"`
InRange *bool `json:"in_range,omitempty"`
}
// PerpMarket represents a perpetual futures market
type PerpMarket struct {
Symbol string `json:"symbol"`
BaseAsset string `json:"base_asset"`
QuoteAsset string `json:"quote_asset"`
IndexPrice float64 `json:"index_price"`
MarkPrice float64 `json:"mark_price"`
FundingRate float64 `json:"funding_rate"`
NextFundingTime int64 `json:"next_funding_time"`
OpenInterest *big.Int `json:"open_interest"`
Volume24h float64 `json:"volume_24h"`
PriceChange24h float64 `json:"price_change_24h"`
MaxLeverage int `json:"max_leverage"`
MinOrderSize *big.Int `json:"min_order_size"`
TickSize float64 `json:"tick_size"`
MaintenanceMargin float64 `json:"maintenance_margin"`
InitialMargin float64 `json:"initial_margin"`
}
// OpenPositionParams holds parameters for opening a perpetual position
type OpenPositionParams struct {
Market string
Side PositionSide
Size *big.Int
Leverage int // 1-100x
OrderType OrderType
LimitPrice *float64
StopLoss *float64
TakeProfit *float64
MarginType MarginType // Default: Cross
ReduceOnly bool
}
// ClosePositionParams holds parameters for closing a perpetual position
type ClosePositionParams struct {
Market string
Size *big.Int // nil = close entire position
OrderType OrderType
LimitPrice *float64
}
// ModifyPositionParams holds parameters for modifying a perpetual position
type ModifyPositionParams struct {
PositionID string
NewLeverage *int
NewMargin *big.Int
NewStopLoss *float64
NewTakeProfit *float64
}
// PerpPosition represents a perpetual position
type PerpPosition struct {
ID string `json:"id"`
Market string `json:"market"`
Side PositionSide `json:"side"`
Size *big.Int `json:"size"`
EntryPrice float64 `json:"entry_price"`
MarkPrice float64 `json:"mark_price"`
LiquidationPrice float64 `json:"liquidation_price"`
Margin *big.Int `json:"margin"`
Leverage int `json:"leverage"`
UnrealizedPNL *big.Int `json:"unrealized_pnl"`
RealizedPNL *big.Int `json:"realized_pnl"`
MarginRatio float64 `json:"margin_ratio"`
StopLoss *float64 `json:"stop_loss,omitempty"`
TakeProfit *float64 `json:"take_profit,omitempty"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
// PerpOrder represents a perpetual order
type PerpOrder struct {
ID string `json:"id"`
Market string `json:"market"`
Side PositionSide `json:"side"`
OrderType OrderType `json:"order_type"`
Size *big.Int `json:"size"`
Price *float64 `json:"price,omitempty"`
FilledSize *big.Int `json:"filled_size"`
Status OrderStatus `json:"status"`
ReduceOnly bool `json:"reduce_only"`
CreatedAt int64 `json:"created_at"`
}
// FundingPayment represents a funding payment record
type FundingPayment struct {
Market string `json:"market"`
Amount *big.Int `json:"amount"`
Rate float64 `json:"rate"`
PositionSize *big.Int `json:"position_size"`
Timestamp int64 `json:"timestamp"`
}
// OrderBookEntry represents an order book entry
type OrderBookEntry struct {
Price float64 `json:"price"`
Size *big.Int `json:"size"`
Orders int `json:"orders"`
}
// OrderBook represents an order book
type OrderBook struct {
Market string `json:"market"`
Bids []OrderBookEntry `json:"bids"`
Asks []OrderBookEntry `json:"asks"`
Timestamp int64 `json:"timestamp"`
}
// LimitOrderParams holds parameters for placing a limit order
type LimitOrderParams struct {
Market string
Side string // "buy" or "sell"
Price float64
Size *big.Int
TimeInForce TimeInForce
PostOnly bool
}
// Order represents a limit order
type Order struct {
ID string `json:"id"`
Market string `json:"market"`
Side string `json:"side"`
Price float64 `json:"price"`
Size *big.Int `json:"size"`
FilledSize *big.Int `json:"filled_size"`
Status OrderStatus `json:"status"`
TimeInForce TimeInForce `json:"time_in_force"`
PostOnly bool `json:"post_only"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
// Farm represents a yield farm
type Farm struct {
ID string `json:"id"`
Name string `json:"name"`
StakeToken Token `json:"stake_token"`
RewardTokens []Token `json:"reward_tokens"`
TVLUSD float64 `json:"tvl_usd"`
APR float64 `json:"apr"`
DailyRewards []*big.Int `json:"daily_rewards"`
LockupPeriod *int64 `json:"lockup_period,omitempty"`
MinStake *big.Int `json:"min_stake,omitempty"`
}
// StakeParams holds parameters for staking
type StakeParams struct {
Farm string
Amount *big.Int
}
// FarmPosition represents a farm position
type FarmPosition struct {
FarmID string `json:"farm_id"`
StakedAmount *big.Int `json:"staked_amount"`
PendingRewards []*big.Int `json:"pending_rewards"`
StakedAt int64 `json:"staked_at"`
UnlockAt *int64 `json:"unlock_at,omitempty"`
}
// OHLCV represents OHLCV candle data
type OHLCV struct {
Timestamp int64 `json:"timestamp"`
Open float64 `json:"open"`
High float64 `json:"high"`
Low float64 `json:"low"`
Close float64 `json:"close"`
Volume float64 `json:"volume"`
}
// TradeHistory represents a trade history entry
type TradeHistory struct {
ID string `json:"id"`
Market string `json:"market"`
Side string `json:"side"`
Price float64 `json:"price"`
Size *big.Int `json:"size"`
Timestamp int64 `json:"timestamp"`
Maker string `json:"maker"`
Taker string `json:"taker"`
}
// VolumeStats represents volume statistics
type VolumeStats struct {
Volume24h float64 `json:"volume_24h"`
Volume7d float64 `json:"volume_7d"`
Volume30d float64 `json:"volume_30d"`
Trades24h int `json:"trades_24h"`
UniqueTraders24h int `json:"unique_traders_24h"`
}
// TVLStats represents TVL statistics
type TVLStats struct {
TotalTVL float64 `json:"total_tvl"`
PoolsTVL float64 `json:"pools_tvl"`
FarmsTVL float64 `json:"farms_tvl"`
PerpsTVL float64 `json:"perps_tvl"`
}
// Subscription represents a WebSocket subscription
type Subscription struct {
ID string
Channel string
Cancel func()
}
// Callback types
type PriceCallback func(price float64)
type TradeCallback func(trade TradeHistory)
type OrderBookCallback func(book OrderBook)
type PositionCallback func(position PerpPosition)
// DexError represents a DEX SDK error
type DexError struct {
Message string
Code string
Status int
}
func (e *DexError) Error() string {
return e.Message
}
// NewDexError creates a new DexError
func NewDexError(message string, code string, status int) *DexError {
return &DexError{
Message: message,
Code: code,
Status: status,
}
}

View file

@ -0,0 +1,97 @@
package io.synor.dex;
/**
* DEX client configuration
*/
public class DexConfig {
private static final String DEFAULT_ENDPOINT = "https://dex.synor.io/v1";
private static final String DEFAULT_WS_ENDPOINT = "wss://dex.synor.io/v1/ws";
private static final long DEFAULT_TIMEOUT = 30000;
private static final int DEFAULT_RETRIES = 3;
private final String apiKey;
private final String endpoint;
private final String wsEndpoint;
private final long timeout;
private final int retries;
private final boolean debug;
private DexConfig(Builder builder) {
this.apiKey = builder.apiKey;
this.endpoint = builder.endpoint != null ? builder.endpoint : DEFAULT_ENDPOINT;
this.wsEndpoint = builder.wsEndpoint != null ? builder.wsEndpoint : DEFAULT_WS_ENDPOINT;
this.timeout = builder.timeout > 0 ? builder.timeout : DEFAULT_TIMEOUT;
this.retries = builder.retries > 0 ? builder.retries : DEFAULT_RETRIES;
this.debug = builder.debug;
}
public String getApiKey() {
return apiKey;
}
public String getEndpoint() {
return endpoint;
}
public String getWsEndpoint() {
return wsEndpoint;
}
public long getTimeout() {
return timeout;
}
public int getRetries() {
return retries;
}
public boolean isDebug() {
return debug;
}
public static Builder builder(String apiKey) {
return new Builder(apiKey);
}
public static class Builder {
private final String apiKey;
private String endpoint;
private String wsEndpoint;
private long timeout;
private int retries;
private boolean debug;
private Builder(String apiKey) {
this.apiKey = apiKey;
}
public Builder endpoint(String endpoint) {
this.endpoint = endpoint;
return this;
}
public Builder wsEndpoint(String wsEndpoint) {
this.wsEndpoint = wsEndpoint;
return this;
}
public Builder timeout(long timeout) {
this.timeout = timeout;
return this;
}
public Builder retries(int retries) {
this.retries = retries;
return this;
}
public Builder debug(boolean debug) {
this.debug = debug;
return this;
}
public DexConfig build() {
return new DexConfig(this);
}
}
}

View file

@ -0,0 +1,23 @@
package io.synor.dex;
/**
* DEX SDK Exception
*/
public class DexException extends RuntimeException {
private final String code;
private final int status;
public DexException(String message, String code, int status) {
super(message);
this.code = code;
this.status = status;
}
public String getCode() {
return code;
}
public int getStatus() {
return status;
}
}

View file

@ -0,0 +1,50 @@
package io.synor.dex;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* Farms and staking sub-client
*/
public class FarmsClient {
private final SynorDex dex;
FarmsClient(SynorDex dex) {
this.dex = dex;
}
public CompletableFuture<List<Farm>> listFarms() {
return dex.getList("/farms", Farm.class);
}
public CompletableFuture<Farm> getFarm(String farmId) {
return dex.get("/farms/" + farmId, Farm.class);
}
public CompletableFuture<FarmPosition> stake(StakeParams params) {
Map<String, Object> body = new HashMap<>();
body.put("farm", params.getFarm());
body.put("amount", params.getAmount().toString());
return dex.post("/farms/stake", body, FarmPosition.class);
}
public CompletableFuture<FarmPosition> unstake(String farm, BigInteger amount) {
Map<String, Object> body = new HashMap<>();
body.put("farm", farm);
body.put("amount", amount.toString());
return dex.post("/farms/unstake", body, FarmPosition.class);
}
public CompletableFuture<ClaimRewardsResult> claimRewards(String farm) {
Map<String, Object> body = new HashMap<>();
body.put("farm", farm);
return dex.post("/farms/claim", body, ClaimRewardsResult.class);
}
public CompletableFuture<List<FarmPosition>> getMyFarmPositions() {
return dex.getList("/farms/positions", FarmPosition.class);
}
}

View file

@ -0,0 +1,45 @@
package io.synor.dex;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* Order book sub-client
*/
public class OrderBookClient {
private final SynorDex dex;
OrderBookClient(SynorDex dex) {
this.dex = dex;
}
public CompletableFuture<OrderBook> getOrderBook(String market, int depth) {
return dex.get("/orderbook/" + market + "?depth=" + depth, OrderBook.class);
}
public CompletableFuture<Order> placeLimitOrder(LimitOrderParams params) {
Map<String, Object> body = new HashMap<>();
body.put("market", params.getMarket());
body.put("side", params.getSide());
body.put("price", params.getPrice());
body.put("size", params.getSize().toString());
body.put("time_in_force", params.getTimeInForce().name());
body.put("post_only", params.isPostOnly());
return dex.post("/orderbook/orders", body, Order.class);
}
public CompletableFuture<Void> cancelOrder(String orderId) {
return dex.delete("/orderbook/orders/" + orderId, Void.class);
}
public CompletableFuture<List<Order>> getOpenOrders(String market) {
String path = market != null ? "/orderbook/orders?market=" + market : "/orderbook/orders";
return dex.getList(path, Order.class);
}
public CompletableFuture<List<Order>> getOrderHistory(int limit) {
return dex.getList("/orderbook/orders/history?limit=" + limit, Order.class);
}
}

View file

@ -0,0 +1,109 @@
package io.synor.dex;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* Perpetual futures sub-client
*/
public class PerpsClient {
private final SynorDex dex;
PerpsClient(SynorDex dex) {
this.dex = dex;
}
public CompletableFuture<List<PerpMarket>> listMarkets() {
return dex.getList("/perps/markets", PerpMarket.class);
}
public CompletableFuture<PerpMarket> getMarket(String symbol) {
return dex.get("/perps/markets/" + symbol, PerpMarket.class);
}
public CompletableFuture<PerpPosition> openPosition(OpenPositionParams params) {
Map<String, Object> body = new HashMap<>();
body.put("market", params.getMarket());
body.put("side", params.getSide().name().toLowerCase());
body.put("size", params.getSize().toString());
body.put("leverage", params.getLeverage());
body.put("order_type", params.getOrderType().name().toLowerCase());
body.put("margin_type", params.getMarginType().name().toLowerCase());
body.put("reduce_only", params.isReduceOnly());
if (params.getLimitPrice() != null) {
body.put("limit_price", params.getLimitPrice());
}
if (params.getStopLoss() != null) {
body.put("stop_loss", params.getStopLoss());
}
if (params.getTakeProfit() != null) {
body.put("take_profit", params.getTakeProfit());
}
return dex.post("/perps/positions", body, PerpPosition.class);
}
public CompletableFuture<PerpPosition> closePosition(ClosePositionParams params) {
Map<String, Object> body = new HashMap<>();
body.put("market", params.getMarket());
body.put("order_type", params.getOrderType().name().toLowerCase());
if (params.getSize() != null) {
body.put("size", params.getSize().toString());
}
if (params.getLimitPrice() != null) {
body.put("limit_price", params.getLimitPrice());
}
return dex.post("/perps/positions/close", body, PerpPosition.class);
}
public CompletableFuture<PerpPosition> modifyPosition(ModifyPositionParams params) {
Map<String, Object> body = new HashMap<>();
if (params.getNewLeverage() != null) {
body.put("new_leverage", params.getNewLeverage());
}
if (params.getNewMargin() != null) {
body.put("new_margin", params.getNewMargin().toString());
}
if (params.getNewStopLoss() != null) {
body.put("new_stop_loss", params.getNewStopLoss());
}
if (params.getNewTakeProfit() != null) {
body.put("new_take_profit", params.getNewTakeProfit());
}
return dex.post("/perps/positions/" + params.getPositionId() + "/modify", body, PerpPosition.class);
}
public CompletableFuture<List<PerpPosition>> getPositions() {
return dex.getList("/perps/positions", PerpPosition.class);
}
public CompletableFuture<PerpPosition> getPosition(String market) {
return dex.get("/perps/positions/" + market, PerpPosition.class);
}
public CompletableFuture<List<PerpOrder>> getOrders() {
return dex.getList("/perps/orders", PerpOrder.class);
}
public CompletableFuture<Void> cancelOrder(String orderId) {
return dex.delete("/perps/orders/" + orderId, Void.class);
}
public CompletableFuture<Integer> cancelAllOrders(String market) {
String path = market != null ? "/perps/orders?market=" + market : "/perps/orders";
return dex.delete(path, CancelResult.class).thenApply(r -> r.cancelled);
}
public CompletableFuture<List<FundingPayment>> getFundingHistory(String market, int limit) {
return dex.getList("/perps/funding/" + market + "?limit=" + limit, FundingPayment.class);
}
public CompletableFuture<FundingRateInfo> getFundingRate(String market) {
return dex.get("/perps/funding/" + market + "/current", FundingRateInfo.class);
}
private static class CancelResult {
int cancelled;
}
}

View file

@ -0,0 +1,326 @@
package io.synor.dex;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import okhttp3.*;
import java.io.IOException;
import java.math.BigInteger;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* Synor DEX SDK Client
*
* Complete decentralized exchange client with support for:
* - AMM swaps (constant product, stable, concentrated)
* - Liquidity provision
* - Perpetual futures (up to 100x leverage)
* - Order books (limit orders)
* - Farming & staking
*/
public class SynorDex implements AutoCloseable {
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private final DexConfig config;
private final OkHttpClient client;
private final Gson gson;
private volatile boolean closed = false;
public final PerpsClient perps;
public final OrderBookClient orderbook;
public final FarmsClient farms;
public SynorDex(DexConfig config) {
this.config = config;
this.client = new OkHttpClient.Builder()
.callTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
.build();
this.gson = new GsonBuilder().create();
this.perps = new PerpsClient(this);
this.orderbook = new OrderBookClient(this);
this.farms = new FarmsClient(this);
}
// Token Operations
public CompletableFuture<Token> getToken(String address) {
return get("/tokens/" + address, Token.class);
}
public CompletableFuture<List<Token>> listTokens() {
return getList("/tokens", Token.class);
}
public CompletableFuture<List<Token>> searchTokens(String query) {
return getList("/tokens/search?q=" + urlEncode(query), Token.class);
}
// Pool Operations
public CompletableFuture<Pool> getPool(String tokenA, String tokenB) {
return get("/pools/" + tokenA + "/" + tokenB, Pool.class);
}
public CompletableFuture<Pool> getPoolById(String poolId) {
return get("/pools/" + poolId, Pool.class);
}
public CompletableFuture<List<Pool>> listPools(PoolFilter filter) {
StringBuilder params = new StringBuilder();
if (filter != null) {
if (filter.getTokens() != null) {
params.append("tokens=").append(String.join(",", filter.getTokens()));
}
if (filter.getMinTvl() != null) {
appendParam(params, "min_tvl", filter.getMinTvl().toString());
}
if (filter.getMinVolume24h() != null) {
appendParam(params, "min_volume", filter.getMinVolume24h().toString());
}
if (filter.getVerified() != null) {
appendParam(params, "verified", filter.getVerified().toString());
}
if (filter.getLimit() != null) {
appendParam(params, "limit", filter.getLimit().toString());
}
if (filter.getOffset() != null) {
appendParam(params, "offset", filter.getOffset().toString());
}
}
String path = params.length() > 0 ? "/pools?" + params : "/pools";
return getList(path, Pool.class);
}
// Swap Operations
public CompletableFuture<Quote> getQuote(QuoteParams params) {
Map<String, Object> body = new HashMap<>();
body.put("token_in", params.getTokenIn());
body.put("token_out", params.getTokenOut());
body.put("amount_in", params.getAmountIn().toString());
body.put("slippage", params.getSlippage() != null ? params.getSlippage() : 0.005);
return post("/swap/quote", body, Quote.class);
}
public CompletableFuture<SwapResult> swap(SwapParams params) {
long deadline = params.getDeadline() != null
? params.getDeadline()
: Instant.now().getEpochSecond() + 1200;
Map<String, Object> body = new HashMap<>();
body.put("token_in", params.getTokenIn());
body.put("token_out", params.getTokenOut());
body.put("amount_in", params.getAmountIn().toString());
body.put("min_amount_out", params.getMinAmountOut().toString());
body.put("deadline", deadline);
if (params.getRecipient() != null) {
body.put("recipient", params.getRecipient());
}
return post("/swap", body, SwapResult.class);
}
// Liquidity Operations
public CompletableFuture<LiquidityResult> addLiquidity(AddLiquidityParams params) {
long deadline = params.getDeadline() != null
? params.getDeadline()
: Instant.now().getEpochSecond() + 1200;
Map<String, Object> body = new HashMap<>();
body.put("token_a", params.getTokenA());
body.put("token_b", params.getTokenB());
body.put("amount_a", params.getAmountA().toString());
body.put("amount_b", params.getAmountB().toString());
body.put("deadline", deadline);
if (params.getMinAmountA() != null) {
body.put("min_amount_a", params.getMinAmountA().toString());
}
if (params.getMinAmountB() != null) {
body.put("min_amount_b", params.getMinAmountB().toString());
}
return post("/liquidity/add", body, LiquidityResult.class);
}
public CompletableFuture<LiquidityResult> removeLiquidity(RemoveLiquidityParams params) {
long deadline = params.getDeadline() != null
? params.getDeadline()
: Instant.now().getEpochSecond() + 1200;
Map<String, Object> body = new HashMap<>();
body.put("pool", params.getPool());
body.put("lp_amount", params.getLpAmount().toString());
body.put("deadline", deadline);
if (params.getMinAmountA() != null) {
body.put("min_amount_a", params.getMinAmountA().toString());
}
if (params.getMinAmountB() != null) {
body.put("min_amount_b", params.getMinAmountB().toString());
}
return post("/liquidity/remove", body, LiquidityResult.class);
}
public CompletableFuture<List<LPPosition>> getMyPositions() {
return getList("/liquidity/positions", LPPosition.class);
}
// Analytics
public CompletableFuture<List<OHLCV>> getPriceHistory(String pair, String interval, int limit) {
return getList("/analytics/candles/" + pair + "?interval=" + interval + "&limit=" + limit, OHLCV.class);
}
public CompletableFuture<List<TradeHistory>> getTradeHistory(String pair, int limit) {
return getList("/analytics/trades/" + pair + "?limit=" + limit, TradeHistory.class);
}
public CompletableFuture<VolumeStats> getVolumeStats() {
return get("/analytics/volume", VolumeStats.class);
}
public CompletableFuture<TVLStats> getTVL() {
return get("/analytics/tvl", TVLStats.class);
}
// Lifecycle
public CompletableFuture<Boolean> healthCheck() {
return get("/health", HealthResponse.class)
.thenApply(r -> "healthy".equals(r.status))
.exceptionally(e -> false);
}
@Override
public void close() {
closed = true;
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
}
public boolean isClosed() {
return closed;
}
// Internal methods
<T> CompletableFuture<T> get(String path, Class<T> responseClass) {
return request("GET", path, null, responseClass);
}
<T> CompletableFuture<List<T>> getList(String path, Class<T> elementClass) {
return request("GET", path, null, List.class)
.thenApply(list -> {
@SuppressWarnings("unchecked")
List<T> result = (List<T>) list;
return result;
});
}
<T> CompletableFuture<T> post(String path, Object body, Class<T> responseClass) {
return request("POST", path, body, responseClass);
}
<T> CompletableFuture<T> delete(String path, Class<T> responseClass) {
return request("DELETE", path, null, responseClass);
}
private <T> CompletableFuture<T> request(String method, String path, Object body, Class<T> responseClass) {
if (closed) {
return CompletableFuture.failedFuture(new DexException("Client has been closed", "CLIENT_CLOSED", 0));
}
CompletableFuture<T> future = new CompletableFuture<>();
Request.Builder requestBuilder = new Request.Builder()
.url(config.getEndpoint() + path)
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + config.getApiKey())
.header("X-SDK-Version", "java/0.1.0");
RequestBody requestBody = body != null
? RequestBody.create(gson.toJson(body), JSON)
: null;
switch (method) {
case "GET":
requestBuilder.get();
break;
case "POST":
requestBuilder.post(requestBody);
break;
case "DELETE":
requestBuilder.delete(requestBody);
break;
}
client.newCall(requestBuilder.build()).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
String bodyStr = responseBody != null ? responseBody.string() : "";
if (!response.isSuccessful()) {
ErrorResponse error = gson.fromJson(bodyStr, ErrorResponse.class);
future.completeExceptionally(new DexException(
error != null && error.message != null ? error.message : "HTTP " + response.code(),
error != null ? error.code : null,
response.code()
));
return;
}
T result = gson.fromJson(bodyStr, responseClass);
future.complete(result);
}
}
});
return future;
}
private void appendParam(StringBuilder params, String key, String value) {
if (params.length() > 0) {
params.append("&");
}
params.append(key).append("=").append(value);
}
private String urlEncode(String value) {
try {
return java.net.URLEncoder.encode(value, "UTF-8");
} catch (Exception e) {
return value;
}
}
DexConfig getConfig() {
return config;
}
OkHttpClient getClient() {
return client;
}
Gson getGson() {
return gson;
}
private static class HealthResponse {
String status;
}
private static class ErrorResponse {
String message;
String code;
}
}

View file

@ -0,0 +1,476 @@
package io.synor.dex;
import java.math.BigInteger;
import java.util.List;
/**
* DEX SDK Types
*/
public class Types {
public enum PoolType {
CONSTANT_PRODUCT,
STABLE,
CONCENTRATED
}
public enum PositionSide {
LONG,
SHORT
}
public enum OrderType {
MARKET,
LIMIT,
STOP_MARKET,
STOP_LIMIT,
TAKE_PROFIT,
TAKE_PROFIT_LIMIT
}
public enum MarginType {
CROSS,
ISOLATED
}
public enum TimeInForce {
GTC,
IOC,
FOK,
GTD
}
public enum OrderStatus {
PENDING,
OPEN,
PARTIALLY_FILLED,
FILLED,
CANCELLED,
EXPIRED
}
}
class Token {
public String address;
public String symbol;
public String name;
public int decimals;
public BigInteger totalSupply;
public Double priceUsd;
public String logoUrl;
public boolean verified;
}
class Pool {
public String id;
public Token tokenA;
public Token tokenB;
public Types.PoolType poolType;
public BigInteger reserveA;
public BigInteger reserveB;
public double fee;
public double tvlUsd;
public double volume24h;
public double apr;
public String lpTokenAddress;
public Integer tickSpacing;
public String sqrtPrice;
}
class PoolFilter {
private List<String> tokens;
private Double minTvl;
private Double minVolume24h;
private Boolean verified;
private Integer limit;
private Integer offset;
public List<String> getTokens() { return tokens; }
public void setTokens(List<String> tokens) { this.tokens = tokens; }
public Double getMinTvl() { return minTvl; }
public void setMinTvl(Double minTvl) { this.minTvl = minTvl; }
public Double getMinVolume24h() { return minVolume24h; }
public void setMinVolume24h(Double minVolume24h) { this.minVolume24h = minVolume24h; }
public Boolean getVerified() { return verified; }
public void setVerified(Boolean verified) { this.verified = verified; }
public Integer getLimit() { return limit; }
public void setLimit(Integer limit) { this.limit = limit; }
public Integer getOffset() { return offset; }
public void setOffset(Integer offset) { this.offset = offset; }
}
class Quote {
public String tokenIn;
public String tokenOut;
public BigInteger amountIn;
public BigInteger amountOut;
public double priceImpact;
public List<String> route;
public BigInteger fee;
public BigInteger minimumReceived;
public long expiresAt;
}
class QuoteParams {
private String tokenIn;
private String tokenOut;
private BigInteger amountIn;
private Double slippage;
public String getTokenIn() { return tokenIn; }
public void setTokenIn(String tokenIn) { this.tokenIn = tokenIn; }
public String getTokenOut() { return tokenOut; }
public void setTokenOut(String tokenOut) { this.tokenOut = tokenOut; }
public BigInteger getAmountIn() { return amountIn; }
public void setAmountIn(BigInteger amountIn) { this.amountIn = amountIn; }
public Double getSlippage() { return slippage; }
public void setSlippage(Double slippage) { this.slippage = slippage; }
}
class SwapParams {
private String tokenIn;
private String tokenOut;
private BigInteger amountIn;
private BigInteger minAmountOut;
private Long deadline;
private String recipient;
public String getTokenIn() { return tokenIn; }
public void setTokenIn(String tokenIn) { this.tokenIn = tokenIn; }
public String getTokenOut() { return tokenOut; }
public void setTokenOut(String tokenOut) { this.tokenOut = tokenOut; }
public BigInteger getAmountIn() { return amountIn; }
public void setAmountIn(BigInteger amountIn) { this.amountIn = amountIn; }
public BigInteger getMinAmountOut() { return minAmountOut; }
public void setMinAmountOut(BigInteger minAmountOut) { this.minAmountOut = minAmountOut; }
public Long getDeadline() { return deadline; }
public void setDeadline(Long deadline) { this.deadline = deadline; }
public String getRecipient() { return recipient; }
public void setRecipient(String recipient) { this.recipient = recipient; }
}
class SwapResult {
public String transactionHash;
public BigInteger amountIn;
public BigInteger amountOut;
public double effectivePrice;
public BigInteger feePaid;
public List<String> route;
}
class AddLiquidityParams {
private String tokenA;
private String tokenB;
private BigInteger amountA;
private BigInteger amountB;
private BigInteger minAmountA;
private BigInteger minAmountB;
private Long deadline;
public String getTokenA() { return tokenA; }
public void setTokenA(String tokenA) { this.tokenA = tokenA; }
public String getTokenB() { return tokenB; }
public void setTokenB(String tokenB) { this.tokenB = tokenB; }
public BigInteger getAmountA() { return amountA; }
public void setAmountA(BigInteger amountA) { this.amountA = amountA; }
public BigInteger getAmountB() { return amountB; }
public void setAmountB(BigInteger amountB) { this.amountB = amountB; }
public BigInteger getMinAmountA() { return minAmountA; }
public void setMinAmountA(BigInteger minAmountA) { this.minAmountA = minAmountA; }
public BigInteger getMinAmountB() { return minAmountB; }
public void setMinAmountB(BigInteger minAmountB) { this.minAmountB = minAmountB; }
public Long getDeadline() { return deadline; }
public void setDeadline(Long deadline) { this.deadline = deadline; }
}
class RemoveLiquidityParams {
private String pool;
private BigInteger lpAmount;
private BigInteger minAmountA;
private BigInteger minAmountB;
private Long deadline;
public String getPool() { return pool; }
public void setPool(String pool) { this.pool = pool; }
public BigInteger getLpAmount() { return lpAmount; }
public void setLpAmount(BigInteger lpAmount) { this.lpAmount = lpAmount; }
public BigInteger getMinAmountA() { return minAmountA; }
public void setMinAmountA(BigInteger minAmountA) { this.minAmountA = minAmountA; }
public BigInteger getMinAmountB() { return minAmountB; }
public void setMinAmountB(BigInteger minAmountB) { this.minAmountB = minAmountB; }
public Long getDeadline() { return deadline; }
public void setDeadline(Long deadline) { this.deadline = deadline; }
}
class LiquidityResult {
public String transactionHash;
public BigInteger amountA;
public BigInteger amountB;
public BigInteger lpTokens;
public double poolShare;
}
class LPPosition {
public String poolId;
public BigInteger lpTokens;
public BigInteger tokenAAmount;
public BigInteger tokenBAmount;
public double valueUsd;
public BigInteger unclaimedFeesA;
public BigInteger unclaimedFeesB;
public double impermanentLoss;
public Integer tickLower;
public Integer tickUpper;
public Boolean inRange;
}
class PerpMarket {
public String symbol;
public String baseAsset;
public String quoteAsset;
public double indexPrice;
public double markPrice;
public double fundingRate;
public long nextFundingTime;
public BigInteger openInterest;
public double volume24h;
public double priceChange24h;
public int maxLeverage;
public BigInteger minOrderSize;
public double tickSize;
public double maintenanceMargin;
public double initialMargin;
}
class OpenPositionParams {
private String market;
private Types.PositionSide side;
private BigInteger size;
private int leverage;
private Types.OrderType orderType;
private Double limitPrice;
private Double stopLoss;
private Double takeProfit;
private Types.MarginType marginType = Types.MarginType.CROSS;
private boolean reduceOnly;
public String getMarket() { return market; }
public void setMarket(String market) { this.market = market; }
public Types.PositionSide getSide() { return side; }
public void setSide(Types.PositionSide side) { this.side = side; }
public BigInteger getSize() { return size; }
public void setSize(BigInteger size) { this.size = size; }
public int getLeverage() { return leverage; }
public void setLeverage(int leverage) { this.leverage = leverage; }
public Types.OrderType getOrderType() { return orderType; }
public void setOrderType(Types.OrderType orderType) { this.orderType = orderType; }
public Double getLimitPrice() { return limitPrice; }
public void setLimitPrice(Double limitPrice) { this.limitPrice = limitPrice; }
public Double getStopLoss() { return stopLoss; }
public void setStopLoss(Double stopLoss) { this.stopLoss = stopLoss; }
public Double getTakeProfit() { return takeProfit; }
public void setTakeProfit(Double takeProfit) { this.takeProfit = takeProfit; }
public Types.MarginType getMarginType() { return marginType; }
public void setMarginType(Types.MarginType marginType) { this.marginType = marginType; }
public boolean isReduceOnly() { return reduceOnly; }
public void setReduceOnly(boolean reduceOnly) { this.reduceOnly = reduceOnly; }
}
class ClosePositionParams {
private String market;
private BigInteger size;
private Types.OrderType orderType = Types.OrderType.MARKET;
private Double limitPrice;
public String getMarket() { return market; }
public void setMarket(String market) { this.market = market; }
public BigInteger getSize() { return size; }
public void setSize(BigInteger size) { this.size = size; }
public Types.OrderType getOrderType() { return orderType; }
public void setOrderType(Types.OrderType orderType) { this.orderType = orderType; }
public Double getLimitPrice() { return limitPrice; }
public void setLimitPrice(Double limitPrice) { this.limitPrice = limitPrice; }
}
class ModifyPositionParams {
private String positionId;
private Integer newLeverage;
private BigInteger newMargin;
private Double newStopLoss;
private Double newTakeProfit;
public String getPositionId() { return positionId; }
public void setPositionId(String positionId) { this.positionId = positionId; }
public Integer getNewLeverage() { return newLeverage; }
public void setNewLeverage(Integer newLeverage) { this.newLeverage = newLeverage; }
public BigInteger getNewMargin() { return newMargin; }
public void setNewMargin(BigInteger newMargin) { this.newMargin = newMargin; }
public Double getNewStopLoss() { return newStopLoss; }
public void setNewStopLoss(Double newStopLoss) { this.newStopLoss = newStopLoss; }
public Double getNewTakeProfit() { return newTakeProfit; }
public void setNewTakeProfit(Double newTakeProfit) { this.newTakeProfit = newTakeProfit; }
}
class PerpPosition {
public String id;
public String market;
public Types.PositionSide side;
public BigInteger size;
public double entryPrice;
public double markPrice;
public double liquidationPrice;
public BigInteger margin;
public int leverage;
public BigInteger unrealizedPnl;
public BigInteger realizedPnl;
public double marginRatio;
public Double stopLoss;
public Double takeProfit;
public long createdAt;
public long updatedAt;
}
class PerpOrder {
public String id;
public String market;
public Types.PositionSide side;
public Types.OrderType orderType;
public BigInteger size;
public Double price;
public BigInteger filledSize;
public Types.OrderStatus status;
public boolean reduceOnly;
public long createdAt;
}
class FundingPayment {
public String market;
public BigInteger amount;
public double rate;
public BigInteger positionSize;
public long timestamp;
}
class OrderBookEntry {
public double price;
public BigInteger size;
public int orders;
}
class OrderBook {
public String market;
public List<OrderBookEntry> bids;
public List<OrderBookEntry> asks;
public long timestamp;
}
class LimitOrderParams {
private String market;
private String side;
private double price;
private BigInteger size;
private Types.TimeInForce timeInForce = Types.TimeInForce.GTC;
private boolean postOnly;
public String getMarket() { return market; }
public void setMarket(String market) { this.market = market; }
public String getSide() { return side; }
public void setSide(String side) { this.side = side; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
public BigInteger getSize() { return size; }
public void setSize(BigInteger size) { this.size = size; }
public Types.TimeInForce getTimeInForce() { return timeInForce; }
public void setTimeInForce(Types.TimeInForce timeInForce) { this.timeInForce = timeInForce; }
public boolean isPostOnly() { return postOnly; }
public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; }
}
class Order {
public String id;
public String market;
public String side;
public double price;
public BigInteger size;
public BigInteger filledSize;
public Types.OrderStatus status;
public Types.TimeInForce timeInForce;
public boolean postOnly;
public long createdAt;
public long updatedAt;
}
class Farm {
public String id;
public String name;
public Token stakeToken;
public List<Token> rewardTokens;
public double tvlUsd;
public double apr;
public List<BigInteger> dailyRewards;
public Long lockupPeriod;
public BigInteger minStake;
}
class StakeParams {
private String farm;
private BigInteger amount;
public String getFarm() { return farm; }
public void setFarm(String farm) { this.farm = farm; }
public BigInteger getAmount() { return amount; }
public void setAmount(BigInteger amount) { this.amount = amount; }
}
class FarmPosition {
public String farmId;
public BigInteger stakedAmount;
public List<BigInteger> pendingRewards;
public long stakedAt;
public Long unlockAt;
}
class OHLCV {
public long timestamp;
public double open;
public double high;
public double low;
public double close;
public double volume;
}
class TradeHistory {
public String id;
public String market;
public String side;
public double price;
public BigInteger size;
public long timestamp;
public String maker;
public String taker;
}
class VolumeStats {
public double volume24h;
public double volume7d;
public double volume30d;
public int trades24h;
public int uniqueTraders24h;
}
class TVLStats {
public double totalTvl;
public double poolsTvl;
public double farmsTvl;
public double perpsTvl;
}
class ClaimRewardsResult {
public BigInteger amount;
public String transactionHash;
}
class FundingRateInfo {
public double rate;
public long nextTime;
}

473
sdk/js/src/dex/client.ts Normal file
View file

@ -0,0 +1,473 @@
/**
* Synor DEX SDK Client
*
* Complete decentralized exchange client with support for:
* - AMM swaps (constant product, stable, concentrated)
* - Liquidity provision
* - Perpetual futures (up to 100x leverage)
* - Order books (limit orders)
* - Farming & staking
*/
import {
DexConfig,
Token,
Pool,
PoolFilter,
Quote,
QuoteParams,
SwapParams,
SwapResult,
AddLiquidityParams,
RemoveLiquidityParams,
LiquidityResult,
LPPosition,
PerpMarket,
OpenPositionParams,
ClosePositionParams,
PerpPosition,
PerpOrder,
ModifyPositionParams,
FundingPayment,
OrderBook,
LimitOrderParams,
Order,
Farm,
StakeParams,
FarmPosition,
OHLCV,
TradeHistory,
VolumeStats,
TVLStats,
Subscription,
PriceCallback,
TradeCallback,
OrderBookCallback,
PositionCallback,
DexError,
} from './types';
const DEFAULT_ENDPOINT = 'https://dex.synor.io/v1';
const DEFAULT_WS_ENDPOINT = 'wss://dex.synor.io/v1/ws';
const DEFAULT_TIMEOUT = 30000;
const DEFAULT_RETRIES = 3;
export class SynorDex {
private config: Required<DexConfig>;
private closed = false;
private ws: WebSocket | null = null;
private subscriptions: Map<string, (data: unknown) => void> = new Map();
public readonly perps: PerpsClient;
public readonly orderbook: OrderBookClient;
public readonly farms: FarmsClient;
constructor(config: DexConfig) {
this.config = {
apiKey: config.apiKey,
endpoint: config.endpoint || DEFAULT_ENDPOINT,
wsEndpoint: config.wsEndpoint || DEFAULT_WS_ENDPOINT,
timeout: config.timeout || DEFAULT_TIMEOUT,
retries: config.retries || DEFAULT_RETRIES,
debug: config.debug || false,
};
this.perps = new PerpsClient(this);
this.orderbook = new OrderBookClient(this);
this.farms = new FarmsClient(this);
}
// Token Operations
async getToken(address: string): Promise<Token> {
return this.get<Token>('/tokens/' + address);
}
async listTokens(): Promise<Token[]> {
return this.get<Token[]>('/tokens');
}
async searchTokens(query: string): Promise<Token[]> {
return this.get<Token[]>('/tokens/search?q=' + encodeURIComponent(query));
}
// Pool Operations
async getPool(tokenA: string, tokenB: string): Promise<Pool> {
return this.get<Pool>('/pools/' + tokenA + '/' + tokenB);
}
async getPoolById(poolId: string): Promise<Pool> {
return this.get<Pool>('/pools/' + poolId);
}
async listPools(filter?: PoolFilter): Promise<Pool[]> {
const params = new URLSearchParams();
if (filter?.tokens) params.set('tokens', filter.tokens.join(','));
if (filter?.minTvl) params.set('min_tvl', filter.minTvl.toString());
if (filter?.minVolume24h) params.set('min_volume', filter.minVolume24h.toString());
if (filter?.verified !== undefined) params.set('verified', String(filter.verified));
if (filter?.limit) params.set('limit', String(filter.limit));
if (filter?.offset) params.set('offset', String(filter.offset));
return this.get<Pool[]>('/pools?' + params.toString());
}
// Swap Operations
async getQuote(params: QuoteParams): Promise<Quote> {
return this.post<Quote>('/swap/quote', {
token_in: params.tokenIn,
token_out: params.tokenOut,
amount_in: params.amountIn.toString(),
slippage: params.slippage || 0.005,
});
}
async swap(params: SwapParams): Promise<SwapResult> {
return this.post<SwapResult>('/swap', {
token_in: params.tokenIn,
token_out: params.tokenOut,
amount_in: params.amountIn.toString(),
min_amount_out: params.minAmountOut.toString(),
deadline: params.deadline || Math.floor(Date.now() / 1000) + 1200,
recipient: params.recipient,
});
}
// Liquidity Operations
async addLiquidity(params: AddLiquidityParams): Promise<LiquidityResult> {
return this.post<LiquidityResult>('/liquidity/add', {
token_a: params.tokenA,
token_b: params.tokenB,
amount_a: params.amountA.toString(),
amount_b: params.amountB.toString(),
min_amount_a: params.minAmountA?.toString(),
min_amount_b: params.minAmountB?.toString(),
deadline: params.deadline || Math.floor(Date.now() / 1000) + 1200,
});
}
async removeLiquidity(params: RemoveLiquidityParams): Promise<LiquidityResult> {
return this.post<LiquidityResult>('/liquidity/remove', {
pool: params.pool,
lp_amount: params.lpAmount.toString(),
min_amount_a: params.minAmountA?.toString(),
min_amount_b: params.minAmountB?.toString(),
deadline: params.deadline || Math.floor(Date.now() / 1000) + 1200,
});
}
async getMyPositions(): Promise<LPPosition[]> {
return this.get<LPPosition[]>('/liquidity/positions');
}
// Analytics
async getPriceHistory(pair: string, interval: string, limit = 100): Promise<OHLCV[]> {
return this.get<OHLCV[]>('/analytics/candles/' + pair + '?interval=' + interval + '&limit=' + limit);
}
async getTradeHistory(pair: string, limit = 50): Promise<TradeHistory[]> {
return this.get<TradeHistory[]>('/analytics/trades/' + pair + '?limit=' + limit);
}
async getVolumeStats(): Promise<VolumeStats> {
return this.get<VolumeStats>('/analytics/volume');
}
async getTVL(): Promise<TVLStats> {
return this.get<TVLStats>('/analytics/tvl');
}
// Subscriptions
async subscribePrice(market: string, callback: PriceCallback): Promise<Subscription> {
return this.subscribe('price', { market }, callback);
}
async subscribeTrades(market: string, callback: TradeCallback): Promise<Subscription> {
return this.subscribe('trades', { market }, callback);
}
async subscribeOrderBook(market: string, callback: OrderBookCallback): Promise<Subscription> {
return this.subscribe('orderbook', { market }, callback);
}
// Lifecycle
async healthCheck(): Promise<boolean> {
try {
const response = await this.get<{ status: string }>('/health');
return response.status === 'healthy';
} catch {
return false;
}
}
close(): void {
this.closed = true;
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.subscriptions.clear();
}
// Internal Methods
async get<T>(path: string): Promise<T> {
return this.request<T>('GET', path);
}
async post<T>(path: string, body: unknown): Promise<T> {
return this.request<T>('POST', path, body);
}
async delete<T>(path: string): Promise<T> {
return this.request<T>('DELETE', path);
}
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
if (this.closed) {
throw new DexError('Client has been closed', 'CLIENT_CLOSED');
}
let lastError: Error | null = null;
for (let attempt = 0; attempt < this.config.retries; attempt++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
const response = await fetch(this.config.endpoint + path, {
method,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.config.apiKey,
'X-SDK-Version': 'js/0.1.0',
},
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new DexError(
error.message || 'HTTP ' + response.status,
error.code,
response.status
);
}
return await response.json();
} catch (error) {
lastError = error as Error;
if (this.config.debug) {
console.error('Attempt ' + (attempt + 1) + ' failed:', error);
}
if (attempt < this.config.retries - 1) {
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
}
}
}
throw lastError || new DexError('Unknown error');
}
private async subscribe<T>(
channel: string,
params: Record<string, unknown>,
callback: (data: T) => void
): Promise<Subscription> {
await this.ensureWebSocket();
const subscriptionId = Math.random().toString(36).substring(2);
this.subscriptions.set(subscriptionId, callback as (data: unknown) => void);
this.ws!.send(JSON.stringify({
type: 'subscribe',
channel,
subscription_id: subscriptionId,
...params,
}));
return {
id: subscriptionId,
channel,
cancel: () => {
this.subscriptions.delete(subscriptionId);
this.ws?.send(JSON.stringify({
type: 'unsubscribe',
subscription_id: subscriptionId,
}));
},
};
}
private async ensureWebSocket(): Promise<void> {
if (this.ws?.readyState === WebSocket.OPEN) return;
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.config.wsEndpoint);
this.ws.onopen = () => {
this.ws!.send(JSON.stringify({
type: 'auth',
api_key: this.config.apiKey,
}));
resolve();
};
this.ws.onerror = reject;
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.subscription_id && this.subscriptions.has(data.subscription_id)) {
this.subscriptions.get(data.subscription_id)!(data.data);
}
};
});
}
}
// Perpetuals Sub-Client
class PerpsClient {
constructor(private dex: SynorDex) {}
async listMarkets(): Promise<PerpMarket[]> {
return this.dex.get<PerpMarket[]>('/perps/markets');
}
async getMarket(symbol: string): Promise<PerpMarket> {
return this.dex.get<PerpMarket>('/perps/markets/' + symbol);
}
async openPosition(params: OpenPositionParams): Promise<PerpPosition> {
return this.dex.post<PerpPosition>('/perps/positions', {
market: params.market,
side: params.side,
size: params.size.toString(),
leverage: params.leverage,
order_type: params.orderType,
limit_price: params.limitPrice,
stop_loss: params.stopLoss,
take_profit: params.takeProfit,
margin_type: params.marginType || 'cross',
reduce_only: params.reduceOnly || false,
});
}
async closePosition(params: ClosePositionParams): Promise<PerpPosition> {
return this.dex.post<PerpPosition>('/perps/positions/close', {
market: params.market,
size: params.size?.toString(),
order_type: params.orderType,
limit_price: params.limitPrice,
});
}
async modifyPosition(params: ModifyPositionParams): Promise<PerpPosition> {
return this.dex.post<PerpPosition>('/perps/positions/' + params.positionId + '/modify', {
new_leverage: params.newLeverage,
new_margin: params.newMargin?.toString(),
new_stop_loss: params.newStopLoss,
new_take_profit: params.newTakeProfit,
});
}
async getPositions(): Promise<PerpPosition[]> {
return this.dex.get<PerpPosition[]>('/perps/positions');
}
async getPosition(market: string): Promise<PerpPosition | null> {
return this.dex.get<PerpPosition | null>('/perps/positions/' + market);
}
async getOrders(): Promise<PerpOrder[]> {
return this.dex.get<PerpOrder[]>('/perps/orders');
}
async cancelOrder(orderId: string): Promise<void> {
await this.dex.delete('/perps/orders/' + orderId);
}
async cancelAllOrders(market?: string): Promise<number> {
const path = market ? '/perps/orders?market=' + market : '/perps/orders';
const result = await this.dex.delete<{ cancelled: number }>(path);
return result.cancelled;
}
async getFundingHistory(market: string, limit = 100): Promise<FundingPayment[]> {
return this.dex.get<FundingPayment[]>('/perps/funding/' + market + '?limit=' + limit);
}
async getFundingRate(market: string): Promise<{ rate: number; nextTime: number }> {
return this.dex.get('/perps/funding/' + market + '/current');
}
async subscribePosition(callback: PositionCallback): Promise<Subscription> {
return (this.dex as any).subscribe('position', {}, callback);
}
}
// Order Book Sub-Client
class OrderBookClient {
constructor(private dex: SynorDex) {}
async getOrderBook(market: string, depth = 20): Promise<OrderBook> {
return this.dex.get<OrderBook>('/orderbook/' + market + '?depth=' + depth);
}
async placeLimitOrder(params: LimitOrderParams): Promise<Order> {
return this.dex.post<Order>('/orderbook/orders', {
market: params.market,
side: params.side,
price: params.price,
size: params.size.toString(),
time_in_force: params.timeInForce || 'GTC',
post_only: params.postOnly || false,
});
}
async cancelOrder(orderId: string): Promise<void> {
await this.dex.delete('/orderbook/orders/' + orderId);
}
async getOpenOrders(market?: string): Promise<Order[]> {
const path = market ? '/orderbook/orders?market=' + market : '/orderbook/orders';
return this.dex.get<Order[]>(path);
}
async getOrderHistory(limit = 50): Promise<Order[]> {
return this.dex.get<Order[]>('/orderbook/orders/history?limit=' + limit);
}
}
// Farms Sub-Client
class FarmsClient {
constructor(private dex: SynorDex) {}
async listFarms(): Promise<Farm[]> {
return this.dex.get<Farm[]>('/farms');
}
async getFarm(farmId: string): Promise<Farm> {
return this.dex.get<Farm>('/farms/' + farmId);
}
async stake(params: StakeParams): Promise<FarmPosition> {
return this.dex.post<FarmPosition>('/farms/stake', {
farm: params.farm,
amount: params.amount.toString(),
});
}
async unstake(farm: string, amount: bigint): Promise<FarmPosition> {
return this.dex.post<FarmPosition>('/farms/unstake', {
farm,
amount: amount.toString(),
});
}
async claimRewards(farm: string): Promise<{ amount: bigint; transactionHash: string }> {
return this.dex.post('/farms/claim', { farm });
}
async getMyFarmPositions(): Promise<FarmPosition[]> {
return this.dex.get<FarmPosition[]>('/farms/positions');
}
}
export default SynorDex;

8
sdk/js/src/dex/index.ts Normal file
View file

@ -0,0 +1,8 @@
/**
* Synor DEX SDK
*
* Decentralized exchange with AMM, perpetual futures, and order books.
*/
export { SynorDex, default } from './client';
export * from './types';

384
sdk/js/src/dex/types.ts Normal file
View file

@ -0,0 +1,384 @@
/**
* Synor DEX SDK Types
*
* Comprehensive types for decentralized exchange operations including
* AMM swaps, liquidity pools, perpetual futures, and order books.
*/
// ==================== Configuration ====================
export interface DexConfig {
apiKey: string;
endpoint?: string;
wsEndpoint?: string;
timeout?: number;
retries?: number;
debug?: boolean;
}
// ==================== Token Types ====================
export interface Token {
address: string;
symbol: string;
name: string;
decimals: number;
logoUri?: string;
verified?: boolean;
}
export interface TokenAmount {
token: Token;
amount: bigint;
formatted: string;
}
// ==================== Pool Types ====================
export interface Pool {
id: string;
tokenA: Token;
tokenB: Token;
reserveA: bigint;
reserveB: bigint;
fee: number; // e.g., 0.003 = 0.3%
tvl: bigint;
volume24h: bigint;
volume7d: bigint;
apr: number;
lpTokenAddress: string;
lpTokenSupply: bigint;
createdAt: number;
}
export interface PoolFilter {
tokens?: string[];
minTvl?: bigint;
minVolume24h?: bigint;
verified?: boolean;
limit?: number;
offset?: number;
}
export type PoolType = 'constant_product' | 'stable' | 'concentrated';
// ==================== Swap Types ====================
export interface QuoteParams {
tokenIn: string;
tokenOut: string;
amountIn: bigint;
slippage?: number; // Default: 0.005 (0.5%)
}
export interface Quote {
tokenIn: Token;
tokenOut: Token;
amountIn: bigint;
amountOut: bigint;
minAmountOut: bigint;
priceImpact: number;
fee: bigint;
route: string[];
executionPrice: number;
pools: string[];
}
export interface SwapParams {
tokenIn: string;
tokenOut: string;
amountIn: bigint;
minAmountOut: bigint;
deadline?: number;
recipient?: string;
}
export interface SwapResult {
transactionHash: string;
amountIn: bigint;
amountOut: bigint;
executionPrice: number;
priceImpact: number;
fee: bigint;
gasUsed: bigint;
route: string[];
}
// ==================== Liquidity Types ====================
export interface AddLiquidityParams {
tokenA: string;
tokenB: string;
amountA: bigint;
amountB: bigint;
minAmountA?: bigint;
minAmountB?: bigint;
deadline?: number;
}
export interface RemoveLiquidityParams {
pool: string;
lpAmount: bigint;
minAmountA?: bigint;
minAmountB?: bigint;
deadline?: number;
}
export interface LiquidityResult {
transactionHash: string;
pool: string;
amountA: bigint;
amountB: bigint;
lpTokens: bigint;
share: number;
}
export interface LPPosition {
pool: Pool;
lpTokens: bigint;
share: number;
valueUsd: number;
tokenAAmount: bigint;
tokenBAmount: bigint;
fees24h: bigint;
feesTotal: bigint;
impermanentLoss: number;
}
// ==================== Perpetual Futures Types ====================
export type PositionSide = 'long' | 'short';
export type OrderType = 'market' | 'limit' | 'stop_loss' | 'take_profit';
export type MarginType = 'cross' | 'isolated';
export interface PerpMarket {
symbol: string; // e.g., "BTC-PERP"
baseToken: Token;
quoteToken: Token; // Usually USDC
indexPrice: number;
markPrice: number;
fundingRate: number; // Current funding rate (8h)
nextFundingTime: number;
openInterest: bigint;
volume24h: bigint;
maxLeverage: number; // e.g., 100 for 100x
maintenanceMargin: number; // e.g., 0.005 = 0.5%
initialMargin: number; // e.g., 0.01 = 1%
tickSize: number;
minOrderSize: bigint;
maxOrderSize: bigint;
}
export interface OpenPositionParams {
market: string;
side: PositionSide;
size: bigint; // Position size in base token
leverage: number; // 1-100x
orderType: OrderType;
limitPrice?: number; // For limit orders
stopLoss?: number;
takeProfit?: number;
marginType?: MarginType;
reduceOnly?: boolean;
}
export interface ClosePositionParams {
market: string;
size?: bigint; // Partial close if specified
orderType: OrderType;
limitPrice?: number;
}
export interface PerpPosition {
id: string;
market: string;
side: PositionSide;
size: bigint;
entryPrice: number;
markPrice: number;
liquidationPrice: number;
margin: bigint;
leverage: number;
marginType: MarginType;
unrealizedPnl: bigint;
realizedPnl: bigint;
fundingPayment: bigint;
createdAt: number;
updatedAt: number;
}
export interface PerpOrder {
id: string;
market: string;
side: PositionSide;
size: bigint;
price: number;
orderType: OrderType;
filled: bigint;
remaining: bigint;
status: 'open' | 'filled' | 'cancelled' | 'expired';
reduceOnly: boolean;
createdAt: number;
}
export interface ModifyPositionParams {
positionId: string;
newLeverage?: number;
newMargin?: bigint; // Add or remove margin
newStopLoss?: number;
newTakeProfit?: number;
}
export interface FundingPayment {
market: string;
amount: bigint;
rate: number;
timestamp: number;
}
// ==================== Order Book Types ====================
export interface OrderBookLevel {
price: number;
size: bigint;
total: bigint;
}
export interface OrderBook {
market: string;
bids: OrderBookLevel[];
asks: OrderBookLevel[];
spread: number;
timestamp: number;
}
export interface LimitOrderParams {
market: string;
side: 'buy' | 'sell';
price: number;
size: bigint;
timeInForce?: 'GTC' | 'IOC' | 'FOK';
postOnly?: boolean;
}
export interface Order {
id: string;
market: string;
side: 'buy' | 'sell';
price: number;
size: bigint;
filled: bigint;
status: 'open' | 'filled' | 'partial' | 'cancelled';
createdAt: number;
}
// ==================== Farming & Staking ====================
export interface Farm {
id: string;
pool: Pool;
rewardToken: Token;
rewardPerSecond: bigint;
totalStaked: bigint;
apr: number;
startTime: number;
endTime?: number;
}
export interface StakeParams {
farm: string;
amount: bigint;
}
export interface FarmPosition {
farm: Farm;
staked: bigint;
pendingRewards: bigint;
share: number;
}
// ==================== Analytics ====================
export interface OHLCV {
timestamp: number;
open: number;
high: number;
low: number;
close: number;
volume: bigint;
}
export interface TradeHistory {
id: string;
market: string;
side: 'buy' | 'sell';
price: number;
size: bigint;
timestamp: number;
maker: string;
taker: string;
}
export interface VolumeStats {
volume24h: bigint;
volume7d: bigint;
volume30d: bigint;
trades24h: number;
uniqueTraders24h: number;
}
export interface TVLStats {
total: bigint;
byPool: Record<string, bigint>;
history: { timestamp: number; tvl: bigint }[];
}
// ==================== Events & Subscriptions ====================
export interface Subscription {
id: string;
channel: string;
cancel: () => void;
}
export type PriceCallback = (price: { market: string; price: number; timestamp: number }) => void;
export type TradeCallback = (trade: TradeHistory) => void;
export type OrderBookCallback = (orderBook: OrderBook) => void;
export type PositionCallback = (position: PerpPosition) => void;
// ==================== Errors ====================
export class DexError extends Error {
code?: string;
statusCode?: number;
constructor(message: string, code?: string, statusCode?: number) {
super(message);
this.name = 'DexError';
this.code = code;
this.statusCode = statusCode;
}
}
export class InsufficientLiquidityError extends DexError {
constructor(message = 'Insufficient liquidity for this trade') {
super(message, 'INSUFFICIENT_LIQUIDITY');
}
}
export class SlippageExceededError extends DexError {
constructor(message = 'Slippage tolerance exceeded') {
super(message, 'SLIPPAGE_EXCEEDED');
}
}
export class InsufficientMarginError extends DexError {
constructor(message = 'Insufficient margin for position') {
super(message, 'INSUFFICIENT_MARGIN');
}
}
export class LiquidationError extends DexError {
constructor(message = 'Position would be liquidated') {
super(message, 'LIQUIDATION_RISK');
}
}

View file

@ -0,0 +1,342 @@
package io.synor.dex
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json
import java.math.BigInteger
import java.time.Instant
import java.util.concurrent.atomic.AtomicBoolean
/**
* Synor DEX SDK Client
*
* Complete decentralized exchange client with support for:
* - AMM swaps (constant product, stable, concentrated)
* - Liquidity provision
* - Perpetual futures (up to 100x leverage)
* - Order books (limit orders)
* - Farming & staking
*/
class SynorDex(private val config: DexConfig) : AutoCloseable {
private val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
install(HttpTimeout) {
requestTimeoutMillis = config.timeout
}
defaultRequest {
header("Content-Type", "application/json")
header("Authorization", "Bearer ${config.apiKey}")
header("X-SDK-Version", "kotlin/0.1.0")
}
}
private val closed = AtomicBoolean(false)
val perps = PerpsClient(this)
val orderbook = OrderBookClient(this)
val farms = FarmsClient(this)
// Token Operations
suspend fun getToken(address: String): Token =
get("/tokens/$address")
suspend fun listTokens(): List<Token> =
get("/tokens")
suspend fun searchTokens(query: String): List<Token> =
get("/tokens/search?q=${query.encodeURLParameter()}")
// Pool Operations
suspend fun getPool(tokenA: String, tokenB: String): Pool =
get("/pools/$tokenA/$tokenB")
suspend fun getPoolById(poolId: String): Pool =
get("/pools/$poolId")
suspend fun listPools(filter: PoolFilter? = null): List<Pool> {
val params = mutableListOf<String>()
filter?.let { f ->
f.tokens?.let { params.add("tokens=${it.joinToString(",")}") }
f.minTvl?.let { params.add("min_tvl=$it") }
f.minVolume24h?.let { params.add("min_volume=$it") }
f.verified?.let { params.add("verified=$it") }
f.limit?.let { params.add("limit=$it") }
f.offset?.let { params.add("offset=$it") }
}
val path = if (params.isEmpty()) "/pools" else "/pools?${params.joinToString("&")}"
return get(path)
}
// Swap Operations
suspend fun getQuote(params: QuoteParams): Quote =
post("/swap/quote", mapOf(
"token_in" to params.tokenIn,
"token_out" to params.tokenOut,
"amount_in" to params.amountIn.toString(),
"slippage" to (params.slippage ?: 0.005)
))
suspend fun swap(params: SwapParams): SwapResult {
val deadline = params.deadline ?: (Instant.now().epochSecond + 1200)
return post("/swap", buildMap {
put("token_in", params.tokenIn)
put("token_out", params.tokenOut)
put("amount_in", params.amountIn.toString())
put("min_amount_out", params.minAmountOut.toString())
put("deadline", deadline)
params.recipient?.let { put("recipient", it) }
})
}
// Liquidity Operations
suspend fun addLiquidity(params: AddLiquidityParams): LiquidityResult {
val deadline = params.deadline ?: (Instant.now().epochSecond + 1200)
return post("/liquidity/add", buildMap {
put("token_a", params.tokenA)
put("token_b", params.tokenB)
put("amount_a", params.amountA.toString())
put("amount_b", params.amountB.toString())
put("deadline", deadline)
params.minAmountA?.let { put("min_amount_a", it.toString()) }
params.minAmountB?.let { put("min_amount_b", it.toString()) }
})
}
suspend fun removeLiquidity(params: RemoveLiquidityParams): LiquidityResult {
val deadline = params.deadline ?: (Instant.now().epochSecond + 1200)
return post("/liquidity/remove", buildMap {
put("pool", params.pool)
put("lp_amount", params.lpAmount.toString())
put("deadline", deadline)
params.minAmountA?.let { put("min_amount_a", it.toString()) }
params.minAmountB?.let { put("min_amount_b", it.toString()) }
})
}
suspend fun getMyPositions(): List<LPPosition> =
get("/liquidity/positions")
// Analytics
suspend fun getPriceHistory(pair: String, interval: String, limit: Int = 100): List<OHLCV> =
get("/analytics/candles/$pair?interval=$interval&limit=$limit")
suspend fun getTradeHistory(pair: String, limit: Int = 50): List<TradeHistory> =
get("/analytics/trades/$pair?limit=$limit")
suspend fun getVolumeStats(): VolumeStats =
get("/analytics/volume")
suspend fun getTVL(): TVLStats =
get("/analytics/tvl")
// Lifecycle
suspend fun healthCheck(): Boolean = try {
val response: HealthResponse = get("/health")
response.status == "healthy"
} catch (e: Exception) {
false
}
override fun close() {
closed.set(true)
client.close()
}
fun isClosed(): Boolean = closed.get()
// Internal methods
internal suspend inline fun <reified T> get(path: String): T {
checkClosed()
return client.get(config.endpoint + path).body()
}
internal suspend inline fun <reified T> post(path: String, body: Map<String, Any?>): T {
checkClosed()
return client.post(config.endpoint + path) {
setBody(body)
}.body()
}
internal suspend inline fun <reified T> delete(path: String): T {
checkClosed()
return client.delete(config.endpoint + path).body()
}
private fun checkClosed() {
if (closed.get()) {
throw DexException("Client has been closed", "CLIENT_CLOSED", 0)
}
}
private data class HealthResponse(val status: String)
}
/**
* DEX client configuration
*/
data class DexConfig(
val apiKey: String,
val endpoint: String = "https://dex.synor.io/v1",
val wsEndpoint: String = "wss://dex.synor.io/v1/ws",
val timeout: Long = 30000,
val retries: Int = 3,
val debug: Boolean = false
)
/**
* DEX SDK Exception
*/
class DexException(
message: String,
val code: String?,
val status: Int
) : RuntimeException(message)
/**
* Perpetual futures sub-client
*/
class PerpsClient(private val dex: SynorDex) {
suspend fun listMarkets(): List<PerpMarket> =
dex.get("/perps/markets")
suspend fun getMarket(symbol: String): PerpMarket =
dex.get("/perps/markets/$symbol")
suspend fun openPosition(params: OpenPositionParams): PerpPosition =
dex.post("/perps/positions", buildMap {
put("market", params.market)
put("side", params.side.name.lowercase())
put("size", params.size.toString())
put("leverage", params.leverage)
put("order_type", params.orderType.name.lowercase())
put("margin_type", params.marginType.name.lowercase())
put("reduce_only", params.reduceOnly)
params.limitPrice?.let { put("limit_price", it) }
params.stopLoss?.let { put("stop_loss", it) }
params.takeProfit?.let { put("take_profit", it) }
})
suspend fun closePosition(params: ClosePositionParams): PerpPosition =
dex.post("/perps/positions/close", buildMap {
put("market", params.market)
put("order_type", params.orderType.name.lowercase())
params.size?.let { put("size", it.toString()) }
params.limitPrice?.let { put("limit_price", it) }
})
suspend fun modifyPosition(params: ModifyPositionParams): PerpPosition =
dex.post("/perps/positions/${params.positionId}/modify", buildMap {
params.newLeverage?.let { put("new_leverage", it) }
params.newMargin?.let { put("new_margin", it.toString()) }
params.newStopLoss?.let { put("new_stop_loss", it) }
params.newTakeProfit?.let { put("new_take_profit", it) }
})
suspend fun getPositions(): List<PerpPosition> =
dex.get("/perps/positions")
suspend fun getPosition(market: String): PerpPosition? =
dex.get("/perps/positions/$market")
suspend fun getOrders(): List<PerpOrder> =
dex.get("/perps/orders")
suspend fun cancelOrder(orderId: String) {
dex.delete<Unit>("/perps/orders/$orderId")
}
suspend fun cancelAllOrders(market: String? = null): Int {
val path = if (market != null) "/perps/orders?market=$market" else "/perps/orders"
val result: CancelResult = dex.delete(path)
return result.cancelled
}
suspend fun getFundingHistory(market: String, limit: Int = 100): List<FundingPayment> =
dex.get("/perps/funding/$market?limit=$limit")
suspend fun getFundingRate(market: String): FundingRateInfo =
dex.get("/perps/funding/$market/current")
private data class CancelResult(val cancelled: Int)
}
/**
* Order book sub-client
*/
class OrderBookClient(private val dex: SynorDex) {
suspend fun getOrderBook(market: String, depth: Int = 20): OrderBook =
dex.get("/orderbook/$market?depth=$depth")
suspend fun placeLimitOrder(params: LimitOrderParams): Order =
dex.post("/orderbook/orders", mapOf(
"market" to params.market,
"side" to params.side,
"price" to params.price,
"size" to params.size.toString(),
"time_in_force" to params.timeInForce.name,
"post_only" to params.postOnly
))
suspend fun cancelOrder(orderId: String) {
dex.delete<Unit>("/orderbook/orders/$orderId")
}
suspend fun getOpenOrders(market: String? = null): List<Order> {
val path = if (market != null) "/orderbook/orders?market=$market" else "/orderbook/orders"
return dex.get(path)
}
suspend fun getOrderHistory(limit: Int = 50): List<Order> =
dex.get("/orderbook/orders/history?limit=$limit")
}
/**
* Farms sub-client
*/
class FarmsClient(private val dex: SynorDex) {
suspend fun listFarms(): List<Farm> =
dex.get("/farms")
suspend fun getFarm(farmId: String): Farm =
dex.get("/farms/$farmId")
suspend fun stake(params: StakeParams): FarmPosition =
dex.post("/farms/stake", mapOf(
"farm" to params.farm,
"amount" to params.amount.toString()
))
suspend fun unstake(farm: String, amount: BigInteger): FarmPosition =
dex.post("/farms/unstake", mapOf(
"farm" to farm,
"amount" to amount.toString()
))
suspend fun claimRewards(farm: String): ClaimRewardsResult =
dex.post("/farms/claim", mapOf("farm" to farm))
suspend fun getMyFarmPositions(): List<FarmPosition> =
dex.get("/farms/positions")
}
private fun String.encodeURLParameter(): String =
java.net.URLEncoder.encode(this, "UTF-8")

View file

@ -0,0 +1,369 @@
package io.synor.dex
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.math.BigInteger
enum class PoolType {
@SerialName("constant_product") CONSTANT_PRODUCT,
@SerialName("stable") STABLE,
@SerialName("concentrated") CONCENTRATED
}
enum class PositionSide {
@SerialName("long") LONG,
@SerialName("short") SHORT
}
enum class OrderType {
@SerialName("market") MARKET,
@SerialName("limit") LIMIT,
@SerialName("stop_market") STOP_MARKET,
@SerialName("stop_limit") STOP_LIMIT,
@SerialName("take_profit") TAKE_PROFIT,
@SerialName("take_profit_limit") TAKE_PROFIT_LIMIT
}
enum class MarginType {
@SerialName("cross") CROSS,
@SerialName("isolated") ISOLATED
}
enum class TimeInForce {
GTC, IOC, FOK, GTD
}
enum class OrderStatus {
@SerialName("pending") PENDING,
@SerialName("open") OPEN,
@SerialName("partially_filled") PARTIALLY_FILLED,
@SerialName("filled") FILLED,
@SerialName("cancelled") CANCELLED,
@SerialName("expired") EXPIRED
}
@Serializable
data class Token(
val address: String,
val symbol: String,
val name: String,
val decimals: Int,
@SerialName("total_supply") val totalSupply: String,
@SerialName("price_usd") val priceUsd: Double? = null,
@SerialName("logo_url") val logoUrl: String? = null,
val verified: Boolean = false
)
@Serializable
data class Pool(
val id: String,
@SerialName("token_a") val tokenA: Token,
@SerialName("token_b") val tokenB: Token,
@SerialName("pool_type") val poolType: PoolType,
@SerialName("reserve_a") val reserveA: String,
@SerialName("reserve_b") val reserveB: String,
val fee: Double,
@SerialName("tvl_usd") val tvlUsd: Double,
@SerialName("volume_24h") val volume24h: Double,
val apr: Double,
@SerialName("lp_token_address") val lpTokenAddress: String,
@SerialName("tick_spacing") val tickSpacing: Int? = null,
@SerialName("sqrt_price") val sqrtPrice: String? = null
)
data class PoolFilter(
val tokens: List<String>? = null,
val minTvl: Double? = null,
val minVolume24h: Double? = null,
val verified: Boolean? = null,
val limit: Int? = null,
val offset: Int? = null
)
@Serializable
data class Quote(
@SerialName("token_in") val tokenIn: String,
@SerialName("token_out") val tokenOut: String,
@SerialName("amount_in") val amountIn: String,
@SerialName("amount_out") val amountOut: String,
@SerialName("price_impact") val priceImpact: Double,
val route: List<String>,
val fee: String,
@SerialName("minimum_received") val minimumReceived: String,
@SerialName("expires_at") val expiresAt: Long
)
data class QuoteParams(
val tokenIn: String,
val tokenOut: String,
val amountIn: BigInteger,
val slippage: Double? = null
)
data class SwapParams(
val tokenIn: String,
val tokenOut: String,
val amountIn: BigInteger,
val minAmountOut: BigInteger,
val deadline: Long? = null,
val recipient: String? = null
)
@Serializable
data class SwapResult(
@SerialName("transaction_hash") val transactionHash: String,
@SerialName("amount_in") val amountIn: String,
@SerialName("amount_out") val amountOut: String,
@SerialName("effective_price") val effectivePrice: Double,
@SerialName("fee_paid") val feePaid: String,
val route: List<String>
)
data class AddLiquidityParams(
val tokenA: String,
val tokenB: String,
val amountA: BigInteger,
val amountB: BigInteger,
val minAmountA: BigInteger? = null,
val minAmountB: BigInteger? = null,
val deadline: Long? = null
)
data class RemoveLiquidityParams(
val pool: String,
val lpAmount: BigInteger,
val minAmountA: BigInteger? = null,
val minAmountB: BigInteger? = null,
val deadline: Long? = null
)
@Serializable
data class LiquidityResult(
@SerialName("transaction_hash") val transactionHash: String,
@SerialName("amount_a") val amountA: String,
@SerialName("amount_b") val amountB: String,
@SerialName("lp_tokens") val lpTokens: String,
@SerialName("pool_share") val poolShare: Double
)
@Serializable
data class LPPosition(
@SerialName("pool_id") val poolId: String,
@SerialName("lp_tokens") val lpTokens: String,
@SerialName("token_a_amount") val tokenAAmount: String,
@SerialName("token_b_amount") val tokenBAmount: String,
@SerialName("value_usd") val valueUsd: Double,
@SerialName("unclaimed_fees_a") val unclaimedFeesA: String,
@SerialName("unclaimed_fees_b") val unclaimedFeesB: String,
@SerialName("impermanent_loss") val impermanentLoss: Double,
@SerialName("tick_lower") val tickLower: Int? = null,
@SerialName("tick_upper") val tickUpper: Int? = null,
@SerialName("in_range") val inRange: Boolean? = null
)
@Serializable
data class PerpMarket(
val symbol: String,
@SerialName("base_asset") val baseAsset: String,
@SerialName("quote_asset") val quoteAsset: String,
@SerialName("index_price") val indexPrice: Double,
@SerialName("mark_price") val markPrice: Double,
@SerialName("funding_rate") val fundingRate: Double,
@SerialName("next_funding_time") val nextFundingTime: Long,
@SerialName("open_interest") val openInterest: String,
@SerialName("volume_24h") val volume24h: Double,
@SerialName("price_change_24h") val priceChange24h: Double,
@SerialName("max_leverage") val maxLeverage: Int,
@SerialName("min_order_size") val minOrderSize: String,
@SerialName("tick_size") val tickSize: Double,
@SerialName("maintenance_margin") val maintenanceMargin: Double,
@SerialName("initial_margin") val initialMargin: Double
)
data class OpenPositionParams(
val market: String,
val side: PositionSide,
val size: BigInteger,
val leverage: Int,
val orderType: OrderType,
val limitPrice: Double? = null,
val stopLoss: Double? = null,
val takeProfit: Double? = null,
val marginType: MarginType = MarginType.CROSS,
val reduceOnly: Boolean = false
)
data class ClosePositionParams(
val market: String,
val size: BigInteger? = null,
val orderType: OrderType = OrderType.MARKET,
val limitPrice: Double? = null
)
data class ModifyPositionParams(
val positionId: String,
val newLeverage: Int? = null,
val newMargin: BigInteger? = null,
val newStopLoss: Double? = null,
val newTakeProfit: Double? = null
)
@Serializable
data class PerpPosition(
val id: String,
val market: String,
val side: PositionSide,
val size: String,
@SerialName("entry_price") val entryPrice: Double,
@SerialName("mark_price") val markPrice: Double,
@SerialName("liquidation_price") val liquidationPrice: Double,
val margin: String,
val leverage: Int,
@SerialName("unrealized_pnl") val unrealizedPnl: String,
@SerialName("realized_pnl") val realizedPnl: String,
@SerialName("margin_ratio") val marginRatio: Double,
@SerialName("stop_loss") val stopLoss: Double? = null,
@SerialName("take_profit") val takeProfit: Double? = null,
@SerialName("created_at") val createdAt: Long = 0,
@SerialName("updated_at") val updatedAt: Long = 0
)
@Serializable
data class PerpOrder(
val id: String,
val market: String,
val side: PositionSide,
@SerialName("order_type") val orderType: OrderType,
val size: String,
val price: Double? = null,
@SerialName("filled_size") val filledSize: String = "0",
val status: OrderStatus,
@SerialName("reduce_only") val reduceOnly: Boolean = false,
@SerialName("created_at") val createdAt: Long = 0
)
@Serializable
data class FundingPayment(
val market: String,
val amount: String,
val rate: Double,
@SerialName("position_size") val positionSize: String,
val timestamp: Long
)
@Serializable
data class FundingRateInfo(
val rate: Double,
@SerialName("next_time") val nextTime: Long
)
@Serializable
data class OrderBookEntry(
val price: Double,
val size: String,
val orders: Int
)
@Serializable
data class OrderBook(
val market: String,
val bids: List<OrderBookEntry>,
val asks: List<OrderBookEntry>,
val timestamp: Long
)
data class LimitOrderParams(
val market: String,
val side: String,
val price: Double,
val size: BigInteger,
val timeInForce: TimeInForce = TimeInForce.GTC,
val postOnly: Boolean = false
)
@Serializable
data class Order(
val id: String,
val market: String,
val side: String,
val price: Double,
val size: String,
@SerialName("filled_size") val filledSize: String,
val status: OrderStatus,
@SerialName("time_in_force") val timeInForce: TimeInForce,
@SerialName("post_only") val postOnly: Boolean,
@SerialName("created_at") val createdAt: Long,
@SerialName("updated_at") val updatedAt: Long
)
@Serializable
data class Farm(
val id: String,
val name: String,
@SerialName("stake_token") val stakeToken: Token,
@SerialName("reward_tokens") val rewardTokens: List<Token>,
@SerialName("tvl_usd") val tvlUsd: Double,
val apr: Double,
@SerialName("daily_rewards") val dailyRewards: List<String>,
@SerialName("lockup_period") val lockupPeriod: Long? = null,
@SerialName("min_stake") val minStake: String? = null
)
data class StakeParams(
val farm: String,
val amount: BigInteger
)
@Serializable
data class FarmPosition(
@SerialName("farm_id") val farmId: String,
@SerialName("staked_amount") val stakedAmount: String,
@SerialName("pending_rewards") val pendingRewards: List<String>,
@SerialName("staked_at") val stakedAt: Long,
@SerialName("unlock_at") val unlockAt: Long? = null
)
@Serializable
data class OHLCV(
val timestamp: Long,
val open: Double,
val high: Double,
val low: Double,
val close: Double,
val volume: Double
)
@Serializable
data class TradeHistory(
val id: String,
val market: String,
val side: String,
val price: Double,
val size: String,
val timestamp: Long,
val maker: String,
val taker: String
)
@Serializable
data class VolumeStats(
@SerialName("volume_24h") val volume24h: Double,
@SerialName("volume_7d") val volume7d: Double,
@SerialName("volume_30d") val volume30d: Double,
@SerialName("trades_24h") val trades24h: Int,
@SerialName("unique_traders_24h") val uniqueTraders24h: Int
)
@Serializable
data class TVLStats(
@SerialName("total_tvl") val totalTvl: Double,
@SerialName("pools_tvl") val poolsTvl: Double,
@SerialName("farms_tvl") val farmsTvl: Double,
@SerialName("perps_tvl") val perpsTvl: Double
)
@Serializable
data class ClaimRewardsResult(
val amount: String,
@SerialName("transaction_hash") val transactionHash: String
)

View file

@ -0,0 +1,96 @@
"""
Synor DEX SDK for Python
Complete decentralized exchange client with support for:
- AMM swaps (constant product, stable, concentrated)
- Liquidity provision
- Perpetual futures (up to 100x leverage)
- Order books (limit orders)
- Farming & staking
"""
from .client import SynorDex
from .types import (
DexConfig,
Token,
Pool,
PoolType,
PoolFilter,
Quote,
QuoteParams,
SwapParams,
SwapResult,
AddLiquidityParams,
RemoveLiquidityParams,
LiquidityResult,
LPPosition,
PerpMarket,
PositionSide,
OrderType,
MarginType,
OpenPositionParams,
ClosePositionParams,
ModifyPositionParams,
PerpPosition,
PerpOrder,
FundingPayment,
OrderBook,
OrderBookEntry,
LimitOrderParams,
TimeInForce,
Order,
OrderStatus,
Farm,
StakeParams,
FarmPosition,
OHLCV,
TradeHistory,
VolumeStats,
TVLStats,
Subscription,
DexError,
)
__all__ = [
"SynorDex",
"DexConfig",
"Token",
"Pool",
"PoolType",
"PoolFilter",
"Quote",
"QuoteParams",
"SwapParams",
"SwapResult",
"AddLiquidityParams",
"RemoveLiquidityParams",
"LiquidityResult",
"LPPosition",
"PerpMarket",
"PositionSide",
"OrderType",
"MarginType",
"OpenPositionParams",
"ClosePositionParams",
"ModifyPositionParams",
"PerpPosition",
"PerpOrder",
"FundingPayment",
"OrderBook",
"OrderBookEntry",
"LimitOrderParams",
"TimeInForce",
"Order",
"OrderStatus",
"Farm",
"StakeParams",
"FarmPosition",
"OHLCV",
"TradeHistory",
"VolumeStats",
"TVLStats",
"Subscription",
"DexError",
]
__version__ = "0.1.0"

View file

@ -0,0 +1,496 @@
"""
Synor DEX SDK Client
Complete decentralized exchange client with support for:
- AMM swaps (constant product, stable, concentrated)
- Liquidity provision
- Perpetual futures (up to 100x leverage)
- Order books (limit orders)
- Farming & staking
"""
import asyncio
import json
import time
import uuid
from typing import Optional, List, Callable, Any, TypeVar, Dict
from urllib.parse import urlencode
import httpx
import websockets
from .types import (
DexConfig,
Token,
Pool,
PoolFilter,
Quote,
QuoteParams,
SwapParams,
SwapResult,
AddLiquidityParams,
RemoveLiquidityParams,
LiquidityResult,
LPPosition,
PerpMarket,
OpenPositionParams,
ClosePositionParams,
ModifyPositionParams,
PerpPosition,
PerpOrder,
FundingPayment,
OrderBook,
LimitOrderParams,
Order,
Farm,
StakeParams,
FarmPosition,
OHLCV,
TradeHistory,
VolumeStats,
TVLStats,
Subscription,
DexError,
)
T = TypeVar("T")
class PerpsClient:
"""Perpetual futures sub-client"""
def __init__(self, dex: "SynorDex"):
self._dex = dex
async def list_markets(self) -> List[PerpMarket]:
"""List all perpetual markets"""
return await self._dex._get("/perps/markets")
async def get_market(self, symbol: str) -> PerpMarket:
"""Get a specific perpetual market"""
return await self._dex._get(f"/perps/markets/{symbol}")
async def open_position(self, params: OpenPositionParams) -> PerpPosition:
"""Open a perpetual position"""
return await self._dex._post("/perps/positions", {
"market": params.market,
"side": params.side.value,
"size": str(params.size),
"leverage": params.leverage,
"order_type": params.order_type.value,
"limit_price": params.limit_price,
"stop_loss": params.stop_loss,
"take_profit": params.take_profit,
"margin_type": params.margin_type.value,
"reduce_only": params.reduce_only,
})
async def close_position(self, params: ClosePositionParams) -> PerpPosition:
"""Close a perpetual position"""
return await self._dex._post("/perps/positions/close", {
"market": params.market,
"size": str(params.size) if params.size else None,
"order_type": params.order_type.value,
"limit_price": params.limit_price,
})
async def modify_position(self, params: ModifyPositionParams) -> PerpPosition:
"""Modify a perpetual position"""
return await self._dex._post(f"/perps/positions/{params.position_id}/modify", {
"new_leverage": params.new_leverage,
"new_margin": str(params.new_margin) if params.new_margin else None,
"new_stop_loss": params.new_stop_loss,
"new_take_profit": params.new_take_profit,
})
async def get_positions(self) -> List[PerpPosition]:
"""Get all open positions"""
return await self._dex._get("/perps/positions")
async def get_position(self, market: str) -> Optional[PerpPosition]:
"""Get position for a specific market"""
return await self._dex._get(f"/perps/positions/{market}")
async def get_orders(self) -> List[PerpOrder]:
"""Get all open orders"""
return await self._dex._get("/perps/orders")
async def cancel_order(self, order_id: str) -> None:
"""Cancel an order"""
await self._dex._delete(f"/perps/orders/{order_id}")
async def cancel_all_orders(self, market: Optional[str] = None) -> int:
"""Cancel all orders, optionally for a specific market"""
path = f"/perps/orders?market={market}" if market else "/perps/orders"
result = await self._dex._delete(path)
return result.get("cancelled", 0)
async def get_funding_history(self, market: str, limit: int = 100) -> List[FundingPayment]:
"""Get funding payment history"""
return await self._dex._get(f"/perps/funding/{market}?limit={limit}")
async def get_funding_rate(self, market: str) -> Dict[str, Any]:
"""Get current funding rate"""
return await self._dex._get(f"/perps/funding/{market}/current")
async def subscribe_position(self, callback: Callable[[PerpPosition], None]) -> Subscription:
"""Subscribe to position updates"""
return await self._dex._subscribe("position", {}, callback)
class OrderBookClient:
"""Order book sub-client"""
def __init__(self, dex: "SynorDex"):
self._dex = dex
async def get_order_book(self, market: str, depth: int = 20) -> OrderBook:
"""Get order book for a market"""
return await self._dex._get(f"/orderbook/{market}?depth={depth}")
async def place_limit_order(self, params: LimitOrderParams) -> Order:
"""Place a limit order"""
return await self._dex._post("/orderbook/orders", {
"market": params.market,
"side": params.side,
"price": params.price,
"size": str(params.size),
"time_in_force": params.time_in_force.value,
"post_only": params.post_only,
})
async def cancel_order(self, order_id: str) -> None:
"""Cancel an order"""
await self._dex._delete(f"/orderbook/orders/{order_id}")
async def get_open_orders(self, market: Optional[str] = None) -> List[Order]:
"""Get all open orders"""
path = f"/orderbook/orders?market={market}" if market else "/orderbook/orders"
return await self._dex._get(path)
async def get_order_history(self, limit: int = 50) -> List[Order]:
"""Get order history"""
return await self._dex._get(f"/orderbook/orders/history?limit={limit}")
class FarmsClient:
"""Farms and staking sub-client"""
def __init__(self, dex: "SynorDex"):
self._dex = dex
async def list_farms(self) -> List[Farm]:
"""List all farms"""
return await self._dex._get("/farms")
async def get_farm(self, farm_id: str) -> Farm:
"""Get a specific farm"""
return await self._dex._get(f"/farms/{farm_id}")
async def stake(self, params: StakeParams) -> FarmPosition:
"""Stake tokens in a farm"""
return await self._dex._post("/farms/stake", {
"farm": params.farm,
"amount": str(params.amount),
})
async def unstake(self, farm: str, amount: int) -> FarmPosition:
"""Unstake tokens from a farm"""
return await self._dex._post("/farms/unstake", {
"farm": farm,
"amount": str(amount),
})
async def claim_rewards(self, farm: str) -> Dict[str, Any]:
"""Claim rewards from a farm"""
return await self._dex._post("/farms/claim", {"farm": farm})
async def get_my_farm_positions(self) -> List[FarmPosition]:
"""Get all farm positions"""
return await self._dex._get("/farms/positions")
class SynorDex:
"""
Synor DEX SDK Client
Complete decentralized exchange client with support for:
- AMM swaps (constant product, stable, concentrated)
- Liquidity provision
- Perpetual futures (up to 100x leverage)
- Order books (limit orders)
- Farming & staking
"""
def __init__(self, config: DexConfig):
self._config = config
self._closed = False
self._ws = None
self._subscriptions: Dict[str, Callable] = {}
self._client = httpx.AsyncClient(
base_url=config.endpoint,
timeout=config.timeout / 1000,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {config.api_key}",
"X-SDK-Version": "python/0.1.0",
},
)
# Sub-clients
self.perps = PerpsClient(self)
self.orderbook = OrderBookClient(self)
self.farms = FarmsClient(self)
# Token Operations
async def get_token(self, address: str) -> Token:
"""Get token information"""
return await self._get(f"/tokens/{address}")
async def list_tokens(self) -> List[Token]:
"""List all tokens"""
return await self._get("/tokens")
async def search_tokens(self, query: str) -> List[Token]:
"""Search tokens"""
return await self._get(f"/tokens/search?q={query}")
# Pool Operations
async def get_pool(self, token_a: str, token_b: str) -> Pool:
"""Get pool for a token pair"""
return await self._get(f"/pools/{token_a}/{token_b}")
async def get_pool_by_id(self, pool_id: str) -> Pool:
"""Get pool by ID"""
return await self._get(f"/pools/{pool_id}")
async def list_pools(self, filter: Optional[PoolFilter] = None) -> List[Pool]:
"""List pools with optional filtering"""
params = {}
if filter:
if filter.tokens:
params["tokens"] = ",".join(filter.tokens)
if filter.min_tvl:
params["min_tvl"] = str(filter.min_tvl)
if filter.min_volume_24h:
params["min_volume"] = str(filter.min_volume_24h)
if filter.verified is not None:
params["verified"] = str(filter.verified).lower()
if filter.limit:
params["limit"] = str(filter.limit)
if filter.offset:
params["offset"] = str(filter.offset)
query = urlencode(params) if params else ""
return await self._get(f"/pools?{query}")
# Swap Operations
async def get_quote(self, params: QuoteParams) -> Quote:
"""Get a swap quote"""
return await self._post("/swap/quote", {
"token_in": params.token_in,
"token_out": params.token_out,
"amount_in": str(params.amount_in),
"slippage": params.slippage,
})
async def swap(self, params: SwapParams) -> SwapResult:
"""Execute a swap"""
deadline = params.deadline or int(time.time()) + 1200
return await self._post("/swap", {
"token_in": params.token_in,
"token_out": params.token_out,
"amount_in": str(params.amount_in),
"min_amount_out": str(params.min_amount_out),
"deadline": deadline,
"recipient": params.recipient,
})
# Liquidity Operations
async def add_liquidity(self, params: AddLiquidityParams) -> LiquidityResult:
"""Add liquidity to a pool"""
deadline = params.deadline or int(time.time()) + 1200
return await self._post("/liquidity/add", {
"token_a": params.token_a,
"token_b": params.token_b,
"amount_a": str(params.amount_a),
"amount_b": str(params.amount_b),
"min_amount_a": str(params.min_amount_a) if params.min_amount_a else None,
"min_amount_b": str(params.min_amount_b) if params.min_amount_b else None,
"deadline": deadline,
})
async def remove_liquidity(self, params: RemoveLiquidityParams) -> LiquidityResult:
"""Remove liquidity from a pool"""
deadline = params.deadline or int(time.time()) + 1200
return await self._post("/liquidity/remove", {
"pool": params.pool,
"lp_amount": str(params.lp_amount),
"min_amount_a": str(params.min_amount_a) if params.min_amount_a else None,
"min_amount_b": str(params.min_amount_b) if params.min_amount_b else None,
"deadline": deadline,
})
async def get_my_positions(self) -> List[LPPosition]:
"""Get all LP positions"""
return await self._get("/liquidity/positions")
# Analytics
async def get_price_history(self, pair: str, interval: str, limit: int = 100) -> List[OHLCV]:
"""Get price history (OHLCV candles)"""
return await self._get(f"/analytics/candles/{pair}?interval={interval}&limit={limit}")
async def get_trade_history(self, pair: str, limit: int = 50) -> List[TradeHistory]:
"""Get trade history"""
return await self._get(f"/analytics/trades/{pair}?limit={limit}")
async def get_volume_stats(self) -> VolumeStats:
"""Get volume statistics"""
return await self._get("/analytics/volume")
async def get_tvl(self) -> TVLStats:
"""Get TVL statistics"""
return await self._get("/analytics/tvl")
# Subscriptions
async def subscribe_price(self, market: str, callback: Callable[[float], None]) -> Subscription:
"""Subscribe to price updates"""
return await self._subscribe("price", {"market": market}, callback)
async def subscribe_trades(self, market: str, callback: Callable[[TradeHistory], None]) -> Subscription:
"""Subscribe to trade updates"""
return await self._subscribe("trades", {"market": market}, callback)
async def subscribe_order_book(self, market: str, callback: Callable[[OrderBook], None]) -> Subscription:
"""Subscribe to order book updates"""
return await self._subscribe("orderbook", {"market": market}, callback)
# Lifecycle
async def health_check(self) -> bool:
"""Check if the service is healthy"""
try:
response = await self._get("/health")
return response.get("status") == "healthy"
except Exception:
return False
async def close(self) -> None:
"""Close the client"""
self._closed = True
if self._ws:
await self._ws.close()
self._ws = None
self._subscriptions.clear()
await self._client.aclose()
# Internal Methods
async def _get(self, path: str) -> Any:
"""Make a GET request"""
return await self._request("GET", path)
async def _post(self, path: str, body: dict) -> Any:
"""Make a POST request"""
return await self._request("POST", path, body)
async def _delete(self, path: str) -> Any:
"""Make a DELETE request"""
return await self._request("DELETE", path)
async def _request(self, method: str, path: str, body: Optional[dict] = None) -> Any:
"""Make an HTTP request with retries"""
if self._closed:
raise DexError("Client has been closed", "CLIENT_CLOSED")
last_error = None
for attempt in range(self._config.retries):
try:
if method == "GET":
response = await self._client.get(path)
elif method == "POST":
response = await self._client.post(path, json=body)
elif method == "DELETE":
response = await self._client.delete(path)
else:
raise DexError(f"Unknown method: {method}")
if not response.is_success:
try:
error = response.json()
except Exception:
error = {}
raise DexError(
error.get("message", f"HTTP {response.status_code}"),
error.get("code"),
response.status_code,
)
return response.json()
except DexError:
raise
except Exception as e:
last_error = e
if self._config.debug:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < self._config.retries - 1:
await asyncio.sleep(2 ** attempt)
raise last_error or DexError("Unknown error")
async def _subscribe(
self,
channel: str,
params: dict,
callback: Callable,
) -> Subscription:
"""Subscribe to a WebSocket channel"""
await self._ensure_websocket()
subscription_id = str(uuid.uuid4())[:8]
self._subscriptions[subscription_id] = callback
await self._ws.send(json.dumps({
"type": "subscribe",
"channel": channel,
"subscription_id": subscription_id,
**params,
}))
def cancel():
self._subscriptions.pop(subscription_id, None)
if self._ws:
asyncio.create_task(self._ws.send(json.dumps({
"type": "unsubscribe",
"subscription_id": subscription_id,
})))
return Subscription(id=subscription_id, channel=channel, cancel=cancel)
async def _ensure_websocket(self) -> None:
"""Ensure WebSocket connection is established"""
if self._ws and self._ws.open:
return
self._ws = await websockets.connect(self._config.ws_endpoint)
# Authenticate
await self._ws.send(json.dumps({
"type": "auth",
"api_key": self._config.api_key,
}))
# Start message handler
asyncio.create_task(self._handle_messages())
async def _handle_messages(self) -> None:
"""Handle incoming WebSocket messages"""
try:
async for message in self._ws:
data = json.loads(message)
subscription_id = data.get("subscription_id")
if subscription_id and subscription_id in self._subscriptions:
self._subscriptions[subscription_id](data.get("data"))
except Exception:
pass # Connection closed

View file

@ -0,0 +1,445 @@
"""
Synor DEX SDK Types
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, List, Callable, Any
from decimal import Decimal
class PoolType(Enum):
"""Type of liquidity pool"""
CONSTANT_PRODUCT = "constant_product" # x * y = k
STABLE = "stable" # Optimized for stablecoins
CONCENTRATED = "concentrated" # Uniswap v3 style
class PositionSide(Enum):
"""Perpetual position side"""
LONG = "long"
SHORT = "short"
class OrderType(Enum):
"""Order type for perpetuals"""
MARKET = "market"
LIMIT = "limit"
STOP_MARKET = "stop_market"
STOP_LIMIT = "stop_limit"
TAKE_PROFIT = "take_profit"
TAKE_PROFIT_LIMIT = "take_profit_limit"
class MarginType(Enum):
"""Margin type for perpetuals"""
CROSS = "cross"
ISOLATED = "isolated"
class TimeInForce(Enum):
"""Time in force for limit orders"""
GTC = "GTC" # Good Till Cancel
IOC = "IOC" # Immediate Or Cancel
FOK = "FOK" # Fill Or Kill
GTD = "GTD" # Good Till Date
class OrderStatus(Enum):
"""Order status"""
PENDING = "pending"
OPEN = "open"
PARTIALLY_FILLED = "partially_filled"
FILLED = "filled"
CANCELLED = "cancelled"
EXPIRED = "expired"
class DexError(Exception):
"""DEX SDK error"""
def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None):
super().__init__(message)
self.message = message
self.code = code
self.status = status
@dataclass
class DexConfig:
"""DEX client configuration"""
api_key: str
endpoint: str = "https://dex.synor.io/v1"
ws_endpoint: str = "wss://dex.synor.io/v1/ws"
timeout: int = 30000
retries: int = 3
debug: bool = False
@dataclass
class Token:
"""Token information"""
address: str
symbol: str
name: str
decimals: int
total_supply: int
price_usd: Optional[float] = None
logo_url: Optional[str] = None
verified: bool = False
@dataclass
class Pool:
"""Liquidity pool information"""
id: str
token_a: Token
token_b: Token
pool_type: PoolType
reserve_a: int
reserve_b: int
fee: float
tvl_usd: float
volume_24h: float
apr: float
lp_token_address: str
tick_spacing: Optional[int] = None # For concentrated liquidity
sqrt_price: Optional[str] = None
@dataclass
class PoolFilter:
"""Pool list filter"""
tokens: Optional[List[str]] = None
min_tvl: Optional[float] = None
min_volume_24h: Optional[float] = None
verified: Optional[bool] = None
limit: Optional[int] = None
offset: Optional[int] = None
@dataclass
class Quote:
"""Swap quote"""
token_in: str
token_out: str
amount_in: int
amount_out: int
price_impact: float
route: List[str]
fee: int
minimum_received: int
expires_at: int
@dataclass
class QuoteParams:
"""Parameters for getting a quote"""
token_in: str
token_out: str
amount_in: int
slippage: float = 0.005
@dataclass
class SwapParams:
"""Parameters for executing a swap"""
token_in: str
token_out: str
amount_in: int
min_amount_out: int
deadline: Optional[int] = None
recipient: Optional[str] = None
@dataclass
class SwapResult:
"""Swap execution result"""
transaction_hash: str
amount_in: int
amount_out: int
effective_price: float
fee_paid: int
route: List[str]
@dataclass
class AddLiquidityParams:
"""Parameters for adding liquidity"""
token_a: str
token_b: str
amount_a: int
amount_b: int
min_amount_a: Optional[int] = None
min_amount_b: Optional[int] = None
deadline: Optional[int] = None
tick_lower: Optional[int] = None # For concentrated liquidity
tick_upper: Optional[int] = None
@dataclass
class RemoveLiquidityParams:
"""Parameters for removing liquidity"""
pool: str
lp_amount: int
min_amount_a: Optional[int] = None
min_amount_b: Optional[int] = None
deadline: Optional[int] = None
@dataclass
class LiquidityResult:
"""Liquidity operation result"""
transaction_hash: str
amount_a: int
amount_b: int
lp_tokens: int
pool_share: float
@dataclass
class LPPosition:
"""LP position"""
pool_id: str
lp_tokens: int
token_a_amount: int
token_b_amount: int
value_usd: float
unclaimed_fees_a: int
unclaimed_fees_b: int
impermanent_loss: float
tick_lower: Optional[int] = None
tick_upper: Optional[int] = None
in_range: Optional[bool] = None
@dataclass
class PerpMarket:
"""Perpetual futures market"""
symbol: str
base_asset: str
quote_asset: str
index_price: float
mark_price: float
funding_rate: float
next_funding_time: int
open_interest: int
volume_24h: float
price_change_24h: float
max_leverage: int
min_order_size: int
tick_size: float
maintenance_margin: float
initial_margin: float
@dataclass
class OpenPositionParams:
"""Parameters for opening a perpetual position"""
market: str
side: PositionSide
size: int
leverage: int # 1-100x
order_type: OrderType
limit_price: Optional[float] = None
stop_loss: Optional[float] = None
take_profit: Optional[float] = None
margin_type: MarginType = MarginType.CROSS
reduce_only: bool = False
@dataclass
class ClosePositionParams:
"""Parameters for closing a perpetual position"""
market: str
size: Optional[int] = None # None = close entire position
order_type: OrderType = OrderType.MARKET
limit_price: Optional[float] = None
@dataclass
class ModifyPositionParams:
"""Parameters for modifying a perpetual position"""
position_id: str
new_leverage: Optional[int] = None
new_margin: Optional[int] = None
new_stop_loss: Optional[float] = None
new_take_profit: Optional[float] = None
@dataclass
class PerpPosition:
"""Perpetual position"""
id: str
market: str
side: PositionSide
size: int
entry_price: float
mark_price: float
liquidation_price: float
margin: int
leverage: int
unrealized_pnl: int
realized_pnl: int
margin_ratio: float
stop_loss: Optional[float] = None
take_profit: Optional[float] = None
created_at: int = 0
updated_at: int = 0
@dataclass
class PerpOrder:
"""Perpetual order"""
id: str
market: str
side: PositionSide
order_type: OrderType
size: int
price: Optional[float] = None
filled_size: int = 0
status: OrderStatus = OrderStatus.PENDING
reduce_only: bool = False
created_at: int = 0
@dataclass
class FundingPayment:
"""Funding payment record"""
market: str
amount: int
rate: float
position_size: int
timestamp: int
@dataclass
class OrderBookEntry:
"""Order book entry"""
price: float
size: int
orders: int
@dataclass
class OrderBook:
"""Order book"""
market: str
bids: List[OrderBookEntry]
asks: List[OrderBookEntry]
timestamp: int
@dataclass
class LimitOrderParams:
"""Parameters for limit orders"""
market: str
side: str # 'buy' or 'sell'
price: float
size: int
time_in_force: TimeInForce = TimeInForce.GTC
post_only: bool = False
@dataclass
class Order:
"""Limit order"""
id: str
market: str
side: str
price: float
size: int
filled_size: int
status: OrderStatus
time_in_force: TimeInForce
post_only: bool
created_at: int
updated_at: int
@dataclass
class Farm:
"""Yield farm"""
id: str
name: str
stake_token: Token
reward_tokens: List[Token]
tvl_usd: float
apr: float
daily_rewards: List[int]
lockup_period: Optional[int] = None
min_stake: Optional[int] = None
@dataclass
class StakeParams:
"""Parameters for staking"""
farm: str
amount: int
@dataclass
class FarmPosition:
"""Farm position"""
farm_id: str
staked_amount: int
pending_rewards: List[int]
staked_at: int
unlock_at: Optional[int] = None
@dataclass
class OHLCV:
"""OHLCV candle data"""
timestamp: int
open: float
high: float
low: float
close: float
volume: float
@dataclass
class TradeHistory:
"""Trade history entry"""
id: str
market: str
side: str
price: float
size: int
timestamp: int
maker: str
taker: str
@dataclass
class VolumeStats:
"""Volume statistics"""
volume_24h: float
volume_7d: float
volume_30d: float
trades_24h: int
unique_traders_24h: int
@dataclass
class TVLStats:
"""TVL statistics"""
total_tvl: float
pools_tvl: float
farms_tvl: float
perps_tvl: float
@dataclass
class Subscription:
"""WebSocket subscription"""
id: str
channel: str
cancel: Callable[[], None]
# Callback types
PriceCallback = Callable[[float], None]
TradeCallback = Callable[[TradeHistory], None]
OrderBookCallback = Callable[[OrderBook], None]
PositionCallback = Callable[[PerpPosition], None]

392
sdk/ruby/lib/synor/dex.rb Normal file
View file

@ -0,0 +1,392 @@
# frozen_string_literal: true
require 'net/http'
require 'uri'
require 'json'
require 'websocket-client-simple'
module Synor
# DEX SDK for Ruby
#
# Complete decentralized exchange client with support for:
# - AMM swaps (constant product, stable, concentrated)
# - Liquidity provision
# - Perpetual futures (up to 100x leverage)
# - Order books (limit orders)
# - Farming & staking
module Dex
VERSION = '0.1.0'
# DEX configuration
class Config
attr_accessor :api_key, :endpoint, :ws_endpoint, :timeout, :retries, :debug
def initialize(
api_key:,
endpoint: 'https://dex.synor.io/v1',
ws_endpoint: 'wss://dex.synor.io/v1/ws',
timeout: 30,
retries: 3,
debug: false
)
@api_key = api_key
@endpoint = endpoint
@ws_endpoint = ws_endpoint
@timeout = timeout
@retries = retries
@debug = debug
end
end
# DEX exception
class DexError < StandardError
attr_reader :code, :status
def initialize(message, code: nil, status: nil)
super(message)
@code = code
@status = status
end
end
# Main DEX client
class Client
attr_reader :perps, :orderbook, :farms
def initialize(config)
@config = config
@closed = false
@perps = PerpsClient.new(self)
@orderbook = OrderBookClient.new(self)
@farms = FarmsClient.new(self)
end
# Token Operations
def get_token(address)
get("/tokens/#{address}")
end
def list_tokens
get('/tokens')
end
def search_tokens(query)
get("/tokens/search?q=#{URI.encode_www_form_component(query)}")
end
# Pool Operations
def get_pool(token_a, token_b)
get("/pools/#{token_a}/#{token_b}")
end
def get_pool_by_id(pool_id)
get("/pools/#{pool_id}")
end
def list_pools(filter = nil)
params = []
if filter
params << "tokens=#{filter[:tokens].join(',')}" if filter[:tokens]
params << "min_tvl=#{filter[:min_tvl]}" if filter[:min_tvl]
params << "min_volume=#{filter[:min_volume_24h]}" if filter[:min_volume_24h]
params << "verified=#{filter[:verified]}" unless filter[:verified].nil?
params << "limit=#{filter[:limit]}" if filter[:limit]
params << "offset=#{filter[:offset]}" if filter[:offset]
end
path = params.empty? ? '/pools' : "/pools?#{params.join('&')}"
get(path)
end
# Swap Operations
def get_quote(params)
post('/swap/quote', {
token_in: params[:token_in],
token_out: params[:token_out],
amount_in: params[:amount_in].to_s,
slippage: params[:slippage] || 0.005
})
end
def swap(params)
deadline = params[:deadline] || (Time.now.to_i + 1200)
body = {
token_in: params[:token_in],
token_out: params[:token_out],
amount_in: params[:amount_in].to_s,
min_amount_out: params[:min_amount_out].to_s,
deadline: deadline
}
body[:recipient] = params[:recipient] if params[:recipient]
post('/swap', body)
end
# Liquidity Operations
def add_liquidity(params)
deadline = params[:deadline] || (Time.now.to_i + 1200)
body = {
token_a: params[:token_a],
token_b: params[:token_b],
amount_a: params[:amount_a].to_s,
amount_b: params[:amount_b].to_s,
deadline: deadline
}
body[:min_amount_a] = params[:min_amount_a].to_s if params[:min_amount_a]
body[:min_amount_b] = params[:min_amount_b].to_s if params[:min_amount_b]
post('/liquidity/add', body)
end
def remove_liquidity(params)
deadline = params[:deadline] || (Time.now.to_i + 1200)
body = {
pool: params[:pool],
lp_amount: params[:lp_amount].to_s,
deadline: deadline
}
body[:min_amount_a] = params[:min_amount_a].to_s if params[:min_amount_a]
body[:min_amount_b] = params[:min_amount_b].to_s if params[:min_amount_b]
post('/liquidity/remove', body)
end
def get_my_positions
get('/liquidity/positions')
end
# Analytics
def get_price_history(pair, interval, limit: 100)
get("/analytics/candles/#{pair}?interval=#{interval}&limit=#{limit}")
end
def get_trade_history(pair, limit: 50)
get("/analytics/trades/#{pair}?limit=#{limit}")
end
def get_volume_stats
get('/analytics/volume')
end
def get_tvl
get('/analytics/tvl')
end
# Lifecycle
def health_check
response = get('/health')
response['status'] == 'healthy'
rescue StandardError
false
end
def close
@closed = true
end
def closed?
@closed
end
# Internal methods
def get(path)
request(:get, path)
end
def post(path, body)
request(:post, path, body)
end
def delete(path)
request(:delete, path)
end
private
def request(method, path, body = nil)
raise DexError.new('Client has been closed', code: 'CLIENT_CLOSED') if @closed
uri = URI.parse("#{@config.endpoint}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.read_timeout = @config.timeout
request = case method
when :get
Net::HTTP::Get.new(uri.request_uri)
when :post
req = Net::HTTP::Post.new(uri.request_uri)
req.body = body.to_json if body
req
when :delete
Net::HTTP::Delete.new(uri.request_uri)
end
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer #{@config.api_key}"
request['X-SDK-Version'] = 'ruby/0.1.0'
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
error = JSON.parse(response.body) rescue {}
raise DexError.new(
error['message'] || "HTTP #{response.code}",
code: error['code'],
status: response.code.to_i
)
end
JSON.parse(response.body)
end
end
# Perpetual futures sub-client
class PerpsClient
def initialize(dex)
@dex = dex
end
def list_markets
@dex.get('/perps/markets')
end
def get_market(symbol)
@dex.get("/perps/markets/#{symbol}")
end
def open_position(params)
body = {
market: params[:market],
side: params[:side].to_s,
size: params[:size].to_s,
leverage: params[:leverage],
order_type: params[:order_type].to_s,
margin_type: (params[:margin_type] || :cross).to_s,
reduce_only: params[:reduce_only] || false
}
body[:limit_price] = params[:limit_price] if params[:limit_price]
body[:stop_loss] = params[:stop_loss] if params[:stop_loss]
body[:take_profit] = params[:take_profit] if params[:take_profit]
@dex.post('/perps/positions', body)
end
def close_position(params)
body = {
market: params[:market],
order_type: (params[:order_type] || :market).to_s
}
body[:size] = params[:size].to_s if params[:size]
body[:limit_price] = params[:limit_price] if params[:limit_price]
@dex.post('/perps/positions/close', body)
end
def get_positions
@dex.get('/perps/positions')
end
def get_position(market)
@dex.get("/perps/positions/#{market}")
end
def get_orders
@dex.get('/perps/orders')
end
def cancel_order(order_id)
@dex.delete("/perps/orders/#{order_id}")
end
def cancel_all_orders(market: nil)
path = market ? "/perps/orders?market=#{market}" : '/perps/orders'
result = @dex.delete(path)
result['cancelled']
end
def get_funding_history(market, limit: 100)
@dex.get("/perps/funding/#{market}?limit=#{limit}")
end
def get_funding_rate(market)
@dex.get("/perps/funding/#{market}/current")
end
end
# Order book sub-client
class OrderBookClient
def initialize(dex)
@dex = dex
end
def get_order_book(market, depth: 20)
@dex.get("/orderbook/#{market}?depth=#{depth}")
end
def place_limit_order(params)
@dex.post('/orderbook/orders', {
market: params[:market],
side: params[:side],
price: params[:price],
size: params[:size].to_s,
time_in_force: (params[:time_in_force] || :GTC).to_s,
post_only: params[:post_only] || false
})
end
def cancel_order(order_id)
@dex.delete("/orderbook/orders/#{order_id}")
end
def get_open_orders(market: nil)
path = market ? "/orderbook/orders?market=#{market}" : '/orderbook/orders'
@dex.get(path)
end
def get_order_history(limit: 50)
@dex.get("/orderbook/orders/history?limit=#{limit}")
end
end
# Farms sub-client
class FarmsClient
def initialize(dex)
@dex = dex
end
def list_farms
@dex.get('/farms')
end
def get_farm(farm_id)
@dex.get("/farms/#{farm_id}")
end
def stake(params)
@dex.post('/farms/stake', {
farm: params[:farm],
amount: params[:amount].to_s
})
end
def unstake(farm, amount)
@dex.post('/farms/unstake', {
farm: farm,
amount: amount.to_s
})
end
def claim_rewards(farm)
@dex.post('/farms/claim', { farm: farm })
end
def get_my_farm_positions
@dex.get('/farms/positions')
end
end
end
end

332
sdk/rust/src/dex/client.rs Normal file
View file

@ -0,0 +1,332 @@
//! Synor DEX Client
use crate::dex::types::*;
use reqwest::Client;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::json;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
/// Main DEX client
pub struct SynorDex {
config: DexConfig,
client: Client,
closed: Arc<AtomicBool>,
pub perps: PerpsClient,
pub orderbook: OrderBookClient,
pub farms: FarmsClient,
}
impl SynorDex {
/// Create a new SynorDex client
pub fn new(config: DexConfig) -> Result<Self, DexError> {
let client = Client::builder()
.timeout(Duration::from_millis(config.timeout_ms))
.build()?;
let closed = Arc::new(AtomicBool::new(false));
Ok(Self {
perps: PerpsClient::new(config.clone(), client.clone(), closed.clone()),
orderbook: OrderBookClient::new(config.clone(), client.clone(), closed.clone()),
farms: FarmsClient::new(config.clone(), client.clone(), closed.clone()),
config,
client,
closed,
})
}
// Token Operations
/// Get token information
pub async fn get_token(&self, address: &str) -> Result<Token, DexError> {
self.get(&format!("/tokens/{}", address)).await
}
/// List all tokens
pub async fn list_tokens(&self) -> Result<Vec<Token>, DexError> {
self.get("/tokens").await
}
/// Search tokens
pub async fn search_tokens(&self, query: &str) -> Result<Vec<Token>, DexError> {
self.get(&format!("/tokens/search?q={}", urlencoding::encode(query))).await
}
// Pool Operations
/// Get pool by token pair
pub async fn get_pool(&self, token_a: &str, token_b: &str) -> Result<Pool, DexError> {
self.get(&format!("/pools/{}/{}", token_a, token_b)).await
}
/// Get pool by ID
pub async fn get_pool_by_id(&self, pool_id: &str) -> Result<Pool, DexError> {
self.get(&format!("/pools/{}", pool_id)).await
}
/// List pools with optional filtering
pub async fn list_pools(&self, filter: Option<PoolFilter>) -> Result<Vec<Pool>, DexError> {
let mut params = Vec::new();
if let Some(f) = filter {
if let Some(tokens) = f.tokens {
params.push(format!("tokens={}", tokens.join(",")));
}
if let Some(min_tvl) = f.min_tvl {
params.push(format!("min_tvl={}", min_tvl));
}
if let Some(min_volume) = f.min_volume_24h {
params.push(format!("min_volume={}", min_volume));
}
if let Some(verified) = f.verified {
params.push(format!("verified={}", verified));
}
if let Some(limit) = f.limit {
params.push(format!("limit={}", limit));
}
if let Some(offset) = f.offset {
params.push(format!("offset={}", offset));
}
}
let path = if params.is_empty() {
"/pools".to_string()
} else {
format!("/pools?{}", params.join("&"))
};
self.get(&path).await
}
// Swap Operations
/// Get swap quote
pub async fn get_quote(&self, params: QuoteParams) -> Result<Quote, DexError> {
self.post("/swap/quote", json!({
"token_in": params.token_in,
"token_out": params.token_out,
"amount_in": params.amount_in,
"slippage": params.slippage.unwrap_or(0.005),
})).await
}
/// Execute swap
pub async fn swap(&self, params: SwapParams) -> Result<SwapResult, DexError> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let deadline = params.deadline.unwrap_or(now + 1200);
let mut body = json!({
"token_in": params.token_in,
"token_out": params.token_out,
"amount_in": params.amount_in,
"min_amount_out": params.min_amount_out,
"deadline": deadline,
});
if let Some(recipient) = params.recipient {
body["recipient"] = json!(recipient);
}
self.post("/swap", body).await
}
// Liquidity Operations
/// Add liquidity to a pool
pub async fn add_liquidity(&self, params: AddLiquidityParams) -> Result<LiquidityResult, DexError> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let deadline = params.deadline.unwrap_or(now + 1200);
let mut body = json!({
"token_a": params.token_a,
"token_b": params.token_b,
"amount_a": params.amount_a,
"amount_b": params.amount_b,
"deadline": deadline,
});
if let Some(min_a) = params.min_amount_a {
body["min_amount_a"] = json!(min_a);
}
if let Some(min_b) = params.min_amount_b {
body["min_amount_b"] = json!(min_b);
}
self.post("/liquidity/add", body).await
}
/// Remove liquidity from a pool
pub async fn remove_liquidity(&self, params: RemoveLiquidityParams) -> Result<LiquidityResult, DexError> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let deadline = params.deadline.unwrap_or(now + 1200);
let mut body = json!({
"pool": params.pool,
"lp_amount": params.lp_amount,
"deadline": deadline,
});
if let Some(min_a) = params.min_amount_a {
body["min_amount_a"] = json!(min_a);
}
if let Some(min_b) = params.min_amount_b {
body["min_amount_b"] = json!(min_b);
}
self.post("/liquidity/remove", body).await
}
/// Get all LP positions
pub async fn get_my_positions(&self) -> Result<Vec<LPPosition>, DexError> {
self.get("/liquidity/positions").await
}
// Analytics
/// Get price history (OHLCV candles)
pub async fn get_price_history(&self, pair: &str, interval: &str, limit: u32) -> Result<Vec<OHLCV>, DexError> {
self.get(&format!("/analytics/candles/{}?interval={}&limit={}", pair, interval, limit)).await
}
/// Get trade history
pub async fn get_trade_history(&self, pair: &str, limit: u32) -> Result<Vec<TradeHistory>, DexError> {
self.get(&format!("/analytics/trades/{}?limit={}", pair, limit)).await
}
/// Get volume statistics
pub async fn get_volume_stats(&self) -> Result<VolumeStats, DexError> {
self.get("/analytics/volume").await
}
/// Get TVL statistics
pub async fn get_tvl(&self) -> Result<TVLStats, DexError> {
self.get("/analytics/tvl").await
}
// Lifecycle
/// Health check
pub async fn health_check(&self) -> bool {
#[derive(Deserialize)]
struct Health {
status: String,
}
match self.get::<Health>("/health").await {
Ok(h) => h.status == "healthy",
Err(_) => false,
}
}
/// Close the client
pub fn close(&self) {
self.closed.store(true, Ordering::SeqCst);
}
/// Check if client is closed
pub fn is_closed(&self) -> bool {
self.closed.load(Ordering::SeqCst)
}
// Internal methods
pub(crate) async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
self.request("GET", path, None::<()>).await
}
pub(crate) async fn post<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: B) -> Result<T, DexError> {
self.request("POST", path, Some(body)).await
}
pub(crate) async fn delete<T: DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
self.request("DELETE", path, None::<()>).await
}
async fn request<T: DeserializeOwned, B: Serialize>(
&self,
method: &str,
path: &str,
body: Option<B>,
) -> Result<T, DexError> {
if self.closed.load(Ordering::SeqCst) {
return Err(DexError::ClientClosed);
}
let mut last_error = None;
for attempt in 0..self.config.retries {
let url = format!("{}{}", self.config.endpoint, path);
let mut request = match method {
"GET" => self.client.get(&url),
"POST" => self.client.post(&url),
"DELETE" => self.client.delete(&url),
_ => return Err(DexError::Http {
message: format!("Unknown method: {}", method),
code: None,
status: None,
}),
};
request = request
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0");
if let Some(ref b) = body {
request = request.json(b);
}
match request.send().await {
Ok(response) => {
let status = response.status();
if !status.is_success() {
#[derive(Deserialize, Default)]
struct ErrorResponse {
message: Option<String>,
code: Option<String>,
}
let error: ErrorResponse = response.json().await.unwrap_or_default();
return Err(DexError::Http {
message: error.message.unwrap_or_else(|| format!("HTTP {}", status)),
code: error.code,
status: Some(status.as_u16()),
});
}
return response.json().await.map_err(DexError::from);
}
Err(e) => {
last_error = Some(e);
if self.config.debug {
eprintln!("Attempt {} failed: {:?}", attempt + 1, last_error);
}
if attempt < self.config.retries - 1 {
tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
}
}
}
}
Err(last_error.map(DexError::from).unwrap_or(DexError::Http {
message: "Unknown error".to_string(),
code: None,
status: None,
}))
}
}
use crate::dex::{PerpsClient, OrderBookClient, FarmsClient};

111
sdk/rust/src/dex/farms.rs Normal file
View file

@ -0,0 +1,111 @@
//! Farms Client
use crate::dex::types::*;
use reqwest::Client;
use serde_json::json;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
/// Farms sub-client
pub struct FarmsClient {
config: DexConfig,
client: Client,
closed: Arc<AtomicBool>,
}
impl FarmsClient {
pub(crate) fn new(config: DexConfig, client: Client, closed: Arc<AtomicBool>) -> Self {
Self { config, client, closed }
}
/// List all farms
pub async fn list_farms(&self) -> Result<Vec<Farm>, DexError> {
self.get("/farms").await
}
/// Get a specific farm
pub async fn get_farm(&self, farm_id: &str) -> Result<Farm, DexError> {
self.get(&format!("/farms/{}", farm_id)).await
}
/// Stake tokens in a farm
pub async fn stake(&self, params: StakeParams) -> Result<FarmPosition, DexError> {
self.post("/farms/stake", json!({
"farm": params.farm,
"amount": params.amount,
})).await
}
/// Unstake tokens from a farm
pub async fn unstake(&self, farm: &str, amount: &str) -> Result<FarmPosition, DexError> {
self.post("/farms/unstake", json!({
"farm": farm,
"amount": amount,
})).await
}
/// Claim rewards from a farm
pub async fn claim_rewards(&self, farm: &str) -> Result<ClaimRewardsResult, DexError> {
self.post("/farms/claim", json!({
"farm": farm,
})).await
}
/// Get all farm positions
pub async fn get_my_farm_positions(&self) -> Result<Vec<FarmPosition>, DexError> {
self.get("/farms/positions").await
}
// Internal methods
async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
if self.closed.load(Ordering::SeqCst) {
return Err(DexError::ClientClosed);
}
let url = format!("{}{}", self.config.endpoint, path);
let response = self.client
.get(&url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
if !response.status().is_success() {
return Err(DexError::Http {
message: format!("HTTP {}", response.status()),
code: None,
status: Some(response.status().as_u16()),
});
}
response.json().await.map_err(DexError::from)
}
async fn post<T: serde::de::DeserializeOwned>(&self, path: &str, body: serde_json::Value) -> Result<T, DexError> {
if self.closed.load(Ordering::SeqCst) {
return Err(DexError::ClientClosed);
}
let url = format!("{}{}", self.config.endpoint, path);
let response = self.client
.post(&url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.json(&body)
.send()
.await?;
if !response.status().is_success() {
return Err(DexError::Http {
message: format!("HTTP {}", response.status()),
code: None,
status: Some(response.status().as_u16()),
});
}
response.json().await.map_err(DexError::from)
}
}

20
sdk/rust/src/dex/mod.rs Normal file
View file

@ -0,0 +1,20 @@
//! Synor DEX SDK for Rust
//!
//! Complete decentralized exchange client with support for:
//! - AMM swaps (constant product, stable, concentrated)
//! - Liquidity provision
//! - Perpetual futures (up to 100x leverage)
//! - Order books (limit orders)
//! - Farming & staking
mod types;
mod client;
mod perps;
mod orderbook;
mod farms;
pub use types::*;
pub use client::*;
pub use perps::*;
pub use orderbook::*;
pub use farms::*;

View file

@ -0,0 +1,134 @@
//! Order Book Client
use crate::dex::types::*;
use reqwest::Client;
use serde_json::json;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
/// Order book sub-client
pub struct OrderBookClient {
config: DexConfig,
client: Client,
closed: Arc<AtomicBool>,
}
impl OrderBookClient {
pub(crate) fn new(config: DexConfig, client: Client, closed: Arc<AtomicBool>) -> Self {
Self { config, client, closed }
}
/// Get order book for a market
pub async fn get_order_book(&self, market: &str, depth: u32) -> Result<OrderBook, DexError> {
self.get(&format!("/orderbook/{}?depth={}", market, depth)).await
}
/// Place a limit order
pub async fn place_limit_order(&self, params: LimitOrderParams) -> Result<Order, DexError> {
self.post("/orderbook/orders", json!({
"market": params.market,
"side": params.side,
"price": params.price,
"size": params.size,
"time_in_force": params.time_in_force,
"post_only": params.post_only,
})).await
}
/// Cancel an order
pub async fn cancel_order(&self, order_id: &str) -> Result<(), DexError> {
self.delete(&format!("/orderbook/orders/{}", order_id)).await
}
/// Get all open orders
pub async fn get_open_orders(&self, market: Option<&str>) -> Result<Vec<Order>, DexError> {
let path = match market {
Some(m) => format!("/orderbook/orders?market={}", m),
None => "/orderbook/orders".to_string(),
};
self.get(&path).await
}
/// Get order history
pub async fn get_order_history(&self, limit: u32) -> Result<Vec<Order>, DexError> {
self.get(&format!("/orderbook/orders/history?limit={}", limit)).await
}
// Internal methods
async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
if self.closed.load(Ordering::SeqCst) {
return Err(DexError::ClientClosed);
}
let url = format!("{}{}", self.config.endpoint, path);
let response = self.client
.get(&url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
if !response.status().is_success() {
return Err(DexError::Http {
message: format!("HTTP {}", response.status()),
code: None,
status: Some(response.status().as_u16()),
});
}
response.json().await.map_err(DexError::from)
}
async fn post<T: serde::de::DeserializeOwned>(&self, path: &str, body: serde_json::Value) -> Result<T, DexError> {
if self.closed.load(Ordering::SeqCst) {
return Err(DexError::ClientClosed);
}
let url = format!("{}{}", self.config.endpoint, path);
let response = self.client
.post(&url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.json(&body)
.send()
.await?;
if !response.status().is_success() {
return Err(DexError::Http {
message: format!("HTTP {}", response.status()),
code: None,
status: Some(response.status().as_u16()),
});
}
response.json().await.map_err(DexError::from)
}
async fn delete<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
if self.closed.load(Ordering::SeqCst) {
return Err(DexError::ClientClosed);
}
let url = format!("{}{}", self.config.endpoint, path);
let response = self.client
.delete(&url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
if !response.status().is_success() {
return Err(DexError::Http {
message: format!("HTTP {}", response.status()),
code: None,
status: Some(response.status().as_u16()),
});
}
response.json().await.map_err(DexError::from)
}
}

222
sdk/rust/src/dex/perps.rs Normal file
View file

@ -0,0 +1,222 @@
//! Perpetuals Client
use crate::dex::types::*;
use reqwest::Client;
use serde_json::json;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
/// Perpetuals sub-client
pub struct PerpsClient {
config: DexConfig,
client: Client,
closed: Arc<AtomicBool>,
}
impl PerpsClient {
pub(crate) fn new(config: DexConfig, client: Client, closed: Arc<AtomicBool>) -> Self {
Self { config, client, closed }
}
/// List all perpetual markets
pub async fn list_markets(&self) -> Result<Vec<PerpMarket>, DexError> {
self.get("/perps/markets").await
}
/// Get a specific perpetual market
pub async fn get_market(&self, symbol: &str) -> Result<PerpMarket, DexError> {
self.get(&format!("/perps/markets/{}", symbol)).await
}
/// Open a perpetual position
pub async fn open_position(&self, params: OpenPositionParams) -> Result<PerpPosition, DexError> {
let mut body = json!({
"market": params.market,
"side": params.side,
"size": params.size,
"leverage": params.leverage,
"order_type": params.order_type,
"margin_type": params.margin_type,
"reduce_only": params.reduce_only,
});
if let Some(price) = params.limit_price {
body["limit_price"] = json!(price);
}
if let Some(sl) = params.stop_loss {
body["stop_loss"] = json!(sl);
}
if let Some(tp) = params.take_profit {
body["take_profit"] = json!(tp);
}
self.post("/perps/positions", body).await
}
/// Close a perpetual position
pub async fn close_position(&self, params: ClosePositionParams) -> Result<PerpPosition, DexError> {
let mut body = json!({
"market": params.market,
"order_type": params.order_type,
});
if let Some(size) = params.size {
body["size"] = json!(size);
}
if let Some(price) = params.limit_price {
body["limit_price"] = json!(price);
}
self.post("/perps/positions/close", body).await
}
/// Modify a perpetual position
pub async fn modify_position(&self, params: ModifyPositionParams) -> Result<PerpPosition, DexError> {
let mut body = json!({});
if let Some(leverage) = params.new_leverage {
body["new_leverage"] = json!(leverage);
}
if let Some(margin) = params.new_margin {
body["new_margin"] = json!(margin);
}
if let Some(sl) = params.new_stop_loss {
body["new_stop_loss"] = json!(sl);
}
if let Some(tp) = params.new_take_profit {
body["new_take_profit"] = json!(tp);
}
self.post(&format!("/perps/positions/{}/modify", params.position_id), body).await
}
/// Get all open positions
pub async fn get_positions(&self) -> Result<Vec<PerpPosition>, DexError> {
self.get("/perps/positions").await
}
/// Get position for a specific market
pub async fn get_position(&self, market: &str) -> Result<Option<PerpPosition>, DexError> {
self.get(&format!("/perps/positions/{}", market)).await
}
/// Get all open orders
pub async fn get_orders(&self) -> Result<Vec<PerpOrder>, DexError> {
self.get("/perps/orders").await
}
/// Cancel an order
pub async fn cancel_order(&self, order_id: &str) -> Result<(), DexError> {
self.delete(&format!("/perps/orders/{}", order_id)).await
}
/// Cancel all orders
pub async fn cancel_all_orders(&self, market: Option<&str>) -> Result<u32, DexError> {
let path = match market {
Some(m) => format!("/perps/orders?market={}", m),
None => "/perps/orders".to_string(),
};
#[derive(serde::Deserialize)]
struct CancelResult {
cancelled: u32,
}
let result: CancelResult = self.delete(&path).await?;
Ok(result.cancelled)
}
/// Get funding payment history
pub async fn get_funding_history(&self, market: &str, limit: u32) -> Result<Vec<FundingPayment>, DexError> {
self.get(&format!("/perps/funding/{}?limit={}", market, limit)).await
}
/// Get current funding rate
pub async fn get_funding_rate(&self, market: &str) -> Result<FundingRateInfo, DexError> {
self.get(&format!("/perps/funding/{}/current", market)).await
}
// Internal methods
async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
use std::sync::atomic::Ordering;
if self.closed.load(Ordering::SeqCst) {
return Err(DexError::ClientClosed);
}
let url = format!("{}{}", self.config.endpoint, path);
let response = self.client
.get(&url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
if !response.status().is_success() {
return Err(DexError::Http {
message: format!("HTTP {}", response.status()),
code: None,
status: Some(response.status().as_u16()),
});
}
response.json().await.map_err(DexError::from)
}
async fn post<T: serde::de::DeserializeOwned>(&self, path: &str, body: serde_json::Value) -> Result<T, DexError> {
use std::sync::atomic::Ordering;
if self.closed.load(Ordering::SeqCst) {
return Err(DexError::ClientClosed);
}
let url = format!("{}{}", self.config.endpoint, path);
let response = self.client
.post(&url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.json(&body)
.send()
.await?;
if !response.status().is_success() {
return Err(DexError::Http {
message: format!("HTTP {}", response.status()),
code: None,
status: Some(response.status().as_u16()),
});
}
response.json().await.map_err(DexError::from)
}
async fn delete<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
use std::sync::atomic::Ordering;
if self.closed.load(Ordering::SeqCst) {
return Err(DexError::ClientClosed);
}
let url = format!("{}{}", self.config.endpoint, path);
let response = self.client
.delete(&url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
if !response.status().is_success() {
return Err(DexError::Http {
message: format!("HTTP {}", response.status()),
code: None,
status: Some(response.status().as_u16()),
});
}
response.json().await.map_err(DexError::from)
}
}

544
sdk/rust/src/dex/types.rs Normal file
View file

@ -0,0 +1,544 @@
//! Synor DEX SDK Types
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
/// DEX SDK Error
#[derive(Error, Debug)]
pub enum DexError {
#[error("HTTP error: {message}")]
Http { message: String, code: Option<String>, status: Option<u16> },
#[error("Client closed")]
ClientClosed,
#[error("WebSocket error: {0}")]
WebSocket(String),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Request error: {0}")]
Request(#[from] reqwest::Error),
}
/// Pool type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PoolType {
ConstantProduct, // x * y = k
Stable, // Optimized for stablecoins
Concentrated, // Uniswap v3 style
}
/// Position side for perpetuals
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PositionSide {
Long,
Short,
}
/// Order type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OrderType {
Market,
Limit,
StopMarket,
StopLimit,
TakeProfit,
TakeProfitLimit,
}
/// Margin type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum MarginType {
#[default]
Cross,
Isolated,
}
/// Time in force
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum TimeInForce {
#[default]
GTC, // Good Till Cancel
IOC, // Immediate Or Cancel
FOK, // Fill Or Kill
GTD, // Good Till Date
}
/// Order status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OrderStatus {
Pending,
Open,
PartiallyFilled,
Filled,
Cancelled,
Expired,
}
/// DEX client configuration
#[derive(Debug, Clone)]
pub struct DexConfig {
pub api_key: String,
pub endpoint: String,
pub ws_endpoint: String,
pub timeout_ms: u64,
pub retries: u32,
pub debug: bool,
}
impl DexConfig {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
endpoint: "https://dex.synor.io/v1".to_string(),
ws_endpoint: "wss://dex.synor.io/v1/ws".to_string(),
timeout_ms: 30000,
retries: 3,
debug: false,
}
}
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
pub fn with_ws_endpoint(mut self, ws_endpoint: impl Into<String>) -> Self {
self.ws_endpoint = ws_endpoint.into();
self
}
pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
self.timeout_ms = timeout_ms;
self
}
pub fn with_retries(mut self, retries: u32) -> Self {
self.retries = retries;
self
}
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
}
/// Token information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Token {
pub address: String,
pub symbol: String,
pub name: String,
pub decimals: u8,
pub total_supply: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub price_usd: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logo_url: Option<String>,
#[serde(default)]
pub verified: bool,
}
/// Liquidity pool
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pool {
pub id: String,
pub token_a: Token,
pub token_b: Token,
pub pool_type: PoolType,
pub reserve_a: String,
pub reserve_b: String,
pub fee: f64,
pub tvl_usd: f64,
pub volume_24h: f64,
pub apr: f64,
pub lp_token_address: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tick_spacing: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sqrt_price: Option<String>,
}
/// Pool filter
#[derive(Debug, Clone, Default)]
pub struct PoolFilter {
pub tokens: Option<Vec<String>>,
pub min_tvl: Option<f64>,
pub min_volume_24h: Option<f64>,
pub verified: Option<bool>,
pub limit: Option<u32>,
pub offset: Option<u32>,
}
/// Swap quote
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Quote {
pub token_in: String,
pub token_out: String,
pub amount_in: String,
pub amount_out: String,
pub price_impact: f64,
pub route: Vec<String>,
pub fee: String,
pub minimum_received: String,
pub expires_at: i64,
}
/// Quote parameters
#[derive(Debug, Clone)]
pub struct QuoteParams {
pub token_in: String,
pub token_out: String,
pub amount_in: String,
pub slippage: Option<f64>,
}
/// Swap parameters
#[derive(Debug, Clone)]
pub struct SwapParams {
pub token_in: String,
pub token_out: String,
pub amount_in: String,
pub min_amount_out: String,
pub deadline: Option<i64>,
pub recipient: Option<String>,
}
/// Swap result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwapResult {
pub transaction_hash: String,
pub amount_in: String,
pub amount_out: String,
pub effective_price: f64,
pub fee_paid: String,
pub route: Vec<String>,
}
/// Add liquidity parameters
#[derive(Debug, Clone)]
pub struct AddLiquidityParams {
pub token_a: String,
pub token_b: String,
pub amount_a: String,
pub amount_b: String,
pub min_amount_a: Option<String>,
pub min_amount_b: Option<String>,
pub deadline: Option<i64>,
pub tick_lower: Option<i32>,
pub tick_upper: Option<i32>,
}
/// Remove liquidity parameters
#[derive(Debug, Clone)]
pub struct RemoveLiquidityParams {
pub pool: String,
pub lp_amount: String,
pub min_amount_a: Option<String>,
pub min_amount_b: Option<String>,
pub deadline: Option<i64>,
}
/// Liquidity result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiquidityResult {
pub transaction_hash: String,
pub amount_a: String,
pub amount_b: String,
pub lp_tokens: String,
pub pool_share: f64,
}
/// LP position
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LPPosition {
pub pool_id: String,
pub lp_tokens: String,
pub token_a_amount: String,
pub token_b_amount: String,
pub value_usd: f64,
pub unclaimed_fees_a: String,
pub unclaimed_fees_b: String,
pub impermanent_loss: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub tick_lower: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tick_upper: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub in_range: Option<bool>,
}
/// Perpetual market
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerpMarket {
pub symbol: String,
pub base_asset: String,
pub quote_asset: String,
pub index_price: f64,
pub mark_price: f64,
pub funding_rate: f64,
pub next_funding_time: i64,
pub open_interest: String,
pub volume_24h: f64,
pub price_change_24h: f64,
pub max_leverage: u32,
pub min_order_size: String,
pub tick_size: f64,
pub maintenance_margin: f64,
pub initial_margin: f64,
}
/// Open position parameters
#[derive(Debug, Clone)]
pub struct OpenPositionParams {
pub market: String,
pub side: PositionSide,
pub size: String,
pub leverage: u32, // 1-100x
pub order_type: OrderType,
pub limit_price: Option<f64>,
pub stop_loss: Option<f64>,
pub take_profit: Option<f64>,
pub margin_type: MarginType,
pub reduce_only: bool,
}
/// Close position parameters
#[derive(Debug, Clone)]
pub struct ClosePositionParams {
pub market: String,
pub size: Option<String>, // None = close entire position
pub order_type: OrderType,
pub limit_price: Option<f64>,
}
/// Modify position parameters
#[derive(Debug, Clone)]
pub struct ModifyPositionParams {
pub position_id: String,
pub new_leverage: Option<u32>,
pub new_margin: Option<String>,
pub new_stop_loss: Option<f64>,
pub new_take_profit: Option<f64>,
}
/// Perpetual position
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerpPosition {
pub id: String,
pub market: String,
pub side: PositionSide,
pub size: String,
pub entry_price: f64,
pub mark_price: f64,
pub liquidation_price: f64,
pub margin: String,
pub leverage: u32,
pub unrealized_pnl: String,
pub realized_pnl: String,
pub margin_ratio: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_loss: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub take_profit: Option<f64>,
#[serde(default)]
pub created_at: i64,
#[serde(default)]
pub updated_at: i64,
}
/// Perpetual order
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerpOrder {
pub id: String,
pub market: String,
pub side: PositionSide,
pub order_type: OrderType,
pub size: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<f64>,
#[serde(default)]
pub filled_size: String,
pub status: OrderStatus,
#[serde(default)]
pub reduce_only: bool,
#[serde(default)]
pub created_at: i64,
}
/// Funding payment
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FundingPayment {
pub market: String,
pub amount: String,
pub rate: f64,
pub position_size: String,
pub timestamp: i64,
}
/// Order book entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBookEntry {
pub price: f64,
pub size: String,
pub orders: u32,
}
/// Order book
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBook {
pub market: String,
pub bids: Vec<OrderBookEntry>,
pub asks: Vec<OrderBookEntry>,
pub timestamp: i64,
}
/// Limit order parameters
#[derive(Debug, Clone)]
pub struct LimitOrderParams {
pub market: String,
pub side: String, // "buy" or "sell"
pub price: f64,
pub size: String,
pub time_in_force: TimeInForce,
pub post_only: bool,
}
/// Limit order
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
pub id: String,
pub market: String,
pub side: String,
pub price: f64,
pub size: String,
pub filled_size: String,
pub status: OrderStatus,
pub time_in_force: TimeInForce,
pub post_only: bool,
pub created_at: i64,
pub updated_at: i64,
}
/// Yield farm
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Farm {
pub id: String,
pub name: String,
pub stake_token: Token,
pub reward_tokens: Vec<Token>,
pub tvl_usd: f64,
pub apr: f64,
pub daily_rewards: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lockup_period: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_stake: Option<String>,
}
/// Stake parameters
#[derive(Debug, Clone)]
pub struct StakeParams {
pub farm: String,
pub amount: String,
}
/// Farm position
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmPosition {
pub farm_id: String,
pub staked_amount: String,
pub pending_rewards: Vec<String>,
pub staked_at: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub unlock_at: Option<i64>,
}
/// OHLCV candle data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OHLCV {
pub timestamp: i64,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
/// Trade history entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeHistory {
pub id: String,
pub market: String,
pub side: String,
pub price: f64,
pub size: String,
pub timestamp: i64,
pub maker: String,
pub taker: String,
}
/// Volume statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolumeStats {
pub volume_24h: f64,
pub volume_7d: f64,
pub volume_30d: f64,
pub trades_24h: u64,
pub unique_traders_24h: u64,
}
/// TVL statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TVLStats {
pub total_tvl: f64,
pub pools_tvl: f64,
pub farms_tvl: f64,
pub perps_tvl: f64,
}
/// Subscription handle
pub struct Subscription {
pub id: String,
pub channel: String,
cancel_tx: Option<tokio::sync::oneshot::Sender<()>>,
}
impl Subscription {
pub fn new(id: String, channel: String, cancel_tx: tokio::sync::oneshot::Sender<()>) -> Self {
Self {
id,
channel,
cancel_tx: Some(cancel_tx),
}
}
pub fn cancel(mut self) {
if let Some(tx) = self.cancel_tx.take() {
let _ = tx.send(());
}
}
}
/// Claim rewards result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaimRewardsResult {
pub amount: String,
pub transaction_hash: String,
}
/// Funding rate info
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FundingRateInfo {
pub rate: f64,
pub next_time: i64,
}

View file

@ -0,0 +1,368 @@
import Foundation
/// Synor DEX SDK Client
///
/// Complete decentralized exchange client with support for:
/// - AMM swaps (constant product, stable, concentrated)
/// - Liquidity provision
/// - Perpetual futures (up to 100x leverage)
/// - Order books (limit orders)
/// - Farming & staking
public class SynorDex {
private let config: DexConfig
private let session: URLSession
private var closed = false
public let perps: PerpsClient
public let orderbook: OrderBookClient
public let farms: FarmsClient
public init(config: DexConfig) {
self.config = config
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = TimeInterval(config.timeout) / 1000
self.session = URLSession(configuration: configuration)
self.perps = PerpsClient()
self.orderbook = OrderBookClient()
self.farms = FarmsClient()
self.perps.dex = self
self.orderbook.dex = self
self.farms.dex = self
}
// MARK: - Token Operations
public func getToken(address: String) async throws -> Token {
try await get("/tokens/\(address)")
}
public func listTokens() async throws -> [Token] {
try await get("/tokens")
}
public func searchTokens(query: String) async throws -> [Token] {
let encoded = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? query
return try await get("/tokens/search?q=\(encoded)")
}
// MARK: - Pool Operations
public func getPool(tokenA: String, tokenB: String) async throws -> Pool {
try await get("/pools/\(tokenA)/\(tokenB)")
}
public func getPoolById(poolId: String) async throws -> Pool {
try await get("/pools/\(poolId)")
}
public func listPools(filter: PoolFilter? = nil) async throws -> [Pool] {
var params: [String] = []
if let f = filter {
if let tokens = f.tokens { params.append("tokens=\(tokens.joined(separator: ","))") }
if let minTvl = f.minTvl { params.append("min_tvl=\(minTvl)") }
if let minVolume = f.minVolume24h { params.append("min_volume=\(minVolume)") }
if let verified = f.verified { params.append("verified=\(verified)") }
if let limit = f.limit { params.append("limit=\(limit)") }
if let offset = f.offset { params.append("offset=\(offset)") }
}
let path = params.isEmpty ? "/pools" : "/pools?\(params.joined(separator: "&"))"
return try await get(path)
}
// MARK: - Swap Operations
public func getQuote(params: QuoteParams) async throws -> Quote {
try await post("/swap/quote", body: [
"token_in": params.tokenIn,
"token_out": params.tokenOut,
"amount_in": String(params.amountIn),
"slippage": params.slippage ?? 0.005
])
}
public func swap(params: SwapParams) async throws -> SwapResult {
let deadline = params.deadline ?? Int(Date().timeIntervalSince1970) + 1200
var body: [String: Any] = [
"token_in": params.tokenIn,
"token_out": params.tokenOut,
"amount_in": String(params.amountIn),
"min_amount_out": String(params.minAmountOut),
"deadline": deadline
]
if let recipient = params.recipient {
body["recipient"] = recipient
}
return try await post("/swap", body: body)
}
// MARK: - Liquidity Operations
public func addLiquidity(params: AddLiquidityParams) async throws -> LiquidityResult {
let deadline = params.deadline ?? Int(Date().timeIntervalSince1970) + 1200
var body: [String: Any] = [
"token_a": params.tokenA,
"token_b": params.tokenB,
"amount_a": String(params.amountA),
"amount_b": String(params.amountB),
"deadline": deadline
]
if let minA = params.minAmountA { body["min_amount_a"] = String(minA) }
if let minB = params.minAmountB { body["min_amount_b"] = String(minB) }
return try await post("/liquidity/add", body: body)
}
public func removeLiquidity(params: RemoveLiquidityParams) async throws -> LiquidityResult {
let deadline = params.deadline ?? Int(Date().timeIntervalSince1970) + 1200
var body: [String: Any] = [
"pool": params.pool,
"lp_amount": String(params.lpAmount),
"deadline": deadline
]
if let minA = params.minAmountA { body["min_amount_a"] = String(minA) }
if let minB = params.minAmountB { body["min_amount_b"] = String(minB) }
return try await post("/liquidity/remove", body: body)
}
public func getMyPositions() async throws -> [LPPosition] {
try await get("/liquidity/positions")
}
// MARK: - Analytics
public func getPriceHistory(pair: String, interval: String, limit: Int = 100) async throws -> [OHLCV] {
try await get("/analytics/candles/\(pair)?interval=\(interval)&limit=\(limit)")
}
public func getTradeHistory(pair: String, limit: Int = 50) async throws -> [TradeHistory] {
try await get("/analytics/trades/\(pair)?limit=\(limit)")
}
public func getVolumeStats() async throws -> VolumeStats {
try await get("/analytics/volume")
}
public func getTVL() async throws -> TVLStats {
try await get("/analytics/tvl")
}
// MARK: - Lifecycle
public func healthCheck() async -> Bool {
do {
let response: HealthResponse = try await get("/health")
return response.status == "healthy"
} catch {
return false
}
}
public func close() {
closed = true
session.invalidateAndCancel()
}
public var isClosed: Bool { closed }
// MARK: - Internal Methods
func get<T: Decodable>(_ path: String) async throws -> T {
try checkClosed()
var request = URLRequest(url: URL(string: config.endpoint + path)!)
request.httpMethod = "GET"
addHeaders(&request)
return try await execute(request)
}
func post<T: Decodable>(_ path: String, body: [String: Any]) async throws -> T {
try checkClosed()
var request = URLRequest(url: URL(string: config.endpoint + path)!)
request.httpMethod = "POST"
request.httpBody = try JSONSerialization.data(withJSONObject: body)
addHeaders(&request)
return try await execute(request)
}
func delete<T: Decodable>(_ path: String) async throws -> T {
try checkClosed()
var request = URLRequest(url: URL(string: config.endpoint + path)!)
request.httpMethod = "DELETE"
addHeaders(&request)
return try await execute(request)
}
private func addHeaders(_ request: inout URLRequest) {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
}
private func execute<T: Decodable>(_ request: URLRequest) async throws -> T {
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw DexError.network("Invalid response")
}
guard (200...299).contains(httpResponse.statusCode) else {
let error = try? JSONDecoder().decode(ErrorResponse.self, from: data)
throw DexError.http(
message: error?.message ?? "HTTP \(httpResponse.statusCode)",
code: error?.code,
status: httpResponse.statusCode
)
}
return try JSONDecoder().decode(T.self, from: data)
}
private func checkClosed() throws {
if closed {
throw DexError.clientClosed
}
}
private struct HealthResponse: Decodable {
let status: String
}
private struct ErrorResponse: Decodable {
let message: String?
let code: String?
}
}
/// DEX client configuration
public struct DexConfig {
public let apiKey: String
public let endpoint: String
public let wsEndpoint: String
public let timeout: Int
public let retries: Int
public let debug: Bool
public init(
apiKey: String,
endpoint: String = "https://dex.synor.io/v1",
wsEndpoint: String = "wss://dex.synor.io/v1/ws",
timeout: Int = 30000,
retries: Int = 3,
debug: Bool = false
) {
self.apiKey = apiKey
self.endpoint = endpoint
self.wsEndpoint = wsEndpoint
self.timeout = timeout
self.retries = retries
self.debug = debug
}
}
/// DEX SDK Error
public enum DexError: Error {
case http(message: String, code: String?, status: Int)
case network(String)
case clientClosed
}
/// Perpetual futures sub-client
public class PerpsClient {
weak var dex: SynorDex?
public func listMarkets() async throws -> [PerpMarket] {
try await dex!.get("/perps/markets")
}
public func getMarket(symbol: String) async throws -> PerpMarket {
try await dex!.get("/perps/markets/\(symbol)")
}
public func openPosition(params: OpenPositionParams) async throws -> PerpPosition {
var body: [String: Any] = [
"market": params.market,
"side": params.side.rawValue,
"size": String(params.size),
"leverage": params.leverage,
"order_type": params.orderType.rawValue,
"margin_type": params.marginType.rawValue,
"reduce_only": params.reduceOnly
]
if let price = params.limitPrice { body["limit_price"] = price }
if let sl = params.stopLoss { body["stop_loss"] = sl }
if let tp = params.takeProfit { body["take_profit"] = tp }
return try await dex!.post("/perps/positions", body: body)
}
public func closePosition(params: ClosePositionParams) async throws -> PerpPosition {
var body: [String: Any] = [
"market": params.market,
"order_type": params.orderType.rawValue
]
if let size = params.size { body["size"] = String(size) }
if let price = params.limitPrice { body["limit_price"] = price }
return try await dex!.post("/perps/positions/close", body: body)
}
public func getPositions() async throws -> [PerpPosition] {
try await dex!.get("/perps/positions")
}
public func getOrders() async throws -> [PerpOrder] {
try await dex!.get("/perps/orders")
}
public func getFundingHistory(market: String, limit: Int = 100) async throws -> [FundingPayment] {
try await dex!.get("/perps/funding/\(market)?limit=\(limit)")
}
}
/// Order book sub-client
public class OrderBookClient {
weak var dex: SynorDex?
public func getOrderBook(market: String, depth: Int = 20) async throws -> OrderBook {
try await dex!.get("/orderbook/\(market)?depth=\(depth)")
}
public func placeLimitOrder(params: LimitOrderParams) async throws -> Order {
try await dex!.post("/orderbook/orders", body: [
"market": params.market,
"side": params.side,
"price": params.price,
"size": String(params.size),
"time_in_force": params.timeInForce.rawValue,
"post_only": params.postOnly
])
}
public func getOpenOrders(market: String? = nil) async throws -> [Order] {
let path = market.map { "/orderbook/orders?market=\($0)" } ?? "/orderbook/orders"
return try await dex!.get(path)
}
}
/// Farms sub-client
public class FarmsClient {
weak var dex: SynorDex?
public func listFarms() async throws -> [Farm] {
try await dex!.get("/farms")
}
public func getFarm(farmId: String) async throws -> Farm {
try await dex!.get("/farms/\(farmId)")
}
public func stake(params: StakeParams) async throws -> FarmPosition {
try await dex!.post("/farms/stake", body: [
"farm": params.farm,
"amount": String(params.amount)
])
}
public func getMyFarmPositions() async throws -> [FarmPosition] {
try await dex!.get("/farms/positions")
}
}

View file

@ -0,0 +1,498 @@
import Foundation
public enum PoolType: String, Codable {
case constantProduct = "constant_product"
case stable = "stable"
case concentrated = "concentrated"
}
public enum PositionSide: String, Codable {
case long = "long"
case short = "short"
}
public enum OrderType: String, Codable {
case market = "market"
case limit = "limit"
case stopMarket = "stop_market"
case stopLimit = "stop_limit"
case takeProfit = "take_profit"
case takeProfitLimit = "take_profit_limit"
}
public enum MarginType: String, Codable {
case cross = "cross"
case isolated = "isolated"
}
public enum TimeInForce: String, Codable {
case GTC, IOC, FOK, GTD
}
public enum OrderStatus: String, Codable {
case pending = "pending"
case open = "open"
case partiallyFilled = "partially_filled"
case filled = "filled"
case cancelled = "cancelled"
case expired = "expired"
}
public struct Token: Codable {
public let address: String
public let symbol: String
public let name: String
public let decimals: Int
public let totalSupply: String
public let priceUsd: Double?
public let logoUrl: String?
public let verified: Bool
enum CodingKeys: String, CodingKey {
case address, symbol, name, decimals, verified
case totalSupply = "total_supply"
case priceUsd = "price_usd"
case logoUrl = "logo_url"
}
}
public struct Pool: Codable {
public let id: String
public let tokenA: Token
public let tokenB: Token
public let poolType: PoolType
public let reserveA: String
public let reserveB: String
public let fee: Double
public let tvlUsd: Double
public let volume24h: Double
public let apr: Double
public let lpTokenAddress: String
public let tickSpacing: Int?
public let sqrtPrice: String?
enum CodingKeys: String, CodingKey {
case id, fee, apr
case tokenA = "token_a"
case tokenB = "token_b"
case poolType = "pool_type"
case reserveA = "reserve_a"
case reserveB = "reserve_b"
case tvlUsd = "tvl_usd"
case volume24h = "volume_24h"
case lpTokenAddress = "lp_token_address"
case tickSpacing = "tick_spacing"
case sqrtPrice = "sqrt_price"
}
}
public struct PoolFilter {
public var tokens: [String]?
public var minTvl: Double?
public var minVolume24h: Double?
public var verified: Bool?
public var limit: Int?
public var offset: Int?
public init() {}
}
public struct Quote: Codable {
public let tokenIn: String
public let tokenOut: String
public let amountIn: String
public let amountOut: String
public let priceImpact: Double
public let route: [String]
public let fee: String
public let minimumReceived: String
public let expiresAt: Int
enum CodingKeys: String, CodingKey {
case route, fee
case tokenIn = "token_in"
case tokenOut = "token_out"
case amountIn = "amount_in"
case amountOut = "amount_out"
case priceImpact = "price_impact"
case minimumReceived = "minimum_received"
case expiresAt = "expires_at"
}
}
public struct QuoteParams {
public let tokenIn: String
public let tokenOut: String
public let amountIn: UInt64
public let slippage: Double?
public init(tokenIn: String, tokenOut: String, amountIn: UInt64, slippage: Double? = nil) {
self.tokenIn = tokenIn
self.tokenOut = tokenOut
self.amountIn = amountIn
self.slippage = slippage
}
}
public struct SwapParams {
public let tokenIn: String
public let tokenOut: String
public let amountIn: UInt64
public let minAmountOut: UInt64
public let deadline: Int?
public let recipient: String?
public init(tokenIn: String, tokenOut: String, amountIn: UInt64, minAmountOut: UInt64, deadline: Int? = nil, recipient: String? = nil) {
self.tokenIn = tokenIn
self.tokenOut = tokenOut
self.amountIn = amountIn
self.minAmountOut = minAmountOut
self.deadline = deadline
self.recipient = recipient
}
}
public struct SwapResult: Codable {
public let transactionHash: String
public let amountIn: String
public let amountOut: String
public let effectivePrice: Double
public let feePaid: String
public let route: [String]
enum CodingKeys: String, CodingKey {
case route
case transactionHash = "transaction_hash"
case amountIn = "amount_in"
case amountOut = "amount_out"
case effectivePrice = "effective_price"
case feePaid = "fee_paid"
}
}
public struct AddLiquidityParams {
public let tokenA: String
public let tokenB: String
public let amountA: UInt64
public let amountB: UInt64
public let minAmountA: UInt64?
public let minAmountB: UInt64?
public let deadline: Int?
public init(tokenA: String, tokenB: String, amountA: UInt64, amountB: UInt64, minAmountA: UInt64? = nil, minAmountB: UInt64? = nil, deadline: Int? = nil) {
self.tokenA = tokenA
self.tokenB = tokenB
self.amountA = amountA
self.amountB = amountB
self.minAmountA = minAmountA
self.minAmountB = minAmountB
self.deadline = deadline
}
}
public struct RemoveLiquidityParams {
public let pool: String
public let lpAmount: UInt64
public let minAmountA: UInt64?
public let minAmountB: UInt64?
public let deadline: Int?
public init(pool: String, lpAmount: UInt64, minAmountA: UInt64? = nil, minAmountB: UInt64? = nil, deadline: Int? = nil) {
self.pool = pool
self.lpAmount = lpAmount
self.minAmountA = minAmountA
self.minAmountB = minAmountB
self.deadline = deadline
}
}
public struct LiquidityResult: Codable {
public let transactionHash: String
public let amountA: String
public let amountB: String
public let lpTokens: String
public let poolShare: Double
enum CodingKeys: String, CodingKey {
case transactionHash = "transaction_hash"
case amountA = "amount_a"
case amountB = "amount_b"
case lpTokens = "lp_tokens"
case poolShare = "pool_share"
}
}
public struct LPPosition: Codable {
public let poolId: String
public let lpTokens: String
public let tokenAAmount: String
public let tokenBAmount: String
public let valueUsd: Double
public let unclaimedFeesA: String
public let unclaimedFeesB: String
public let impermanentLoss: Double
enum CodingKeys: String, CodingKey {
case poolId = "pool_id"
case lpTokens = "lp_tokens"
case tokenAAmount = "token_a_amount"
case tokenBAmount = "token_b_amount"
case valueUsd = "value_usd"
case unclaimedFeesA = "unclaimed_fees_a"
case unclaimedFeesB = "unclaimed_fees_b"
case impermanentLoss = "impermanent_loss"
}
}
public struct PerpMarket: Codable {
public let symbol: String
public let baseAsset: String
public let quoteAsset: String
public let indexPrice: Double
public let markPrice: Double
public let fundingRate: Double
public let nextFundingTime: Int
public let openInterest: String
public let volume24h: Double
public let maxLeverage: Int
enum CodingKeys: String, CodingKey {
case symbol
case baseAsset = "base_asset"
case quoteAsset = "quote_asset"
case indexPrice = "index_price"
case markPrice = "mark_price"
case fundingRate = "funding_rate"
case nextFundingTime = "next_funding_time"
case openInterest = "open_interest"
case volume24h = "volume_24h"
case maxLeverage = "max_leverage"
}
}
public struct OpenPositionParams {
public let market: String
public let side: PositionSide
public let size: UInt64
public let leverage: Int
public let orderType: OrderType
public let limitPrice: Double?
public let stopLoss: Double?
public let takeProfit: Double?
public let marginType: MarginType
public let reduceOnly: Bool
public init(market: String, side: PositionSide, size: UInt64, leverage: Int, orderType: OrderType, limitPrice: Double? = nil, stopLoss: Double? = nil, takeProfit: Double? = nil, marginType: MarginType = .cross, reduceOnly: Bool = false) {
self.market = market
self.side = side
self.size = size
self.leverage = leverage
self.orderType = orderType
self.limitPrice = limitPrice
self.stopLoss = stopLoss
self.takeProfit = takeProfit
self.marginType = marginType
self.reduceOnly = reduceOnly
}
}
public struct ClosePositionParams {
public let market: String
public let size: UInt64?
public let orderType: OrderType
public let limitPrice: Double?
public init(market: String, size: UInt64? = nil, orderType: OrderType = .market, limitPrice: Double? = nil) {
self.market = market
self.size = size
self.orderType = orderType
self.limitPrice = limitPrice
}
}
public struct PerpPosition: Codable {
public let id: String
public let market: String
public let side: PositionSide
public let size: String
public let entryPrice: Double
public let markPrice: Double
public let liquidationPrice: Double
public let margin: String
public let leverage: Int
public let unrealizedPnl: String
enum CodingKeys: String, CodingKey {
case id, market, side, size, margin, leverage
case entryPrice = "entry_price"
case markPrice = "mark_price"
case liquidationPrice = "liquidation_price"
case unrealizedPnl = "unrealized_pnl"
}
}
public struct PerpOrder: Codable {
public let id: String
public let market: String
public let side: PositionSide
public let orderType: OrderType
public let size: String
public let price: Double?
public let status: OrderStatus
enum CodingKeys: String, CodingKey {
case id, market, side, size, price, status
case orderType = "order_type"
}
}
public struct FundingPayment: Codable {
public let market: String
public let amount: String
public let rate: Double
public let positionSize: String
public let timestamp: Int
enum CodingKeys: String, CodingKey {
case market, amount, rate, timestamp
case positionSize = "position_size"
}
}
public struct OrderBookEntry: Codable {
public let price: Double
public let size: String
public let orders: Int
}
public struct OrderBook: Codable {
public let market: String
public let bids: [OrderBookEntry]
public let asks: [OrderBookEntry]
public let timestamp: Int
}
public struct LimitOrderParams {
public let market: String
public let side: String
public let price: Double
public let size: UInt64
public let timeInForce: TimeInForce
public let postOnly: Bool
public init(market: String, side: String, price: Double, size: UInt64, timeInForce: TimeInForce = .GTC, postOnly: Bool = false) {
self.market = market
self.side = side
self.price = price
self.size = size
self.timeInForce = timeInForce
self.postOnly = postOnly
}
}
public struct Order: Codable {
public let id: String
public let market: String
public let side: String
public let price: Double
public let size: String
public let filledSize: String
public let status: OrderStatus
public let timeInForce: TimeInForce
public let postOnly: Bool
enum CodingKeys: String, CodingKey {
case id, market, side, price, size, status
case filledSize = "filled_size"
case timeInForce = "time_in_force"
case postOnly = "post_only"
}
}
public struct Farm: Codable {
public let id: String
public let name: String
public let stakeToken: Token
public let rewardTokens: [Token]
public let tvlUsd: Double
public let apr: Double
enum CodingKeys: String, CodingKey {
case id, name, apr
case stakeToken = "stake_token"
case rewardTokens = "reward_tokens"
case tvlUsd = "tvl_usd"
}
}
public struct StakeParams {
public let farm: String
public let amount: UInt64
public init(farm: String, amount: UInt64) {
self.farm = farm
self.amount = amount
}
}
public struct FarmPosition: Codable {
public let farmId: String
public let stakedAmount: String
public let pendingRewards: [String]
public let stakedAt: Int
enum CodingKeys: String, CodingKey {
case farmId = "farm_id"
case stakedAmount = "staked_amount"
case pendingRewards = "pending_rewards"
case stakedAt = "staked_at"
}
}
public struct OHLCV: Codable {
public let timestamp: Int
public let open: Double
public let high: Double
public let low: Double
public let close: Double
public let volume: Double
}
public struct TradeHistory: Codable {
public let id: String
public let market: String
public let side: String
public let price: Double
public let size: String
public let timestamp: Int
public let maker: String
public let taker: String
}
public struct VolumeStats: Codable {
public let volume24h: Double
public let volume7d: Double
public let volume30d: Double
public let trades24h: Int
enum CodingKeys: String, CodingKey {
case trades24h = "trades_24h"
case volume24h = "volume_24h"
case volume7d = "volume_7d"
case volume30d = "volume_30d"
}
}
public struct TVLStats: Codable {
public let totalTvl: Double
public let poolsTvl: Double
public let farmsTvl: Double
public let perpsTvl: Double
enum CodingKeys: String, CodingKey {
case totalTvl = "total_tvl"
case poolsTvl = "pools_tvl"
case farmsTvl = "farms_tvl"
case perpsTvl = "perps_tvl"
}
}