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
278 lines
7.5 KiB
Dart
278 lines
7.5 KiB
Dart
/// Synor IBC SDK for Flutter/Dart
|
|
///
|
|
/// Inter-Blockchain Communication (IBC) protocol for cross-chain interoperability.
|
|
library synor_ibc;
|
|
|
|
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
import 'types.dart';
|
|
|
|
export 'types.dart';
|
|
|
|
/// Main IBC client
|
|
class SynorIbc {
|
|
final IbcConfig config;
|
|
final http.Client _client;
|
|
bool _closed = false;
|
|
|
|
late final LightClientClient clients;
|
|
late final ConnectionsClient connections;
|
|
late final ChannelsClient channels;
|
|
late final TransferClient transfer;
|
|
late final SwapsClient swaps;
|
|
|
|
SynorIbc(this.config) : _client = http.Client() {
|
|
clients = LightClientClient(this);
|
|
connections = ConnectionsClient(this);
|
|
channels = ChannelsClient(this);
|
|
transfer = TransferClient(this);
|
|
swaps = SwapsClient(this);
|
|
}
|
|
|
|
String get chainId => config.chainId;
|
|
|
|
Future<Map<String, dynamic>> getChainInfo() async {
|
|
return _get('/chain');
|
|
}
|
|
|
|
Future<Height> getHeight() async {
|
|
final result = await _get('/chain/height');
|
|
return Height.fromJson(result);
|
|
}
|
|
|
|
Future<bool> healthCheck() async {
|
|
try {
|
|
final result = await _get('/health');
|
|
return result['status'] == 'healthy';
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void close() {
|
|
_closed = true;
|
|
_client.close();
|
|
}
|
|
|
|
bool get isClosed => _closed;
|
|
|
|
// Internal HTTP methods
|
|
Future<Map<String, dynamic>> _get(String path) async {
|
|
_checkClosed();
|
|
final response = await _client.get(
|
|
Uri.parse('${config.endpoint}$path'),
|
|
headers: _headers,
|
|
);
|
|
return _handleResponse(response);
|
|
}
|
|
|
|
Future<Map<String, dynamic>> _post(String path, Map<String, dynamic> body) async {
|
|
_checkClosed();
|
|
final response = await _client.post(
|
|
Uri.parse('${config.endpoint}$path'),
|
|
headers: _headers,
|
|
body: jsonEncode(body),
|
|
);
|
|
return _handleResponse(response);
|
|
}
|
|
|
|
Map<String, String> get _headers => {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer ${config.apiKey}',
|
|
'X-SDK-Version': 'flutter/0.1.0',
|
|
'X-Chain-Id': config.chainId,
|
|
};
|
|
|
|
Map<String, dynamic> _handleResponse(http.Response response) {
|
|
if (response.statusCode >= 400) {
|
|
Map<String, dynamic>? error;
|
|
try {
|
|
error = jsonDecode(response.body);
|
|
} catch (_) {}
|
|
throw IbcException(
|
|
error?['message'] ?? 'HTTP ${response.statusCode}',
|
|
error?['code'],
|
|
response.statusCode,
|
|
);
|
|
}
|
|
return jsonDecode(response.body);
|
|
}
|
|
|
|
void _checkClosed() {
|
|
if (_closed) {
|
|
throw const IbcException('Client has been closed', 'CLIENT_CLOSED');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Light client sub-client
|
|
class LightClientClient {
|
|
final SynorIbc _ibc;
|
|
LightClientClient(this._ibc);
|
|
|
|
Future<ClientId> create({
|
|
required ClientType clientType,
|
|
required ClientState clientState,
|
|
required Map<String, dynamic> consensusState,
|
|
}) async {
|
|
final result = await _ibc._post('/clients', {
|
|
'client_type': clientType.name,
|
|
'client_state': clientState.toJson(),
|
|
'consensus_state': consensusState,
|
|
});
|
|
return ClientId(result['client_id']);
|
|
}
|
|
|
|
Future<ClientState> getState(ClientId clientId) async {
|
|
final result = await _ibc._get('/clients/${clientId.id}/state');
|
|
return ClientState.fromJson(result);
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> list() async {
|
|
final result = await _ibc._get('/clients');
|
|
return List<Map<String, dynamic>>.from(result['clients'] ?? []);
|
|
}
|
|
}
|
|
|
|
/// Connections sub-client
|
|
class ConnectionsClient {
|
|
final SynorIbc _ibc;
|
|
ConnectionsClient(this._ibc);
|
|
|
|
Future<ConnectionId> openInit({
|
|
required ClientId clientId,
|
|
required ClientId counterpartyClientId,
|
|
}) async {
|
|
final result = await _ibc._post('/connections/init', {
|
|
'client_id': clientId.id,
|
|
'counterparty_client_id': counterpartyClientId.id,
|
|
});
|
|
return ConnectionId(result['connection_id']);
|
|
}
|
|
|
|
Future<Map<String, dynamic>> get(ConnectionId connectionId) async {
|
|
return _ibc._get('/connections/${connectionId.id}');
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> list() async {
|
|
final result = await _ibc._get('/connections');
|
|
return List<Map<String, dynamic>>.from(result['connections'] ?? []);
|
|
}
|
|
}
|
|
|
|
/// Channels sub-client
|
|
class ChannelsClient {
|
|
final SynorIbc _ibc;
|
|
ChannelsClient(this._ibc);
|
|
|
|
Future<void> bindPort(PortId portId, String module) async {
|
|
await _ibc._post('/ports/bind', {'port_id': portId.id, 'module': module});
|
|
}
|
|
|
|
Future<ChannelId> openInit({
|
|
required PortId portId,
|
|
required ChannelOrder ordering,
|
|
required ConnectionId connectionId,
|
|
required PortId counterpartyPort,
|
|
required String version,
|
|
}) async {
|
|
final result = await _ibc._post('/channels/init', {
|
|
'port_id': portId.id,
|
|
'ordering': ordering.name,
|
|
'connection_id': connectionId.id,
|
|
'counterparty_port': counterpartyPort.id,
|
|
'version': version,
|
|
});
|
|
return ChannelId(result['channel_id']);
|
|
}
|
|
|
|
Future<Map<String, dynamic>> get(PortId portId, ChannelId channelId) async {
|
|
return _ibc._get('/channels/${portId.id}/${channelId.id}');
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> list() async {
|
|
final result = await _ibc._get('/channels');
|
|
return List<Map<String, dynamic>>.from(result['channels'] ?? []);
|
|
}
|
|
}
|
|
|
|
/// Transfer sub-client (ICS-20)
|
|
class TransferClient {
|
|
final SynorIbc _ibc;
|
|
TransferClient(this._ibc);
|
|
|
|
Future<Map<String, dynamic>> transfer({
|
|
required String sourcePort,
|
|
required String sourceChannel,
|
|
required String denom,
|
|
required String amount,
|
|
required String sender,
|
|
required String receiver,
|
|
Timeout? timeout,
|
|
String? memo,
|
|
}) async {
|
|
final body = <String, dynamic>{
|
|
'source_port': sourcePort,
|
|
'source_channel': sourceChannel,
|
|
'token': {'denom': denom, 'amount': amount},
|
|
'sender': sender,
|
|
'receiver': receiver,
|
|
};
|
|
if (timeout != null) {
|
|
body['timeout_height'] = timeout.height.toJson();
|
|
body['timeout_timestamp'] = timeout.timestamp.toString();
|
|
}
|
|
if (memo != null) body['memo'] = memo;
|
|
return _ibc._post('/transfer', body);
|
|
}
|
|
|
|
Future<Map<String, dynamic>> getDenomTrace(String ibcDenom) async {
|
|
return _ibc._get('/transfer/denom_trace/$ibcDenom');
|
|
}
|
|
}
|
|
|
|
/// Swaps sub-client (HTLC)
|
|
class SwapsClient {
|
|
final SynorIbc _ibc;
|
|
SwapsClient(this._ibc);
|
|
|
|
Future<Map<String, dynamic>> initiate({
|
|
required String responder,
|
|
required Map<String, dynamic> initiatorAsset,
|
|
required Map<String, dynamic> responderAsset,
|
|
}) async {
|
|
return _ibc._post('/swaps/initiate', {
|
|
'responder': responder,
|
|
'initiator_asset': initiatorAsset,
|
|
'responder_asset': responderAsset,
|
|
});
|
|
}
|
|
|
|
Future<void> lock(SwapId swapId) async {
|
|
await _ibc._post('/swaps/${swapId.id}/lock', {});
|
|
}
|
|
|
|
Future<Map<String, dynamic>> respond(SwapId swapId, Map<String, dynamic> asset) async {
|
|
return _ibc._post('/swaps/${swapId.id}/respond', {'asset': asset});
|
|
}
|
|
|
|
Future<Map<String, dynamic>> claim(SwapId swapId, List<int> secret) async {
|
|
return _ibc._post('/swaps/${swapId.id}/claim', {
|
|
'secret': base64Encode(secret),
|
|
});
|
|
}
|
|
|
|
Future<Map<String, dynamic>> refund(SwapId swapId) async {
|
|
return _ibc._post('/swaps/${swapId.id}/refund', {});
|
|
}
|
|
|
|
Future<AtomicSwap> get(SwapId swapId) async {
|
|
final result = await _ibc._get('/swaps/${swapId.id}');
|
|
return AtomicSwap.fromJson(result);
|
|
}
|
|
|
|
Future<List<AtomicSwap>> listActive() async {
|
|
final result = await _ibc._get('/swaps/active');
|
|
return (result['swaps'] as List).map((e) => AtomicSwap.fromJson(e)).toList();
|
|
}
|
|
}
|