From 4913d8ca7622060ea8cca057ad1e65db9976c61b Mon Sep 17 00:00:00 2001 From: swrup Date: Tue, 3 Feb 2026 15:56:05 +0100 Subject: [PATCH] offline tool: add global-fees cmd --- global_fees.json | 1 + src/amount.ml | 2 ++ src/amount.mli | 4 +++ src/http_management.ml | 9 +++---- src/timestamp.ml | 34 +++++++++++++++++++++++++ src/timestamp.mli | 9 +++++++ tools/offline.ml | 57 ++++++++++++++++++++++++++++++++++++++++++ tools/offline_impl.ml | 53 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 global_fees.json diff --git a/global_fees.json b/global_fees.json new file mode 100644 index 00000000..137aab14 --- /dev/null +++ b/global_fees.json @@ -0,0 +1 @@ +{"start_date":{"t_s":1234.5},"end_date":{"t_s":99999999.900000006},"history_fee":"EUR:0.00","account_fee":"EUR:0.00","purse_fee":"EUR:0.00","history_expiration":{"t_s":9999999000000},"purse_account_limit":1,"purse_timeout":{"t_s":9999999000000},"master_sig":"C3XVRW8E4NRFRHETFY6F44PTYDEK8S35RFR5RR690A21HAK7T6P9KBBGXBD87T0XQ0MPQMTDJFT14MJZSHND3XEB8HQ7FQETVXNM008="} \ No newline at end of file diff --git a/src/amount.ml b/src/amount.ml index 2a49a548..65fde44d 100644 --- a/src/amount.ml +++ b/src/amount.ml @@ -117,3 +117,5 @@ let bin_nbo = |+ field beint32 (fun t -> t.fraction) |+ field (bytes currency_len) (fun t -> pad_currency t.currency) |> sealr + +let dummy_value = "DUMMY:0.0" |> of_string |> Result.get_ok diff --git a/src/amount.mli b/src/amount.mli index e2572401..ab6c7da7 100644 --- a/src/amount.mli +++ b/src/amount.mli @@ -25,3 +25,7 @@ val jsont : t Jsont.t (* only for encoding *) val bin : t Bin.t val bin_nbo : t Bin.t + +(* TODO + dummy value to use as placeholder for WIP *) +val dummy_value : t diff --git a/src/http_management.ml b/src/http_management.ml index 81568cbd..e7a1d628 100644 --- a/src/http_management.ml +++ b/src/http_management.ml @@ -433,13 +433,10 @@ module Global_fees = struct purse_timeout; master_sig; } = - (* TODO what is kyc_timeout, kyc_fee ? *) - let dummy_amount = - Amount.make ~sign:None ~currency:"EUR" ~value:0_L ~fraction:0_l - |> Result.get_ok - in + (* TODO KYC + what is kyc_timeout, kyc_fee ? *) let kyc_timeout = None in - let kyc_fee = dummy_amount in + let kyc_fee = Amount.dummy_value in (* * *) let open Bin_sig.GlobalFees in verify_f ~f:Sm.verify_with_master_key master_sig diff --git a/src/timestamp.ml b/src/timestamp.ml index 1b203aec..ca86bcd3 100644 --- a/src/timestamp.ml +++ b/src/timestamp.ml @@ -23,6 +23,23 @@ let of_span_exn = function (* -- *) +(* TODO + this doesn't handle "never" value, and truncate time *) +let of_string s = + match Float.of_string_opt s with + | None -> Error "Timestamp.of_string failure: not a float" + | Some v -> ( + match Ptime.of_float_s v with + | None -> Error "Timestamp.of_string failure" + | Some v -> Ok (Some v)) + +let pp fmt t = + match t with + | None -> Fmt.pf fmt {|"never"|} + | Some v -> + let v = Ptime.to_float_s v in + Fmt.pf fmt {|%f|} v + (* microseconds since the UNIX Epoch, or "never" if None *) let jsont = let number_or_never_jsont = @@ -80,6 +97,23 @@ let compare a b = module Span = struct type t = span + (* TODO + this doesn't handle "never" value, and truncate time *) + let of_string s = + match Float.of_string_opt s with + | None -> Error "Timestamp.Span.of_string failure: not a float" + | Some v -> ( + match Ptime.Span.of_float_s v with + | None -> Error "Timestamp.Span.of_string failure" + | Some v -> Ok (Some v)) + + let pp fmt t = + match t with + | None -> Fmt.pf fmt {|"forever"|} + | Some v -> + let v = Ptime.Span.to_float_s v in + Fmt.pf fmt {|%f|} v + let to_int64 ptime = ptime |> Ptime.Span.to_float_s |> Int64.of_float let of_int64 i = diff --git a/src/timestamp.mli b/src/timestamp.mli index 44520180..6f62f335 100644 --- a/src/timestamp.mli +++ b/src/timestamp.mli @@ -10,6 +10,11 @@ type span = Ptime.Span.t option module Span : sig type t = span + val of_string : string -> (t, string) result + val pp : Stdlib.Format.formatter -> t -> unit + + (* - *) + val jsont : t Jsont.t val bin : t Bin.t val bin_nbo : t Bin.t @@ -21,6 +26,10 @@ val add_span_exn : t -> span -> t val of_span_exn : span -> t val compare : t -> t -> int option +(* used for offline tool argument conversion *) +val of_string : string -> (t, string) result +val pp : Stdlib.Format.formatter -> t -> unit + (* - *) val jsont : t Jsont.t val bin : t Bin.t diff --git a/tools/offline.ml b/tools/offline.ml index 833f1e37..2f9137e9 100644 --- a/tools/offline.ml +++ b/tools/offline.ml @@ -9,6 +9,18 @@ let default_request_file = "request.json" (*let to_term_ret = function Error e -> `Error (false, e) | Ok () -> `Ok ()*) let to_term_ret = Fun.id +let timestamp = + Arg.Conv.make ~docv:"timestamp argument" ~parser:Timestamp.of_string + ~pp:Timestamp.pp () + +let relative_time = + Arg.Conv.make ~docv:"relative time argument" ~parser:Timestamp.Span.of_string + ~pp:Timestamp.Span.pp () + +let amount = + Arg.Conv.make ~docv:"amount argument" ~parser:Amount.of_string ~pp:Amount.pp + () + let setup_cmd = let doc = "Generate offline master keys" in let output = @@ -94,6 +106,50 @@ let revoke_signkey_cmd = let+ output = output and+ master_key = master_key and+ signkey = signkey in revoke_signkey ~output ~master_key ~signkey |> to_term_ret +let global_fees_cmd = + let doc = "Provides global fee configuration." in + let start_date = + Arg.(required & opt (some timestamp) None & info [ "start_date" ]) + in + let end_date = + Arg.(required & opt (some timestamp) None & info [ "end_date" ]) + in + let history_fee = + Arg.(required & opt (some amount) None & info [ "history_fee" ]) + in + let account_fee = + Arg.(required & opt (some amount) None & info [ "account_fee" ]) + in + let purse_fee = + Arg.(required & opt (some amount) None & info [ "purse_fee" ]) + in + let history_expiration = + Arg.( + required & opt (some relative_time) None & info [ "history_expiration" ]) + in + let purse_account_limit = + Arg.(required & opt (some int) None & info [ "purse_account_limit" ]) + in + let purse_timeout = + Arg.(required & opt (some relative_time) None & info [ "purse_timeout" ]) + in + Cmd.make (Cmd.info "global-fees" ~doc) + @@ + let+ output = output + and+ master_key = master_key + and+ start_date = start_date + and+ end_date = end_date + and+ history_fee = history_fee + and+ account_fee = account_fee + and+ purse_fee = purse_fee + and+ history_expiration = history_expiration + and+ purse_account_limit = purse_account_limit + and+ purse_timeout = purse_timeout in + global_fees ~output ~master_key ~start_date ~end_date ~history_fee + ~account_fee ~purse_fee ~history_expiration ~purse_account_limit + ~purse_timeout + |> to_term_ret + let cli = let info = let doc = "MTE Offline CLI tool" in @@ -107,6 +163,7 @@ let cli = upload_cmd; revoke_denom_cmd; revoke_signkey_cmd; + global_fees_cmd; ] let main () = Cmd.eval_result cli diff --git a/tools/offline_impl.ml b/tools/offline_impl.ml index 38ea35c3..00036b7a 100644 --- a/tools/offline_impl.ml +++ b/tools/offline_impl.ml @@ -75,3 +75,56 @@ let revoke_signkey ~output ~master_key ~signkey = let* s = Api.encode Api.SignkeyRevocationSignature.jsont v in let* () = write_file output s in Ok () + +[@@@ocaml.warning "-27"] + +let global_fees ~output ~master_key ~start_date ~end_date ~history_fee + ~account_fee ~purse_fee ~history_expiration ~purse_account_limit + ~purse_timeout = + let open Crypto in + let* key = read_file master_key in + let* key = EddsaPrivateKey.of_octets key in + let* purse_account_limit = + match + purse_account_limit >= 0 + && purse_account_limit <= Int32.to_int Int32.max_int + with + | false -> Error "invalid purse_account_limit value" + | true -> Ok (Int32.of_int purse_account_limit) + in + let master_sig = + let open Bin_sig.GlobalFees in + (* TODO KYC *) + let kyc_timeout = None in + let kyc_fee = Amount.dummy_value in + sign_f ~f:(EddsaSignature.sign ~key) + { + start_date; + end_date; + purse_timeout; + kyc_timeout; + history_expiration; + history_fee; + kyc_fee; + account_fee; + purse_fee; + purse_account_limit; + } + in + let global_fees = + Api.GlobalFees. + { + start_date; + end_date; + purse_timeout; + history_expiration; + history_fee; + account_fee; + purse_fee; + purse_account_limit; + master_sig; + } + in + let* s = Api.encode Api.GlobalFees.jsont global_fees in + let* () = write_file output s in + Ok ()