Skip to content

Sisyphus is a Flutter-based crypto app that displays real-time candlestick charts using Binance WebSockets APIs. The app provides a seamless experience for traders and crypto enthusiasts to monitor market trends efficiently.

License

Notifications You must be signed in to change notification settings

Raks-Javac/SSip-Mobile-Crypto-App--Flutter-Binance-Websockets-API-

Repository files navigation

📈 Sisyphus

Sisyphus is a Flutter-based crypto app that displays real-time candlestick charts using Binance WebSockets APIs. The app provides a seamless experience for traders and crypto enthusiasts to monitor market trends efficiently.

🚀 Features

  • 📊 Real-time candlestick charts.
  • 🔄 WebSocket integration for live updates.
  • 📈 Supports multiple cryptocurrencies.
  • 🌓 Light and Dark theme modes.
  • 📱 Responsive UI for both mobile and tablet.

🛠️ Tech Stack

  • Flutter: For building cross-platform mobile apps.
  • WebSockets: For live financial data.
  • Riverpod: For state management.
  • Candlesticks: Custom implementation using charts using the 'candlesticks' package

📸 Screenshots or GIFs

Screenshot 1 Screenshot 2 Screenshot 3 Screenshot 4 Screenshot 5 Screenshot 6 Screenshot 7 GIF Demo

📦 Installation

  1. Clone the repository:

    git clone https://github.com/Raks-Javac/Sisyphus.git
    cd Sisyphus
  2. Install dependencies:

    flutter pub get
  3. Run the app:

    flutter run
  4. Ensure you are on VPN or a solid network to allow connection to binance websocket(Optional)

📁 Project Structure

The project methodology adopted is a project based method, while feature based isolates feature, the reason project based was chosen for this was because it was just to demonstrates binance websockets with flutter, while "Keeping It Simple Solid"

Sisyphus/
├── lib/
│   ├── main.dart          # Entry point
│   ├── models/            # Data models
│   ├── services/          # WebSocket and API handling
│   ├── widgets/           # UI components
│   ├── screens/           # Screens for different sections
├── assets/                # containing svgs, images and fonts
├── pubspec.yaml

For assets, textstyle and color configurations, you can check the resources folder /res, where "ThemeExtensions" was set up for dynamic theming.

🔗 WebSocket API Integration

The app uses WebSocket APIs to fetch live market data. Below is a sample implementation:

import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:sisyphus/models/candlestick.dart';
import 'package:sisyphus/models/orderbook.dart';
import 'package:sisyphus/services/websocket_service.dart';

class BinanceApiService {
  final WebSocketService _webSocketService;
  final String _baseUrl = 'https://api.binance.com/api/v3';
  final String _wsBaseUrl = 'wss://stream.binance.com:9443/ws';

  BinanceApiService(this._webSocketService);

  Future<void> connectToWebSocket() async {
    await _webSocketService.connect(_wsBaseUrl);
  }

  void subscribeToCandlesticks(String symbol, String interval,
      Function(List<SisyphusCandlestick>) onData) {
    final streamName = '${symbol.toLowerCase()}@kline_$interval';

    _webSocketService.subscribe(streamName, (data) {
      if (data != null && data.containsKey('k')) {
        final kline = data['k'];
        final candlestick = SisyphusCandlestick.fromJson(kline);
        onData([candlestick]);
      }
    });

    _webSocketService.send(jsonEncode({
      'method': 'SUBSCRIBE',
      'params': [streamName],
      'id': 1
    }));
  }

  void subscribeToOrderbook(String symbol, Function(Orderbook) onData) {
    final streamName = '${symbol.toLowerCase()}@depth20@100ms';

    _webSocketService.subscribe(streamName, (data) {
      if (data != null) {
        final orderbook = Orderbook.fromJson(data);
        onData(orderbook);
      }
    });

    _webSocketService.send(jsonEncode({
      'method': 'SUBSCRIBE',
      'params': [streamName],
      'id': 2
    }));
  }

  void unsubscribeFromCandlesticks(String symbol, String interval) {
    final streamName = '${symbol.toLowerCase()}@kline_$interval';

    _webSocketService.send(jsonEncode({
      'method': 'UNSUBSCRIBE',
      'params': [streamName],
      'id': 3
    }));
  }

  void unsubscribeFromOrderbook(String symbol) {
    final streamName = '${symbol.toLowerCase()}@depth20@100ms';

    _webSocketService.send(jsonEncode({
      'method': 'UNSUBSCRIBE',
      'params': [streamName],
      'id': 4
    }));
  }

  Future<List<SisyphusCandlestick>> getHistoricalCandlesticks(
      String symbol, String interval,
      {int limit = 500}) async {
    final url =
        '$_baseUrl/klines?symbol=$symbol&interval=$interval&limit=$limit';
    final response = await http.get(Uri.parse(url));

    if (response.statusCode == 200) {
      final List<dynamic> data = jsonDecode(response.body);
      return data.map((item) => SisyphusCandlestick.fromList(item)).toList();
    } else {
      throw Exception('Failed to load historical candlesticks');
    }
  }

  Future<Orderbook> getOrderbook(String symbol, {int limit = 20}) async {
    final url = '$_baseUrl/depth?symbol=$symbol&limit=$limit';
    final response = await http.get(Uri.parse(url));

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return Orderbook.fromJson(data);
    } else {
      throw Exception('Failed to load orderbook');
    }
  }

  void dispose() {
    _webSocketService.disconnect();
  }
}

📊 Candlestick Implementation

Custom candlestick charts were implemented using Flutter widgets. Key components include:

  • OHLC (Open, High, Low, Close) data parsing.
  • Dynamic scaling for different timeframes.
  • Touch and zoom interactions.
import 'package:candlesticks/candlesticks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:sisyphus/models/candlestick.dart';
import 'package:sisyphus/res/assets.dart';
import 'package:sisyphus/res/gap.dart';
import 'package:sisyphus/res/theme/app_theme.dart';
import 'package:sisyphus/widgets/chart_widgets.dart';
import 'package:sisyphus/widgets/render_assets.dart';

class CryptoChart extends StatefulWidget {
  final List<SisyphusCandlestick> candles;
  final String symbol;
  final Color upColor;
  final Color downColor;
  final Color backgroundColor;
  final Color gridColor;
  final Color textColor;
  final bool showVolume;
  final bool showHeader;
  final bool showFooter;
  final Function()? onLoadMoreCandles;

  const CryptoChart({
    super.key,
    required this.candles,
    this.symbol = 'BTC/USD',
    this.upColor = const Color(0xFF25C26E),
    this.downColor = const Color(0xFFFF6B4A),
    this.backgroundColor = const Color(0xFF1E2029),
    this.gridColor = const Color(0x33FFFFFF),
    this.textColor = Colors.white,
    this.showVolume = true,
    this.showHeader = true,
    this.showFooter = true,
    this.onLoadMoreCandles,
  });

  @override
  State<CryptoChart> createState() => _CryptoChartState();
}

class _CryptoChartState extends State<CryptoChart> {
  late List<Candle> _convertedCandles;

  @override
  void initState() {
    super.initState();
    _updateCandles();
  }

  @override
  void didUpdateWidget(CryptoChart oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.candles != oldWidget.candles) {
      _updateCandles();
    }
  }

  void _updateCandles() {
    _convertedCandles = widget.candles
        .map((c) => Candle(
            date: DateTime.fromMillisecondsSinceEpoch(c.openTime),
            high: c.high,
            low: c.low,
            open: c.open,
            close: c.close,
            volume: c.volume))
        .toList();
  }

  @override
  Widget build(BuildContext context) {
    if (widget.candles.isEmpty) {
      return Center(
        child: Text(
          'No data available',
          style: TextStyle(color: widget.textColor),
        ),
      );
    }

    return Container(
      color: widget.backgroundColor,
      child: Stack(
        children: [
          Column(
            children: [
              // Main chart area
              Expanded(
                child: Candlesticks(
                  candles: _convertedCandles,
                ),
              ),

              // Footer with volume info

              const SizedBox(height: 16),

              Padding(
                padding: EdgeInsetsDirectional.symmetric(horizontal: 10),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      'Vol(BTC): ${widget.candles.last.volume.toStringAsFixed(4)}K',
                      style: context.textStyles.caption.copyWith(
                        color: context.colors.chartGreen,
                      ),
                    ),
                    Text(
                      'Vol(USDT): ${widget.candles.last.quoteAssetVolume.toStringAsFixed(4)}B',
                      style: context.textStyles.caption.copyWith(
                        color: context.colors.chartRed,
                      ),
                    ),
                  ],
                ),
              ),

              const SizedBox(height: 16),
            ],
          ),
          Positioned(
            top: 30,
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                children: [
                  addHorizontalSpacing(15),
                  SWidgetRenderSvg(svgPath: borderedDropDown),
                  addHorizontalSpacing(5),
                  Row(
                    children: [
                      Text(
                        'BTC/USD',
                        style: context.textStyles.body2.copyWith(
                          fontSize: 10.sp,
                        ),
                      ),
                      addHorizontalSpacing(20),
                      PriceInfo(
                        label: 'O',
                        color: widget.candles.last.open > 0
                            ? context.colors.positiveGreen
                            : context.colors.negativeRed,
                        value: widget.candles.last.open.toStringAsFixed(2),
                      ),
                      addHorizontalSpacing(20),
                      PriceInfo(
                        label: 'H',
                        color: widget.candles.last.high > 0
                            ? context.colors.positiveGreen
                            : context.colors.negativeRed,
                        value: widget.candles.last.high.toStringAsFixed(2),
                      ),
                      addHorizontalSpacing(20),
                      PriceInfo(
                        label: 'L',
                        color: widget.candles.last.low > 0
                            ? context.colors.positiveGreen
                            : context.colors.negativeRed,
                        value: widget.candles.last.low.toStringAsFixed(2),
                      ),
                      addHorizontalSpacing(20),
                      PriceInfo(
                        label: 'C',
                        color: widget.candles.last.close > 0
                            ? context.colors.positiveGreen
                            : context.colors.negativeRed,
                        value: widget.candles.last.close.toStringAsFixed(2),
                      ),
                    ],
                  )
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🤝 Contributing

Contributions are welcome! Please open an issue or submit a pull request.

📞 Contact

For support, reach out to Rufai Kudus Adeboye at rufaikudus2014@gmail.com.


Don't forget to star the repo if you find it useful!

About

Sisyphus is a Flutter-based crypto app that displays real-time candlestick charts using Binance WebSockets APIs. The app provides a seamless experience for traders and crypto enthusiasts to monitor market trends efficiently.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published