Implement Inter-Blockchain Communication (IBC) SDK with full ICS protocol support across all 12 programming languages: - JavaScript/TypeScript, Python, Go, Rust - Java, Kotlin, Swift, Flutter/Dart - C, C++, C#/.NET, Ruby Features: - Light client management (Tendermint, Solo Machine, WASM) - Connection handshake (4-way: Init, Try, Ack, Confirm) - Channel management with ordered/unordered support - ICS-20 fungible token transfers - HTLC atomic swaps with hashlock (SHA256) and timelock - Packet relay with timeout handling
469 lines
11 KiB
C++
469 lines
11 KiB
C++
/**
|
|
* @file ibc.hpp
|
|
* @brief Synor IBC SDK for C++
|
|
*
|
|
* Inter-Blockchain Communication (IBC) protocol for cross-chain interoperability.
|
|
*/
|
|
|
|
#ifndef SYNOR_IBC_HPP
|
|
#define SYNOR_IBC_HPP
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <optional>
|
|
#include <memory>
|
|
#include <cstdint>
|
|
#include <future>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
namespace synor {
|
|
namespace ibc {
|
|
|
|
using json = nlohmann::json;
|
|
|
|
/**
|
|
* IBC Height representation
|
|
*/
|
|
struct Height {
|
|
uint64_t revision_number = 0;
|
|
uint64_t revision_height = 1;
|
|
|
|
bool is_zero() const {
|
|
return revision_number == 0 && revision_height == 0;
|
|
}
|
|
|
|
Height increment() const {
|
|
return Height{revision_number, revision_height + 1};
|
|
}
|
|
|
|
static Height from_json(const json& j) {
|
|
return Height{
|
|
j.value("revision_number", uint64_t(0)),
|
|
j.value("revision_height", uint64_t(1))
|
|
};
|
|
}
|
|
|
|
json to_json() const {
|
|
return {
|
|
{"revision_number", revision_number},
|
|
{"revision_height", revision_height}
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Light client types
|
|
*/
|
|
enum class ClientType {
|
|
Tendermint,
|
|
SoloMachine,
|
|
Localhost,
|
|
Wasm
|
|
};
|
|
|
|
inline std::string to_string(ClientType type) {
|
|
switch (type) {
|
|
case ClientType::Tendermint: return "tendermint";
|
|
case ClientType::SoloMachine: return "solo_machine";
|
|
case ClientType::Localhost: return "localhost";
|
|
case ClientType::Wasm: return "wasm";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
/**
|
|
* Trust level configuration
|
|
*/
|
|
struct TrustLevel {
|
|
int numerator = 1;
|
|
int denominator = 3;
|
|
|
|
static TrustLevel from_json(const json& j) {
|
|
return TrustLevel{
|
|
j.value("numerator", 1),
|
|
j.value("denominator", 3)
|
|
};
|
|
}
|
|
|
|
json to_json() const {
|
|
return {{"numerator", numerator}, {"denominator", denominator}};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Light client state
|
|
*/
|
|
struct ClientState {
|
|
std::string chain_id;
|
|
TrustLevel trust_level;
|
|
uint64_t trusting_period;
|
|
uint64_t unbonding_period;
|
|
uint64_t max_clock_drift;
|
|
Height latest_height;
|
|
std::optional<Height> frozen_height;
|
|
|
|
static ClientState from_json(const json& j) {
|
|
ClientState state;
|
|
state.chain_id = j.at("chain_id").get<std::string>();
|
|
state.trust_level = TrustLevel::from_json(j.at("trust_level"));
|
|
state.trusting_period = j.at("trusting_period").get<uint64_t>();
|
|
state.unbonding_period = j.at("unbonding_period").get<uint64_t>();
|
|
state.max_clock_drift = j.at("max_clock_drift").get<uint64_t>();
|
|
state.latest_height = Height::from_json(j.at("latest_height"));
|
|
if (j.contains("frozen_height") && !j["frozen_height"].is_null()) {
|
|
state.frozen_height = Height::from_json(j["frozen_height"]);
|
|
}
|
|
return state;
|
|
}
|
|
|
|
json to_json() const {
|
|
json j = {
|
|
{"chain_id", chain_id},
|
|
{"trust_level", trust_level.to_json()},
|
|
{"trusting_period", trusting_period},
|
|
{"unbonding_period", unbonding_period},
|
|
{"max_clock_drift", max_clock_drift},
|
|
{"latest_height", latest_height.to_json()}
|
|
};
|
|
if (frozen_height) {
|
|
j["frozen_height"] = frozen_height->to_json();
|
|
}
|
|
return j;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Connection state
|
|
*/
|
|
enum class ConnectionState {
|
|
Uninitialized,
|
|
Init,
|
|
TryOpen,
|
|
Open
|
|
};
|
|
|
|
/**
|
|
* Channel ordering
|
|
*/
|
|
enum class ChannelOrder {
|
|
Unordered,
|
|
Ordered
|
|
};
|
|
|
|
inline std::string to_string(ChannelOrder order) {
|
|
return order == ChannelOrder::Ordered ? "ordered" : "unordered";
|
|
}
|
|
|
|
/**
|
|
* Channel state
|
|
*/
|
|
enum class ChannelState {
|
|
Uninitialized,
|
|
Init,
|
|
TryOpen,
|
|
Open,
|
|
Closed
|
|
};
|
|
|
|
/**
|
|
* Timeout information
|
|
*/
|
|
struct Timeout {
|
|
Height height;
|
|
uint64_t timestamp = 0;
|
|
|
|
static Timeout from_height(uint64_t h) {
|
|
return Timeout{{0, h}, 0};
|
|
}
|
|
|
|
static Timeout from_timestamp(uint64_t ts) {
|
|
return Timeout{{}, ts};
|
|
}
|
|
|
|
json to_json() const {
|
|
return {
|
|
{"height", height.to_json()},
|
|
{"timestamp", std::to_string(timestamp)}
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Transfer packet data (ICS-20)
|
|
*/
|
|
struct FungibleTokenPacketData {
|
|
std::string denom;
|
|
std::string amount;
|
|
std::string sender;
|
|
std::string receiver;
|
|
std::string memo;
|
|
|
|
bool is_native() const {
|
|
return denom.find('/') == std::string::npos;
|
|
}
|
|
|
|
static FungibleTokenPacketData from_json(const json& j) {
|
|
return FungibleTokenPacketData{
|
|
j.at("denom").get<std::string>(),
|
|
j.at("amount").get<std::string>(),
|
|
j.at("sender").get<std::string>(),
|
|
j.at("receiver").get<std::string>(),
|
|
j.value("memo", "")
|
|
};
|
|
}
|
|
|
|
json to_json() const {
|
|
return {
|
|
{"denom", denom},
|
|
{"amount", amount},
|
|
{"sender", sender},
|
|
{"receiver", receiver},
|
|
{"memo", memo}
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Swap state
|
|
*/
|
|
enum class SwapState {
|
|
Pending,
|
|
Locked,
|
|
Completed,
|
|
Refunded,
|
|
Expired,
|
|
Cancelled
|
|
};
|
|
|
|
inline SwapState swap_state_from_string(const std::string& s) {
|
|
if (s == "pending") return SwapState::Pending;
|
|
if (s == "locked") return SwapState::Locked;
|
|
if (s == "completed") return SwapState::Completed;
|
|
if (s == "refunded") return SwapState::Refunded;
|
|
if (s == "expired") return SwapState::Expired;
|
|
return SwapState::Cancelled;
|
|
}
|
|
|
|
/**
|
|
* Atomic swap
|
|
*/
|
|
struct AtomicSwap {
|
|
std::string swap_id;
|
|
SwapState state;
|
|
json initiator_htlc;
|
|
std::optional<json> responder_htlc;
|
|
|
|
static AtomicSwap from_json(const json& j) {
|
|
AtomicSwap swap;
|
|
swap.swap_id = j.at("swap_id").at("id").get<std::string>();
|
|
swap.state = swap_state_from_string(j.at("state").get<std::string>());
|
|
swap.initiator_htlc = j.at("initiator_htlc");
|
|
if (j.contains("responder_htlc") && !j["responder_htlc"].is_null()) {
|
|
swap.responder_htlc = j["responder_htlc"];
|
|
}
|
|
return swap;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* IBC SDK configuration
|
|
*/
|
|
struct IbcConfig {
|
|
std::string api_key;
|
|
std::string endpoint = "https://ibc.synor.io/v1";
|
|
std::string ws_endpoint = "wss://ibc.synor.io/v1/ws";
|
|
int timeout = 30;
|
|
int retries = 3;
|
|
std::string chain_id = "synor-1";
|
|
bool debug = false;
|
|
};
|
|
|
|
/**
|
|
* IBC exception
|
|
*/
|
|
class IbcException : public std::exception {
|
|
public:
|
|
IbcException(const std::string& message,
|
|
const std::string& code = "",
|
|
int status = 0)
|
|
: message_(message), code_(code), status_(status) {}
|
|
|
|
const char* what() const noexcept override { return message_.c_str(); }
|
|
const std::string& code() const { return code_; }
|
|
int status() const { return status_; }
|
|
|
|
private:
|
|
std::string message_;
|
|
std::string code_;
|
|
int status_;
|
|
};
|
|
|
|
// Forward declarations
|
|
class SynorIbc;
|
|
|
|
/**
|
|
* Light client sub-client
|
|
*/
|
|
class LightClientClient {
|
|
public:
|
|
explicit LightClientClient(SynorIbc& ibc) : ibc_(ibc) {}
|
|
|
|
std::future<std::string> create(ClientType client_type,
|
|
const ClientState& client_state,
|
|
const json& consensus_state);
|
|
|
|
std::future<ClientState> get_state(const std::string& client_id);
|
|
|
|
std::future<std::vector<json>> list();
|
|
|
|
private:
|
|
SynorIbc& ibc_;
|
|
};
|
|
|
|
/**
|
|
* Connections sub-client
|
|
*/
|
|
class ConnectionsClient {
|
|
public:
|
|
explicit ConnectionsClient(SynorIbc& ibc) : ibc_(ibc) {}
|
|
|
|
std::future<std::string> open_init(const std::string& client_id,
|
|
const std::string& counterparty_client_id);
|
|
|
|
std::future<json> get(const std::string& connection_id);
|
|
|
|
std::future<std::vector<json>> list();
|
|
|
|
private:
|
|
SynorIbc& ibc_;
|
|
};
|
|
|
|
/**
|
|
* Channels sub-client
|
|
*/
|
|
class ChannelsClient {
|
|
public:
|
|
explicit ChannelsClient(SynorIbc& ibc) : ibc_(ibc) {}
|
|
|
|
std::future<void> bind_port(const std::string& port_id, const std::string& module);
|
|
|
|
std::future<std::string> open_init(const std::string& port_id,
|
|
ChannelOrder ordering,
|
|
const std::string& connection_id,
|
|
const std::string& counterparty_port,
|
|
const std::string& version);
|
|
|
|
std::future<json> get(const std::string& port_id, const std::string& channel_id);
|
|
|
|
std::future<std::vector<json>> list();
|
|
|
|
private:
|
|
SynorIbc& ibc_;
|
|
};
|
|
|
|
/**
|
|
* Transfer sub-client (ICS-20)
|
|
*/
|
|
class TransferClient {
|
|
public:
|
|
explicit TransferClient(SynorIbc& ibc) : ibc_(ibc) {}
|
|
|
|
std::future<json> transfer(const std::string& source_port,
|
|
const std::string& source_channel,
|
|
const std::string& denom,
|
|
const std::string& amount,
|
|
const std::string& sender,
|
|
const std::string& receiver,
|
|
std::optional<Timeout> timeout = std::nullopt,
|
|
std::optional<std::string> memo = std::nullopt);
|
|
|
|
std::future<json> get_denom_trace(const std::string& ibc_denom);
|
|
|
|
private:
|
|
SynorIbc& ibc_;
|
|
};
|
|
|
|
/**
|
|
* Swaps sub-client (HTLC)
|
|
*/
|
|
class SwapsClient {
|
|
public:
|
|
explicit SwapsClient(SynorIbc& ibc) : ibc_(ibc) {}
|
|
|
|
std::future<json> initiate(const std::string& responder,
|
|
const json& initiator_asset,
|
|
const json& responder_asset);
|
|
|
|
std::future<void> lock(const std::string& swap_id);
|
|
|
|
std::future<json> respond(const std::string& swap_id, const json& asset);
|
|
|
|
std::future<json> claim(const std::string& swap_id,
|
|
const std::vector<uint8_t>& secret);
|
|
|
|
std::future<json> refund(const std::string& swap_id);
|
|
|
|
std::future<AtomicSwap> get(const std::string& swap_id);
|
|
|
|
std::future<std::vector<AtomicSwap>> list_active();
|
|
|
|
private:
|
|
SynorIbc& ibc_;
|
|
};
|
|
|
|
/**
|
|
* Main IBC client
|
|
*/
|
|
class SynorIbc {
|
|
public:
|
|
explicit SynorIbc(const IbcConfig& config);
|
|
~SynorIbc();
|
|
|
|
// Non-copyable
|
|
SynorIbc(const SynorIbc&) = delete;
|
|
SynorIbc& operator=(const SynorIbc&) = delete;
|
|
|
|
// Movable
|
|
SynorIbc(SynorIbc&&) noexcept;
|
|
SynorIbc& operator=(SynorIbc&&) noexcept;
|
|
|
|
const std::string& chain_id() const { return config_.chain_id; }
|
|
|
|
std::future<json> get_chain_info();
|
|
std::future<Height> get_height();
|
|
std::future<bool> health_check();
|
|
|
|
void close();
|
|
bool is_closed() const { return closed_; }
|
|
|
|
LightClientClient& clients() { return *clients_; }
|
|
ConnectionsClient& connections() { return *connections_; }
|
|
ChannelsClient& channels() { return *channels_; }
|
|
TransferClient& transfer() { return *transfer_; }
|
|
SwapsClient& swaps() { return *swaps_; }
|
|
|
|
// Internal HTTP methods (used by sub-clients)
|
|
std::future<json> get(const std::string& path);
|
|
std::future<json> post(const std::string& path, const json& body);
|
|
|
|
private:
|
|
void check_closed() const;
|
|
|
|
IbcConfig config_;
|
|
bool closed_ = false;
|
|
|
|
std::unique_ptr<LightClientClient> clients_;
|
|
std::unique_ptr<ConnectionsClient> connections_;
|
|
std::unique_ptr<ChannelsClient> channels_;
|
|
std::unique_ptr<TransferClient> transfer_;
|
|
std::unique_ptr<SwapsClient> swaps_;
|
|
|
|
// HTTP client implementation (pimpl)
|
|
class Impl;
|
|
std::unique_ptr<Impl> impl_;
|
|
};
|
|
|
|
} // namespace ibc
|
|
} // namespace synor
|
|
|
|
#endif // SYNOR_IBC_HPP
|