newton_cli/commands/
task.rs

1use alloy::primitives::{Address, Bytes, U256};
2use clap::{Parser, Subcommand};
3use eyre::Context;
4use newton_prover_core::config::NewtonAvsConfig;
5use newton_prover_rpc::types::{
6    CreateTaskAndWaitRequest, ICreateTaskAndWaitRequest, ITaskIntent, SignedHashable, TaskIntent,
7};
8use serde_json::Value;
9use std::{
10    path::PathBuf,
11    sync::atomic::{AtomicU64, Ordering},
12};
13use tracing::info;
14
15use crate::config::NewtonCliConfig;
16
17/// Task commands
18#[derive(Debug, Parser)]
19#[command(name = "task")]
20pub struct TaskCommand {
21    #[command(subcommand)]
22    pub subcommand: TaskSubcommand,
23}
24
25#[derive(Debug, Subcommand)]
26pub enum TaskSubcommand {
27    /// Submit evaluation request to prover AVS
28    #[command(name = "submit-evaluation-request")]
29    SubmitEvaluationRequest(SubmitEvaluationRequestCommand),
30}
31
32/// Submit evaluation request command
33#[derive(Debug, Parser)]
34pub struct SubmitEvaluationRequestCommand {
35    /// Path to task JSON file
36    #[arg(long)]
37    task_json: PathBuf,
38
39    #[arg(long, env = "PRIVATE_KEY")]
40    private_key: Option<String>,
41}
42
43// Static counter for JSON-RPC request IDs
44static NEXT_ID: AtomicU64 = AtomicU64::new(0);
45
46fn get_next_id() -> u64 {
47    NEXT_ID.fetch_add(1, Ordering::Relaxed) + 1
48}
49
50fn create_json_rpc_request_payload(method: &str, params: serde_json::Value) -> serde_json::Value {
51    serde_json::json!({
52        "jsonrpc": "2.0",
53        "id": get_next_id(),
54        "method": method,
55        "params": params,
56    })
57}
58
59// Helper function to convert hex string to U256
60fn hex_to_u256(hex_str: &str) -> eyre::Result<U256> {
61    let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
62    U256::from_str_radix(hex_str, 16).map_err(|e| eyre::eyre!("Failed to parse hex string '{}': {}", hex_str, e))
63}
64
65fn get_prover_avs_url(chain_id: u64, deployment_env: &str) -> eyre::Result<String> {
66    match (deployment_env, chain_id) {
67        ("stagef", 11155111) => Ok("https://prover-avs.stagef.sepolia.newt.foundation".to_string()),
68        ("stagef", 1) => Ok("https://prover-avs.stagef.newt.foundation".to_string()),
69        ("prod", 11155111) => Ok("https://prover-avs.sepolia.newt.foundation".to_string()),
70        ("prod", 1) => Ok("https://prover-avs.newt.foundation".to_string()),
71        _ => Err(eyre::eyre!(
72            "Unsupported combination: DEPLOYMENT_ENV={}, CHAIN_ID={}",
73            deployment_env,
74            chain_id
75        )),
76    }
77}
78
79async fn http_post(url: &str, body: &serde_json::Value) -> eyre::Result<serde_json::Value> {
80    let client = reqwest::Client::new();
81
82    let response = client
83        .post(url)
84        .header("Content-Type", "application/json")
85        .json(body)
86        .send()
87        .await?;
88
89    let status = response.status();
90
91    if !status.is_success() {
92        let error_text = response.text().await?;
93        return Err(eyre::eyre!("HTTP error {}: {}", status, error_text));
94    }
95
96    let response_json: serde_json::Value = response.json().await?;
97    Ok(response_json)
98}
99
100// Normalize value/chainId - handles bigint, number, or hex string
101fn normalize_to_u256(value: &serde_json::Value) -> eyre::Result<U256> {
102    match value {
103        serde_json::Value::String(s) => hex_to_u256(s),
104        serde_json::Value::Number(n) => {
105            let num = n.as_u64().ok_or_else(|| eyre::eyre!("Number too large for u64"))?;
106            Ok(U256::from(num))
107        }
108        _ => Err(eyre::eyre!("Invalid value type: expected string or number")),
109    }
110}
111
112// Main normalize function - takes a JSON intent and returns hex versions of chain id and value
113fn normalize_intent(intent: &serde_json::Value) -> eyre::Result<serde_json::Value> {
114    let mut normalized = intent.clone();
115
116    // Normalize value
117    if let Some(value) = normalized.get("value") {
118        let normalized_value = normalize_to_u256(value)?;
119        normalized["value"] = serde_json::Value::String(format!("0x{:x}", normalized_value));
120    }
121
122    // Normalize chainId
123    if let Some(chain_id) = normalized.get("chainId") {
124        let normalized_chain_id = normalize_to_u256(chain_id)?;
125        normalized["chainId"] = serde_json::Value::String(format!("0x{:x}", normalized_chain_id));
126    }
127
128    Ok(normalized)
129}
130
131// Helper to convert hex string to Bytes
132fn hex_to_bytes(hex_str: &str) -> eyre::Result<Bytes> {
133    let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
134    let bytes = hex::decode(hex_str).map_err(|e| eyre::eyre!("Failed to decode hex: {}", e))?;
135    Ok(Bytes::copy_from_slice(&bytes))
136}
137
138// Helper functions to get values from JSON
139fn get_address(value: &Value) -> eyre::Result<Address> {
140    let s = value
141        .as_str()
142        .ok_or_else(|| eyre::eyre!("Expected string for address"))?;
143    s.parse::<Address>().map_err(|e| eyre::eyre!("Invalid address: {}", e))
144}
145
146fn get_bytes(value: &Value) -> eyre::Result<Bytes> {
147    if value.is_null() {
148        return Ok(Bytes::new());
149    }
150    if let Some(s) = value.as_str() {
151        hex_to_bytes(s)
152    } else {
153        Err(eyre::eyre!("Expected string for bytes"))
154    }
155}
156
157fn get_u256(value: &Value) -> eyre::Result<U256> {
158    if let Some(s) = value.as_str() {
159        hex_to_u256(s)
160    } else if let Some(n) = value.as_u64() {
161        Ok(U256::from(n))
162    } else {
163        Err(eyre::eyre!("Expected string or number for U256"))
164    }
165}
166
167// Convert raw JSON intent to TaskIntent
168fn json_intent_to_task_intent(intent: &serde_json::Value) -> eyre::Result<TaskIntent> {
169    // Normalize value and chainId first
170    let normalized_intent = normalize_intent(intent)?;
171
172    let from = get_address(
173        normalized_intent
174            .get("from")
175            .ok_or_else(|| eyre::eyre!("Missing from"))?,
176    )?;
177    let to = get_address(normalized_intent.get("to").ok_or_else(|| eyre::eyre!("Missing to"))?)?;
178    let value = get_u256(
179        normalized_intent
180            .get("value")
181            .ok_or_else(|| eyre::eyre!("Missing value"))?,
182    )?;
183    let chain_id = get_u256(
184        normalized_intent
185            .get("chainId")
186            .ok_or_else(|| eyre::eyre!("Missing chainId"))?,
187    )?;
188
189    // Get data as hex string (with or without 0x prefix)
190    let data_str = normalized_intent
191        .get("data")
192        .ok_or_else(|| eyre::eyre!("Missing data"))?
193        .as_str()
194        .ok_or_else(|| eyre::eyre!("data must be a string"))?;
195    let data = if data_str.starts_with("0x") {
196        data_str.to_string()
197    } else {
198        format!("0x{}", data_str)
199    };
200
201    // Get function signature as hex string (with or without 0x prefix)
202    let function_signature = normalized_intent
203        .get("functionSignature")
204        .and_then(|v| v.as_str())
205        .map(|s| {
206            if s.starts_with("0x") {
207                s.to_string()
208            } else {
209                format!("0x{}", s)
210            }
211        })
212        .unwrap_or_default();
213
214    Ok(TaskIntent {
215        from,
216        to,
217        value,
218        data,
219        chain_id,
220        function_signature,
221    })
222}
223
224impl TaskCommand {
225    /// Execute the task command
226    pub async fn execute(self: Box<Self>, config: NewtonAvsConfig<NewtonCliConfig>) -> eyre::Result<()> {
227        match self.subcommand {
228            TaskSubcommand::SubmitEvaluationRequest(cmd) => {
229                let private_key = cmd
230                    .private_key
231                    .ok_or_else(|| eyre::eyre!("PRIVATE_KEY is required (set via --private-key or env var)"))?;
232
233                info!("Reading task JSON from: {:?}", cmd.task_json);
234                let contents = std::fs::read_to_string(&cmd.task_json)
235                    .with_context(|| format!("Failed to read task JSON file: {:?}", cmd.task_json))?;
236
237                let task: serde_json::Value = serde_json::from_str(&contents)
238                    .with_context(|| format!("Failed to parse task JSON: {:?}", cmd.task_json))?;
239
240                let intent_json = task
241                    .get("intent")
242                    .ok_or_else(|| eyre::eyre!("Missing 'intent' field in task"))?;
243
244                // Convert raw JSON to TaskIntent (with normalization)
245                let task_intent = json_intent_to_task_intent(intent_json)?;
246
247                // Convert TaskIntent to ITaskIntent
248                let i_task_intent = ITaskIntent::try_from(&task_intent)
249                    .with_context(|| "Failed to convert TaskIntent to ITaskIntent")?;
250
251                let intent_sig = task
252                    .get("intentSignature")
253                    .map(get_bytes)
254                    .transpose()?
255                    .unwrap_or_default();
256
257                // Get policy client
258                let policy_client = get_address(
259                    task.get("policyClient")
260                        .ok_or_else(|| eyre::eyre!("Missing policyClient"))?,
261                )?;
262
263                // Get optional fields
264                let quorum_number = task.get("quorumNumber").map(get_bytes).transpose()?.unwrap_or_default();
265                let quorum_threshold_percentage = task
266                    .get("quorumThresholdPercentage")
267                    .and_then(|v| v.as_u64())
268                    .map(|v| v as u32)
269                    .unwrap_or(0);
270                let wasm_args = task.get("wasmArgs").map(get_bytes).transpose()?.unwrap_or_default();
271                let timeout = task.get("timeout").and_then(|v| v.as_u64()).unwrap_or(60);
272
273                // Build ICreateTaskAndWaitRequest
274                let i_request = ICreateTaskAndWaitRequest {
275                    policyClient: policy_client,
276                    intent: i_task_intent,
277                    intentSignature: intent_sig,
278                    quorumNumber: quorum_number,
279                    quorumThresholdPercentage: quorum_threshold_percentage,
280                    wasmArgs: wasm_args,
281                    timeout,
282                };
283
284                // Sign using the existing RPC type method
285                let signed_request = CreateTaskAndWaitRequest::signed_from(&i_request, &private_key);
286
287                // Serialize to JSON for JSON-RPC request
288                let signed_request_json =
289                    serde_json::to_value(&signed_request).with_context(|| "Failed to serialize signed request")?;
290
291                let payload = create_json_rpc_request_payload(
292                    "newton_createTaskAndWait",
293                    serde_json::Value::Array(vec![signed_request_json]),
294                );
295
296                let chain_id = config.chain_id;
297                let deployment_env = std::env::var("DEPLOYMENT_ENV").unwrap_or_else(|_| "prod".to_string());
298                let prover_avs_url = get_prover_avs_url(chain_id, &deployment_env)?;
299
300                info!("Submitting evaluation request to: {}", prover_avs_url);
301                let response = http_post(&prover_avs_url, &payload).await?;
302
303                info!("Response: {}", serde_json::to_string_pretty(&response)?);
304
305                Ok(())
306            }
307        }
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314    use std::str::FromStr;
315
316    #[test]
317    fn test_get_next_id() {
318        // Reset counter by creating new instances (note: this is a static, so we can't truly reset)
319        // Just verify it returns incrementing values
320        let id1 = get_next_id();
321        let id2 = get_next_id();
322        let id3 = get_next_id();
323        assert!(id2 > id1);
324        assert!(id3 > id2);
325    }
326
327    #[test]
328    fn test_create_json_rpc_request_payload() {
329        let method = "test_method";
330        let params = serde_json::json!({"key": "value"});
331        let payload = create_json_rpc_request_payload(method, params.clone());
332
333        assert_eq!(payload["jsonrpc"], "2.0");
334        assert_eq!(payload["method"], method);
335        assert_eq!(payload["params"], params);
336        assert!(payload["id"].is_number());
337    }
338
339    #[test]
340    fn test_hex_to_u256_with_prefix() {
341        let result = hex_to_u256("0x1a2b").unwrap();
342        assert_eq!(result, U256::from(0x1a2b));
343    }
344
345    #[test]
346    fn test_hex_to_u256_without_prefix() {
347        let result = hex_to_u256("1a2b").unwrap();
348        assert_eq!(result, U256::from(0x1a2b));
349    }
350
351    #[test]
352    fn test_hex_to_u256_large_value() {
353        let large_hex = "0xffffffffffffffffffffffffffffffff";
354        let result = hex_to_u256(large_hex).unwrap();
355        assert!(result > U256::from(u64::MAX));
356    }
357
358    #[test]
359    fn test_hex_to_u256_invalid_hex() {
360        let result = hex_to_u256("0xinvalid");
361        assert!(result.is_err());
362        assert!(result.unwrap_err().to_string().contains("Failed to parse hex string"));
363    }
364
365    #[test]
366    fn test_hex_to_u256_empty_string() {
367        let result = hex_to_u256("");
368        // Empty string should fail or return 0
369        assert!(result.is_ok() || result.is_err());
370    }
371
372    #[test]
373    fn test_get_prover_avs_url_stagef_sepolia() {
374        let url = get_prover_avs_url(11155111, "stagef").unwrap();
375        assert_eq!(url, "https://prover-avs.stagef.sepolia.newt.foundation");
376    }
377
378    #[test]
379    fn test_get_prover_avs_url_stagef_mainnet() {
380        let url = get_prover_avs_url(1, "stagef").unwrap();
381        assert_eq!(url, "https://prover-avs.stagef.newt.foundation");
382    }
383
384    #[test]
385    fn test_get_prover_avs_url_prod_sepolia() {
386        let url = get_prover_avs_url(11155111, "prod").unwrap();
387        assert_eq!(url, "https://prover-avs.sepolia.newt.foundation");
388    }
389
390    #[test]
391    fn test_get_prover_avs_url_prod_mainnet() {
392        let url = get_prover_avs_url(1, "prod").unwrap();
393        assert_eq!(url, "https://prover-avs.newt.foundation");
394    }
395
396    #[test]
397    fn test_get_prover_avs_url_unsupported() {
398        let result = get_prover_avs_url(999, "stagef");
399        assert!(result.is_err());
400        assert!(result.unwrap_err().to_string().contains("Unsupported combination"));
401    }
402
403    #[test]
404    fn test_normalize_to_u256_from_hex_string() {
405        let value = serde_json::json!("0x64");
406        let result = normalize_to_u256(&value).unwrap();
407        assert_eq!(result, U256::from(100));
408    }
409
410    #[test]
411    fn test_normalize_to_u256_from_decimal_string() {
412        // Note: normalize_to_u256 only handles hex strings (via hex_to_u256), not decimal strings
413        // A decimal string like "100" will be interpreted as hex (which is 256 in decimal)
414        let value = serde_json::json!("100");
415        let result = normalize_to_u256(&value).unwrap();
416        // "100" as hex = 0x100 = 256 in decimal
417        assert_eq!(result, U256::from(256));
418    }
419
420    #[test]
421    fn test_normalize_to_u256_from_number() {
422        let value = serde_json::json!(42);
423        let result = normalize_to_u256(&value).unwrap();
424        assert_eq!(result, U256::from(42));
425    }
426
427    #[test]
428    fn test_normalize_to_u256_invalid_type() {
429        let value = serde_json::json!(true);
430        let result = normalize_to_u256(&value);
431        assert!(result.is_err());
432        assert!(result.unwrap_err().to_string().contains("Invalid value type"));
433    }
434
435    #[test]
436    fn test_normalize_intent_with_value_and_chainid() {
437        let intent = serde_json::json!({
438            "value": "0x64",
439            "chainId": 11155111,
440            "from": "0x0000000000000000000000000000000000000001",
441            "to": "0x0000000000000000000000000000000000000002"
442        });
443        let result = normalize_intent(&intent).unwrap();
444        assert_eq!(result["value"], "0x64");
445        assert_eq!(result["chainId"], "0xaa36a7"); // 11155111 in hex
446    }
447
448    #[test]
449    fn test_normalize_intent_with_number_value() {
450        let intent = serde_json::json!({
451            "value": 100,
452            "chainId": "0x1",
453            "from": "0x0000000000000000000000000000000000000001",
454            "to": "0x0000000000000000000000000000000000000002"
455        });
456        let result = normalize_intent(&intent).unwrap();
457        assert_eq!(result["value"], "0x64"); // 100 in hex
458        assert_eq!(result["chainId"], "0x1");
459    }
460
461    #[test]
462    fn test_normalize_intent_without_value_or_chainid() {
463        let intent = serde_json::json!({
464            "from": "0x0000000000000000000000000000000000000001",
465            "to": "0x0000000000000000000000000000000000000002"
466        });
467        let result = normalize_intent(&intent).unwrap();
468        assert_eq!(result["from"], "0x0000000000000000000000000000000000000001");
469        assert_eq!(result["to"], "0x0000000000000000000000000000000000000002");
470    }
471
472    #[test]
473    fn test_normalize_intent_invalid_value() {
474        let intent = serde_json::json!({
475            "value": "invalid",
476            "chainId": 1
477        });
478        let result = normalize_intent(&intent);
479        assert!(result.is_err());
480    }
481
482    #[test]
483    fn test_hex_to_bytes_with_prefix() {
484        let result = hex_to_bytes("0x1234").unwrap();
485        assert_eq!(result.len(), 2);
486        assert_eq!(result[0], 0x12);
487        assert_eq!(result[1], 0x34);
488    }
489
490    #[test]
491    fn test_hex_to_bytes_without_prefix() {
492        let result = hex_to_bytes("1234").unwrap();
493        assert_eq!(result.len(), 2);
494        assert_eq!(result[0], 0x12);
495        assert_eq!(result[1], 0x34);
496    }
497
498    #[test]
499    fn test_hex_to_bytes_empty() {
500        let result = hex_to_bytes("0x").unwrap();
501        assert_eq!(result.len(), 0);
502    }
503
504    #[test]
505    fn test_hex_to_bytes_invalid_hex() {
506        let result = hex_to_bytes("0xinvalid");
507        assert!(result.is_err());
508        assert!(result.unwrap_err().to_string().contains("Failed to decode hex"));
509    }
510
511    #[test]
512    fn test_get_address_valid() {
513        let value = serde_json::json!("0x0000000000000000000000000000000000000001");
514        let result = get_address(&value).unwrap();
515        assert_eq!(
516            result,
517            Address::from_str("0x0000000000000000000000000000000000000001").unwrap()
518        );
519    }
520
521    #[test]
522    fn test_get_address_invalid_type() {
523        let value = serde_json::json!(123);
524        let result = get_address(&value);
525        assert!(result.is_err());
526        assert!(result.unwrap_err().to_string().contains("Expected string"));
527    }
528
529    #[test]
530    fn test_get_address_invalid_address() {
531        let value = serde_json::json!("not_an_address");
532        let result = get_address(&value);
533        assert!(result.is_err());
534        assert!(result.unwrap_err().to_string().contains("Invalid address"));
535    }
536
537    #[test]
538    fn test_get_bytes_null() {
539        let value = serde_json::Value::Null;
540        let result = get_bytes(&value).unwrap();
541        assert_eq!(result.len(), 0);
542    }
543
544    #[test]
545    fn test_get_bytes_hex_string() {
546        let value = serde_json::json!("0x1234");
547        let result = get_bytes(&value).unwrap();
548        assert_eq!(result.len(), 2);
549        assert_eq!(result[0], 0x12);
550        assert_eq!(result[1], 0x34);
551    }
552
553    #[test]
554    fn test_get_bytes_invalid_type() {
555        let value = serde_json::json!(123);
556        let result = get_bytes(&value);
557        assert!(result.is_err());
558        assert!(result.unwrap_err().to_string().contains("Expected string"));
559    }
560
561    #[test]
562    fn test_get_u256_from_hex_string() {
563        let value = serde_json::json!("0x64");
564        let result = get_u256(&value).unwrap();
565        assert_eq!(result, U256::from(100));
566    }
567
568    #[test]
569    fn test_get_u256_from_number() {
570        let value = serde_json::json!(42);
571        let result = get_u256(&value).unwrap();
572        assert_eq!(result, U256::from(42));
573    }
574
575    #[test]
576    fn test_get_u256_invalid_type() {
577        let value = serde_json::json!(true);
578        let result = get_u256(&value);
579        assert!(result.is_err());
580        assert!(result.unwrap_err().to_string().contains("Expected string or number"));
581    }
582
583    #[test]
584    fn test_json_intent_to_task_intent_complete() {
585        let intent = serde_json::json!({
586            "from": "0x0000000000000000000000000000000000000001",
587            "to": "0x0000000000000000000000000000000000000002",
588            "value": "0x64",
589            "chainId": 11155111,
590            "data": "0x1234"
591        });
592        let result = json_intent_to_task_intent(&intent).unwrap();
593        assert_eq!(
594            result.from,
595            Address::from_str("0x0000000000000000000000000000000000000001").unwrap()
596        );
597        assert_eq!(
598            result.to,
599            Address::from_str("0x0000000000000000000000000000000000000002").unwrap()
600        );
601        assert_eq!(result.value, U256::from(100));
602        assert_eq!(result.chain_id, U256::from(11155111));
603        assert_eq!(result.data, "0x1234");
604    }
605
606    #[test]
607    fn test_json_intent_to_task_intent_with_function_signature() {
608        let intent = serde_json::json!({
609            "from": "0x0000000000000000000000000000000000000001",
610            "to": "0x0000000000000000000000000000000000000002",
611            "value": 100,
612            "chainId": "0x1",
613            "data": "1234",
614            "functionSignature": "0xabcd"
615        });
616        let result = json_intent_to_task_intent(&intent).unwrap();
617        assert_eq!(result.function_signature, "0xabcd");
618        assert_eq!(result.data, "0x1234"); // Should add 0x prefix
619    }
620
621    #[test]
622    fn test_json_intent_to_task_intent_without_function_signature() {
623        let intent = serde_json::json!({
624            "from": "0x0000000000000000000000000000000000000001",
625            "to": "0x0000000000000000000000000000000000000002",
626            "value": 100,
627            "chainId": 1,
628            "data": "0x1234"
629        });
630        let result = json_intent_to_task_intent(&intent).unwrap();
631        assert_eq!(result.function_signature, "");
632    }
633
634    #[test]
635    fn test_json_intent_to_task_intent_missing_from() {
636        let intent = serde_json::json!({
637            "to": "0x0000000000000000000000000000000000000002",
638            "value": 100,
639            "chainId": 1,
640            "data": "0x1234"
641        });
642        let result = json_intent_to_task_intent(&intent);
643        assert!(result.is_err());
644        assert!(result.unwrap_err().to_string().contains("Missing from"));
645    }
646
647    #[test]
648    fn test_json_intent_to_task_intent_missing_to() {
649        let intent = serde_json::json!({
650            "from": "0x0000000000000000000000000000000000000001",
651            "value": 100,
652            "chainId": 1,
653            "data": "0x1234"
654        });
655        let result = json_intent_to_task_intent(&intent);
656        assert!(result.is_err());
657        assert!(result.unwrap_err().to_string().contains("Missing to"));
658    }
659
660    #[test]
661    fn test_json_intent_to_task_intent_missing_value() {
662        let intent = serde_json::json!({
663            "from": "0x0000000000000000000000000000000000000001",
664            "to": "0x0000000000000000000000000000000000000002",
665            "chainId": 1,
666            "data": "0x1234"
667        });
668        let result = json_intent_to_task_intent(&intent);
669        assert!(result.is_err());
670        assert!(result.unwrap_err().to_string().contains("Missing value"));
671    }
672
673    #[test]
674    fn test_json_intent_to_task_intent_missing_chainid() {
675        let intent = serde_json::json!({
676            "from": "0x0000000000000000000000000000000000000001",
677            "to": "0x0000000000000000000000000000000000000002",
678            "value": 100,
679            "data": "0x1234"
680        });
681        let result = json_intent_to_task_intent(&intent);
682        assert!(result.is_err());
683        assert!(result.unwrap_err().to_string().contains("Missing chainId"));
684    }
685
686    #[test]
687    fn test_json_intent_to_task_intent_missing_data() {
688        let intent = serde_json::json!({
689            "from": "0x0000000000000000000000000000000000000001",
690            "to": "0x0000000000000000000000000000000000000002",
691            "value": 100,
692            "chainId": 1
693        });
694        let result = json_intent_to_task_intent(&intent);
695        assert!(result.is_err());
696        assert!(result.unwrap_err().to_string().contains("Missing data"));
697    }
698
699    #[test]
700    fn test_json_intent_to_task_intent_data_not_string() {
701        let intent = serde_json::json!({
702            "from": "0x0000000000000000000000000000000000000001",
703            "to": "0x0000000000000000000000000000000000000002",
704            "value": 100,
705            "chainId": 1,
706            "data": 123
707        });
708        let result = json_intent_to_task_intent(&intent);
709        assert!(result.is_err());
710        assert!(result.unwrap_err().to_string().contains("data must be a string"));
711    }
712}