2025-12-03 16:19:11 +01:00
|
|
|
open Syntax
|
|
|
|
|
open Api
|
|
|
|
|
module String_map = Stdlib.Map.Make (Stdlib.String)
|
|
|
|
|
|
2026-02-07 21:25:49 +01:00
|
|
|
(* TODO mirage-crypto
|
|
|
|
|
is this ok?
|
|
|
|
|
maybe don't use the same RNG-initialization as the one used to generate keys *)
|
|
|
|
|
let seed req _server _env =
|
|
|
|
|
Logs.info (fun m -> m "GET /seed");
|
|
|
|
|
(* RNG is initialized by Vif.run *)
|
|
|
|
|
let s = Mirage_crypto_rng.generate 64 in
|
|
|
|
|
let open Vif.Response in
|
|
|
|
|
let open Syntax in
|
|
|
|
|
let* () = add ~field:"content-type" "application/octet-stream" in
|
|
|
|
|
let* () = with_string req s in
|
|
|
|
|
respond `OK
|
|
|
|
|
|
|
|
|
|
let config req _server _env =
|
|
|
|
|
Logs.info (fun m -> m "GET /config");
|
2026-02-23 07:24:40 +01:00
|
|
|
let res = Api.encode Api.ExchangeVersionResponse.jsont config in
|
|
|
|
|
Respond.result res req
|
2026-02-07 21:25:49 +01:00
|
|
|
|
2026-02-27 17:49:54 +01:00
|
|
|
(* not implemented:
|
|
|
|
|
- kyc
|
|
|
|
|
- wads
|
|
|
|
|
- account limits
|
|
|
|
|
- zero limited operations
|
|
|
|
|
- recoup
|
|
|
|
|
- extensions *)
|
2026-02-17 09:30:20 +01:00
|
|
|
let mk_keys ~db_conn (module Keys : Keys.S) ~last_issue_date =
|
2026-02-27 17:49:54 +01:00
|
|
|
let version = Libtool_version.mte_protocol_version in
|
2025-12-03 16:19:11 +01:00
|
|
|
let base_url = Config.base_url in
|
|
|
|
|
let currency = Config.currency in
|
|
|
|
|
let shopping_url = Config.shopping_url in
|
|
|
|
|
let open_banking_gateway = Config.open_banking_gateway_url in
|
|
|
|
|
let bank_compliance_language = Config.bank_compliance_language in
|
2026-02-23 07:24:40 +01:00
|
|
|
let currency_specification = Api.currency_specification in
|
2025-12-03 16:19:11 +01:00
|
|
|
let tiny_amount = Config.tiny_amount in
|
|
|
|
|
let stefan_abs = Config.stefan_abs in
|
|
|
|
|
let stefan_log = Config.stefan_log in
|
|
|
|
|
let stefan_lin = Config.stefan_lin in
|
2026-02-23 09:02:39 +01:00
|
|
|
(* type of the asset. "fiat", "crypto", "regional" or "stock". *)
|
|
|
|
|
let asset_type = "fiat" in
|
2026-02-17 09:30:20 +01:00
|
|
|
let* accounts = Pg.get_wire_accounts db_conn () |> unwrap_err_caqti in
|
2025-12-03 16:19:11 +01:00
|
|
|
let* wire_fees =
|
2026-02-23 09:02:39 +01:00
|
|
|
(* wire_methods? *)
|
|
|
|
|
let wire_method = "x-taler-bank" in
|
2025-12-03 16:19:11 +01:00
|
|
|
let+ wire_fees =
|
|
|
|
|
Pg.get_wire_fees db_conn ~wire_method |> unwrap_err_caqti
|
|
|
|
|
in
|
|
|
|
|
String_map.singleton wire_method wire_fees
|
|
|
|
|
in
|
2026-02-23 09:02:39 +01:00
|
|
|
let wads = [] in
|
2025-12-03 16:19:11 +01:00
|
|
|
let kyc_enabled = false in
|
2026-02-23 09:02:39 +01:00
|
|
|
let disable_direct_deposit = false in
|
2025-12-03 16:19:11 +01:00
|
|
|
let master_public_key = Config.master_public_key in
|
2026-02-05 15:31:49 +01:00
|
|
|
let reserve_closing_delay = Config.Exchangedb.idle_reserve_expiration_time in
|
2025-12-03 16:19:11 +01:00
|
|
|
let wallet_balance_limit_without_kyc = None in
|
|
|
|
|
let hard_limits = [] in
|
|
|
|
|
let zero_limits = [] in
|
2026-02-23 09:02:39 +01:00
|
|
|
|
|
|
|
|
let* denom_l = Keys.denominations () in
|
2026-03-03 01:36:12 +01:00
|
|
|
let list_issue_date = Keys.denominations_last_change () in
|
2026-02-23 09:02:39 +01:00
|
|
|
let denom_l =
|
2025-12-03 16:19:11 +01:00
|
|
|
(* if `?last_issue_date` query param does not exactly match the `stamp_start`
|
|
|
|
|
of one of the denomination keys, all keys are returned *)
|
2026-02-23 09:02:39 +01:00
|
|
|
let stamp_start_opt =
|
2025-12-03 16:19:11 +01:00
|
|
|
match last_issue_date with
|
2026-02-23 09:02:39 +01:00
|
|
|
| None -> None
|
|
|
|
|
| Some timestamp ->
|
|
|
|
|
List.find_map
|
|
|
|
|
(fun v ->
|
2026-02-26 17:04:48 +01:00
|
|
|
if Timestamp.equal v.Denomination.stamp_start timestamp then
|
2026-02-23 09:02:39 +01:00
|
|
|
Some v.stamp_start
|
|
|
|
|
else None)
|
|
|
|
|
denom_l
|
2025-12-03 16:19:11 +01:00
|
|
|
in
|
2026-02-23 09:02:39 +01:00
|
|
|
match stamp_start_opt with
|
|
|
|
|
| None -> denom_l
|
|
|
|
|
| Some timestamp ->
|
|
|
|
|
List.filter
|
2026-02-27 22:44:49 +01:00
|
|
|
(fun v -> Timestamp.compare timestamp v.Denomination.stamp_start <= 0)
|
2026-02-23 09:02:39 +01:00
|
|
|
denom_l
|
2025-12-03 16:19:11 +01:00
|
|
|
in
|
2026-02-23 09:02:39 +01:00
|
|
|
let* denominations = Denomination.make_denom_group_sorted denom_l in
|
2025-12-03 16:19:11 +01:00
|
|
|
|
2026-02-23 09:02:39 +01:00
|
|
|
let* signkeys = Keys.signkeys () in
|
2025-12-03 16:19:11 +01:00
|
|
|
|
2026-02-23 09:02:39 +01:00
|
|
|
(* the eddsa pub key used to sign exchange_sig *)
|
|
|
|
|
let* exchange_pub =
|
2026-02-26 17:04:48 +01:00
|
|
|
let now = Timestamp.of_ptime (Ptime_clock.now ()) in
|
|
|
|
|
let opt =
|
|
|
|
|
List.find_opt (fun sk -> Signkey.is_valid_at ~timestamp:now sk) signkeys
|
|
|
|
|
in
|
|
|
|
|
match opt with
|
|
|
|
|
| None -> Fmt.error "exchange has no active signkey"
|
|
|
|
|
| Some sk -> Ok sk.pub
|
2025-12-03 16:19:11 +01:00
|
|
|
in
|
2026-02-26 17:04:48 +01:00
|
|
|
let signkeys = List.map Api.SignKey.of_signkey signkeys in
|
2026-02-23 09:02:39 +01:00
|
|
|
|
|
|
|
|
(* ! depends on denominations order *)
|
2025-12-03 16:19:11 +01:00
|
|
|
let exchange_sig =
|
2026-02-05 19:26:59 +01:00
|
|
|
let open Signatures.ExchangeKeySet in
|
2026-02-27 17:49:54 +01:00
|
|
|
signf (Keys.sign exchange_pub)
|
|
|
|
|
R.
|
|
|
|
|
{
|
|
|
|
|
list_issue_date;
|
|
|
|
|
hc= Denomination.hash_over_master_sigs denominations;
|
|
|
|
|
}
|
2025-12-03 16:19:11 +01:00
|
|
|
in
|
|
|
|
|
|
2026-02-23 09:02:39 +01:00
|
|
|
let recoup = (* /recoup *) [] in
|
2025-12-03 16:19:11 +01:00
|
|
|
let* global_fees =
|
2026-02-05 15:31:49 +01:00
|
|
|
Pg.get_global_fees db_conn ~start_date:Timestamp.zero |> unwrap_err_caqti
|
2025-12-03 16:19:11 +01:00
|
|
|
in
|
|
|
|
|
let* auditors =
|
2026-02-23 09:02:39 +01:00
|
|
|
(* /auditors/$AUDITOR_PUB/$H_DENOM_PUB *)
|
2025-12-03 16:19:11 +01:00
|
|
|
Pg.get_auditor_keys db_conn
|
|
|
|
|
in
|
|
|
|
|
let extensions = None in
|
|
|
|
|
let extensions_sig = None in
|
|
|
|
|
Ok
|
|
|
|
|
ExchangeKeysResponse.
|
|
|
|
|
{
|
|
|
|
|
version;
|
|
|
|
|
base_url;
|
|
|
|
|
currency;
|
|
|
|
|
shopping_url;
|
|
|
|
|
open_banking_gateway;
|
|
|
|
|
bank_compliance_language;
|
|
|
|
|
currency_specification;
|
|
|
|
|
tiny_amount;
|
|
|
|
|
stefan_abs;
|
|
|
|
|
stefan_log;
|
|
|
|
|
stefan_lin;
|
|
|
|
|
asset_type;
|
|
|
|
|
accounts;
|
|
|
|
|
wire_fees;
|
|
|
|
|
wads;
|
|
|
|
|
kyc_enabled;
|
|
|
|
|
disable_direct_deposit;
|
|
|
|
|
master_public_key;
|
|
|
|
|
reserve_closing_delay;
|
|
|
|
|
wallet_balance_limit_without_kyc;
|
|
|
|
|
hard_limits;
|
|
|
|
|
zero_limits;
|
|
|
|
|
denominations;
|
|
|
|
|
exchange_sig;
|
|
|
|
|
exchange_pub;
|
|
|
|
|
recoup;
|
|
|
|
|
global_fees;
|
|
|
|
|
list_issue_date;
|
|
|
|
|
auditors;
|
|
|
|
|
signkeys;
|
|
|
|
|
extensions;
|
|
|
|
|
extensions_sig;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let jsont = ExchangeKeysResponse.jsont
|
|
|
|
|
|
2026-02-07 21:25:49 +01:00
|
|
|
let keys req server _env =
|
2026-02-07 00:11:25 +01:00
|
|
|
Logs.info (fun m -> m "GET /keys");
|
2025-12-03 16:19:11 +01:00
|
|
|
let db_conn = Vif.Server.device Devices.db_connection server in
|
2026-02-17 09:30:20 +01:00
|
|
|
let keys = Vif.Server.device Devices.keys server in
|
2025-12-03 16:19:11 +01:00
|
|
|
let res =
|
2026-02-05 15:31:49 +01:00
|
|
|
let* last_issue_date =
|
|
|
|
|
match Vif.Queries.get req "last_issue_date" with
|
|
|
|
|
| [] -> Ok None
|
2026-02-23 07:24:40 +01:00
|
|
|
| s :: _ -> (
|
|
|
|
|
match Int64.of_string_opt s with
|
2026-02-23 09:02:39 +01:00
|
|
|
| None -> Error "invalid `?last_issue_date` query param, not an int"
|
2026-02-23 07:24:40 +01:00
|
|
|
| Some n -> Ok (Some (Timestamp.of_s n)))
|
2026-02-05 15:31:49 +01:00
|
|
|
in
|
2026-02-17 09:30:20 +01:00
|
|
|
let* v = mk_keys ~db_conn keys ~last_issue_date in
|
2026-02-23 07:24:40 +01:00
|
|
|
Api.encode jsont v
|
2025-12-03 16:19:11 +01:00
|
|
|
in
|
2026-02-12 12:13:30 +01:00
|
|
|
Respond.result res req
|