Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Indian Bond Module Demo (Flutter)

This repository now includes a runnable Flutter entrypoint that launches the NSE CBRICS Bonds preview UI using local mock data.

## Run locally

1. Ensure Flutter SDK is installed (`flutter --version`).
2. In this repository root, generate platform folders (only first time):

```bash
flutter create .
```

3. Get dependencies:

```bash
flutter pub get
```

4. Run app:

```bash
flutter run
```

The app boots to `BondPreviewPage`, so it works without backend APIs.
53 changes: 53 additions & 0 deletions lib/features/bonds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Bond Module (Indian Market via NSE CBRICS)

This module provides a practical baseline implementation for trading Indian bonds inside a Flutter app.

## What is included

- Domain entities for `Bond` and `BondOrder`
- Repository contract and implementation
- `PlaceBondOrder` use case for validation before OMS submission
- Data source for NSE CBRICS OMS-style REST endpoints
- Mock transport for local feature preview without backend
- Basic controller for loading instruments/orders and placing orders
- Starter UI:
- `BondPage` instrument list with pull-to-refresh
- `BondOrderTicket` order entry widget embedded per bond row
- `BondPreviewPage` for quick local demo

## Expected NSE CBRICS endpoints

- `GET /bonds` -> `{ "data": [ ...bond ] }`
- `POST /orders` -> returns either `{ "data": { ...order } }` or `{ ...order }`
- `GET /orders/open` -> `{ "data": [ ...order ] }`

## Example wiring (real backend)

```dart
final dataSource = NseCbricsDataSource(
baseUrl: 'https://your-nse-cbrics-host/api/v1',
apiKey: 'YOUR_API_KEY',
http: yourHttpTransport,
);

final repository = BondRepositoryImpl(dataSource);
final controller = BondController(repository);

MaterialApp(home: BondPage(controller: controller));
```

## Preview wiring (no backend)

```dart
MaterialApp(home: BondPreviewPage());
```

`BondPreviewPage` uses `MockNseCbricsTransport` with in-memory sample bonds and orders so you can verify UI and order flow quickly.

## Next steps for production

- Add auth token refresh and request signing required by your broker
- Add order modification/cancel flows
- Add lot-size, tick-size, and risk-rule validation from live exchange masters
- Add pagination, bond search, and filter chips for yields/maturity buckets
- Add tests (unit + widget + API contract)
69 changes: 69 additions & 0 deletions lib/features/bonds/data/datasources/mock_nse_cbrics_transport.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'dart:convert';

import 'nse_cbrics_datasource.dart';

/// Local in-memory transport to preview the Bonds module UI without backend connectivity.
class MockNseCbricsTransport implements HttpTransport {
final List<Map<String, dynamic>> _orders = [];

@override
Future<String> get(String path, {Map<String, String>? headers}) async {
if (path.endsWith('/bonds')) {
return jsonEncode({
'data': [
{
'isin': 'INE123A08018',
'symbol': 'GSEC_2033_7.26',
'issuer': 'Government of India',
'maturityDate': '2033-04-06',
'couponRate': 7.26,
'faceValue': 100,
'category': 'G-Sec',
},
{
'isin': 'INE987B07042',
'symbol': 'SDL_MH_2031_7.10',
'issuer': 'State of Maharashtra',
'maturityDate': '2031-11-15',
'couponRate': 7.10,
'faceValue': 100,
'category': 'SDL',
},
],
});
}

if (path.endsWith('/orders/open')) {
return jsonEncode({'data': _orders});
}

throw UnsupportedError('GET not supported in mock transport for path: $path');
}

@override
Future<String> post(
String path, {
Map<String, String>? headers,
Object? body,
}) async {
if (!path.endsWith('/orders')) {
throw UnsupportedError('POST not supported in mock transport for path: $path');
}

final request = jsonDecode(body as String) as Map<String, dynamic>;
final order = {
'orderId': 'MOCK-${_orders.length + 1}',
'isin': request['isin'],
'side': request['side'],
'type': request['type'],
'status': 'OPEN',
'quantity': request['quantity'],
'filledQuantity': 0,
'limitPrice': request['limitPrice'],
'createdAt': DateTime.now().toUtc().toIso8601String(),
};

_orders.insert(0, order);
return jsonEncode({'data': order});
}
}
76 changes: 76 additions & 0 deletions lib/features/bonds/data/datasources/nse_cbrics_datasource.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'dart:convert';

import '../models/bond_model.dart';
import '../models/bond_order_model.dart';
import '../../domain/entities/bond_order.dart';

abstract class HttpTransport {
Future<String> get(String path, {Map<String, String>? headers});

Future<String> post(
String path, {
Map<String, String>? headers,
Object? body,
});
}
Comment on lines +7 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n lib/features/bonds/data/datasources/nse_cbrics_datasource.dart

Repository: SumitUni7/Test-web

Length of output: 2697


🏁 Script executed:

# Search for HttpTransport interface and implementations
rg "class HttpTransport|abstract class HttpTransport" -A 10

Repository: SumitUni7/Test-web

Length of output: 984


🏁 Script executed:

# Search for usages of HttpTransport methods
rg "\.get\(|\.post\(" lib/features/bonds/data/datasources/ -B 2 -A 3

Repository: SumitUni7/Test-web

Length of output: 1824


🏁 Script executed:

# Check if there are any response handling or status checks in the datasource
rg "statusCode|status|200|400|500" lib/features/bonds/data/datasources/ -A 2 -B 2

Repository: SumitUni7/Test-web

Length of output: 548


🏁 Script executed:

# Search for HttpResponse or similar response wrapper classes
rg "class HttpResponse|statusCode.*body|response.*statusCode" --type dart

Repository: SumitUni7/Test-web

Length of output: 44


🏁 Script executed:

# Find all implementations of HttpTransport
rg "class.*HttpTransport|implements HttpTransport" --type dart -A 5

Repository: SumitUni7/Test-web

Length of output: 1246


🏁 Script executed:

# Search for any error handling or try-catch blocks around HTTP calls
rg "try|catch|Exception|Error" lib/features/bonds/data/datasources/ -B 2 -A 2

Repository: SumitUni7/Test-web

Length of output: 972


🏁 Script executed:

# Check if there are any repository implementations that handle errors
rg "class.*Repository|implements.*Repository" lib/features/bonds/data/ --type dart -A 10

Repository: SumitUni7/Test-web

Length of output: 1068


🏁 Script executed:

# Search for any file that might implement the real HTTP transport
fd ".*transport.*\.dart" lib/features/bonds/

Repository: SumitUni7/Test-web

Length of output: 128


🏁 Script executed:

# Check pubspec for HTTP dependencies
cat pubspec.yaml | grep -A 5 -B 5 "http\|dio"

Repository: SumitUni7/Test-web

Length of output: 44


🏁 Script executed:

# Search for http.Client or similar HTTP implementations
rg "http\.Client|Dio|HttpClient" --type dart

Repository: SumitUni7/Test-web

Length of output: 44


🏁 Script executed:

# Check the repository structure
find . -type f -name "*.dart" | grep -E "(test|spec|transport)" | head -20

Repository: SumitUni7/Test-web

Length of output: 130


🏁 Script executed:

# Search for any integration or real HTTP implementation
find . -type f -name "*.dart" | head -30

Repository: SumitUni7/Test-web

Length of output: 851


🏁 Script executed:

# Check if there's a pubspec.yaml at all and look for HTTP dependencies
find . -name "pubspec.yaml" -exec cat {} \;

Repository: SumitUni7/Test-web

Length of output: 308


HttpTransport must expose HTTP status codes, not only response body.

The datasource cannot currently distinguish successful HTTP responses from error responses (4xx/5xx), so failed requests will be passed to jsonDecode() as if they were valid. This affects all three methods: fetchBonds() (line 34), submitOrder() (line 50), and fetchOpenOrders() (line 68).

Change the interface to return a response object with both status code and body:

Suggested fix
 abstract class HttpTransport {
-  Future<String> get(String path, {Map<String, String>? headers});
+  Future<HttpResponse> get(String path, {Map<String, String>? headers});

-  Future<String> post(
+  Future<HttpResponse> post(
     String path, {
     Map<String, String>? headers,
     Object? body,
   });
 }
+
+class HttpResponse {
+  final int statusCode;
+  final String body;
+
+  const HttpResponse({required this.statusCode, required this.body});
+}

Then validate status before parsing:

-    final raw = await http.get('$baseUrl/bonds', headers: _headers);
-    final payload = jsonDecode(raw) as Map<String, dynamic>;
+    final response = await http.get('$baseUrl/bonds', headers: _headers);
+    if (response.statusCode < 200 || response.statusCode >= 300) {
+      throw StateError('fetchBonds failed: ${response.statusCode}');
+    }
+    final payload = jsonDecode(response.body) as Map<String, dynamic>;

Apply the same pattern to submitOrder() and fetchOpenOrders().

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
abstract class HttpTransport {
Future<String> get(String path, {Map<String, String>? headers});
Future<String> post(
String path, {
Map<String, String>? headers,
Object? body,
});
}
abstract class HttpTransport {
Future<HttpResponse> get(String path, {Map<String, String>? headers});
Future<HttpResponse> post(
String path, {
Map<String, String>? headers,
Object? body,
});
}
class HttpResponse {
final int statusCode;
final String body;
const HttpResponse({required this.statusCode, required this.body});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/data/datasources/nse_cbrics_datasource.dart` around lines
7 - 15, Update the HttpTransport contract to return a response object containing
both statusCode and body (instead of Future<String>) and adjust callers to check
status before parsing: modify the abstract class HttpTransport methods get/post
to return a type (e.g., HttpResponse { int statusCode; String body; }) and then
in NseCbricsDatasource.fetchBonds(), submitOrder(), and fetchOpenOrders()
inspect response.statusCode (accept 200/2xx) and only call
jsonDecode(response.body) on success; for non-OK statuses throw or return a
meaningful error including response.statusCode and response.body so error
responses aren’t passed to jsonDecode.


class NseCbricsDataSource {
final String baseUrl;
final String apiKey;
final HttpTransport http;

const NseCbricsDataSource({
required this.baseUrl,
required this.apiKey,
required this.http,
});

Map<String, String> get _headers => {
'Content-Type': 'application/json',
'X-API-KEY': apiKey,
};

Future<List<BondModel>> fetchBonds() async {
final raw = await http.get('$baseUrl/bonds', headers: _headers);
final payload = jsonDecode(raw) as Map<String, dynamic>;
final items = payload['data'] as List<dynamic>;

Comment on lines +35 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard response envelope shape before casting data.

payload['data'] is assumed to always exist and match expected types. If API returns an error envelope or schema drift, this throws at runtime (or parses a non-order map in submitOrder).

Suggested defensive parsing
+  Map<String, dynamic> _decodeObject(String raw) {
+    final decoded = jsonDecode(raw);
+    if (decoded is! Map<String, dynamic>) {
+      throw const FormatException('Expected JSON object response');
+    }
+    return decoded;
+  }
+
+  List<Map<String, dynamic>> _readDataList(Map<String, dynamic> payload) {
+    final data = payload['data'];
+    if (data is! List) {
+      throw const FormatException('Expected `data` to be a list');
+    }
+    return data.map((e) {
+      if (e is! Map<String, dynamic>) {
+        throw const FormatException('Expected list items to be JSON objects');
+      }
+      return e;
+    }).toList(growable: false);
+  }
...
-    final payload = jsonDecode(raw) as Map<String, dynamic>;
-    final items = payload['data'] as List<dynamic>;
+    final payload = _decodeObject(raw);
+    final items = _readDataList(payload);
...
-    final payload = jsonDecode(raw) as Map<String, dynamic>;
-    final orderMap = (payload['data'] ?? payload) as Map<String, dynamic>;
+    final payload = _decodeObject(raw);
+    final data = payload['data'];
+    if (data is! Map<String, dynamic>) {
+      throw const FormatException('Expected `data` to be an order object');
+    }
+    final orderMap = data;
...
-    final payload = jsonDecode(raw) as Map<String, dynamic>;
-    final items = payload['data'] as List<dynamic>;
+    final payload = _decodeObject(raw);
+    final items = _readDataList(payload);

Also applies to: 62-64, 69-70

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/data/datasources/nse_cbrics_datasource.dart` around lines
35 - 37, The code assumes jsonDecode(raw) yields a Map with a List under key
'data' (variables payload and items); add defensive checks to validate the
envelope before casting: ensure jsonDecode(raw) is a Map<String, dynamic>, that
payload.containsKey('data'), and that payload['data'] is a List<dynamic>
(otherwise return a safe default, throw a descriptive exception, or handle the
API error envelope); apply the same guard/validation to the other parsing sites
referenced (the other payload/data casts and the submitOrder parsing) so all
casts are protected and errors include contextual messages indicating which
endpoint/operation failed.

return items
.map((item) => BondModel.fromJson(item as Map<String, dynamic>))
.toList();
}

Future<BondOrderModel> submitOrder({
required String isin,
required BondOrderSide side,
required BondOrderType type,
required int quantity,
double? limitPrice,
}) async {
final raw = await http.post(
'$baseUrl/orders',
headers: _headers,
body: jsonEncode({
'isin': isin,
'side': side.name.toUpperCase(),
'type': type.name.toUpperCase(),
'quantity': quantity,
if (limitPrice != null) 'limitPrice': limitPrice,
}),
);

final payload = jsonDecode(raw) as Map<String, dynamic>;
final orderMap = (payload['data'] ?? payload) as Map<String, dynamic>;
return BondOrderModel.fromJson(orderMap);
}

Future<List<BondOrderModel>> fetchOpenOrders() async {
final raw = await http.get('$baseUrl/orders/open', headers: _headers);
final payload = jsonDecode(raw) as Map<String, dynamic>;
final items = payload['data'] as List<dynamic>;

return items
.map((item) => BondOrderModel.fromJson(item as Map<String, dynamic>))
.toList();
}
}
25 changes: 25 additions & 0 deletions lib/features/bonds/data/models/bond_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import '../../domain/entities/bond.dart';

class BondModel extends Bond {
const BondModel({
required super.isin,
required super.symbol,
required super.issuer,
required super.maturityDate,
required super.couponRate,
required super.faceValue,
required super.category,
});

factory BondModel.fromJson(Map<String, dynamic> json) {
return BondModel(
isin: json['isin'] as String,
symbol: json['symbol'] as String,
issuer: json['issuer'] as String,
maturityDate: DateTime.parse(json['maturityDate'] as String),
couponRate: (json['couponRate'] as num).toDouble(),
faceValue: (json['faceValue'] as num).toDouble(),
category: json['category'] as String,
);
Comment on lines +14 to +23
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n lib/features/bonds/data/models/bond_model.dart | head -50

Repository: SumitUni7/Test-web

Length of output: 974


🏁 Script executed:

# Search for other model files with fromJson patterns
find lib -name "*_model.dart" -type f | head -20

Repository: SumitUni7/Test-web

Length of output: 161


🏁 Script executed:

# Search for usages of BondModel.fromJson
rg "BondModel\.fromJson|\.fromJson.*bond" lib/

Repository: SumitUni7/Test-web

Length of output: 302


🏁 Script executed:

# Check the Bond entity
find lib -name "bond.dart" -path "*/domain/entities/*" | xargs cat

Repository: SumitUni7/Test-web

Length of output: 469


🏁 Script executed:

cat -n lib/features/bonds/data/models/bond_order_model.dart

Repository: SumitUni7/Test-web

Length of output: 2642


🏁 Script executed:

cat -n lib/features/bonds/data/datasources/nse_cbrics_datasource.dart | head -100

Repository: SumitUni7/Test-web

Length of output: 2697


Harden JSON parsing in BondModel.fromJson to match BondOrderModel patterns.

The current implementation uses unsafe casts that fail with opaque runtime errors on malformed API payloads. BondOrderModel in the same codebase demonstrates the pattern: validate fields and throw contextual errors (see enum decoders). BondModel should follow suit—add validation for strings, dates, and numeric fields so failures are diagnosable rather than cryptic TypeError or FormatException from runtime casts.

Specifically:

  • Direct as String and as num casts will crash on type mismatches
  • DateTime.parse() has no try-catch for malformed dates
  • nse_cbrics_datasource.dart has no error handling, so failures propagate uncontrolled

Add field-level validation helpers (following BondOrderModel's pattern) to throw FormatException with context before returning BondModel.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/data/models/bond_model.dart` around lines 14 - 23, The
BondModel.fromJson deserialization uses unsafe casts and DateTime.parse that can
throw opaque runtime errors; update BondModel.fromJson to validate each field
(isin, symbol, issuer, maturityDate, couponRate, faceValue, category) using
helper validators similar to the BondOrderModel enum decoders: check types (is
String / is num), parse numbers to double safely, wrap DateTime.parse in
try/catch and validate format, and throw FormatException with a clear contextual
message for each invalid field; also ensure callers in
nse_cbrics_datasource.dart propagate or handle these FormatExceptions instead of
letting raw TypeError/FormatException surface.

}
}
70 changes: 70 additions & 0 deletions lib/features/bonds/data/models/bond_order_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import '../../domain/entities/bond_order.dart';

class BondOrderModel extends BondOrder {
const BondOrderModel({
required super.orderId,
required super.isin,
required super.side,
required super.type,
required super.status,
required super.quantity,
required super.filledQuantity,
super.limitPrice,
required super.createdAt,
});

factory BondOrderModel.fromJson(Map<String, dynamic> json) {
return BondOrderModel(
orderId: json['orderId'] as String,
isin: json['isin'] as String,
side: _decodeSide(json['side'] as String),
type: _decodeType(json['type'] as String),
status: _decodeStatus((json['status'] as String?) ?? 'PENDING'),
quantity: (json['quantity'] as num).toInt(),
filledQuantity: ((json['filledQuantity'] as num?) ?? 0).toInt(),
Comment on lines +23 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent silent quantity truncation during JSON decode.

Line 23 and Line 24 use .toInt(), which will silently truncate fractional payloads (e.g., 10.9 -> 10). This can corrupt order state instead of rejecting malformed data.

Proposed fix
   factory BondOrderModel.fromJson(Map<String, dynamic> json) {
     return BondOrderModel(
       orderId: json['orderId'] as String,
       isin: json['isin'] as String,
       side: _decodeSide(json['side'] as String),
       type: _decodeType(json['type'] as String),
       status: _decodeStatus((json['status'] as String?) ?? 'PENDING'),
-      quantity: (json['quantity'] as num).toInt(),
-      filledQuantity: ((json['filledQuantity'] as num?) ?? 0).toInt(),
+      quantity: _asStrictInt(json['quantity'], 'quantity'),
+      filledQuantity: _asStrictInt(json['filledQuantity'] ?? 0, 'filledQuantity'),
       limitPrice: (json['limitPrice'] as num?)?.toDouble(),
       createdAt: DateTime.parse(json['createdAt'] as String),
     );
   }
+
+  static int _asStrictInt(Object? value, String field) {
+    if (value is int) return value;
+    if (value is num && value == value.roundToDouble()) return value.toInt();
+    throw ArgumentError.value(value, field, 'Expected an integer value');
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
quantity: (json['quantity'] as num).toInt(),
filledQuantity: ((json['filledQuantity'] as num?) ?? 0).toInt(),
factory BondOrderModel.fromJson(Map<String, dynamic> json) {
return BondOrderModel(
orderId: json['orderId'] as String,
isin: json['isin'] as String,
side: _decodeSide(json['side'] as String),
type: _decodeType(json['type'] as String),
status: _decodeStatus((json['status'] as String?) ?? 'PENDING'),
quantity: _asStrictInt(json['quantity'], 'quantity'),
filledQuantity: _asStrictInt(json['filledQuantity'] ?? 0, 'filledQuantity'),
limitPrice: (json['limitPrice'] as num?)?.toDouble(),
createdAt: DateTime.parse(json['createdAt'] as String),
);
}
static int _asStrictInt(Object? value, String field) {
if (value is int) return value;
if (value is num && value == value.roundToDouble()) return value.toInt();
throw ArgumentError.value(value, field, 'Expected an integer value');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/data/models/bond_order_model.dart` around lines 23 - 24,
The current JSON decode for the BondOrderModel fields quantity and
filledQuantity uses .toInt(), which silently truncates fractional numbers;
instead validate the incoming num values before converting and fail loudly on
malformed data: check (json['quantity'] as num) and (json['filledQuantity'] as
num?) to ensure they are whole numbers (e.g., num % 1 == 0) and throw a
FormatException (or return an error) if not, then safely convert to int; apply
this validation in the BondOrderModel JSON constructor/fromJson where quantity
and filledQuantity are parsed to prevent silent truncation.

limitPrice: (json['limitPrice'] as num?)?.toDouble(),
createdAt: DateTime.parse(json['createdAt'] as String),
);
}

static BondOrderSide _decodeSide(String side) {
switch (side.toUpperCase()) {
case 'BUY':
return BondOrderSide.buy;
case 'SELL':
return BondOrderSide.sell;
default:
throw ArgumentError.value(side, 'side', 'Unsupported side');
}
}

static BondOrderType _decodeType(String type) {
switch (type.toUpperCase()) {
case 'MARKET':
return BondOrderType.market;
case 'LIMIT':
return BondOrderType.limit;
default:
throw ArgumentError.value(type, 'type', 'Unsupported order type');
}
}

static BondOrderStatus _decodeStatus(String status) {
switch (status.toUpperCase()) {
case 'PENDING':
return BondOrderStatus.pending;
case 'OPEN':
return BondOrderStatus.open;
case 'PARTIALLY_FILLED':
return BondOrderStatus.partiallyFilled;
case 'FILLED':
return BondOrderStatus.filled;
case 'CANCELLED':
return BondOrderStatus.cancelled;
case 'REJECTED':
return BondOrderStatus.rejected;
default:
return BondOrderStatus.pending;
}
}
}
33 changes: 33 additions & 0 deletions lib/features/bonds/data/repositories/bond_repository_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import '../../domain/entities/bond.dart';
import '../../domain/entities/bond_order.dart';
import '../../domain/repositories/bond_repository.dart';
import '../datasources/nse_cbrics_datasource.dart';

class BondRepositoryImpl implements BondRepository {
final NseCbricsDataSource _dataSource;

const BondRepositoryImpl(this._dataSource);

@override
Future<List<Bond>> getAvailableBonds() => _dataSource.fetchBonds();

@override
Future<List<BondOrder>> getOpenOrders() => _dataSource.fetchOpenOrders();

@override
Future<BondOrder> placeOrder({
required String isin,
required BondOrderSide side,
required BondOrderType type,
required int quantity,
double? limitPrice,
}) {
return _dataSource.submitOrder(
isin: isin,
side: side,
type: type,
quantity: quantity,
limitPrice: limitPrice,
);
}
}
19 changes: 19 additions & 0 deletions lib/features/bonds/domain/entities/bond.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class Bond {
final String isin;
final String symbol;
final String issuer;
final DateTime maturityDate;
final double couponRate;
final double faceValue;
final String category;

const Bond({
required this.isin,
required this.symbol,
required this.issuer,
required this.maturityDate,
required this.couponRate,
required this.faceValue,
required this.category,
});
}
Loading