added paynym response objects and refactored paynym.is api

This commit is contained in:
julian 2023-01-02 15:16:01 -06:00
parent f9491f8215
commit f3b1d11a46
10 changed files with 299 additions and 81 deletions

View file

@ -0,0 +1,20 @@
class PaynymClaim {
final String claimed;
final String token;
PaynymClaim(this.claimed, this.token);
PaynymClaim.fromMap(Map<String, dynamic> map)
: claimed = map["claimed"] as String,
token = map["token"] as String;
Map<String, dynamic> toMap() => {
"claimed": claimed,
"token": token,
};
@override
String toString() {
return toMap().toString();
}
}

View file

@ -0,0 +1,23 @@
class PaynymFollow {
final String follower;
final String following;
final String token;
PaynymFollow(this.follower, this.following, this.token);
PaynymFollow.fromMap(Map<String, dynamic> map)
: follower = map["follower"] as String,
following = map["following"] as String,
token = map["token"] as String;
Map<String, dynamic> toMap() => {
"follower": follower,
"following": following,
"token": token,
};
@override
String toString() {
return toMap().toString();
}
}

View file

@ -0,0 +1,7 @@
class PaynymResponse<T> {
final T? value;
final int statusCode;
final String message;
PaynymResponse(this.value, this.statusCode, this.message);
}

View file

@ -0,0 +1,23 @@
class PaynymUnfollow {
final String follower;
final String unfollowing;
final String token;
PaynymUnfollow(this.follower, this.unfollowing, this.token);
PaynymUnfollow.fromMap(Map<String, dynamic> map)
: follower = map["follower"] as String,
unfollowing = map["unfollowing"] as String,
token = map["token"] as String;
Map<String, dynamic> toMap() => {
"follower": follower,
"unfollowing": unfollowing,
"token": token,
};
@override
String toString() {
return toMap().toString();
}
}

View file

@ -70,7 +70,7 @@ class _AddNewPaynymFollowViewState
} }
setState(() { setState(() {
_searchResult = paynymAccount; _searchResult = paynymAccount.value;
}); });
} }
} }

View file

@ -108,7 +108,7 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
debugPrint("created:$created"); debugPrint("created:$created");
if (created.claimed) { if (created.value!.claimed) {
// payment code already claimed // payment code already claimed
debugPrint("pcode already claimed!!"); debugPrint("pcode already claimed!!");
if (mounted) { if (mounted) {
@ -126,18 +126,19 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
// sign token with notification private key // sign token with notification private key
final signature = final signature =
await wallet.signStringWithNotificationKey(token); await wallet.signStringWithNotificationKey(token.value!);
// claim paynym account // claim paynym account
final claim = final claim = await ref
await ref.read(paynymAPIProvider).claim(token, signature); .read(paynymAPIProvider)
.claim(token.value!, signature);
if (claim["claimed"] == pCode.toString()) { if (claim.value?.claimed == pCode.toString()) {
final account = final account =
await ref.read(paynymAPIProvider).nym(pCode.toString()); await ref.read(paynymAPIProvider).nym(pCode.toString());
ref.read(myPaynymAccountStateProvider.state).state = ref.read(myPaynymAccountStateProvider.state).state =
account!; account.value!;
if (mounted) { if (mounted) {
Navigator.of(context).popUntil( Navigator.of(context).popUntil(
ModalRoute.withName( ModalRoute.withName(

View file

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
import 'package:stackwallet/models/paynym/paynym_response.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart'; import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
@ -127,27 +128,27 @@ class _PaynymFollowToggleButtonState
final myPCode = await wallet.getPaymentCode(); final myPCode = await wallet.getPaymentCode();
String token = await ref.read(paynymAPIProvider).token(myPCode.toString()); PaynymResponse<String> token =
await ref.read(paynymAPIProvider).token(myPCode.toString());
// sign token with notification private key // sign token with notification private key
String signature = await wallet.signStringWithNotificationKey(token); String signature = await wallet.signStringWithNotificationKey(token.value!);
var result = await ref var result = await ref.read(paynymAPIProvider).follow(
.read(paynymAPIProvider) token.value!, signature, followedAccount.value!.codes.first.code);
.follow(token, signature, followedAccount!.codes.first.code);
int i = 0; int i = 0;
for (; for (;
i < 10 && result["message"] == "401 Unauthorized - Bad signature"; i < 10 &&
result.statusCode == 401; //"401 Unauthorized - Bad signature";
i++) { i++) {
token = await ref.read(paynymAPIProvider).token(myPCode.toString()); token = await ref.read(paynymAPIProvider).token(myPCode.toString());
// sign token with notification private key // sign token with notification private key
signature = await wallet.signStringWithNotificationKey(token); signature = await wallet.signStringWithNotificationKey(token.value!);
result = await ref result = await ref.read(paynymAPIProvider).follow(
.read(paynymAPIProvider) token.value!, signature, followedAccount.value!.codes.first.code);
.follow(token, signature, followedAccount!.codes.first.code);
await Future<void>.delayed(const Duration(milliseconds: 200)); await Future<void>.delayed(const Duration(milliseconds: 200));
print("RRR result: $result"); print("RRR result: $result");
@ -155,7 +156,7 @@ class _PaynymFollowToggleButtonState
print("Follow result: $result on try $i"); print("Follow result: $result on try $i");
if (result["following"] == followedAccount!.nymID) { if (result.value!.following == followedAccount.value!.nymID) {
if (!loadingPopped && mounted) { if (!loadingPopped && mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
@ -163,16 +164,16 @@ class _PaynymFollowToggleButtonState
unawaited( unawaited(
showFloatingFlushBar( showFloatingFlushBar(
type: FlushBarType.success, type: FlushBarType.success,
message: "You are following ${followedAccount.nymName}", message: "You are following ${followedAccount.value!.nymName}",
context: context, context: context,
), ),
); );
ref.read(myPaynymAccountStateProvider.state).state!.following.add( ref.read(myPaynymAccountStateProvider.state).state!.following.add(
PaynymAccountLite( PaynymAccountLite(
followedAccount.nymID, followedAccount.value!.nymID,
followedAccount.nymName, followedAccount.value!.nymName,
followedAccount.codes.first.code, followedAccount.value!.codes.first.code,
followedAccount.codes.first.segwit, followedAccount.value!.codes.first.segwit,
), ),
); );
@ -189,7 +190,7 @@ class _PaynymFollowToggleButtonState
unawaited( unawaited(
showFloatingFlushBar( showFloatingFlushBar(
type: FlushBarType.warning, type: FlushBarType.warning,
message: "Failed to follow ${followedAccount.nymName}", message: "Failed to follow ${followedAccount.value!.nymName}",
context: context, context: context,
), ),
); );
@ -222,34 +223,34 @@ class _PaynymFollowToggleButtonState
final myPCode = await wallet.getPaymentCode(); final myPCode = await wallet.getPaymentCode();
String token = await ref.read(paynymAPIProvider).token(myPCode.toString()); PaynymResponse<String> token =
await ref.read(paynymAPIProvider).token(myPCode.toString());
// sign token with notification private key // sign token with notification private key
String signature = await wallet.signStringWithNotificationKey(token); String signature = await wallet.signStringWithNotificationKey(token.value!);
var result = await ref var result = await ref.read(paynymAPIProvider).unfollow(
.read(paynymAPIProvider) token.value!, signature, followedAccount.value!.codes.first.code);
.follow(token, signature, followedAccount!.codes.first.code);
int i = 0; int i = 0;
for (; for (;
i < 10 && result["message"] == "401 Unauthorized - Bad signature"; i < 10 &&
result.statusCode == 401; //"401 Unauthorized - Bad signature";
i++) { i++) {
token = await ref.read(paynymAPIProvider).token(myPCode.toString()); token = await ref.read(paynymAPIProvider).token(myPCode.toString());
// sign token with notification private key // sign token with notification private key
signature = await wallet.signStringWithNotificationKey(token); signature = await wallet.signStringWithNotificationKey(token.value!);
result = await ref result = await ref.read(paynymAPIProvider).unfollow(
.read(paynymAPIProvider) token.value!, signature, followedAccount.value!.codes.first.code);
.unfollow(token, signature, followedAccount!.codes.first.code);
await Future<void>.delayed(const Duration(milliseconds: 200)); await Future<void>.delayed(const Duration(milliseconds: 200));
print("RRR result: $result"); print("unfollow RRR result: $result");
} }
print("Unfollow result: $result on try $i"); print("Unfollow result: $result on try $i");
if (result["unfollowing"] == followedAccount!.nymID) { if (result.value!.unfollowing == followedAccount.value!.nymID) {
if (!loadingPopped && mounted) { if (!loadingPopped && mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
@ -257,7 +258,7 @@ class _PaynymFollowToggleButtonState
unawaited( unawaited(
showFloatingFlushBar( showFloatingFlushBar(
type: FlushBarType.success, type: FlushBarType.success,
message: "You have unfollowed ${followedAccount.nymName}", message: "You have unfollowed ${followedAccount.value!.nymName}",
context: context, context: context,
), ),
); );
@ -265,7 +266,7 @@ class _PaynymFollowToggleButtonState
.read(myPaynymAccountStateProvider.state) .read(myPaynymAccountStateProvider.state)
.state! .state!
.following .following
.removeWhere((e) => e.nymId == followedAccount.nymID); .removeWhere((e) => e.nymId == followedAccount.value!.nymID);
setState(() { setState(() {
isFollowing = false; isFollowing = false;
@ -280,7 +281,7 @@ class _PaynymFollowToggleButtonState
unawaited( unawaited(
showFloatingFlushBar( showFloatingFlushBar(
type: FlushBarType.warning, type: FlushBarType.warning,
message: "Failed to unfollow ${followedAccount.nymName}", message: "Failed to unfollow ${followedAccount.value!.nymName}",
context: context, context: context,
), ),
); );

View file

@ -134,9 +134,10 @@ class _WalletNavigationBarState extends State<WalletNavigationBar> {
Navigator.of(context).pop(); Navigator.of(context).pop();
// check if account exists and for matching code to see if claimed // check if account exists and for matching code to see if claimed
if (account != null && account.codes.first.claimed) { if (account.value != null &&
account.value!.codes.first.claimed) {
ref.read(myPaynymAccountStateProvider.state).state = ref.read(myPaynymAccountStateProvider.state).state =
account; account.value!;
await Navigator.of(context).pushNamed( await Navigator.of(context).pushNamed(
PaynymHomeView.routeName, PaynymHomeView.routeName,

View file

@ -1,4 +1,4 @@
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/utilities/paynym_api.dart'; import 'package:stackwallet/utilities/paynym_is_api.dart';
final paynymAPIProvider = Provider<PaynymAPI>((_) => PaynymAPI()); final paynymAPIProvider = Provider<PaynymIsApi>((_) => PaynymIsApi());

View file

@ -4,12 +4,19 @@ import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:stackwallet/models/paynym/created_paynym.dart'; import 'package:stackwallet/models/paynym/created_paynym.dart';
import 'package:stackwallet/models/paynym/paynym_account.dart'; import 'package:stackwallet/models/paynym/paynym_account.dart';
import 'package:stackwallet/models/paynym/paynym_claim.dart';
import 'package:stackwallet/models/paynym/paynym_follow.dart';
import 'package:stackwallet/models/paynym/paynym_response.dart';
import 'package:stackwallet/models/paynym/paynym_unfollow.dart';
import 'package:tuple/tuple.dart';
class PaynymAPI { // todo: better error message parsing (from response itself?)
class PaynymIsApi {
static const String baseURL = "https://paynym.is/api"; static const String baseURL = "https://paynym.is/api";
static const String version = "/v1"; static const String version = "/v1";
Future<Map<String, dynamic>> _post( Future<Tuple2<Map<String, dynamic>, int>> _post(
String endpoint, String endpoint,
Map<String, dynamic> body, [ Map<String, dynamic> body, [
Map<String, String> additionalHeaders = const {}, Map<String, String> additionalHeaders = const {},
@ -29,7 +36,10 @@ class PaynymAPI {
debugPrint("Paynym response code: ${response.statusCode}"); debugPrint("Paynym response code: ${response.statusCode}");
debugPrint("Paynym response body: ${response.body}"); debugPrint("Paynym response body: ${response.body}");
return jsonDecode(response.body) as Map<String, dynamic>; return Tuple2(
jsonDecode(response.body) as Map<String, dynamic>,
response.statusCode,
);
} }
// ### `/api/v1/create` // ### `/api/v1/create`
@ -77,9 +87,28 @@ class PaynymAPI {
// //
// //
// ------ // ------
Future<CreatedPaynym> create(String code) async { Future<PaynymResponse<CreatedPaynym>> create(String code) async {
final map = await _post("/create", {"code": code}); final result = await _post("/create", {"code": code});
return CreatedPaynym.fromMap(map);
String message;
CreatedPaynym? value;
switch (result.item2) {
case 201:
message = "PayNym created successfully";
value = CreatedPaynym.fromMap(result.item1);
break;
case 200:
message = "PayNym already exists";
value = CreatedPaynym.fromMap(result.item1);
break;
case 400:
message = "Bad request";
break;
default:
message = "Unknown error";
}
return PaynymResponse(value, result.item2, message);
} }
// ### `/api/v1/token` // ### `/api/v1/token`
@ -120,9 +149,27 @@ class PaynymAPI {
// //
// //
// ------ // ------
Future<String> token(String code) async { Future<PaynymResponse<String>> token(String code) async {
final map = await _post("/token", {"code": code}); final result = await _post("/token", {"code": code});
return map["token"] as String;
String message;
String? value;
switch (result.item2) {
case 200:
message = "Token was successfully updated";
value = result.item1["token"] as String;
break;
case 404:
message = "Payment code was not found";
break;
case 400:
message = "Bad request";
break;
default:
message = "Unknown error";
}
return PaynymResponse(value, result.item2, message);
} }
// ### `/api/v1/nym` // ### `/api/v1/nym`
@ -175,17 +222,43 @@ class PaynymAPI {
// | 200 | Nym found and returned | // | 200 | Nym found and returned |
// | 404 | Nym not found | // | 404 | Nym not found |
// | 400 | Bad request | // | 400 | Bad request |
Future<PaynymAccount?> nym(String code, [bool compact = false]) async { Future<PaynymResponse<PaynymAccount>> nym(String code,
[bool compact = false]) async {
final Map<String, dynamic> requestBody = {"nym": code}; final Map<String, dynamic> requestBody = {"nym": code};
if (compact) { if (compact) {
requestBody["compact"] = true; requestBody["compact"] = true;
} }
String message;
PaynymAccount? value;
int statusCode;
try { try {
final map = await _post("/nym", requestBody); final result = await _post("/nym", requestBody);
return PaynymAccount.fromMap(map);
} catch (_) { statusCode = result.item2;
return null;
switch (result.item2) {
case 200:
message = "Nym found and returned";
value = PaynymAccount.fromMap(result.item1);
break;
case 404:
message = "Nym not found";
break;
case 400:
message = "Bad request";
break;
default:
message = "Unknown error";
statusCode = -1;
}
} catch (e) {
value = null;
message = e.toString();
statusCode = -1;
} }
return PaynymResponse(value, statusCode, message);
} }
// ## Authenticated Requests // ## Authenticated Requests
@ -238,8 +311,31 @@ class PaynymAPI {
// | 400 | Bad request | // | 400 | Bad request |
// //
// ------ // ------
Future<Map<String, dynamic>> claim(String token, String signature) async { Future<PaynymResponse<PaynymClaim>> claim(
return _post("/claim", {"signature": signature}, {"auth-token": token}); String token,
String signature,
) async {
final result = await _post(
"/claim",
{"signature": signature},
{"auth-token": token},
);
String message;
PaynymClaim? value;
switch (result.item2) {
case 200:
message = "Payment code successfully claimed";
value = PaynymClaim.fromMap(result.item1);
break;
case 400:
message = "Bad request";
break;
default:
message = "Unknown error";
}
return PaynymResponse(value, result.item2, message);
} }
// ### `/api/v1/follow` // ### `/api/v1/follow`
@ -284,12 +380,12 @@ class PaynymAPI {
// | 401 | Unauthorized token or signature or Unclaimed payment code | // | 401 | Unauthorized token or signature or Unclaimed payment code |
// //
// ------ // ------
Future<Map<String, dynamic>> follow( Future<PaynymResponse<PaynymFollow>> follow(
String token, String token,
String signature, String signature,
String target, String target,
) async { ) async {
return _post( final result = await _post(
"/follow", "/follow",
{ {
"target": target, "target": target,
@ -299,6 +395,28 @@ class PaynymAPI {
"auth-token": token, "auth-token": token,
}, },
); );
String message;
PaynymFollow? value;
switch (result.item2) {
case 200:
message = "Added to followers";
value = PaynymFollow.fromMap(result.item1);
break;
case 404:
message = "Payment code not found";
break;
case 400:
message = "Bad request";
break;
case 401:
message = "Unauthorized token or signature or Unclaimed payment code";
break;
default:
message = "Unknown error";
}
return PaynymResponse(value, result.item2, message);
} }
// ### `/api/v1/unfollow` // ### `/api/v1/unfollow`
@ -343,12 +461,12 @@ class PaynymAPI {
// | 401 | Unauthorized token or signature or Unclaimed payment code | // | 401 | Unauthorized token or signature or Unclaimed payment code |
// //
// ------ // ------
Future<Map<String, dynamic>> unfollow( Future<PaynymResponse<PaynymUnfollow>> unfollow(
String token, String token,
String signature, String signature,
String target, String target,
) async { ) async {
return _post( final result = await _post(
"/unfollow", "/unfollow",
{ {
"target": target, "target": target,
@ -358,6 +476,28 @@ class PaynymAPI {
"auth-token": token, "auth-token": token,
}, },
); );
String message;
PaynymUnfollow? value;
switch (result.item2) {
case 200:
message = "Unfollowed successfully";
value = PaynymUnfollow.fromMap(result.item1);
break;
case 404:
message = "Payment code not found";
break;
case 400:
message = "Bad request";
break;
case 401:
message = "Unauthorized token or signature or Unclaimed payment code";
break;
default:
message = "Unknown error";
}
return PaynymResponse(value, result.item2, message);
} }
// ### `/api/v1/nym/add` // ### `/api/v1/nym/add`
@ -404,22 +544,24 @@ class PaynymAPI {
// | 401 | Unauthorized token or signature or Unclaimed payment code | // | 401 | Unauthorized token or signature or Unclaimed payment code |
// //
// ------ // ------
Future<Map<String, dynamic>> add(
String token, // NOT USED
String signature, // Future<Map<String, dynamic>> add(
String nym, // String token,
String code, // String signature,
) async { // String nym,
return _post( // String code,
"/add", // ) async {
{ // return _post(
"nym": nym, // "/add",
"code": code, // {
"signature": signature, // "nym": nym,
}, // "code": code,
{ // "signature": signature,
"auth-token": token, // },
}, // {
); // "auth-token": token,
} // },
// );
// }
} }