Skip to content
This repository was archived by the owner on Dec 12, 2024. It is now read-only.

Commit e1fd3f0

Browse files
author
Diane Huxley
committed
Merge branch 'main' into rfq-private-salt
* main: roll back path dep req from `1.9.0` to `1.8.3` (#13) fix: payin/payout deserialization (#11) re-export lib (#10) feat: add http client (#9)
2 parents 2dde08b + 5ff5088 commit e1fd3f0

20 files changed

+739
-50
lines changed

lib/src/http_client.dart

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export './http_client/models/create_exchange_request.dart';
2+
export './http_client/models/exchange.dart';
3+
export './http_client/models/get_offerings_filter.dart';
4+
export './http_client/tbdex_http_client.dart';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'package:tbdex/src/protocol/models/rfq.dart';
2+
3+
class CreateExchangeRequest {
4+
final Rfq rfq;
5+
final String? replyTo;
6+
7+
CreateExchangeRequest({
8+
required this.rfq,
9+
this.replyTo,
10+
});
11+
12+
Map<String, dynamic> toJson() {
13+
return {
14+
'rfq': rfq.toJson(),
15+
if (replyTo != null) 'replyTo': replyTo,
16+
};
17+
}
18+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import 'package:tbdex/src/protocol/models/message.dart';
2+
3+
typedef Exchange = List<Message>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class GetOfferingsFilter {
2+
final String? payinCurrency;
3+
final String? payoutCurrency;
4+
final String? id;
5+
6+
GetOfferingsFilter({
7+
this.payinCurrency,
8+
this.payoutCurrency,
9+
this.id,
10+
});
11+
12+
factory GetOfferingsFilter.fromJson(Map<String, dynamic> json) {
13+
return GetOfferingsFilter(
14+
payinCurrency: json['payinCurrency'],
15+
payoutCurrency: json['payoutCurrency'],
16+
id: json['id'],
17+
);
18+
}
19+
20+
Map<String, dynamic> toJson() {
21+
return {
22+
if (payinCurrency != null) 'payinCurrency': payinCurrency,
23+
if (payoutCurrency != null) 'payoutCurrency': payoutCurrency,
24+
if (id != null) 'id': id,
25+
};
26+
}
27+
}
+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import 'dart:convert';
2+
3+
import 'package:http/http.dart' as http;
4+
import 'package:tbdex/src/http_client/models/create_exchange_request.dart';
5+
import 'package:tbdex/src/http_client/models/exchange.dart';
6+
import 'package:tbdex/src/http_client/models/get_offerings_filter.dart';
7+
import 'package:tbdex/src/protocol/models/close.dart';
8+
import 'package:tbdex/src/protocol/models/offering.dart';
9+
import 'package:tbdex/src/protocol/models/order.dart';
10+
import 'package:tbdex/src/protocol/models/rfq.dart';
11+
import 'package:tbdex/src/protocol/parser.dart';
12+
import 'package:tbdex/src/protocol/validator.dart';
13+
import 'package:typeid/typeid.dart';
14+
import 'package:web5/web5.dart';
15+
16+
class TbdexHttpClient {
17+
TbdexHttpClient._();
18+
19+
static const _jsonHeader = 'application/json';
20+
static const _expirationDuration = Duration(minutes: 5);
21+
22+
static http.Client _client = http.Client();
23+
24+
// ignore: avoid_setters_without_getters
25+
static set client(http.Client client) {
26+
_client = client;
27+
}
28+
29+
static Future<Exchange> getExchange(
30+
BearerDid did,
31+
String pfiDid,
32+
String exchangeId,
33+
) async {
34+
final requestToken = await _generateRequestToken(did, pfiDid);
35+
final pfiServiceEndpoint = await _getPfiServiceEndpoint(pfiDid);
36+
final url = Uri.parse('$pfiServiceEndpoint/exchanges/$exchangeId');
37+
38+
final response = await _client.get(
39+
url,
40+
headers: {
41+
'Authorization': 'Bearer $requestToken',
42+
},
43+
);
44+
45+
if (response.statusCode != 200) {
46+
throw Exception('failed to fetch exchange: ${response.body}');
47+
}
48+
49+
return Parser.parseExchange(response.body);
50+
}
51+
52+
static Future<List<Exchange>> getExchanges(
53+
BearerDid did,
54+
String pfiDid,
55+
) async {
56+
final requestToken = await _generateRequestToken(did, pfiDid);
57+
final pfiServiceEndpoint = await _getPfiServiceEndpoint(pfiDid);
58+
final url = Uri.parse('$pfiServiceEndpoint/exchanges/');
59+
60+
final response = await _client.get(
61+
url,
62+
headers: {
63+
'Authorization': 'Bearer $requestToken',
64+
},
65+
);
66+
67+
if (response.statusCode != 200) {
68+
throw Exception('failed to fetch exchanges: ${response.body}');
69+
}
70+
71+
return Parser.parseExchanges(response.body);
72+
}
73+
74+
static Future<List<Offering>> getOfferings(
75+
String pfiDid, {
76+
GetOfferingsFilter? filter,
77+
}) async {
78+
final pfiServiceEndpoint = await _getPfiServiceEndpoint(pfiDid);
79+
final url = Uri.parse('$pfiServiceEndpoint/offerings/').replace(
80+
queryParameters: filter?.toJson(),
81+
);
82+
83+
final response = await _client.get(url);
84+
85+
if (response.statusCode != 200) {
86+
throw Exception(response);
87+
}
88+
89+
return Parser.parseOfferings(response.body);
90+
}
91+
92+
static Future<void> createExchange(
93+
Rfq rfq, {
94+
String? replyTo,
95+
}) async {
96+
Validator.validateMessage(rfq);
97+
final pfiDid = rfq.metadata.to;
98+
final body = jsonEncode(
99+
CreateExchangeRequest(rfq: rfq, replyTo: replyTo),
100+
);
101+
102+
await _submitMessage(pfiDid, body);
103+
}
104+
105+
static Future<void> submitOrder(Order order) async {
106+
Validator.validateMessage(order);
107+
final pfiDid = order.metadata.to;
108+
final exchangeId = order.metadata.exchangeId;
109+
final body = jsonEncode(order.toJson());
110+
111+
await _submitMessage(pfiDid, body, exchangeId: exchangeId);
112+
}
113+
114+
static Future<void> submitClose(Close close) async {
115+
Validator.validateMessage(close);
116+
final pfiDid = close.metadata.to;
117+
final exchangeId = close.metadata.exchangeId;
118+
final body = jsonEncode(close.toJson());
119+
120+
await _submitMessage(pfiDid, body, exchangeId: exchangeId);
121+
}
122+
123+
static Future<void> _submitMessage(
124+
String pfiDid,
125+
String requestBody, {
126+
String? exchangeId,
127+
}) async {
128+
final pfiServiceEndpoint = await _getPfiServiceEndpoint(pfiDid);
129+
final path = '/exchanges${exchangeId != null ? '/$exchangeId' : ''}';
130+
final url = Uri.parse(pfiServiceEndpoint + path);
131+
132+
final response = await _client.post(
133+
url,
134+
headers: {'Content-Type': _jsonHeader},
135+
body: requestBody,
136+
);
137+
138+
if (response.statusCode != 201) {
139+
throw Exception(response);
140+
}
141+
}
142+
143+
static Future<String> _getPfiServiceEndpoint(String pfiDid) async {
144+
final didResolutionResult = await DidResolver.resolve(pfiDid);
145+
146+
if (didResolutionResult.didDocument == null) {
147+
throw Exception('did resolution failed');
148+
}
149+
150+
final service = didResolutionResult.didDocument?.service?.firstWhere(
151+
(service) => service.type == 'PFI',
152+
orElse: () => throw Exception('did does not have service of type PFI'),
153+
);
154+
155+
return service?.serviceEndpoint ?? '';
156+
}
157+
158+
static Future<String> _generateRequestToken(
159+
BearerDid did,
160+
String pfiDid,
161+
) async {
162+
final nowEpochSeconds = DateTime.now().millisecondsSinceEpoch ~/ 1000;
163+
final exp = nowEpochSeconds + _expirationDuration.inSeconds;
164+
165+
return Jwt.sign(
166+
did: did,
167+
payload: JwtClaims(
168+
aud: pfiDid,
169+
iss: did.uri,
170+
exp: exp,
171+
iat: nowEpochSeconds,
172+
jti: TypeId.generate(''),
173+
),
174+
);
175+
}
176+
}

lib/src/protocol.dart

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export './protocol/models/close.dart';
2+
export './protocol/models/message.dart';
3+
export './protocol/models/message_data.dart';
4+
export './protocol/models/offering.dart';
5+
export './protocol/models/order.dart';
6+
export './protocol/models/order_status.dart';
7+
export './protocol/models/quote.dart';
8+
export './protocol/models/resource.dart';
9+
export './protocol/models/resource_data.dart';
10+
export './protocol/models/rfq.dart';
11+
export './protocol/parser.dart';
12+
export './protocol/validator.dart';

lib/src/protocol/exceptions.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ enum TbdexExceptionCode {
44
parserJsonNull,
55
parserKindRequired,
66
parserInvalidJson,
7-
parserMetadataRequired,
7+
parserMetadataMalformed,
88
parserUnknownMessageKind,
99
parserUnknownResourceKind,
1010
messageSignatureMissing,

lib/src/protocol/models/resource_data.dart

+6-2
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,9 @@ class PayinMethod {
145145
name: json['name'],
146146
description: json['description'],
147147
group: json['group'],
148-
requiredPaymentDetails: JsonSchema.create(json['requiredPaymentDetails']),
148+
requiredPaymentDetails: json['requiredPaymentDetails'] != null
149+
? JsonSchema.create(json['requiredPaymentDetails'])
150+
: null,
149151
fee: json['fee'],
150152
min: json['min'],
151153
max: json['max'],
@@ -199,7 +201,9 @@ class PayoutMethod {
199201
name: json['name'],
200202
description: json['description'],
201203
group: json['group'],
202-
requiredPaymentDetails: JsonSchema.create(json['requiredPaymentDetails']),
204+
requiredPaymentDetails: json['requiredPaymentDetails'] != null
205+
? JsonSchema.create(json['requiredPaymentDetails'])
206+
: null,
203207
fee: json['fee'],
204208
min: json['min'],
205209
max: json['max'],

0 commit comments

Comments
 (0)