synor/sdk/flutter/lib/src/governance/client.dart
Gulshan Yadav 6607223c9e feat(sdk): complete Phase 4 SDKs for all remaining languages
Add Economics, Governance, and Mining SDKs for:
- Java: Full SDK with CompletableFuture async operations
- Kotlin: Coroutine-based SDK with suspend functions
- Swift: Modern Swift SDK with async/await
- Flutter/Dart: Complete Dart SDK with Future-based API
- C: Header files and implementations with opaque handles
- C++: Modern C++17 with std::future and PIMPL pattern
- C#: Records, async/await Tasks, and IDisposable
- Ruby: Struct-based types with Faraday HTTP client

Also includes minor Dart lint fixes (const exceptions).
2026-01-28 08:33:20 +05:30

250 lines
9.1 KiB
Dart

/// Synor Governance SDK Client for Flutter
library synor_governance;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'types.dart';
export 'types.dart';
/// Synor Governance Client
class SynorGovernance {
final GovernanceConfig config;
final http.Client _client;
bool _closed = false;
static const _finalStatuses = {
ProposalStatus.passed,
ProposalStatus.rejected,
ProposalStatus.executed,
ProposalStatus.cancelled,
};
SynorGovernance(this.config) : _client = http.Client();
// ==================== Proposal Operations ====================
Future<Proposal> createProposal(ProposalDraft proposal) async {
final resp = await _request('POST', '/proposals', proposal.toJson());
return Proposal.fromJson(resp);
}
Future<Proposal> getProposal(String proposalId) async {
final resp = await _request('GET', '/proposals/${Uri.encodeComponent(proposalId)}');
return Proposal.fromJson(resp);
}
Future<List<Proposal>> listProposals({ProposalFilter? filter}) async {
final params = <String>[];
if (filter?.status != null) params.add('status=${filter!.status!.name}');
if (filter?.proposer != null) params.add('proposer=${Uri.encodeComponent(filter!.proposer!)}');
if (filter?.daoId != null) params.add('daoId=${Uri.encodeComponent(filter!.daoId!)}');
if (filter?.limit != null) params.add('limit=${filter!.limit}');
if (filter?.offset != null) params.add('offset=${filter!.offset}');
final path = params.isEmpty ? '/proposals' : '/proposals?${params.join('&')}';
final resp = await _request('GET', path);
return (resp['proposals'] as List).map((e) => Proposal.fromJson(e)).toList();
}
Future<Proposal> cancelProposal(String proposalId) async {
final resp = await _request('POST', '/proposals/${Uri.encodeComponent(proposalId)}/cancel');
return Proposal.fromJson(resp);
}
Future<Proposal> executeProposal(String proposalId) async {
final resp = await _request('POST', '/proposals/${Uri.encodeComponent(proposalId)}/execute');
return Proposal.fromJson(resp);
}
Future<Proposal> waitForProposal(
String proposalId, {
Duration pollInterval = const Duration(minutes: 1),
Duration maxWait = const Duration(days: 7),
}) async {
final deadline = DateTime.now().add(maxWait);
while (DateTime.now().isBefore(deadline)) {
final proposal = await getProposal(proposalId);
if (_finalStatuses.contains(proposal.status)) return proposal;
await Future.delayed(pollInterval);
}
throw const GovernanceException('Timeout waiting for proposal completion');
}
// ==================== Voting Operations ====================
Future<VoteReceipt> vote(String proposalId, Vote vote, {String? weight}) async {
final body = <String, dynamic>{'choice': vote.choice.name};
if (vote.reason != null) body['reason'] = vote.reason;
if (weight != null) body['weight'] = weight;
final resp = await _request('POST', '/proposals/${Uri.encodeComponent(proposalId)}/vote', body);
return VoteReceipt.fromJson(resp);
}
Future<List<VoteReceipt>> getVotes(String proposalId) async {
final resp = await _request('GET', '/proposals/${Uri.encodeComponent(proposalId)}/votes');
return (resp['votes'] as List).map((e) => VoteReceipt.fromJson(e)).toList();
}
Future<VoteReceipt> getMyVote(String proposalId) async {
final resp = await _request('GET', '/proposals/${Uri.encodeComponent(proposalId)}/votes/me');
return VoteReceipt.fromJson(resp);
}
Future<DelegationReceipt> delegate(String delegatee, {String? amount}) async {
final body = <String, dynamic>{'delegatee': delegatee};
if (amount != null) body['amount'] = amount;
final resp = await _request('POST', '/voting/delegate', body);
return DelegationReceipt.fromJson(resp);
}
Future<DelegationReceipt> undelegate(String delegatee) async {
final resp = await _request('POST', '/voting/undelegate', {'delegatee': delegatee});
return DelegationReceipt.fromJson(resp);
}
Future<VotingPower> getVotingPower(String address) async {
final resp = await _request('GET', '/voting/power/${Uri.encodeComponent(address)}');
return VotingPower.fromJson(resp);
}
Future<List<DelegationReceipt>> getDelegations(String address) async {
final resp = await _request('GET', '/voting/delegations/${Uri.encodeComponent(address)}');
return (resp['delegations'] as List).map((e) => DelegationReceipt.fromJson(e)).toList();
}
// ==================== DAO Operations ====================
Future<Dao> createDao(DaoConfig daoConfig) async {
final resp = await _request('POST', '/daos', daoConfig.toJson());
return Dao.fromJson(resp);
}
Future<Dao> getDao(String daoId) async {
final resp = await _request('GET', '/daos/${Uri.encodeComponent(daoId)}');
return Dao.fromJson(resp);
}
Future<List<Dao>> listDaos({int? limit, int? offset}) async {
final params = <String>[];
if (limit != null) params.add('limit=$limit');
if (offset != null) params.add('offset=$offset');
final path = params.isEmpty ? '/daos' : '/daos?${params.join('&')}';
final resp = await _request('GET', path);
return (resp['daos'] as List).map((e) => Dao.fromJson(e)).toList();
}
Future<DaoTreasury> getDaoTreasury(String daoId) async {
final resp = await _request('GET', '/daos/${Uri.encodeComponent(daoId)}/treasury');
return DaoTreasury.fromJson(resp);
}
Future<List<String>> getDaoMembers(String daoId) async {
final resp = await _request('GET', '/daos/${Uri.encodeComponent(daoId)}/members');
return (resp['members'] as List).cast<String>();
}
// ==================== Vesting Operations ====================
Future<VestingContract> createVestingSchedule(VestingSchedule schedule) async {
final resp = await _request('POST', '/vesting', schedule.toJson());
return VestingContract.fromJson(resp);
}
Future<VestingContract> getVestingContract(String contractId) async {
final resp = await _request('GET', '/vesting/${Uri.encodeComponent(contractId)}');
return VestingContract.fromJson(resp);
}
Future<List<VestingContract>> listVestingContracts({String? beneficiary}) async {
final path = beneficiary != null ? '/vesting?beneficiary=${Uri.encodeComponent(beneficiary)}' : '/vesting';
final resp = await _request('GET', path);
return (resp['contracts'] as List).map((e) => VestingContract.fromJson(e)).toList();
}
Future<ClaimReceipt> claimVested(String contractId) async {
final resp = await _request('POST', '/vesting/${Uri.encodeComponent(contractId)}/claim');
return ClaimReceipt.fromJson(resp);
}
Future<VestingContract> revokeVesting(String contractId) async {
final resp = await _request('POST', '/vesting/${Uri.encodeComponent(contractId)}/revoke');
return VestingContract.fromJson(resp);
}
Future<String> getReleasableAmount(String contractId) async {
final resp = await _request('GET', '/vesting/${Uri.encodeComponent(contractId)}/releasable');
return resp['amount'] as String;
}
// ==================== Lifecycle ====================
void close() {
_closed = true;
_client.close();
}
bool get isClosed => _closed;
Future<bool> healthCheck() async {
try {
final resp = await _request('GET', '/health');
return resp['status'] == 'healthy';
} catch (_) {
return false;
}
}
// ==================== Private Methods ====================
Future<Map<String, dynamic>> _request(String method, String path, [Map<String, dynamic>? body]) async {
if (_closed) throw const GovernanceException('Client has been closed');
Exception? lastError;
for (var attempt = 0; attempt < config.retries; attempt++) {
try {
return await _doRequest(method, path, body);
} catch (e) {
lastError = e as Exception;
if (attempt < config.retries - 1) {
await Future.delayed(Duration(seconds: 1 << attempt));
}
}
}
throw lastError ?? const GovernanceException('Unknown error');
}
Future<Map<String, dynamic>> _doRequest(String method, String path, Map<String, dynamic>? body) async {
final uri = Uri.parse('${config.endpoint}$path');
final headers = {
'Authorization': 'Bearer ${config.apiKey}',
'Content-Type': 'application/json',
'X-SDK-Version': 'flutter/0.1.0',
};
http.Response response;
switch (method) {
case 'GET':
response = await _client.get(uri, headers: headers).timeout(config.timeout);
break;
case 'POST':
response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null).timeout(config.timeout);
break;
case 'DELETE':
response = await _client.delete(uri, headers: headers).timeout(config.timeout);
break;
default:
throw GovernanceException('Unknown method: $method');
}
final respBody = jsonDecode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 400) {
throw GovernanceException(
respBody['message'] as String? ?? 'HTTP ${response.statusCode}',
code: respBody['code'] as String?,
statusCode: response.statusCode,
);
}
return respBody;
}
}