Skip to main content

futu_mcp/tools/
trade_write.rs

1//! MCP trade-write tools (place/modify/cancel/reconfirm).
2
3use futu_auth::CheckCtx;
4use rmcp::{
5    RoleServer, handler::server::wrapper::Parameters, service::RequestContext, tool, tool_router,
6};
7
8use crate::guard;
9use crate::handlers;
10use crate::tool_args::*;
11use crate::tool_auth::{http_bearer_token, outcome_key_id_from_snapshot};
12
13use super::FutuServer;
14
15#[tool_router(router = trade_write_tool_router, vis = "pub(crate)")]
16impl FutuServer {
17    // ------- 交易写入(需 --enable-trading) -------
18
19    #[tool(
20        description = "⚠️ REAL MONEY when env=real. Place an order on a live brokerage account. REQUIRES futu-mcp started with --enable-trading; real env additionally requires --allow-real-trading; gateway must have been unlocked via `futu_unlock_trade` first. **Market-session fields**: US pre/post-market orders must pass the same FTAPI fields as C++ OpenD (`fill_outside_rth=true` and/or `session`: 1=RTH, 2=ETH, 3=ALL, 4=OVERNIGHT; optional `time_in_force`: 0=DAY, 1=GTC, 2=IOC, 3=GTD). Backend still validates symbol, order_type, TIF, and account eligibility; if backend refuses, daemon returns the server error rather than queuing locally."
21    )]
22    async fn futu_place_order(
23        &self,
24        Parameters(req): Parameters<PlaceOrderReq>,
25        req_ctx: RequestContext<RoleServer>,
26    ) -> std::result::Result<String, String> {
27        // per-call key 优先级:tool args `api_key` > HTTP Authorization Bearer > startup key
28        let header_token = http_bearer_token(&req_ctx);
29        let override_key = req.api_key.as_deref().or(header_token.as_deref());
30        let args_hash = guard::args_short_hash(&req);
31        tracing::warn!(
32            target: futu_auth::audit::TARGET,
33            iface = "mcp",
34            endpoint = "futu_place_order",
35            env = %req.env,
36            market = %req.market,
37            acc_id = req.acc_id.unwrap_or(0), // v1.4.105 T-D1: Option<u64> → u64 for audit log
38            card_num_provided = req.card_num.is_some(),
39            side = %req.side,
40            order_type = %req.order_type,
41            code = %req.code,
42            qty = req.qty,
43            // v1.4.90 P2-C: Option<f64> → f64 NaN sentinel; was `?req.price`
44            // 之前 record_debug 把 Some(400.0) 编为 string "Some(400.0)" 让下游 jq 数值聚合炸
45            price = crate::state::audit_fmt::opt_f64(req.price),
46            time_in_force = req.time_in_force.unwrap_or(-1),
47            fill_outside_rth = req.fill_outside_rth.unwrap_or(false),
48            session = req.session.unwrap_or(-1),
49            args_hash = %args_hash,
50            outcome = "request",
51            "place_order request received"
52        );
53        req.validate()?;
54        // codex round 1 F2 (P2) v1.4.105: caller key strict pre-check FIRST,
55        // **再** client_or_err + resolve. invalid Bearer 在此 fail-closed,
56        // 不再触发 daemon GetAccList + startup-key allowed_card_nums leak.
57        let caller_key_rec = match self.require_caller_key_strict("futu_place_order", override_key)
58        {
59            Ok(rec) => rec,
60            Err(reject_json) => return Err(reject_json),
61        };
62        // codex round 2 F1 (P2) v1.4.105: 早期 trade-scope 校验 — 在
63        // `client_or_err` + `resolve_acc_id_with_card_num` 之前. 防 valid
64        // 但**非-trade** key (e.g. qot:read only) 触发 daemon GetAccList +
65        // card_num 探测 not-found/ambiguous/existence timing.
66        if let Some(reject) =
67            self.require_trading_scope_only("futu_place_order", &req.env, caller_key_rec.as_ref())
68        {
69            return Err(reject);
70        }
71        // v1.4.105 D12 (Phase 2): resolve card_num → acc_id 在 scope check 前.
72        // require_trading 的 CheckCtx 需要 final acc_id 做 allowed_acc_ids 校验,
73        // 所以必须 client_or_err + resolve 提前. card_num resolve 多花 1 次
74        // GetAccList RPC (~10ms 本地 daemon).
75        let client = self.client_or_err().await?;
76        // v1.4.105 D12 contract-hardening 补丁: 同步传 allowed_card_nums 做 string-level
77        // whitelist 校验 (resolve 前). caller key 配置非空时, user 输 card_num
78        // 必须 ∈ 白名单 — UX clear.
79        // codex F2: 用 caller_key_rec (require_caller_key_strict 已验) 而非
80        // current_key_rec(override_key) 重新查 — 避免 silent fallback startup.
81        let allowed_card_nums = caller_key_rec
82            .as_ref()
83            .and_then(|r| r.allowed_card_nums.as_deref());
84        // v1.4.106 codex round 2 F1 case 2 (P1) fix: caller-snapshot acc_id
85        // 早过滤 — 受限 key 不再能 enumerate 其他用户的 acc_id (timing leak
86        // via 0-match/1-match/N-match 差异).
87        let caller_allowed_acc_ids = caller_key_rec
88            .as_ref()
89            .and_then(|r| r.allowed_acc_ids.as_ref());
90        let resolved_acc_id = match handlers::trade_write::resolve_acc_id_with_card_num(
91            &client,
92            req.acc_id.unwrap_or(0),
93            req.card_num.as_deref(),
94            allowed_card_nums,
95            caller_allowed_acc_ids,
96        )
97        .await
98        {
99            Ok(id) => id,
100            Err(msg) => return Self::tool_err(msg),
101        };
102        let ctx = CheckCtx {
103            market: req.market.trim().to_ascii_uppercase(),
104            symbol: format!(
105                "{}.{}",
106                req.market.trim().to_ascii_uppercase(),
107                req.code.trim()
108            ),
109            order_value: req.price.map(|p| p * req.qty),
110            trd_side: Some(req.side.trim().to_ascii_uppercase()),
111            acc_id: Some(resolved_acc_id), // v1.4.35; v1.4.105 D12: resolved
112            mutation_no_exposure: false,
113            currency: None,
114        };
115        if let Some(rej) =
116            self.require_trading("futu_place_order", &req.env, Some(ctx), override_key)
117        {
118            // MED-2: scope 拒绝 → Err(rmcp set is_error=true)
119            return Err(rej);
120        }
121        let result = Self::wrap_result(
122            handlers::trade_write::place_order(
123                &client,
124                handlers::trade_write::PlaceOrderInput {
125                    env: &req.env,
126                    acc_id: resolved_acc_id,
127                    market: &req.market,
128                    side: &req.side,
129                    order_type: &req.order_type,
130                    code: &req.code,
131                    qty: req.qty,
132                    price: req.price,
133                    time_in_force: req.time_in_force,
134                    fill_outside_rth: req.fill_outside_rth,
135                    session: req.session,
136                    expire_time: req.expire_time.as_deref(),
137                    jp_acc_type: req.jp_acc_type,
138                    idempotency_key: req.idempotency_key.clone(),
139                    // v1.4.53 F1 条件单
140                    stop_price: req.stop_price,
141                    trail_type: req.trail_type,
142                    trail_value: req.trail_value,
143                    trail_spread: req.trail_spread,
144                },
145            )
146            .await,
147        );
148        // codex round 1 F4 (P2) v1.4.106: emit_trade_outcome 用 caller_key_rec
149        // snapshot (require_caller_key_strict 已 lock 住), **不**调
150        // current_key_id(override_key) 重新 verify — 防 SIGHUP reload 在
151        // daemon dispatch 中途 revoke/narrow per-call key, audit 记录被误归
152        // 属到 startup key 或 fallback. snapshot reuse pattern 与 unlock_trade
153        // (codex round 1 F5) / list_accounts (codex round 1 F3) 一致.
154        let startup_key = self.state.authed_key();
155        let outcome_key_id =
156            outcome_key_id_from_snapshot(caller_key_rec.as_ref(), startup_key.as_ref());
157        guard::emit_trade_outcome(
158            "futu_place_order",
159            outcome_key_id,
160            &args_hash,
161            Self::result_as_str(&result),
162        );
163        result
164    }
165
166    #[tool(
167        description = "⚠️ REAL MONEY when env=real. Modify an existing live order (change qty/price, cancel, disable/enable/delete). REQUIRES --enable-trading; real env needs --allow-real-trading. For simple cancel, prefer `futu_cancel_order`. **Market hours requirement**: same as `futu_place_order` — off-hours hit server-side refusal regardless of `op`."
168    )]
169    async fn futu_modify_order(
170        &self,
171        Parameters(req): Parameters<ModifyOrderReq>,
172        req_ctx: RequestContext<RoleServer>,
173    ) -> std::result::Result<String, String> {
174        let header_token = http_bearer_token(&req_ctx);
175        let override_key = req.api_key.as_deref().or(header_token.as_deref());
176        let args_hash = guard::args_short_hash(&req);
177        tracing::warn!(
178            target: futu_auth::audit::TARGET,
179            iface = "mcp",
180            endpoint = "futu_modify_order",
181            env = %req.env,
182            market = %req.market,
183            acc_id = req.acc_id.unwrap_or(0), // v1.4.105 T-D1: Option<u64> → u64 for audit log
184            card_num_provided = req.card_num.is_some(),
185            order_id = %req.order_id,
186            op = %req.op,
187            // v1.4.90 P2-C: Option<f64> → f64 NaN sentinel
188            qty = crate::state::audit_fmt::opt_f64(req.qty),
189            price = crate::state::audit_fmt::opt_f64(req.price),
190            args_hash = %args_hash,
191            outcome = "request",
192            "modify_order request received"
193        );
194        req.validate()?;
195        // codex round 1 F2 (P2) v1.4.105: caller key strict pre-check FIRST.
196        let caller_key_rec = match self.require_caller_key_strict("futu_modify_order", override_key)
197        {
198            Ok(rec) => rec,
199            Err(reject_json) => return Err(reject_json),
200        };
201        // codex round 2 F1 (P2) v1.4.105: 早期 trade-scope 校验 (同 place_order).
202        if let Some(reject) =
203            self.require_trading_scope_only("futu_modify_order", &req.env, caller_key_rec.as_ref())
204        {
205            return Err(reject);
206        }
207        // v1.4.105 D12: client_or_err + resolve card_num → acc_id 提前 (scope check 用)
208        let client = self.client_or_err().await?;
209        // v1.4.105 D12 contract-hardening 补丁: 同步传 allowed_card_nums 做 string-level
210        // whitelist 校验 (resolve 前). caller key 配置非空时, user 输 card_num
211        // 必须 ∈ 白名单 — UX clear.
212        // codex F2: 用 caller_key_rec (require_caller_key_strict 已验) 而非
213        // current_key_rec(override_key) 重新查.
214        let allowed_card_nums = caller_key_rec
215            .as_ref()
216            .and_then(|r| r.allowed_card_nums.as_deref());
217        // v1.4.106 codex round 2 F1 case 2 (P1) fix: caller-snapshot acc_id
218        // 早过滤 (同 place_order).
219        let caller_allowed_acc_ids = caller_key_rec
220            .as_ref()
221            .and_then(|r| r.allowed_acc_ids.as_ref());
222        let resolved_acc_id = match handlers::trade_write::resolve_acc_id_with_card_num(
223            &client,
224            req.acc_id.unwrap_or(0),
225            req.card_num.as_deref(),
226            allowed_card_nums,
227            caller_allowed_acc_ids,
228        )
229        .await
230        {
231            Ok(id) => id,
232            Err(msg) => return Self::tool_err(msg),
233        };
234        // modify 改单 API 只给 order_id,symbol/金额/side 都推不出来 → 填空 ctx
235        // 好让 market 白名单 / 时段 / 速率限制照样生效(symbol / 单笔 / 日累计 / side
236        // 这几项在 limits.rs 里都已 skip-empty-ctx)
237        let mutation_ctx = CheckCtx {
238            market: req.market.trim().to_ascii_uppercase(),
239            symbol: String::new(),
240            order_value: None,
241            trd_side: None,
242            acc_id: Some(resolved_acc_id), // v1.4.35; v1.4.105 D12: resolved
243            mutation_no_exposure: false,
244            currency: None,
245        };
246        if let Some(rej) = self.require_trading(
247            "futu_modify_order",
248            &req.env,
249            Some(mutation_ctx),
250            override_key,
251        ) {
252            // MED-2: scope 拒绝 → Err(rmcp set is_error=true)
253            return Err(rej);
254        }
255        let result = Self::wrap_result(
256            handlers::trade_write::modify_order(
257                &client,
258                handlers::trade_write::ModifyOrderInput {
259                    env: &req.env,
260                    acc_id: resolved_acc_id,
261                    market: &req.market,
262                    order_id: &req.order_id,
263                    op: &req.op,
264                    qty: req.qty,
265                    price: req.price,
266                    jp_acc_type: req.jp_acc_type,
267                    idempotency_key: req.idempotency_key.clone(),
268                },
269            )
270            .await,
271        );
272        // codex round 1 F4 (P2) v1.4.106: snapshot reuse — 见 place_order 同段注释.
273        let startup_key = self.state.authed_key();
274        let outcome_key_id =
275            outcome_key_id_from_snapshot(caller_key_rec.as_ref(), startup_key.as_ref());
276        guard::emit_trade_outcome(
277            "futu_modify_order",
278            outcome_key_id,
279            &args_hash,
280            Self::result_as_str(&result),
281        );
282        result
283    }
284
285    #[tool(
286        description = "⚠️ REAL MONEY when env=real. Cancel a live order by order_id. REQUIRES --enable-trading; real env needs --allow-real-trading. Convenience wrapper over `futu_modify_order` with op=CANCEL. Same market-hours requirement as `futu_place_order`."
287    )]
288    async fn futu_cancel_order(
289        &self,
290        Parameters(req): Parameters<CancelOrderReq>,
291        req_ctx: RequestContext<RoleServer>,
292    ) -> std::result::Result<String, String> {
293        let header_token = http_bearer_token(&req_ctx);
294        let override_key = req.api_key.as_deref().or(header_token.as_deref());
295        let args_hash = guard::args_short_hash(&req);
296        tracing::warn!(
297            target: futu_auth::audit::TARGET,
298            iface = "mcp",
299            endpoint = "futu_cancel_order",
300            env = %req.env,
301            market = %req.market,
302            acc_id = req.acc_id.unwrap_or(0), // v1.4.105 T-D1: Option<u64> → u64 for audit log
303            card_num_provided = req.card_num.is_some(),
304            order_id = %req.order_id,
305            args_hash = %args_hash,
306            outcome = "request",
307            "cancel_order request received"
308        );
309        // codex round 1 F2 (P2) v1.4.105: caller key strict pre-check FIRST.
310        let caller_key_rec = match self.require_caller_key_strict("futu_cancel_order", override_key)
311        {
312            Ok(rec) => rec,
313            Err(reject_json) => return Err(reject_json),
314        };
315        // codex round 2 F1 (P2) v1.4.105: 早期 trade-scope 校验 (同 place_order).
316        if let Some(reject) =
317            self.require_trading_scope_only("futu_cancel_order", &req.env, caller_key_rec.as_ref())
318        {
319            return Err(reject);
320        }
321        // v1.4.105 D12: client_or_err + resolve card_num → acc_id 提前 (scope check 用)
322        let client = self.client_or_err().await?;
323        // v1.4.105 D12 contract-hardening 补丁: 同步传 allowed_card_nums 做 string-level
324        // whitelist 校验 (resolve 前). caller key 配置非空时, user 输 card_num
325        // 必须 ∈ 白名单 — UX clear.
326        // codex F2: 用 caller_key_rec (require_caller_key_strict 已验) 而非
327        // current_key_rec(override_key) 重新查.
328        let allowed_card_nums = caller_key_rec
329            .as_ref()
330            .and_then(|r| r.allowed_card_nums.as_deref());
331        // v1.4.106 codex round 2 F1 case 2 (P1) fix: caller-snapshot acc_id
332        // 早过滤 (同 place_order).
333        let caller_allowed_acc_ids = caller_key_rec
334            .as_ref()
335            .and_then(|r| r.allowed_acc_ids.as_ref());
336        let resolved_acc_id = match handlers::trade_write::resolve_acc_id_with_card_num(
337            &client,
338            req.acc_id.unwrap_or(0),
339            req.card_num.as_deref(),
340            allowed_card_nums,
341            caller_allowed_acc_ids,
342        )
343        .await
344        {
345            Ok(id) => id,
346            Err(msg) => return Self::tool_err(msg),
347        };
348        let mutation_ctx = CheckCtx {
349            market: req.market.trim().to_ascii_uppercase(),
350            symbol: String::new(),
351            order_value: None,
352            trd_side: None,
353            acc_id: Some(resolved_acc_id), // v1.4.35; v1.4.105 D12: resolved
354            mutation_no_exposure: false,
355            currency: None,
356        };
357        if let Some(rej) = self.require_trading(
358            "futu_cancel_order",
359            &req.env,
360            Some(mutation_ctx),
361            override_key,
362        ) {
363            // MED-2: scope 拒绝 → Err(rmcp set is_error=true)
364            return Err(rej);
365        }
366        let result = Self::wrap_result(
367            handlers::trade_write::cancel_order(
368                &client,
369                &req.env,
370                resolved_acc_id,
371                &req.market,
372                &req.order_id,
373                req.jp_acc_type,
374                req.idempotency_key.clone(),
375            )
376            .await,
377        );
378        // codex round 1 F4 (P2) v1.4.106: snapshot reuse — 见 place_order 同段注释.
379        let startup_key = self.state.authed_key();
380        let outcome_key_id =
381            outcome_key_id_from_snapshot(caller_key_rec.as_ref(), startup_key.as_ref());
382        guard::emit_trade_outcome(
383            "futu_cancel_order",
384            outcome_key_id,
385            &args_hash,
386            Self::result_as_str(&result),
387        );
388        result
389    }
390
391    #[tool(
392        description = "⚠️ REAL MONEY when env=real. Reconfirm a pending high-risk or price-warning order by numeric order_id. REQUIRES --enable-trading; real env needs --allow-real-trading; gateway must have been unlocked via `futu_unlock_trade` first. Use only for orders that the backend explicitly asked to reconfirm."
393    )]
394    async fn futu_reconfirm_order(
395        &self,
396        Parameters(req): Parameters<ReconfirmOrderReq>,
397        req_ctx: RequestContext<RoleServer>,
398    ) -> std::result::Result<String, String> {
399        let header_token = http_bearer_token(&req_ctx);
400        let override_key = req.api_key.as_deref().or(header_token.as_deref());
401        let args_hash = guard::args_short_hash(&req);
402        tracing::warn!(
403            target: futu_auth::audit::TARGET,
404            iface = "mcp",
405            endpoint = "futu_reconfirm_order",
406            env = %req.env,
407            market = %req.market,
408            acc_id = req.acc_id.unwrap_or(0),
409            card_num_provided = req.card_num.is_some(),
410            order_id = %req.order_id,
411            reason = req.reason,
412            args_hash = %args_hash,
413            outcome = "request",
414            "reconfirm_order request received"
415        );
416        let caller_key_rec =
417            match self.require_caller_key_strict("futu_reconfirm_order", override_key) {
418                Ok(rec) => rec,
419                Err(reject_json) => return Err(reject_json),
420            };
421        if let Some(reject) = self.require_trading_scope_only(
422            "futu_reconfirm_order",
423            &req.env,
424            caller_key_rec.as_ref(),
425        ) {
426            return Err(reject);
427        }
428        let client = self.client_or_err().await?;
429        let allowed_card_nums = caller_key_rec
430            .as_ref()
431            .and_then(|r| r.allowed_card_nums.as_deref());
432        let caller_allowed_acc_ids = caller_key_rec
433            .as_ref()
434            .and_then(|r| r.allowed_acc_ids.as_ref());
435        let resolved_acc_id = match handlers::trade_write::resolve_acc_id_with_card_num(
436            &client,
437            req.acc_id.unwrap_or(0),
438            req.card_num.as_deref(),
439            allowed_card_nums,
440            caller_allowed_acc_ids,
441        )
442        .await
443        {
444            Ok(id) => id,
445            Err(msg) => return Self::tool_err(msg),
446        };
447        let mutation_ctx = CheckCtx {
448            market: req.market.trim().to_ascii_uppercase(),
449            symbol: String::new(),
450            order_value: None,
451            trd_side: None,
452            acc_id: Some(resolved_acc_id),
453            mutation_no_exposure: false,
454            currency: None,
455        };
456        if let Some(rej) = self.require_trading(
457            "futu_reconfirm_order",
458            &req.env,
459            Some(mutation_ctx),
460            override_key,
461        ) {
462            return Err(rej);
463        }
464        let result = Self::wrap_result(
465            handlers::trade_write::reconfirm_order(
466                &client,
467                handlers::trade_write::ReconfirmOrderInput {
468                    env: &req.env,
469                    acc_id: resolved_acc_id,
470                    market: &req.market,
471                    order_id: &req.order_id,
472                    reason: req.reason,
473                    jp_acc_type: req.jp_acc_type,
474                },
475            )
476            .await,
477        );
478        let startup_key = self.state.authed_key();
479        let outcome_key_id =
480            outcome_key_id_from_snapshot(caller_key_rec.as_ref(), startup_key.as_ref());
481        guard::emit_trade_outcome(
482            "futu_reconfirm_order",
483            outcome_key_id,
484            &args_hash,
485            Self::result_as_str(&result),
486        );
487        result
488    }
489
490    #[tool(
491        description = "Cancel all pending orders for an account in a specific market. `market` is REQUIRED (HK / US / HKCC / A_SH / A_SZ / SG / JP / AU / CA). Python SDK: OpenTradeContext.cancel_all_order. REQUIRES --enable-trading. Real env requires --allow-real-trading. DANGER: unrecoverable — cancels every pending order in the specified market immediately."
492    )]
493    async fn futu_cancel_all_order(
494        &self,
495        Parameters(req): Parameters<CancelAllOrderReq>,
496        req_ctx: RequestContext<RoleServer>,
497    ) -> std::result::Result<String, String> {
498        let header_token = http_bearer_token(&req_ctx);
499        let override_key = req.api_key.as_deref().or(header_token.as_deref());
500        let args_hash = guard::args_short_hash(&req);
501        tracing::warn!(
502            target: futu_auth::audit::TARGET,
503            iface = "mcp",
504            endpoint = "futu_cancel_all_order",
505            env = %req.env,
506            market = %req.market,
507            acc_id = req.acc_id,
508            args_hash = %args_hash,
509            outcome = "request",
510            "cancel_all_order request received"
511        );
512        // v1.4.34 MCP-3b 修:market 是必填,空字符串下发到后端会炸
513        // `unknown trd market ""` —— 这是个模糊的内部错误,LLM 客户端没法自修。
514        // 在 tool 层前置校验给清晰的必填提示 + 合法值列表。
515        // v1.4.84 §5 B4: 用集中的 validate() (其他 tool 也复用类似模式)
516        req.validate()?;
517        // codex round 1 F4 (P2) v1.4.106: lock caller key snapshot **早**
518        // (在 require_trading + daemon dispatch 之前), 用于 emit_trade_outcome
519        // 防 SIGHUP race. 与 place/modify/cancel_order 的 require_caller_key_strict
520        // 同语义 — invalid override 立即 fail-closed, scope mode 关闭则 Ok(None).
521        let caller_key_rec =
522            match self.require_caller_key_strict("futu_cancel_all_order", override_key) {
523                Ok(rec) => rec,
524                Err(reject_json) => return Err(reject_json),
525            };
526        let market_trimmed = req.market.trim();
527        let mutation_ctx = CheckCtx {
528            market: market_trimmed.to_ascii_uppercase(),
529            symbol: String::new(),
530            order_value: None,
531            trd_side: None,
532            acc_id: Some(req.acc_id), // v1.4.35
533            mutation_no_exposure: false,
534            currency: None,
535        };
536        if let Some(rej) = self.require_trading(
537            "futu_cancel_all_order",
538            &req.env,
539            Some(mutation_ctx),
540            override_key,
541        ) {
542            // MED-2: scope 拒绝 → Err(rmcp set is_error=true)
543            return Err(rej);
544        }
545        let client = self.client_or_err().await?;
546        let result = Self::wrap_result(
547            handlers::trade_write::cancel_all_order(&client, &req.env, req.acc_id, &req.market)
548                .await,
549        );
550        // codex round 1 F4 (P2) v1.4.106: snapshot reuse — 见 place_order 同段注释.
551        let startup_key = self.state.authed_key();
552        let outcome_key_id =
553            outcome_key_id_from_snapshot(caller_key_rec.as_ref(), startup_key.as_ref());
554        guard::emit_trade_outcome(
555            "futu_cancel_all_order",
556            outcome_key_id,
557            &args_hash,
558            Self::result_as_str(&result),
559        );
560        result
561    }
562}