synor/sdk/go/ibc/client.go
Gulshan Yadav 97add23062 feat(sdk): implement IBC SDK for all 12 languages
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
2026-01-28 12:53:46 +05:30

385 lines
11 KiB
Go

// Package ibc provides the Synor IBC SDK for Go.
package ibc
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// SynorIbc is the main IBC client
type SynorIbc struct {
config Config
client *http.Client
closed bool
// Sub-clients
Clients *LightClientClient
Connections *ConnectionsClient
Channels *ChannelsClient
Packets *PacketsClient
Transfer *TransferClient
Swaps *SwapsClient
}
// NewSynorIbc creates a new IBC client
func NewSynorIbc(config Config) *SynorIbc {
ibc := &SynorIbc{
config: config,
client: &http.Client{Timeout: config.Timeout},
}
ibc.Clients = &LightClientClient{ibc: ibc}
ibc.Connections = &ConnectionsClient{ibc: ibc}
ibc.Channels = &ChannelsClient{ibc: ibc}
ibc.Packets = &PacketsClient{ibc: ibc}
ibc.Transfer = &TransferClient{ibc: ibc}
ibc.Swaps = &SwapsClient{ibc: ibc}
return ibc
}
// ChainID returns the chain ID
func (c *SynorIbc) ChainID() string {
return c.config.ChainID
}
// GetChainInfo returns chain information
func (c *SynorIbc) GetChainInfo(ctx context.Context) (map[string]interface{}, error) {
var result map[string]interface{}
err := c.get(ctx, "/chain", &result)
return result, err
}
// GetHeight returns current height
func (c *SynorIbc) GetHeight(ctx context.Context) (Height, error) {
var result Height
err := c.get(ctx, "/chain/height", &result)
return result, err
}
// HealthCheck performs a health check
func (c *SynorIbc) HealthCheck(ctx context.Context) bool {
var result map[string]string
if err := c.get(ctx, "/health", &result); err != nil {
return false
}
return result["status"] == "healthy"
}
// Close closes the client
func (c *SynorIbc) Close() {
c.closed = true
}
// IsClosed returns whether client is closed
func (c *SynorIbc) IsClosed() bool {
return c.closed
}
func (c *SynorIbc) get(ctx context.Context, path string, result interface{}) error {
return c.request(ctx, "GET", path, nil, result)
}
func (c *SynorIbc) post(ctx context.Context, path string, body, result interface{}) error {
return c.request(ctx, "POST", path, body, result)
}
func (c *SynorIbc) delete(ctx context.Context, path string, result interface{}) error {
return c.request(ctx, "DELETE", path, nil, result)
}
func (c *SynorIbc) request(ctx context.Context, method, path string, body, result interface{}) error {
if c.closed {
return &IbcError{Message: "Client has been closed", Code: "CLIENT_CLOSED"}
}
var bodyReader io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return err
}
bodyReader = bytes.NewReader(data)
}
url := c.config.Endpoint + path
var lastErr error
for attempt := 0; attempt <= c.config.Retries; attempt++ {
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
req.Header.Set("X-SDK-Version", "go/0.1.0")
req.Header.Set("X-Chain-Id", c.config.ChainID)
resp, err := c.client.Do(req)
if err != nil {
lastErr = err
time.Sleep(time.Duration(1<<attempt) * 100 * time.Millisecond)
continue
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
var errResp map[string]interface{}
json.NewDecoder(resp.Body).Decode(&errResp)
msg := fmt.Sprintf("HTTP %d", resp.StatusCode)
if m, ok := errResp["message"].(string); ok {
msg = m
}
code := ""
if c, ok := errResp["code"].(string); ok {
code = c
}
return &IbcError{Message: msg, Code: code, Status: resp.StatusCode}
}
if result != nil {
return json.NewDecoder(resp.Body).Decode(result)
}
return nil
}
return lastErr
}
// LightClientClient handles light client operations
type LightClientClient struct {
ibc *SynorIbc
}
// Create creates a new light client
func (c *LightClientClient) Create(ctx context.Context, clientType ClientType, clientState ClientState, consensusState ConsensusState) (ClientId, error) {
var result struct {
ClientID string `json:"client_id"`
}
err := c.ibc.post(ctx, "/clients", map[string]interface{}{
"client_type": clientType,
"client_state": clientState,
"consensus_state": consensusState,
}, &result)
return ClientId{ID: result.ClientID}, err
}
// Update updates a light client
func (c *LightClientClient) Update(ctx context.Context, clientId ClientId, header Header) (Height, error) {
var result Height
err := c.ibc.post(ctx, "/clients/"+clientId.ID+"/update", map[string]interface{}{
"header": header,
}, &result)
return result, err
}
// GetState returns client state
func (c *LightClientClient) GetState(ctx context.Context, clientId ClientId) (ClientState, error) {
var result ClientState
err := c.ibc.get(ctx, "/clients/"+clientId.ID+"/state", &result)
return result, err
}
// List returns all clients
func (c *LightClientClient) List(ctx context.Context) ([]map[string]interface{}, error) {
var result []map[string]interface{}
err := c.ibc.get(ctx, "/clients", &result)
return result, err
}
// ConnectionsClient handles connection operations
type ConnectionsClient struct {
ibc *SynorIbc
}
// OpenInit initializes a connection
func (c *ConnectionsClient) OpenInit(ctx context.Context, clientId, counterpartyClientId ClientId) (ConnectionId, error) {
var result struct {
ConnectionID string `json:"connection_id"`
}
err := c.ibc.post(ctx, "/connections/init", map[string]interface{}{
"client_id": clientId.ID,
"counterparty_client_id": counterpartyClientId.ID,
}, &result)
return ConnectionId{ID: result.ConnectionID}, err
}
// Get returns a connection
func (c *ConnectionsClient) Get(ctx context.Context, connectionId ConnectionId) (ConnectionEnd, error) {
var result ConnectionEnd
err := c.ibc.get(ctx, "/connections/"+connectionId.ID, &result)
return result, err
}
// List returns all connections
func (c *ConnectionsClient) List(ctx context.Context) ([]map[string]interface{}, error) {
var result []map[string]interface{}
err := c.ibc.get(ctx, "/connections", &result)
return result, err
}
// ChannelsClient handles channel operations
type ChannelsClient struct {
ibc *SynorIbc
}
// BindPort binds a port
func (c *ChannelsClient) BindPort(ctx context.Context, portId PortId, module string) error {
return c.ibc.post(ctx, "/ports/bind", map[string]interface{}{
"port_id": portId.ID,
"module": module,
}, nil)
}
// OpenInit initializes a channel
func (c *ChannelsClient) OpenInit(ctx context.Context, portId PortId, ordering ChannelOrder, connectionId ConnectionId, counterpartyPort PortId, version string) (ChannelId, error) {
var result struct {
ChannelID string `json:"channel_id"`
}
err := c.ibc.post(ctx, "/channels/init", map[string]interface{}{
"port_id": portId.ID,
"ordering": ordering,
"connection_id": connectionId.ID,
"counterparty_port": counterpartyPort.ID,
"version": version,
}, &result)
return ChannelId{ID: result.ChannelID}, err
}
// Get returns a channel
func (c *ChannelsClient) Get(ctx context.Context, portId PortId, channelId ChannelId) (Channel, error) {
var result Channel
err := c.ibc.get(ctx, "/channels/"+portId.ID+"/"+channelId.ID, &result)
return result, err
}
// List returns all channels
func (c *ChannelsClient) List(ctx context.Context) ([]map[string]interface{}, error) {
var result []map[string]interface{}
err := c.ibc.get(ctx, "/channels", &result)
return result, err
}
// PacketsClient handles packet operations
type PacketsClient struct {
ibc *SynorIbc
}
// Send sends a packet
func (c *PacketsClient) Send(ctx context.Context, sourcePort PortId, sourceChannel ChannelId, data []byte, timeout Timeout) (map[string]interface{}, error) {
var result map[string]interface{}
err := c.ibc.post(ctx, "/packets/send", map[string]interface{}{
"source_port": sourcePort.ID,
"source_channel": sourceChannel.ID,
"data": base64.StdEncoding.EncodeToString(data),
"timeout_height": timeout.Height,
"timeout_timestamp": timeout.Timestamp,
}, &result)
return result, err
}
// TransferClient handles ICS-20 transfers
type TransferClient struct {
ibc *SynorIbc
}
// Transfer sends tokens to another chain
func (c *TransferClient) Transfer(ctx context.Context, sourcePort, sourceChannel, denom, amount, sender, receiver string, timeout *Timeout, memo string) (map[string]interface{}, error) {
body := map[string]interface{}{
"source_port": sourcePort,
"source_channel": sourceChannel,
"token": map[string]string{
"denom": denom,
"amount": amount,
},
"sender": sender,
"receiver": receiver,
"memo": memo,
}
if timeout != nil {
body["timeout_height"] = timeout.Height
body["timeout_timestamp"] = timeout.Timestamp
}
var result map[string]interface{}
err := c.ibc.post(ctx, "/transfer", body, &result)
return result, err
}
// GetDenomTrace returns denom trace
func (c *TransferClient) GetDenomTrace(ctx context.Context, ibcDenom string) (map[string]string, error) {
var result map[string]string
err := c.ibc.get(ctx, "/transfer/denom_trace/"+ibcDenom, &result)
return result, err
}
// SwapsClient handles atomic swap operations
type SwapsClient struct {
ibc *SynorIbc
}
// Initiate starts an atomic swap
func (c *SwapsClient) Initiate(ctx context.Context, responder string, initiatorAsset, responderAsset SwapAsset) (map[string]interface{}, error) {
var result map[string]interface{}
err := c.ibc.post(ctx, "/swaps/initiate", map[string]interface{}{
"responder": responder,
"initiator_asset": initiatorAsset,
"responder_asset": responderAsset,
}, &result)
return result, err
}
// Lock locks the initiator's tokens
func (c *SwapsClient) Lock(ctx context.Context, swapId SwapId) error {
return c.ibc.post(ctx, "/swaps/"+swapId.ID+"/lock", nil, nil)
}
// Respond responds to a swap
func (c *SwapsClient) Respond(ctx context.Context, swapId SwapId, asset SwapAsset) (Htlc, error) {
var result Htlc
err := c.ibc.post(ctx, "/swaps/"+swapId.ID+"/respond", map[string]interface{}{
"asset": asset,
}, &result)
return result, err
}
// Claim claims tokens with secret
func (c *SwapsClient) Claim(ctx context.Context, swapId SwapId, secret []byte) (map[string]interface{}, error) {
var result map[string]interface{}
err := c.ibc.post(ctx, "/swaps/"+swapId.ID+"/claim", map[string]interface{}{
"secret": base64.StdEncoding.EncodeToString(secret),
}, &result)
return result, err
}
// Refund refunds an expired swap
func (c *SwapsClient) Refund(ctx context.Context, swapId SwapId) (map[string]interface{}, error) {
var result map[string]interface{}
err := c.ibc.post(ctx, "/swaps/"+swapId.ID+"/refund", nil, &result)
return result, err
}
// Get returns a swap
func (c *SwapsClient) Get(ctx context.Context, swapId SwapId) (AtomicSwap, error) {
var result AtomicSwap
err := c.ibc.get(ctx, "/swaps/"+swapId.ID, &result)
return result, err
}
// ListActive returns active swaps
func (c *SwapsClient) ListActive(ctx context.Context) ([]AtomicSwap, error) {
var result []AtomicSwap
err := c.ibc.get(ctx, "/swaps/active", &result)
return result, err
}
// VerifySecret verifies a hashlock with secret
func (c *SwapsClient) VerifySecret(hashlock Hashlock, secret []byte) bool {
hash := sha256.Sum256(secret)
return bytes.Equal(hashlock.Hash, hash[:])
}