mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-26 21:39:21 +00:00
566 lines
14 KiB
Dart
566 lines
14 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:stackwallet/models/paynym/created_paynym.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';
|
|
|
|
// todo: better error message parsing (from response itself?)
|
|
|
|
class PaynymIsApi {
|
|
static const String baseURL = "https://paynym.is/api";
|
|
static const String version = "/v1";
|
|
|
|
Future<Tuple2<Map<String, dynamic>, int>> _post(
|
|
String endpoint,
|
|
Map<String, dynamic> body, [
|
|
Map<String, String> additionalHeaders = const {},
|
|
]) async {
|
|
String url = baseURL +
|
|
version +
|
|
(endpoint.startsWith("/") ? endpoint : "/$endpoint");
|
|
final uri = Uri.parse(url);
|
|
final response = await http.post(
|
|
uri,
|
|
headers: {
|
|
'Content-Type': 'application/json; charset=UTF-8',
|
|
}..addAll(additionalHeaders),
|
|
body: jsonEncode(body),
|
|
);
|
|
|
|
debugPrint("Paynym response code: ${response.statusCode}");
|
|
debugPrint("Paynym response body: ${response.body}");
|
|
|
|
return Tuple2(
|
|
jsonDecode(response.body) as Map<String, dynamic>,
|
|
response.statusCode,
|
|
);
|
|
}
|
|
|
|
// ### `/api/v1/create`
|
|
//
|
|
// Create a new PayNym entry in the database.
|
|
//
|
|
//
|
|
//
|
|
// **Request**
|
|
//
|
|
// ```json
|
|
// POST /api/v1/create
|
|
// content-type: application/json
|
|
//
|
|
// {
|
|
// "code":"PM8T..."
|
|
// }
|
|
//
|
|
// ```
|
|
//
|
|
// | Value | Key |
|
|
// | ----- | -------------------- |
|
|
// | code | A valid payment code |
|
|
//
|
|
//
|
|
//
|
|
// **Response** (201)
|
|
//
|
|
// ```json
|
|
// {
|
|
// "claimed": false,
|
|
// "nymID": "v9pJm...",
|
|
// "nymName": "snowysea",
|
|
// "segwit": true,
|
|
// "token": "IlBNOF...",
|
|
// }
|
|
// ```
|
|
//
|
|
// | Code | Meaning |
|
|
// | ---- | --------------------------- |
|
|
// | 201 | PayNym created successfully |
|
|
// | 200 | PayNym already exists |
|
|
// | 400 | Bad request |
|
|
//
|
|
//
|
|
//
|
|
// ------
|
|
Future<PaynymResponse<CreatedPaynym>> create(String code) async {
|
|
final result = await _post("/create", {"code": code});
|
|
|
|
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 = result.item1["message"] as String? ?? "Unknown error";
|
|
}
|
|
return PaynymResponse(value, result.item2, message);
|
|
}
|
|
|
|
// ### `/api/v1/token`
|
|
//
|
|
// Update the verification token in the database. A token is valid for 24 hours and only for a single authenticated call. The payment code must be in the database or the request will return `404`
|
|
//
|
|
//
|
|
//
|
|
// **Request**
|
|
//
|
|
// ```json
|
|
// POST /api/v1/token/
|
|
// content-type: application/json
|
|
//
|
|
// {"code":"PM8T..."}
|
|
// ```
|
|
//
|
|
// | Value | Key |
|
|
// | ----- | -------------------- |
|
|
// | code | A valid payment code |
|
|
//
|
|
//
|
|
//
|
|
// **Response** (200)
|
|
//
|
|
// ```json
|
|
// {
|
|
// "token": "DP7S3w..."
|
|
// }
|
|
// ```
|
|
//
|
|
// | Code | Meaning |
|
|
// | ---- | ------------------------------ |
|
|
// | 200 | Token was successfully updated |
|
|
// | 404 | Payment code was not found |
|
|
// | 400 | Bad request |
|
|
//
|
|
//
|
|
//
|
|
// ------
|
|
Future<PaynymResponse<String>> token(String code) async {
|
|
final result = await _post("/token", {"code": code});
|
|
|
|
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 = result.item1["message"] as String? ?? "Unknown error";
|
|
}
|
|
return PaynymResponse(value, result.item2, message);
|
|
}
|
|
|
|
// ### `/api/v1/nym`
|
|
//
|
|
// Returns all known information about a PayNym account including any other payment codes associated with this Nym.
|
|
//
|
|
//
|
|
//
|
|
// **Request**
|
|
//
|
|
// ```json
|
|
// POST /api/v1/nym/
|
|
// content-type: application/json
|
|
//
|
|
// {"nym":"PM8T..."}
|
|
// ```
|
|
//
|
|
// | Value | Key |
|
|
// | ----- | ---------------------------------------- |
|
|
// | nym | A valid payment `code`, `nymID`, or `nymName` |
|
|
//
|
|
//
|
|
//
|
|
// **Response** (200)
|
|
//
|
|
// ```json
|
|
// {
|
|
// "codes": [
|
|
// {
|
|
// "claimed": true,
|
|
// "segwit": true,
|
|
// "code": "PM8T..."
|
|
// }
|
|
// ],
|
|
// "followers": [
|
|
// {
|
|
// "nymId": "5iEpU..."
|
|
// }
|
|
// ],
|
|
// "following": [],
|
|
// "nymID": "wXGgdC...",
|
|
// "nymName": "littlevoice"
|
|
// }
|
|
// ```
|
|
//
|
|
// If the `compact=true` parameter is added to the URL, follower and following will not returned. This can achieve faster requests.
|
|
//
|
|
// | Code | Meaning |
|
|
// | ---- | ---------------------- |
|
|
// | 200 | Nym found and returned |
|
|
// | 404 | Nym not found |
|
|
// | 400 | Bad request |
|
|
Future<PaynymResponse<PaynymAccount>> nym(String code,
|
|
[bool compact = false]) async {
|
|
final Map<String, dynamic> requestBody = {"nym": code};
|
|
if (compact) {
|
|
requestBody["compact"] = true;
|
|
}
|
|
|
|
String message;
|
|
PaynymAccount? value;
|
|
int statusCode;
|
|
|
|
try {
|
|
final result = await _post("/nym", requestBody);
|
|
|
|
statusCode = result.item2;
|
|
|
|
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 = result.item1["message"] as String? ?? "Unknown error";
|
|
}
|
|
} catch (e) {
|
|
value = null;
|
|
message = e.toString();
|
|
statusCode = -1;
|
|
}
|
|
return PaynymResponse(value, statusCode, message);
|
|
}
|
|
|
|
// ## Authenticated Requests
|
|
//
|
|
//
|
|
//
|
|
// ### Making authenticated requests
|
|
//
|
|
// 1. Set an `auth-token` header containing the `token`
|
|
// 2. Sign the `token` with the private key of the notification address of the primary payment code
|
|
// 3. Add the `signature` to the body of the request.
|
|
// 4. A token can only be used once per authenticated request. A new `token` will be returned in the response of a successful authenticated request
|
|
//
|
|
|
|
// ### `/api/v1/claim`
|
|
//
|
|
// Claim ownership of a payment code added to a newly created PayNym identity.
|
|
//
|
|
//
|
|
//
|
|
// **Request**
|
|
//
|
|
// ```json
|
|
// POST /api/v1/claim
|
|
// content-type: application/json
|
|
// auth-token: IlBNOFRKWmt...
|
|
//
|
|
//
|
|
// {"signature":"..."}
|
|
// ```
|
|
//
|
|
// | Value | Key |
|
|
// | --------- | ---------------------------------------- |
|
|
// | signature | The `token` signed by the BIP47 notification address |
|
|
//
|
|
//
|
|
//
|
|
// **Response** (200)
|
|
//
|
|
// ```json
|
|
// {
|
|
// "claimed" : "PM8T...",
|
|
// "token" : "IlBNOFRKSmt..."
|
|
// }
|
|
// ```
|
|
//
|
|
// | Code | Meaning |
|
|
// | ---- | --------------------------------- |
|
|
// | 200 | Payment code successfully claimed |
|
|
// | 400 | Bad request |
|
|
//
|
|
// ------
|
|
Future<PaynymResponse<PaynymClaim>> claim(
|
|
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 = result.item1["message"] as String? ?? "Unknown error";
|
|
}
|
|
return PaynymResponse(value, result.item2, message);
|
|
}
|
|
|
|
// ### `/api/v1/follow`
|
|
//
|
|
// Follow another PayNym account.
|
|
//
|
|
//
|
|
//
|
|
// **Request**
|
|
//
|
|
// ```json
|
|
// POST /api/v1/follow/
|
|
// content-type: application/json
|
|
// auth-token: IlBNOFRKWmt...
|
|
//
|
|
// {
|
|
// "target": "wXGgdC...",
|
|
// "signature":"..."
|
|
// }
|
|
// ```
|
|
//
|
|
// | Key | Value |
|
|
// | --------- | ---------------------------------------- |
|
|
// | target | The payment code to follow |
|
|
// | signature | The `token` signed by the BIP47 notification address |
|
|
//
|
|
// **Response** (200)
|
|
//
|
|
// ```json
|
|
// {
|
|
// "follower": "5iEpU...",
|
|
// "following": "wXGgdC...",
|
|
// "token" : "IlBNOFRKSmt..."
|
|
// }
|
|
// ```
|
|
//
|
|
// | Code | Meaning |
|
|
// | ---- | ---------------------------------------- |
|
|
// | 200 | Added to followers |
|
|
// | 404 | Payment code not found |
|
|
// | 400 | Bad request |
|
|
// | 401 | Unauthorized token or signature or Unclaimed payment code |
|
|
//
|
|
// ------
|
|
Future<PaynymResponse<PaynymFollow>> follow(
|
|
String token,
|
|
String signature,
|
|
String target,
|
|
) async {
|
|
final result = await _post(
|
|
"/follow",
|
|
{
|
|
"target": target,
|
|
"signature": signature,
|
|
},
|
|
{
|
|
"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 = result.item1["message"] as String? ?? "Unknown error";
|
|
}
|
|
return PaynymResponse(value, result.item2, message);
|
|
}
|
|
|
|
// ### `/api/v1/unfollow`
|
|
//
|
|
// Unfollow another PayNym account.
|
|
//
|
|
//
|
|
//
|
|
// **Request**
|
|
//
|
|
// ```json
|
|
// POST /api/v1/unfollow/
|
|
// content-type: application/json
|
|
// auth-token: IlBNOFRKWmt...
|
|
//
|
|
// {
|
|
// "target": "wXGgdC...",
|
|
// "signature":"..."
|
|
// }
|
|
// ```
|
|
//
|
|
// | Key | Value |
|
|
// | --------- | ---------------------------------------- |
|
|
// | target | The payment code to unfollow |
|
|
// | signature | The `token` signed by the BIP47 notification address |
|
|
//
|
|
// **Response** (200)
|
|
//
|
|
// ```json
|
|
// {
|
|
// "follower": "5iEpU...",
|
|
// "unfollowing": "wXGgdC...",
|
|
// "token" : "IlBNOFRKSmt..."
|
|
// }
|
|
// ```
|
|
//
|
|
// | Code | Meaning |
|
|
// | ---- | ---------------------------------------- |
|
|
// | 200 | Unfollowed successfully |
|
|
// | 404 | Payment code not found |
|
|
// | 400 | Bad request |
|
|
// | 401 | Unauthorized token or signature or Unclaimed payment code |
|
|
//
|
|
// ------
|
|
Future<PaynymResponse<PaynymUnfollow>> unfollow(
|
|
String token,
|
|
String signature,
|
|
String target,
|
|
) async {
|
|
final result = await _post(
|
|
"/unfollow",
|
|
{
|
|
"target": target,
|
|
"signature": signature,
|
|
},
|
|
{
|
|
"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 = result.item1["message"] as String? ?? "Unknown error";
|
|
}
|
|
return PaynymResponse(value, result.item2, message);
|
|
}
|
|
|
|
// ### `/api/v1/nym/add`
|
|
//
|
|
// Add a new payment code to an existing Nym
|
|
//
|
|
//
|
|
//
|
|
// **Request**
|
|
//
|
|
// ```json
|
|
// POST /api/v1/nym/add
|
|
// content-type: application/json
|
|
// auth-token: IlBNOFRKWmt...
|
|
//
|
|
// {
|
|
// "nym": "wXGgdC...",
|
|
// "code":"PM8T...",
|
|
// "signature":"..."
|
|
// }
|
|
// ```
|
|
//
|
|
// | Key | Value |
|
|
// | --------- | ------------------------------------------------------------ |
|
|
// | nym | A valid payment `code`, `nymID`, or `nymName` |
|
|
// | code | A valid payment code |
|
|
// | signature | The `token` signed by the BIP47 notification address of the primary payment code. |
|
|
//
|
|
// **Response** (200)
|
|
//
|
|
// ```json
|
|
// {
|
|
// "code":"PM8T...",
|
|
// "segwit": true,
|
|
// "token" : "IlBNOFRKSmt..."
|
|
// }
|
|
// ```
|
|
//
|
|
// | Code | Meaning |
|
|
// | ---- | --------------------------------------------------------- |
|
|
// | 200 | Nym updated successfully |
|
|
// | 404 | Nym not found |
|
|
// | 400 | Bad request |
|
|
// | 401 | Unauthorized token or signature or Unclaimed payment code |
|
|
//
|
|
// ------
|
|
|
|
// NOT USED
|
|
// Future<Map<String, dynamic>> add(
|
|
// String token,
|
|
// String signature,
|
|
// String nym,
|
|
// String code,
|
|
// ) async {
|
|
// return _post(
|
|
// "/add",
|
|
// {
|
|
// "nym": nym,
|
|
// "code": code,
|
|
// "signature": signature,
|
|
// },
|
|
// {
|
|
// "auth-token": token,
|
|
// },
|
|
// );
|
|
// }
|
|
}
|