From dd01a061167d8f5138e0102d198d732353490ffb Mon Sep 17 00:00:00 2001 From: Gulshan Yadav Date: Tue, 27 Jan 2026 02:00:41 +0530 Subject: [PATCH] feat(sdk): add Database, Hosting, and Bridge SDKs for Flutter Phase 3 SDK expansion - Flutter/Dart implementations: - Database SDK: Multi-model database with Key-Value, Document, Vector, and Time Series stores - Hosting SDK: Decentralized web hosting with domain management, DNS, deployments, and SSL provisioning - Bridge SDK: Cross-chain asset transfers with lock-mint and burn-unlock patterns All SDKs follow consistent patterns with: - Async/await API using Futures - Proper error handling with typed exceptions - Configurable retry logic and timeouts - Full type safety with Dart's type system --- sdk/flutter/lib/src/bridge/client.dart | 325 ++++++++++++ sdk/flutter/lib/src/bridge/types.dart | 636 +++++++++++++++++++++++ sdk/flutter/lib/src/database/client.dart | 246 +++++++++ sdk/flutter/lib/src/database/types.dart | 224 ++++++++ sdk/flutter/lib/src/hosting/client.dart | 253 +++++++++ sdk/flutter/lib/src/hosting/types.dart | 397 ++++++++++++++ sdk/flutter/lib/synor_sdk.dart | 28 + 7 files changed, 2109 insertions(+) create mode 100644 sdk/flutter/lib/src/bridge/client.dart create mode 100644 sdk/flutter/lib/src/bridge/types.dart create mode 100644 sdk/flutter/lib/src/database/client.dart create mode 100644 sdk/flutter/lib/src/database/types.dart create mode 100644 sdk/flutter/lib/src/hosting/client.dart create mode 100644 sdk/flutter/lib/src/hosting/types.dart diff --git a/sdk/flutter/lib/src/bridge/client.dart b/sdk/flutter/lib/src/bridge/client.dart new file mode 100644 index 0000000..72b1ac0 --- /dev/null +++ b/sdk/flutter/lib/src/bridge/client.dart @@ -0,0 +1,325 @@ +/// Synor Bridge SDK Client for Flutter +/// +/// Cross-chain asset transfers with lock-mint and burn-unlock patterns. +library synor_bridge; + +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'types.dart'; + +export 'types.dart'; + +/// Synor Bridge Client +class SynorBridge { + final BridgeConfig config; + final http.Client _client; + bool _closed = false; + + static const _finalStatuses = { + TransferStatus.completed, + TransferStatus.failed, + TransferStatus.refunded, + }; + + SynorBridge(this.config) : _client = http.Client(); + + // ==================== Chain Operations ==================== + + Future> getSupportedChains() async { + final resp = await _request('GET', '/chains'); + return (resp['chains'] as List).map((e) => Chain.fromJson(e)).toList(); + } + + Future getChain(ChainId chainId) async { + final resp = await _request('GET', '/chains/${chainId.name}'); + return Chain.fromJson(resp); + } + + Future isChainSupported(ChainId chainId) async { + try { + final chain = await getChain(chainId); + return chain.supported; + } catch (_) { + return false; + } + } + + // ==================== Asset Operations ==================== + + Future> getSupportedAssets(ChainId chainId) async { + final resp = await _request('GET', '/chains/${chainId.name}/assets'); + return (resp['assets'] as List).map((e) => Asset.fromJson(e)).toList(); + } + + Future getAsset(String assetId) async { + final resp = await _request('GET', '/assets/${Uri.encodeComponent(assetId)}'); + return Asset.fromJson(resp); + } + + Future getWrappedAsset(String originalAssetId, ChainId targetChain) async { + final path = '/assets/${Uri.encodeComponent(originalAssetId)}/wrapped/${targetChain.name}'; + final resp = await _request('GET', path); + return WrappedAsset.fromJson(resp); + } + + // ==================== Fee & Rate Operations ==================== + + Future estimateFee(String asset, String amount, ChainId sourceChain, ChainId targetChain) async { + final resp = await _request('POST', '/fees/estimate', { + 'asset': asset, + 'amount': amount, + 'sourceChain': sourceChain.name, + 'targetChain': targetChain.name, + }); + return FeeEstimate.fromJson(resp); + } + + Future getExchangeRate(String fromAsset, String toAsset) async { + final path = '/rates/${Uri.encodeComponent(fromAsset)}/${Uri.encodeComponent(toAsset)}'; + final resp = await _request('GET', path); + return ExchangeRate.fromJson(resp); + } + + // ==================== Lock-Mint Flow ==================== + + Future lock(String asset, String amount, ChainId targetChain, {LockOptions? options}) async { + final body = { + 'asset': asset, + 'amount': amount, + 'targetChain': targetChain.name, + }; + if (options != null) body.addAll(options.toJson()); + final resp = await _request('POST', '/transfers/lock', body); + return LockReceipt.fromJson(resp); + } + + Future getLockProof(String lockReceiptId) async { + final resp = await _request('GET', '/transfers/lock/${Uri.encodeComponent(lockReceiptId)}/proof'); + return LockProof.fromJson(resp); + } + + Future waitForLockProof( + String lockReceiptId, { + Duration pollInterval = const Duration(seconds: 5), + Duration maxWait = const Duration(minutes: 10), + }) async { + final deadline = DateTime.now().add(maxWait); + while (DateTime.now().isBefore(deadline)) { + try { + return await getLockProof(lockReceiptId); + } on BridgeException catch (e) { + if (e.isConfirmationsPending) { + await Future.delayed(pollInterval); + continue; + } + rethrow; + } + } + throw const BridgeException('Timeout waiting for lock proof', code: 'CONFIRMATIONS_PENDING'); + } + + Future mint(LockProof proof, String targetAddress, {MintOptions? options}) async { + final body = { + 'proof': proof.toJson(), + 'targetAddress': targetAddress, + }; + if (options != null) body.addAll(options.toJson()); + final resp = await _request('POST', '/transfers/mint', body); + return SignedTransaction.fromJson(resp); + } + + // ==================== Burn-Unlock Flow ==================== + + Future burn(String wrappedAsset, String amount, {BurnOptions? options}) async { + final body = { + 'wrappedAsset': wrappedAsset, + 'amount': amount, + }; + if (options != null) body.addAll(options.toJson()); + final resp = await _request('POST', '/transfers/burn', body); + return BurnReceipt.fromJson(resp); + } + + Future getBurnProof(String burnReceiptId) async { + final resp = await _request('GET', '/transfers/burn/${Uri.encodeComponent(burnReceiptId)}/proof'); + return BurnProof.fromJson(resp); + } + + Future waitForBurnProof( + String burnReceiptId, { + Duration pollInterval = const Duration(seconds: 5), + Duration maxWait = const Duration(minutes: 10), + }) async { + final deadline = DateTime.now().add(maxWait); + while (DateTime.now().isBefore(deadline)) { + try { + return await getBurnProof(burnReceiptId); + } on BridgeException catch (e) { + if (e.isConfirmationsPending) { + await Future.delayed(pollInterval); + continue; + } + rethrow; + } + } + throw const BridgeException('Timeout waiting for burn proof', code: 'CONFIRMATIONS_PENDING'); + } + + Future unlock(BurnProof proof, {UnlockOptions? options}) async { + final body = {'proof': proof.toJson()}; + if (options != null) body.addAll(options.toJson()); + final resp = await _request('POST', '/transfers/unlock', body); + return SignedTransaction.fromJson(resp); + } + + // ==================== Transfer Management ==================== + + Future getTransfer(String transferId) async { + final resp = await _request('GET', '/transfers/${Uri.encodeComponent(transferId)}'); + return Transfer.fromJson(resp); + } + + Future getTransferStatus(String transferId) async { + final transfer = await getTransfer(transferId); + return transfer.status; + } + + Future> listTransfers({TransferFilter? filter}) async { + var path = '/transfers'; + final params = []; + if (filter?.status != null) params.add('status=${filter!.status!.name}'); + if (filter?.sourceChain != null) params.add('sourceChain=${filter!.sourceChain!.name}'); + if (filter?.targetChain != null) params.add('targetChain=${filter!.targetChain!.name}'); + if (filter?.limit != null) params.add('limit=${filter!.limit}'); + if (filter?.offset != null) params.add('offset=${filter!.offset}'); + if (params.isNotEmpty) path = '$path?${params.join('&')}'; + + final resp = await _request('GET', path); + return (resp['transfers'] as List).map((e) => Transfer.fromJson(e)).toList(); + } + + Future waitForTransfer( + String transferId, { + Duration pollInterval = const Duration(seconds: 10), + Duration maxWait = const Duration(minutes: 30), + }) async { + final deadline = DateTime.now().add(maxWait); + while (DateTime.now().isBefore(deadline)) { + final transfer = await getTransfer(transferId); + if (_finalStatuses.contains(transfer.status)) { + return transfer; + } + await Future.delayed(pollInterval); + } + throw const BridgeException('Timeout waiting for transfer completion'); + } + + // ==================== Convenience Methods ==================== + + Future bridgeTo( + String asset, + String amount, + ChainId targetChain, + String targetAddress, { + LockOptions? lockOptions, + MintOptions? mintOptions, + }) async { + final lockReceipt = await lock(asset, amount, targetChain, options: lockOptions); + if (config.debug) print('Locked: ${lockReceipt.id}, waiting for confirmations...'); + + final proof = await waitForLockProof(lockReceipt.id); + if (config.debug) print('Proof ready, minting on ${targetChain.name}...'); + + await mint(proof, targetAddress, options: mintOptions); + return waitForTransfer(lockReceipt.id); + } + + Future bridgeBack( + String wrappedAsset, + String amount, { + BurnOptions? burnOptions, + UnlockOptions? unlockOptions, + }) async { + final burnReceipt = await burn(wrappedAsset, amount, options: burnOptions); + if (config.debug) print('Burned: ${burnReceipt.id}, waiting for confirmations...'); + + final proof = await waitForBurnProof(burnReceipt.id); + if (config.debug) print('Proof ready, unlocking on ${burnReceipt.targetChain.name}...'); + + await unlock(proof, options: unlockOptions); + return waitForTransfer(burnReceipt.id); + } + + // ==================== Lifecycle ==================== + + void close() { + _closed = true; + _client.close(); + } + + bool get isClosed => _closed; + + Future healthCheck() async { + try { + final resp = await _request('GET', '/health'); + return resp['status'] == 'healthy'; + } catch (_) { + return false; + } + } + + // ==================== Private Methods ==================== + + Future> _request(String method, String path, [Map? body]) async { + if (_closed) throw const BridgeException('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 BridgeException('Unknown error'); + } + + Future> _doRequest(String method, String path, Map? 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); + break; + case 'POST': + response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null); + break; + case 'DELETE': + response = await _client.delete(uri, headers: headers); + break; + default: + throw BridgeException('Unknown method: $method'); + } + + if (response.statusCode >= 400) { + final error = jsonDecode(response.body) as Map; + throw BridgeException( + error['message'] as String? ?? 'HTTP ${response.statusCode}', + code: error['code'] as String?, + statusCode: response.statusCode, + ); + } + + if (response.body.isEmpty) return {}; + return jsonDecode(response.body) as Map; + } +} diff --git a/sdk/flutter/lib/src/bridge/types.dart b/sdk/flutter/lib/src/bridge/types.dart new file mode 100644 index 0000000..22cd2d4 --- /dev/null +++ b/sdk/flutter/lib/src/bridge/types.dart @@ -0,0 +1,636 @@ +/// Synor Bridge SDK Types for Flutter +/// +/// Cross-chain asset transfers with lock-mint and burn-unlock patterns. +library synor_bridge_types; + +// ==================== Chain Types ==================== + +/// Supported blockchain networks +enum ChainId { synor, ethereum, polygon, arbitrum, optimism, bsc, avalanche, solana, cosmos } + +/// Native currency info +class NativeCurrency { + final String name; + final String symbol; + final int decimals; + + const NativeCurrency({required this.name, required this.symbol, required this.decimals}); + + factory NativeCurrency.fromJson(Map json) => NativeCurrency( + name: json['name'] as String, + symbol: json['symbol'] as String, + decimals: json['decimals'] as int, + ); +} + +/// Chain information +class Chain { + final ChainId id; + final String name; + final int chainId; + final String rpcUrl; + final String explorerUrl; + final NativeCurrency nativeCurrency; + final int confirmations; + final int estimatedBlockTime; + final bool supported; + + const Chain({ + required this.id, + required this.name, + required this.chainId, + required this.rpcUrl, + required this.explorerUrl, + required this.nativeCurrency, + required this.confirmations, + required this.estimatedBlockTime, + required this.supported, + }); + + factory Chain.fromJson(Map json) => Chain( + id: ChainId.values.firstWhere((e) => e.name == json['id']), + name: json['name'] as String, + chainId: json['chainId'] as int, + rpcUrl: json['rpcUrl'] as String, + explorerUrl: json['explorerUrl'] as String, + nativeCurrency: NativeCurrency.fromJson(json['nativeCurrency']), + confirmations: json['confirmations'] as int, + estimatedBlockTime: json['estimatedBlockTime'] as int, + supported: json['supported'] as bool, + ); +} + +// ==================== Asset Types ==================== + +/// Asset type +enum AssetType { native, erc20, erc721, erc1155 } + +/// Asset information +class Asset { + final String id; + final String symbol; + final String name; + final AssetType type; + final ChainId chain; + final String? contractAddress; + final int decimals; + final String? logoUrl; + final bool verified; + + const Asset({ + required this.id, + required this.symbol, + required this.name, + required this.type, + required this.chain, + this.contractAddress, + required this.decimals, + this.logoUrl, + this.verified = false, + }); + + factory Asset.fromJson(Map json) => Asset( + id: json['id'] as String, + symbol: json['symbol'] as String, + name: json['name'] as String, + type: AssetType.values.firstWhere((e) => e.name == json['type']), + chain: ChainId.values.firstWhere((e) => e.name == json['chain']), + contractAddress: json['contractAddress'] as String?, + decimals: json['decimals'] as int, + logoUrl: json['logoUrl'] as String?, + verified: json['verified'] as bool? ?? false, + ); + + Map toJson() => { + 'id': id, + 'symbol': symbol, + 'name': name, + 'type': type.name, + 'chain': chain.name, + if (contractAddress != null) 'contractAddress': contractAddress, + 'decimals': decimals, + if (logoUrl != null) 'logoUrl': logoUrl, + 'verified': verified, + }; +} + +/// Wrapped asset mapping +class WrappedAsset { + final Asset originalAsset; + final Asset wrappedAsset; + final ChainId chain; + final String bridgeContract; + + const WrappedAsset({ + required this.originalAsset, + required this.wrappedAsset, + required this.chain, + required this.bridgeContract, + }); + + factory WrappedAsset.fromJson(Map json) => WrappedAsset( + originalAsset: Asset.fromJson(json['originalAsset']), + wrappedAsset: Asset.fromJson(json['wrappedAsset']), + chain: ChainId.values.firstWhere((e) => e.name == json['chain']), + bridgeContract: json['bridgeContract'] as String, + ); +} + +// ==================== Transfer Types ==================== + +/// Transfer status +enum TransferStatus { pending, locked, confirming, minting, completed, failed, refunded } + +/// Transfer direction +enum TransferDirection { lockMint, burnUnlock } + +/// Validator signature +class ValidatorSignature { + final String validator; + final String signature; + final int timestamp; + + const ValidatorSignature({ + required this.validator, + required this.signature, + required this.timestamp, + }); + + factory ValidatorSignature.fromJson(Map json) => ValidatorSignature( + validator: json['validator'] as String, + signature: json['signature'] as String, + timestamp: json['timestamp'] as int, + ); + + Map toJson() => { + 'validator': validator, + 'signature': signature, + 'timestamp': timestamp, + }; +} + +/// Lock receipt +class LockReceipt { + final String id; + final String txHash; + final ChainId sourceChain; + final ChainId targetChain; + final Asset asset; + final String amount; + final String sender; + final String recipient; + final int lockTimestamp; + final int confirmations; + final int requiredConfirmations; + + const LockReceipt({ + required this.id, + required this.txHash, + required this.sourceChain, + required this.targetChain, + required this.asset, + required this.amount, + required this.sender, + required this.recipient, + required this.lockTimestamp, + required this.confirmations, + required this.requiredConfirmations, + }); + + factory LockReceipt.fromJson(Map json) => LockReceipt( + id: json['id'] as String, + txHash: json['txHash'] as String, + sourceChain: ChainId.values.firstWhere((e) => e.name == json['sourceChain']), + targetChain: ChainId.values.firstWhere((e) => e.name == json['targetChain']), + asset: Asset.fromJson(json['asset']), + amount: json['amount'] as String, + sender: json['sender'] as String, + recipient: json['recipient'] as String, + lockTimestamp: json['lockTimestamp'] as int, + confirmations: json['confirmations'] as int, + requiredConfirmations: json['requiredConfirmations'] as int, + ); + + Map toJson() => { + 'id': id, + 'txHash': txHash, + 'sourceChain': sourceChain.name, + 'targetChain': targetChain.name, + 'asset': asset.toJson(), + 'amount': amount, + 'sender': sender, + 'recipient': recipient, + 'lockTimestamp': lockTimestamp, + 'confirmations': confirmations, + 'requiredConfirmations': requiredConfirmations, + }; +} + +/// Lock proof +class LockProof { + final LockReceipt lockReceipt; + final List merkleProof; + final String blockHeader; + final List signatures; + + const LockProof({ + required this.lockReceipt, + required this.merkleProof, + required this.blockHeader, + required this.signatures, + }); + + factory LockProof.fromJson(Map json) => LockProof( + lockReceipt: LockReceipt.fromJson(json['lockReceipt']), + merkleProof: List.from(json['merkleProof']), + blockHeader: json['blockHeader'] as String, + signatures: (json['signatures'] as List) + .map((e) => ValidatorSignature.fromJson(e)) + .toList(), + ); + + Map toJson() => { + 'lockReceipt': lockReceipt.toJson(), + 'merkleProof': merkleProof, + 'blockHeader': blockHeader, + 'signatures': signatures.map((s) => s.toJson()).toList(), + }; +} + +/// Burn receipt +class BurnReceipt { + final String id; + final String txHash; + final ChainId sourceChain; + final ChainId targetChain; + final Asset wrappedAsset; + final Asset originalAsset; + final String amount; + final String sender; + final String recipient; + final int burnTimestamp; + final int confirmations; + final int requiredConfirmations; + + const BurnReceipt({ + required this.id, + required this.txHash, + required this.sourceChain, + required this.targetChain, + required this.wrappedAsset, + required this.originalAsset, + required this.amount, + required this.sender, + required this.recipient, + required this.burnTimestamp, + required this.confirmations, + required this.requiredConfirmations, + }); + + factory BurnReceipt.fromJson(Map json) => BurnReceipt( + id: json['id'] as String, + txHash: json['txHash'] as String, + sourceChain: ChainId.values.firstWhere((e) => e.name == json['sourceChain']), + targetChain: ChainId.values.firstWhere((e) => e.name == json['targetChain']), + wrappedAsset: Asset.fromJson(json['wrappedAsset']), + originalAsset: Asset.fromJson(json['originalAsset']), + amount: json['amount'] as String, + sender: json['sender'] as String, + recipient: json['recipient'] as String, + burnTimestamp: json['burnTimestamp'] as int, + confirmations: json['confirmations'] as int, + requiredConfirmations: json['requiredConfirmations'] as int, + ); + + Map toJson() => { + 'id': id, + 'txHash': txHash, + 'sourceChain': sourceChain.name, + 'targetChain': targetChain.name, + 'wrappedAsset': wrappedAsset.toJson(), + 'originalAsset': originalAsset.toJson(), + 'amount': amount, + 'sender': sender, + 'recipient': recipient, + 'burnTimestamp': burnTimestamp, + 'confirmations': confirmations, + 'requiredConfirmations': requiredConfirmations, + }; +} + +/// Burn proof +class BurnProof { + final BurnReceipt burnReceipt; + final List merkleProof; + final String blockHeader; + final List signatures; + + const BurnProof({ + required this.burnReceipt, + required this.merkleProof, + required this.blockHeader, + required this.signatures, + }); + + factory BurnProof.fromJson(Map json) => BurnProof( + burnReceipt: BurnReceipt.fromJson(json['burnReceipt']), + merkleProof: List.from(json['merkleProof']), + blockHeader: json['blockHeader'] as String, + signatures: (json['signatures'] as List) + .map((e) => ValidatorSignature.fromJson(e)) + .toList(), + ); + + Map toJson() => { + 'burnReceipt': burnReceipt.toJson(), + 'merkleProof': merkleProof, + 'blockHeader': blockHeader, + 'signatures': signatures.map((s) => s.toJson()).toList(), + }; +} + +/// Transfer record +class Transfer { + final String id; + final TransferDirection direction; + final TransferStatus status; + final ChainId sourceChain; + final ChainId targetChain; + final Asset asset; + final String amount; + final String sender; + final String recipient; + final String? sourceTxHash; + final String? targetTxHash; + final String fee; + final Asset feeAsset; + final int createdAt; + final int updatedAt; + final int? completedAt; + final String? errorMessage; + + const Transfer({ + required this.id, + required this.direction, + required this.status, + required this.sourceChain, + required this.targetChain, + required this.asset, + required this.amount, + required this.sender, + required this.recipient, + this.sourceTxHash, + this.targetTxHash, + required this.fee, + required this.feeAsset, + required this.createdAt, + required this.updatedAt, + this.completedAt, + this.errorMessage, + }); + + factory Transfer.fromJson(Map json) => Transfer( + id: json['id'] as String, + direction: json['direction'] == 'lock_mint' + ? TransferDirection.lockMint + : TransferDirection.burnUnlock, + status: TransferStatus.values.firstWhere((e) => e.name == json['status']), + sourceChain: ChainId.values.firstWhere((e) => e.name == json['sourceChain']), + targetChain: ChainId.values.firstWhere((e) => e.name == json['targetChain']), + asset: Asset.fromJson(json['asset']), + amount: json['amount'] as String, + sender: json['sender'] as String, + recipient: json['recipient'] as String, + sourceTxHash: json['sourceTxHash'] as String?, + targetTxHash: json['targetTxHash'] as String?, + fee: json['fee'] as String, + feeAsset: Asset.fromJson(json['feeAsset']), + createdAt: json['createdAt'] as int, + updatedAt: json['updatedAt'] as int, + completedAt: json['completedAt'] as int?, + errorMessage: json['errorMessage'] as String?, + ); +} + +// ==================== Fee Types ==================== + +/// Fee estimate +class FeeEstimate { + final String bridgeFee; + final String gasFeeSource; + final String gasFeeTarget; + final String totalFee; + final Asset feeAsset; + final int estimatedTime; + final String? exchangeRate; + + const FeeEstimate({ + required this.bridgeFee, + required this.gasFeeSource, + required this.gasFeeTarget, + required this.totalFee, + required this.feeAsset, + required this.estimatedTime, + this.exchangeRate, + }); + + factory FeeEstimate.fromJson(Map json) => FeeEstimate( + bridgeFee: json['bridgeFee'] as String, + gasFeeSource: json['gasFeeSource'] as String, + gasFeeTarget: json['gasFeeTarget'] as String, + totalFee: json['totalFee'] as String, + feeAsset: Asset.fromJson(json['feeAsset']), + estimatedTime: json['estimatedTime'] as int, + exchangeRate: json['exchangeRate'] as String?, + ); +} + +/// Exchange rate +class ExchangeRate { + final Asset fromAsset; + final Asset toAsset; + final String rate; + final String inverseRate; + final int lastUpdated; + final String source; + + const ExchangeRate({ + required this.fromAsset, + required this.toAsset, + required this.rate, + required this.inverseRate, + required this.lastUpdated, + required this.source, + }); + + factory ExchangeRate.fromJson(Map json) => ExchangeRate( + fromAsset: Asset.fromJson(json['fromAsset']), + toAsset: Asset.fromJson(json['toAsset']), + rate: json['rate'] as String, + inverseRate: json['inverseRate'] as String, + lastUpdated: json['lastUpdated'] as int, + source: json['source'] as String, + ); +} + +/// Signed transaction +class SignedTransaction { + final String txHash; + final ChainId chain; + final String from; + final String to; + final String value; + final String data; + final String gasLimit; + final String? gasPrice; + final String? maxFeePerGas; + final String? maxPriorityFeePerGas; + final int nonce; + final String signature; + + const SignedTransaction({ + required this.txHash, + required this.chain, + required this.from, + required this.to, + required this.value, + required this.data, + required this.gasLimit, + this.gasPrice, + this.maxFeePerGas, + this.maxPriorityFeePerGas, + required this.nonce, + required this.signature, + }); + + factory SignedTransaction.fromJson(Map json) => SignedTransaction( + txHash: json['txHash'] as String, + chain: ChainId.values.firstWhere((e) => e.name == json['chain']), + from: json['from'] as String, + to: json['to'] as String, + value: json['value'] as String, + data: json['data'] as String, + gasLimit: json['gasLimit'] as String, + gasPrice: json['gasPrice'] as String?, + maxFeePerGas: json['maxFeePerGas'] as String?, + maxPriorityFeePerGas: json['maxPriorityFeePerGas'] as String?, + nonce: json['nonce'] as int, + signature: json['signature'] as String, + ); +} + +// ==================== Options & Filter ==================== + +/// Lock options +class LockOptions { + final String? recipient; + final int? deadline; + final double? slippage; + + const LockOptions({this.recipient, this.deadline, this.slippage}); + + Map toJson() => { + if (recipient != null) 'recipient': recipient, + if (deadline != null) 'deadline': deadline, + if (slippage != null) 'slippage': slippage, + }; +} + +/// Mint options +class MintOptions { + final String? gasLimit; + final String? maxFeePerGas; + final String? maxPriorityFeePerGas; + + const MintOptions({this.gasLimit, this.maxFeePerGas, this.maxPriorityFeePerGas}); + + Map toJson() => { + if (gasLimit != null) 'gasLimit': gasLimit, + if (maxFeePerGas != null) 'maxFeePerGas': maxFeePerGas, + if (maxPriorityFeePerGas != null) 'maxPriorityFeePerGas': maxPriorityFeePerGas, + }; +} + +/// Burn options +class BurnOptions { + final String? recipient; + final int? deadline; + + const BurnOptions({this.recipient, this.deadline}); + + Map toJson() => { + if (recipient != null) 'recipient': recipient, + if (deadline != null) 'deadline': deadline, + }; +} + +/// Unlock options +class UnlockOptions { + final String? gasLimit; + final String? gasPrice; + + const UnlockOptions({this.gasLimit, this.gasPrice}); + + Map toJson() => { + if (gasLimit != null) 'gasLimit': gasLimit, + if (gasPrice != null) 'gasPrice': gasPrice, + }; +} + +/// Transfer filter +class TransferFilter { + final TransferStatus? status; + final ChainId? sourceChain; + final ChainId? targetChain; + final String? asset; + final String? sender; + final String? recipient; + final int? limit; + final int? offset; + + const TransferFilter({ + this.status, + this.sourceChain, + this.targetChain, + this.asset, + this.sender, + this.recipient, + this.limit, + this.offset, + }); +} + +// ==================== Config ==================== + +/// Bridge configuration +class BridgeConfig { + final String apiKey; + final String endpoint; + final Duration timeout; + final int retries; + final bool debug; + + const BridgeConfig({ + required this.apiKey, + this.endpoint = 'https://bridge.synor.io/v1', + this.timeout = const Duration(seconds: 60), + this.retries = 3, + this.debug = false, + }); +} + +// ==================== Error ==================== + +/// Bridge error +class BridgeException implements Exception { + final String message; + final String? code; + final int? statusCode; + + const BridgeException(this.message, {this.code, this.statusCode}); + + bool get isConfirmationsPending => code == 'CONFIRMATIONS_PENDING'; + + @override + String toString() => 'BridgeException: $message'; +} diff --git a/sdk/flutter/lib/src/database/client.dart b/sdk/flutter/lib/src/database/client.dart new file mode 100644 index 0000000..6eaea49 --- /dev/null +++ b/sdk/flutter/lib/src/database/client.dart @@ -0,0 +1,246 @@ +/// Synor Database SDK Client for Flutter +/// +/// Multi-model database with Key-Value, Document, Vector, and Time Series stores. +library synor_database; + +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'types.dart'; + +export 'types.dart'; + +/// Synor Database Client +class SynorDatabase { + final DatabaseConfig config; + final http.Client _client; + bool _closed = false; + + late final KeyValueStore kv; + late final DocumentStore documents; + late final VectorStore vectors; + late final TimeSeriesStore timeseries; + + SynorDatabase(this.config) : _client = http.Client() { + kv = KeyValueStore(this); + documents = DocumentStore(this); + vectors = VectorStore(this); + timeseries = TimeSeriesStore(this); + } + + /// Close the client + void close() { + _closed = true; + _client.close(); + } + + /// Check if closed + bool get isClosed => _closed; + + /// Health check + Future healthCheck() async { + try { + final resp = await _request('GET', '/health'); + return resp['status'] == 'healthy'; + } catch (_) { + return false; + } + } + + Future> _request( + String method, + String path, [ + Map? body, + ]) async { + if (_closed) throw DatabaseException('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 ?? DatabaseException('Unknown error'); + } + + Future> _doRequest( + String method, + String path, + Map? 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); + break; + case 'POST': + response = await _client.post(uri, headers: headers, body: jsonEncode(body)); + break; + case 'PUT': + response = await _client.put(uri, headers: headers, body: jsonEncode(body)); + break; + case 'PATCH': + response = await _client.patch(uri, headers: headers, body: jsonEncode(body)); + break; + case 'DELETE': + response = await _client.delete(uri, headers: headers, body: body != null ? jsonEncode(body) : null); + break; + default: + throw DatabaseException('Unknown method: $method'); + } + + if (response.statusCode >= 400) { + final error = jsonDecode(response.body) as Map; + throw DatabaseException( + error['message'] as String? ?? 'HTTP ${response.statusCode}', + code: error['code'] as String?, + statusCode: response.statusCode, + ); + } + + return jsonDecode(response.body) as Map; + } +} + +/// Key-Value Store +class KeyValueStore { + final SynorDatabase _db; + KeyValueStore(this._db); + + Future get(String key) async { + final resp = await _db._request('GET', '/kv/${Uri.encodeComponent(key)}'); + return resp['value']; + } + + Future set(String key, dynamic value, {int? ttl}) async { + await _db._request('PUT', '/kv/${Uri.encodeComponent(key)}', { + 'key': key, + 'value': value, + if (ttl != null) 'ttl': ttl, + }); + } + + Future delete(String key) async { + await _db._request('DELETE', '/kv/${Uri.encodeComponent(key)}'); + } + + Future> list(String prefix) async { + final resp = await _db._request('GET', '/kv?prefix=${Uri.encodeComponent(prefix)}'); + return (resp['items'] as List).map((e) => KeyValue.fromJson(e)).toList(); + } +} + +/// Document Store +class DocumentStore { + final SynorDatabase _db; + DocumentStore(this._db); + + Future create(String collection, Map document) async { + final resp = await _db._request( + 'POST', + '/collections/${Uri.encodeComponent(collection)}/documents', + document, + ); + return resp['id'] as String; + } + + Future get(String collection, String id) async { + final resp = await _db._request( + 'GET', + '/collections/${Uri.encodeComponent(collection)}/documents/${Uri.encodeComponent(id)}', + ); + return Document.fromJson(resp); + } + + Future update(String collection, String id, Map update) async { + await _db._request( + 'PATCH', + '/collections/${Uri.encodeComponent(collection)}/documents/${Uri.encodeComponent(id)}', + update, + ); + } + + Future delete(String collection, String id) async { + await _db._request( + 'DELETE', + '/collections/${Uri.encodeComponent(collection)}/documents/${Uri.encodeComponent(id)}', + ); + } + + Future> query(String collection, Query query) async { + final resp = await _db._request( + 'POST', + '/collections/${Uri.encodeComponent(collection)}/query', + query.toJson(), + ); + return (resp['documents'] as List).map((e) => Document.fromJson(e)).toList(); + } +} + +/// Vector Store +class VectorStore { + final SynorDatabase _db; + VectorStore(this._db); + + Future upsert(String collection, List vectors) async { + await _db._request( + 'POST', + '/vectors/${Uri.encodeComponent(collection)}/upsert', + {'vectors': vectors.map((v) => v.toJson()).toList()}, + ); + } + + Future> search(String collection, List vector, int k) async { + final resp = await _db._request( + 'POST', + '/vectors/${Uri.encodeComponent(collection)}/search', + {'vector': vector, 'k': k}, + ); + return (resp['results'] as List).map((e) => SearchResult.fromJson(e)).toList(); + } + + Future delete(String collection, List ids) async { + await _db._request( + 'DELETE', + '/vectors/${Uri.encodeComponent(collection)}', + {'ids': ids}, + ); + } +} + +/// Time Series Store +class TimeSeriesStore { + final SynorDatabase _db; + TimeSeriesStore(this._db); + + Future write(String series, List points) async { + await _db._request( + 'POST', + '/timeseries/${Uri.encodeComponent(series)}/write', + {'points': points.map((p) => p.toJson()).toList()}, + ); + } + + Future> query(String series, TimeRange range, {Aggregation? aggregation}) async { + final body = {'range': range.toJson()}; + if (aggregation != null) body['aggregation'] = aggregation.toJson(); + + final resp = await _db._request( + 'POST', + '/timeseries/${Uri.encodeComponent(series)}/query', + body, + ); + return (resp['points'] as List).map((e) => DataPoint.fromJson(e)).toList(); + } +} diff --git a/sdk/flutter/lib/src/database/types.dart b/sdk/flutter/lib/src/database/types.dart new file mode 100644 index 0000000..13934f4 --- /dev/null +++ b/sdk/flutter/lib/src/database/types.dart @@ -0,0 +1,224 @@ +/// Synor Database SDK Types for Flutter +/// +/// Multi-model database with Key-Value, Document, Vector, and Time Series stores. +library synor_database_types; + +// ==================== Config ==================== + +/// Database configuration +class DatabaseConfig { + final String apiKey; + final String endpoint; + final Duration timeout; + final int retries; + final bool debug; + + const DatabaseConfig({ + required this.apiKey, + this.endpoint = 'https://db.synor.io/v1', + this.timeout = const Duration(seconds: 60), + this.retries = 3, + this.debug = false, + }); +} + +// ==================== Key-Value Types ==================== + +/// Key-Value entry +class KeyValue { + final String key; + final dynamic value; + final int? ttl; + final DateTime? createdAt; + final DateTime? updatedAt; + + const KeyValue({ + required this.key, + required this.value, + this.ttl, + this.createdAt, + this.updatedAt, + }); + + factory KeyValue.fromJson(Map json) => KeyValue( + key: json['key'] as String, + value: json['value'], + ttl: json['ttl'] as int?, + createdAt: json['createdAt'] != null + ? DateTime.fromMillisecondsSinceEpoch(json['createdAt'] as int) + : null, + updatedAt: json['updatedAt'] != null + ? DateTime.fromMillisecondsSinceEpoch(json['updatedAt'] as int) + : null, + ); +} + +// ==================== Document Types ==================== + +/// Document record +class Document { + final String id; + final String collection; + final Map data; + final DateTime createdAt; + final DateTime updatedAt; + + const Document({ + required this.id, + required this.collection, + required this.data, + required this.createdAt, + required this.updatedAt, + }); + + factory Document.fromJson(Map json) => Document( + id: json['id'] as String, + collection: json['collection'] as String, + data: Map.from(json['data'] as Map), + createdAt: DateTime.fromMillisecondsSinceEpoch(json['createdAt'] as int), + updatedAt: DateTime.fromMillisecondsSinceEpoch(json['updatedAt'] as int), + ); +} + +/// Query builder +class Query { + final Map? filter; + final Map? sort; + final int? limit; + final int? offset; + final List? projection; + + const Query({ + this.filter, + this.sort, + this.limit, + this.offset, + this.projection, + }); + + Map toJson() { + final json = {}; + if (filter != null) json['filter'] = filter; + if (sort != null) json['sort'] = sort; + if (limit != null) json['limit'] = limit; + if (offset != null) json['offset'] = offset; + if (projection != null) json['projection'] = projection; + return json; + } +} + +// ==================== Vector Types ==================== + +/// Vector entry for upsert +class VectorEntry { + final String id; + final List vector; + final Map? metadata; + + const VectorEntry({ + required this.id, + required this.vector, + this.metadata, + }); + + Map toJson() => { + 'id': id, + 'vector': vector, + if (metadata != null) 'metadata': metadata, + }; +} + +/// Vector search result +class SearchResult { + final String id; + final double score; + final List? vector; + final Map? metadata; + + const SearchResult({ + required this.id, + required this.score, + this.vector, + this.metadata, + }); + + factory SearchResult.fromJson(Map json) => SearchResult( + id: json['id'] as String, + score: (json['score'] as num).toDouble(), + vector: json['vector'] != null + ? List.from((json['vector'] as List).map((e) => (e as num).toDouble())) + : null, + metadata: json['metadata'] != null + ? Map.from(json['metadata'] as Map) + : null, + ); +} + +// ==================== Time Series Types ==================== + +/// Data point +class DataPoint { + final DateTime timestamp; + final double value; + final Map? tags; + + const DataPoint({ + required this.timestamp, + required this.value, + this.tags, + }); + + factory DataPoint.fromJson(Map json) => DataPoint( + timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int), + value: (json['value'] as num).toDouble(), + tags: json['tags'] != null + ? Map.from(json['tags'] as Map) + : null, + ); + + Map toJson() => { + 'timestamp': timestamp.millisecondsSinceEpoch, + 'value': value, + if (tags != null) 'tags': tags, + }; +} + +/// Time range +class TimeRange { + final DateTime start; + final DateTime end; + + const TimeRange({required this.start, required this.end}); + + Map toJson() => { + 'start': start.millisecondsSinceEpoch, + 'end': end.millisecondsSinceEpoch, + }; +} + +/// Aggregation options +class Aggregation { + final String function; // sum, avg, min, max, count + final String interval; // 1m, 5m, 1h, 1d + + const Aggregation({required this.function, required this.interval}); + + Map toJson() => { + 'function': function, + 'interval': interval, + }; +} + +// ==================== Error Types ==================== + +/// Database error +class DatabaseException implements Exception { + final String message; + final String? code; + final int? statusCode; + + const DatabaseException(this.message, {this.code, this.statusCode}); + + @override + String toString() => 'DatabaseException: $message'; +} diff --git a/sdk/flutter/lib/src/hosting/client.dart b/sdk/flutter/lib/src/hosting/client.dart new file mode 100644 index 0000000..8004fc7 --- /dev/null +++ b/sdk/flutter/lib/src/hosting/client.dart @@ -0,0 +1,253 @@ +/// Synor Hosting SDK Client for Flutter +/// +/// Decentralized web hosting with domain management, DNS, and deployments. +library synor_hosting; + +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'types.dart'; + +export 'types.dart'; + +/// Synor Hosting Client +class SynorHosting { + final HostingConfig config; + final http.Client _client; + bool _closed = false; + + SynorHosting(this.config) : _client = http.Client(); + + // ==================== Domain Operations ==================== + + Future checkAvailability(String name) async { + final resp = await _request('GET', '/domains/check/${Uri.encodeComponent(name)}'); + return DomainAvailability.fromJson(resp); + } + + Future registerDomain(String name, {RegisterDomainOptions? options}) async { + final body = {'name': name}; + if (options != null) body.addAll(options.toJson()); + final resp = await _request('POST', '/domains', body); + return Domain.fromJson(resp); + } + + Future getDomain(String name) async { + final resp = await _request('GET', '/domains/${Uri.encodeComponent(name)}'); + return Domain.fromJson(resp); + } + + Future> listDomains() async { + final resp = await _request('GET', '/domains'); + return (resp['domains'] as List).map((e) => Domain.fromJson(e)).toList(); + } + + Future updateDomainRecord(String name, DomainRecord record) async { + final resp = await _request('PUT', '/domains/${Uri.encodeComponent(name)}/record', record.toJson()); + return Domain.fromJson(resp); + } + + Future resolveDomain(String name) async { + final resp = await _request('GET', '/domains/${Uri.encodeComponent(name)}/resolve'); + return DomainRecord.fromJson(resp); + } + + Future renewDomain(String name, int years) async { + final resp = await _request('POST', '/domains/${Uri.encodeComponent(name)}/renew', {'years': years}); + return Domain.fromJson(resp); + } + + // ==================== DNS Operations ==================== + + Future getDnsZone(String domain) async { + final resp = await _request('GET', '/dns/${Uri.encodeComponent(domain)}'); + return DnsZone.fromJson(resp); + } + + Future setDnsRecords(String domain, List records) async { + final resp = await _request('PUT', '/dns/${Uri.encodeComponent(domain)}', { + 'records': records.map((r) => r.toJson()).toList(), + }); + return DnsZone.fromJson(resp); + } + + Future addDnsRecord(String domain, DnsRecord record) async { + final resp = await _request('POST', '/dns/${Uri.encodeComponent(domain)}/records', record.toJson()); + return DnsZone.fromJson(resp); + } + + Future deleteDnsRecord(String domain, String recordType, String name) async { + final path = '/dns/${Uri.encodeComponent(domain)}/records/$recordType/${Uri.encodeComponent(name)}'; + final resp = await _request('DELETE', path); + return DnsZone.fromJson(resp); + } + + // ==================== Deployment Operations ==================== + + Future deploy(String cid, {DeployOptions? options}) async { + final body = {'cid': cid}; + if (options != null) body.addAll(options.toJson()); + final resp = await _request('POST', '/deployments', body); + return Deployment.fromJson(resp); + } + + Future getDeployment(String id) async { + final resp = await _request('GET', '/deployments/${Uri.encodeComponent(id)}'); + return Deployment.fromJson(resp); + } + + Future> listDeployments({String? domain}) async { + final path = domain != null ? '/deployments?domain=${Uri.encodeComponent(domain)}' : '/deployments'; + final resp = await _request('GET', path); + return (resp['deployments'] as List).map((e) => Deployment.fromJson(e)).toList(); + } + + Future rollback(String domain, String deploymentId) async { + final resp = await _request('POST', '/deployments/${Uri.encodeComponent(deploymentId)}/rollback', { + 'domain': domain, + }); + return Deployment.fromJson(resp); + } + + Future deleteDeployment(String id) async { + await _request('DELETE', '/deployments/${Uri.encodeComponent(id)}'); + } + + // ==================== SSL Operations ==================== + + Future provisionSsl(String domain, {ProvisionSslOptions? options}) async { + final resp = await _request('POST', '/ssl/${Uri.encodeComponent(domain)}', options?.toJson()); + return Certificate.fromJson(resp); + } + + Future getCertificate(String domain) async { + final resp = await _request('GET', '/ssl/${Uri.encodeComponent(domain)}'); + return Certificate.fromJson(resp); + } + + Future renewCertificate(String domain) async { + final resp = await _request('POST', '/ssl/${Uri.encodeComponent(domain)}/renew'); + return Certificate.fromJson(resp); + } + + Future deleteCertificate(String domain) async { + await _request('DELETE', '/ssl/${Uri.encodeComponent(domain)}'); + } + + // ==================== Site Configuration ==================== + + Future> getSiteConfig(String domain) async { + return await _request('GET', '/sites/${Uri.encodeComponent(domain)}/config'); + } + + Future> updateSiteConfig(String domain, Map siteConfig) async { + return await _request('PATCH', '/sites/${Uri.encodeComponent(domain)}/config', siteConfig); + } + + Future purgeCache(String domain, {List? paths}) async { + final resp = await _request('DELETE', '/sites/${Uri.encodeComponent(domain)}/cache', { + if (paths != null) 'paths': paths, + }); + return resp['purged'] as int; + } + + // ==================== Analytics ==================== + + Future getAnalytics(String domain, {AnalyticsOptions? options}) async { + var path = '/sites/${Uri.encodeComponent(domain)}/analytics'; + final params = []; + if (options?.period != null) params.add('period=${Uri.encodeComponent(options!.period!)}'); + if (options?.start != null) params.add('start=${Uri.encodeComponent(options!.start!)}'); + if (options?.end != null) params.add('end=${Uri.encodeComponent(options!.end!)}'); + if (params.isNotEmpty) path = '$path?${params.join('&')}'; + + final resp = await _request('GET', path); + return AnalyticsData.fromJson(resp); + } + + // ==================== Lifecycle ==================== + + void close() { + _closed = true; + _client.close(); + } + + bool get isClosed => _closed; + + Future healthCheck() async { + try { + final resp = await _request('GET', '/health'); + return resp['status'] == 'healthy'; + } catch (_) { + return false; + } + } + + // ==================== Private Methods ==================== + + Future> _request( + String method, + String path, [ + Map? body, + ]) async { + if (_closed) throw HostingException('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 ?? HostingException('Unknown error'); + } + + Future> _doRequest( + String method, + String path, + Map? 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); + break; + case 'POST': + response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null); + break; + case 'PUT': + response = await _client.put(uri, headers: headers, body: jsonEncode(body)); + break; + case 'PATCH': + response = await _client.patch(uri, headers: headers, body: jsonEncode(body)); + break; + case 'DELETE': + response = await _client.delete(uri, headers: headers, body: body != null ? jsonEncode(body) : null); + break; + default: + throw HostingException('Unknown method: $method'); + } + + if (response.statusCode >= 400) { + final error = jsonDecode(response.body) as Map; + throw HostingException( + error['message'] as String? ?? 'HTTP ${response.statusCode}', + code: error['code'] as String?, + statusCode: response.statusCode, + ); + } + + if (response.body.isEmpty) return {}; + return jsonDecode(response.body) as Map; + } +} diff --git a/sdk/flutter/lib/src/hosting/types.dart b/sdk/flutter/lib/src/hosting/types.dart new file mode 100644 index 0000000..8d31d89 --- /dev/null +++ b/sdk/flutter/lib/src/hosting/types.dart @@ -0,0 +1,397 @@ +/// Synor Hosting SDK Types for Flutter +/// +/// Decentralized web hosting with domain management, DNS, and deployments. +library synor_hosting_types; + +// ==================== Config ==================== + +/// Hosting configuration +class HostingConfig { + final String apiKey; + final String endpoint; + final Duration timeout; + final int retries; + final bool debug; + + const HostingConfig({ + required this.apiKey, + this.endpoint = 'https://hosting.synor.io/v1', + this.timeout = const Duration(seconds: 60), + this.retries = 3, + this.debug = false, + }); +} + +// ==================== Domain Types ==================== + +/// Domain status +enum DomainStatus { pending, active, expired, suspended } + +/// Domain information +class Domain { + final String name; + final DomainStatus status; + final String owner; + final DateTime registeredAt; + final DateTime expiresAt; + final bool autoRenew; + final DomainRecord? records; + + const Domain({ + required this.name, + required this.status, + required this.owner, + required this.registeredAt, + required this.expiresAt, + required this.autoRenew, + this.records, + }); + + factory Domain.fromJson(Map json) => Domain( + name: json['name'] as String, + status: DomainStatus.values.firstWhere( + (e) => e.name == json['status'], + orElse: () => DomainStatus.pending), + owner: json['owner'] as String, + registeredAt: DateTime.fromMillisecondsSinceEpoch(json['registeredAt'] as int), + expiresAt: DateTime.fromMillisecondsSinceEpoch(json['expiresAt'] as int), + autoRenew: json['autoRenew'] as bool? ?? false, + records: json['records'] != null ? DomainRecord.fromJson(json['records']) : null, + ); +} + +/// Domain record +class DomainRecord { + final String? cid; + final List? ipv4; + final List? ipv6; + final String? cname; + final List? txt; + final Map? metadata; + + const DomainRecord({ + this.cid, + this.ipv4, + this.ipv6, + this.cname, + this.txt, + this.metadata, + }); + + factory DomainRecord.fromJson(Map json) => DomainRecord( + cid: json['cid'] as String?, + ipv4: (json['ipv4'] as List?)?.cast(), + ipv6: (json['ipv6'] as List?)?.cast(), + cname: json['cname'] as String?, + txt: (json['txt'] as List?)?.cast(), + metadata: (json['metadata'] as Map?)?.cast(), + ); + + Map toJson() => { + if (cid != null) 'cid': cid, + if (ipv4 != null) 'ipv4': ipv4, + if (ipv6 != null) 'ipv6': ipv6, + if (cname != null) 'cname': cname, + if (txt != null) 'txt': txt, + if (metadata != null) 'metadata': metadata, + }; +} + +/// Domain availability +class DomainAvailability { + final String name; + final bool available; + final double? price; + final bool premium; + + const DomainAvailability({ + required this.name, + required this.available, + this.price, + this.premium = false, + }); + + factory DomainAvailability.fromJson(Map json) => DomainAvailability( + name: json['name'] as String, + available: json['available'] as bool, + price: (json['price'] as num?)?.toDouble(), + premium: json['premium'] as bool? ?? false, + ); +} + +/// Options for registering a domain +class RegisterDomainOptions { + final int? years; + final bool? autoRenew; + + const RegisterDomainOptions({this.years, this.autoRenew}); + + Map toJson() => { + if (years != null) 'years': years, + if (autoRenew != null) 'autoRenew': autoRenew, + }; +} + +// ==================== DNS Types ==================== + +/// DNS record type +enum DnsRecordType { A, AAAA, CNAME, TXT, MX, NS, SRV, CAA } + +/// DNS record +class DnsRecord { + final DnsRecordType type; + final String name; + final String value; + final int ttl; + final int? priority; + + const DnsRecord({ + required this.type, + required this.name, + required this.value, + this.ttl = 3600, + this.priority, + }); + + factory DnsRecord.fromJson(Map json) => DnsRecord( + type: DnsRecordType.values.firstWhere((e) => e.name == json['type']), + name: json['name'] as String, + value: json['value'] as String, + ttl: json['ttl'] as int? ?? 3600, + priority: json['priority'] as int?, + ); + + Map toJson() => { + 'type': type.name, + 'name': name, + 'value': value, + 'ttl': ttl, + if (priority != null) 'priority': priority, + }; +} + +/// DNS zone +class DnsZone { + final String domain; + final List records; + final DateTime updatedAt; + + const DnsZone({ + required this.domain, + required this.records, + required this.updatedAt, + }); + + factory DnsZone.fromJson(Map json) => DnsZone( + domain: json['domain'] as String, + records: (json['records'] as List).map((e) => DnsRecord.fromJson(e)).toList(), + updatedAt: DateTime.fromMillisecondsSinceEpoch(json['updatedAt'] as int), + ); +} + +// ==================== Deployment Types ==================== + +/// Deployment status +enum DeploymentStatus { pending, building, deploying, active, failed, inactive } + +/// Deployment information +class Deployment { + final String id; + final String domain; + final String cid; + final DeploymentStatus status; + final String url; + final DateTime createdAt; + final DateTime updatedAt; + final String? buildLogs; + final String? errorMessage; + + const Deployment({ + required this.id, + required this.domain, + required this.cid, + required this.status, + required this.url, + required this.createdAt, + required this.updatedAt, + this.buildLogs, + this.errorMessage, + }); + + factory Deployment.fromJson(Map json) => Deployment( + id: json['id'] as String, + domain: json['domain'] as String, + cid: json['cid'] as String, + status: DeploymentStatus.values.firstWhere( + (e) => e.name == json['status'], + orElse: () => DeploymentStatus.pending), + url: json['url'] as String, + createdAt: DateTime.fromMillisecondsSinceEpoch(json['createdAt'] as int), + updatedAt: DateTime.fromMillisecondsSinceEpoch(json['updatedAt'] as int), + buildLogs: json['buildLogs'] as String?, + errorMessage: json['errorMessage'] as String?, + ); +} + +/// Deploy options +class DeployOptions { + final String? domain; + final String? subdomain; + final bool? spa; + final bool? cleanUrls; + final bool? trailingSlash; + + const DeployOptions({ + this.domain, + this.subdomain, + this.spa, + this.cleanUrls, + this.trailingSlash, + }); + + Map toJson() => { + if (domain != null) 'domain': domain, + if (subdomain != null) 'subdomain': subdomain, + if (spa != null) 'spa': spa, + if (cleanUrls != null) 'cleanUrls': cleanUrls, + if (trailingSlash != null) 'trailingSlash': trailingSlash, + }; +} + +// ==================== SSL Types ==================== + +/// Certificate status +enum CertificateStatus { pending, issued, expired, revoked } + +/// SSL certificate +class Certificate { + final String domain; + final CertificateStatus status; + final bool autoRenew; + final String issuer; + final DateTime? issuedAt; + final DateTime? expiresAt; + final String? fingerprint; + + const Certificate({ + required this.domain, + required this.status, + required this.autoRenew, + required this.issuer, + this.issuedAt, + this.expiresAt, + this.fingerprint, + }); + + factory Certificate.fromJson(Map json) => Certificate( + domain: json['domain'] as String, + status: CertificateStatus.values.firstWhere( + (e) => e.name == json['status'], + orElse: () => CertificateStatus.pending), + autoRenew: json['autoRenew'] as bool? ?? false, + issuer: json['issuer'] as String, + issuedAt: json['issuedAt'] != null + ? DateTime.fromMillisecondsSinceEpoch(json['issuedAt'] as int) + : null, + expiresAt: json['expiresAt'] != null + ? DateTime.fromMillisecondsSinceEpoch(json['expiresAt'] as int) + : null, + fingerprint: json['fingerprint'] as String?, + ); +} + +/// Provision SSL options +class ProvisionSslOptions { + final bool? includeWww; + final bool? autoRenew; + + const ProvisionSslOptions({this.includeWww, this.autoRenew}); + + Map toJson() => { + if (includeWww != null) 'includeWww': includeWww, + if (autoRenew != null) 'autoRenew': autoRenew, + }; +} + +// ==================== Analytics Types ==================== + +/// Analytics data +class AnalyticsData { + final String domain; + final String period; + final int pageViews; + final int uniqueVisitors; + final int bandwidth; + final List topPages; + final List topReferrers; + final List topCountries; + + const AnalyticsData({ + required this.domain, + required this.period, + required this.pageViews, + required this.uniqueVisitors, + required this.bandwidth, + required this.topPages, + required this.topReferrers, + required this.topCountries, + }); + + factory AnalyticsData.fromJson(Map json) => AnalyticsData( + domain: json['domain'] as String, + period: json['period'] as String, + pageViews: json['pageViews'] as int, + uniqueVisitors: json['uniqueVisitors'] as int, + bandwidth: json['bandwidth'] as int, + topPages: (json['topPages'] as List).map((e) => PageView.fromJson(e)).toList(), + topReferrers: (json['topReferrers'] as List).map((e) => Referrer.fromJson(e)).toList(), + topCountries: (json['topCountries'] as List).map((e) => Country.fromJson(e)).toList(), + ); +} + +class PageView { + final String path; + final int views; + const PageView({required this.path, required this.views}); + factory PageView.fromJson(Map json) => + PageView(path: json['path'] as String, views: json['views'] as int); +} + +class Referrer { + final String referrer; + final int count; + const Referrer({required this.referrer, required this.count}); + factory Referrer.fromJson(Map json) => + Referrer(referrer: json['referrer'] as String, count: json['count'] as int); +} + +class Country { + final String country; + final int count; + const Country({required this.country, required this.count}); + factory Country.fromJson(Map json) => + Country(country: json['country'] as String, count: json['count'] as int); +} + +/// Analytics options +class AnalyticsOptions { + final String? period; + final String? start; + final String? end; + + const AnalyticsOptions({this.period, this.start, this.end}); +} + +// ==================== Error Types ==================== + +/// Hosting error +class HostingException implements Exception { + final String message; + final String? code; + final int? statusCode; + + const HostingException(this.message, {this.code, this.statusCode}); + + @override + String toString() => 'HostingException: $message'; +} diff --git a/sdk/flutter/lib/synor_sdk.dart b/sdk/flutter/lib/synor_sdk.dart index 51b0cd7..3e9b355 100644 --- a/sdk/flutter/lib/synor_sdk.dart +++ b/sdk/flutter/lib/synor_sdk.dart @@ -5,6 +5,9 @@ /// - **Wallet**: Key management and transaction signing /// - **RPC**: Blockchain queries and subscriptions /// - **Storage**: IPFS-compatible decentralized storage +/// - **Database**: Multi-model database (KV, Document, Vector, TimeSeries) +/// - **Hosting**: Decentralized web hosting +/// - **Bridge**: Cross-chain asset transfers /// /// ## Quick Start /// @@ -28,10 +31,25 @@ /// final upload = await storage.upload(Uint8List.fromList(data)); /// print('CID: ${upload.cid}'); /// +/// // Database operations +/// final db = SynorDatabase(DatabaseConfig(apiKey: 'your-api-key')); +/// await db.kv.set('key', 'value'); +/// +/// // Hosting operations +/// final hosting = SynorHosting(HostingConfig(apiKey: 'your-api-key')); +/// final deployment = await hosting.deploy('Qm...'); +/// +/// // Bridge operations +/// final bridge = SynorBridge(BridgeConfig(apiKey: 'your-api-key')); +/// final transfer = await bridge.bridgeTo('SYNR', '1000', ChainId.ethereum, '0x...'); +/// /// // Clean up /// wallet.close(); /// rpc.close(); /// storage.close(); +/// db.close(); +/// hosting.close(); +/// bridge.close(); /// } /// ``` /// @@ -42,6 +60,7 @@ /// - RPC: `RpcNetwork`, `RpcPriority`, `RpcTransaction` /// - Storage: `PinStatus`, `HashAlgorithm`, `EntryType` /// - Compute: `Precision`, `ProcessorType`, `Priority` (as `ComputePriority`) +/// - Bridge: `ChainId`, `TransferStatus`, `TransferDirection` library synor_sdk; // Compute SDK - hide Priority to avoid conflict with Wallet @@ -56,5 +75,14 @@ export 'src/rpc/synor_rpc.dart'; // Storage SDK export 'src/storage/synor_storage.dart'; +// Database SDK +export 'src/database/client.dart'; + +// Hosting SDK +export 'src/hosting/client.dart'; + +// Bridge SDK - hide types that conflict with other SDKs +export 'src/bridge/client.dart' hide FeeEstimate, SignedTransaction; + // Re-export Compute Priority with alias-friendly access // Users can import compute directly for Priority: import 'package:synor_sdk/synor_compute.dart';