/// 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> getChainInfo() async { return _get('/chain'); } Future getHeight() async { final result = await _get('/chain/height'); return Height.fromJson(result); } Future 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> _get(String path) async { _checkClosed(); final response = await _client.get( Uri.parse('${config.endpoint}$path'), headers: _headers, ); return _handleResponse(response); } Future> _post(String path, Map body) async { _checkClosed(); final response = await _client.post( Uri.parse('${config.endpoint}$path'), headers: _headers, body: jsonEncode(body), ); return _handleResponse(response); } Map get _headers => { 'Content-Type': 'application/json', 'Authorization': 'Bearer ${config.apiKey}', 'X-SDK-Version': 'flutter/0.1.0', 'X-Chain-Id': config.chainId, }; Map _handleResponse(http.Response response) { if (response.statusCode >= 400) { Map? 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 create({ required ClientType clientType, required ClientState clientState, required Map 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 getState(ClientId clientId) async { final result = await _ibc._get('/clients/${clientId.id}/state'); return ClientState.fromJson(result); } Future>> list() async { final result = await _ibc._get('/clients'); return List>.from(result['clients'] ?? []); } } /// Connections sub-client class ConnectionsClient { final SynorIbc _ibc; ConnectionsClient(this._ibc); Future 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> get(ConnectionId connectionId) async { return _ibc._get('/connections/${connectionId.id}'); } Future>> list() async { final result = await _ibc._get('/connections'); return List>.from(result['connections'] ?? []); } } /// Channels sub-client class ChannelsClient { final SynorIbc _ibc; ChannelsClient(this._ibc); Future bindPort(PortId portId, String module) async { await _ibc._post('/ports/bind', {'port_id': portId.id, 'module': module}); } Future 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> get(PortId portId, ChannelId channelId) async { return _ibc._get('/channels/${portId.id}/${channelId.id}'); } Future>> list() async { final result = await _ibc._get('/channels'); return List>.from(result['channels'] ?? []); } } /// Transfer sub-client (ICS-20) class TransferClient { final SynorIbc _ibc; TransferClient(this._ibc); Future> 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 = { '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> getDenomTrace(String ibcDenom) async { return _ibc._get('/transfer/denom_trace/$ibcDenom'); } } /// Swaps sub-client (HTLC) class SwapsClient { final SynorIbc _ibc; SwapsClient(this._ibc); Future> initiate({ required String responder, required Map initiatorAsset, required Map responderAsset, }) async { return _ibc._post('/swaps/initiate', { 'responder': responder, 'initiator_asset': initiatorAsset, 'responder_asset': responderAsset, }); } Future lock(SwapId swapId) async { await _ibc._post('/swaps/${swapId.id}/lock', {}); } Future> respond(SwapId swapId, Map asset) async { return _ibc._post('/swaps/${swapId.id}/respond', {'asset': asset}); } Future> claim(SwapId swapId, List secret) async { return _ibc._post('/swaps/${swapId.id}/claim', { 'secret': base64Encode(secret), }); } Future> refund(SwapId swapId) async { return _ibc._post('/swaps/${swapId.id}/refund', {}); } Future get(SwapId swapId) async { final result = await _ibc._get('/swaps/${swapId.id}'); return AtomicSwap.fromJson(result); } Future> listActive() async { final result = await _ibc._get('/swaps/active'); return (result['swaps'] as List).map((e) => AtomicSwap.fromJson(e)).toList(); } }