1pub mod webhook;
2
3pub use webhook::{
4 ActivateWebhookParams, BitcoinWalletFilterByListTemplate, BitcoinWalletFilterInput,
5 BitcoinWalletFilterTemplate, CreateWebhookFromTemplateParams, EvmAbiFilterByListTemplate,
6 EvmAbiFilterInput, EvmAbiFilterTemplate, EvmContractEventsByListTemplate,
7 EvmContractEventsInput, EvmContractEventsTemplate, EvmWalletFilterByListTemplate,
8 EvmWalletFilterInput, EvmWalletFilterTemplate, GetWebhooksParams,
9 HyperliquidWalletEventsFilterByListTemplate, HyperliquidWalletEventsFilterInput,
10 HyperliquidWalletEventsFilterTemplate, ListWebhooksResponse, SolanaWalletFilterByListTemplate,
11 SolanaWalletFilterInput, SolanaWalletFilterTemplate,
12 StellarWalletTransactionsFilterByListTemplate, StellarWalletTransactionsFilterInput,
13 StellarWalletTransactionsFilterTemplate, TemplateArgs, UpdateWebhookParams,
14 UpdateWebhookTemplateParams, Webhook, WebhookDestinationAttributes,
15 WebhookEnabledCountResponse, WebhookPageInfo, WebhookStartFrom, WebhookTemplateId,
16 XrplWalletFilterByListTemplate, XrplWalletFilterInput, XrplWalletFilterTemplate,
17};
18
19use crate::{config::WebhooksConfig, errors::SdkError, SdkConfig};
20
21const WEBHOOKS_BASE_URL: &str = "https://api.quicknode.com/webhooks/rest/v1/";
22
23pub(crate) struct ResolvedWebhooksConfig {
24 pub(crate) base_url: reqwest::Url,
25}
26
27impl ResolvedWebhooksConfig {
28 pub(crate) fn from_config(config: Option<&WebhooksConfig>) -> Result<Self, SdkError> {
29 let url_str = config
30 .and_then(|s| s.base_url.as_deref())
31 .unwrap_or(WEBHOOKS_BASE_URL);
32 let mut base_url = reqwest::Url::parse(url_str)?;
33 if !base_url.path().ends_with('/') {
34 base_url.set_path(&format!("{}/", base_url.path()));
35 }
36 Ok(Self { base_url })
37 }
38}
39
40#[derive(Debug, Clone)]
45pub struct WebhooksApiClient {
46 config: SdkConfig,
47}
48
49impl WebhooksApiClient {
50 pub fn new(config: SdkConfig) -> Self {
51 Self { config }
52 }
53
54 pub async fn list_webhooks(
60 &self,
61 params: &GetWebhooksParams,
62 ) -> Result<ListWebhooksResponse, SdkError> {
63 let mut url = self.config.webhooks().base_url.join("webhooks")?;
64 {
65 let mut pairs = url.query_pairs_mut();
66 if let Some(v) = params.limit {
67 pairs.append_pair("limit", &v.to_string());
68 }
69 if let Some(v) = params.offset {
70 pairs.append_pair("offset", &v.to_string());
71 }
72 }
73 let resp = self
74 .config
75 .http_client()
76 .get(url)
77 .send()
78 .await
79 .map_err(SdkError::Http)?;
80 let status = resp.status();
81 let body = resp.text().await.map_err(SdkError::Http)?;
82 if !status.is_success() {
83 return Err(SdkError::Api { status, body });
84 }
85 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
86 }
87
88 pub async fn delete_all_webhooks(&self) -> Result<(), SdkError> {
91 let url = self.config.webhooks().base_url.join("webhooks")?;
92 let resp = self
93 .config
94 .http_client()
95 .delete(url)
96 .send()
97 .await
98 .map_err(SdkError::Http)?;
99 let status = resp.status();
100 if !status.is_success() {
101 let body = resp.text().await.map_err(SdkError::Http)?;
102 return Err(SdkError::Api { status, body });
103 }
104 Ok(())
105 }
106
107 pub async fn get_webhook(&self, id: &str) -> Result<Webhook, SdkError> {
113 let url = self
114 .config
115 .webhooks()
116 .base_url
117 .join(&format!("webhooks/{id}"))?;
118 let resp = self
119 .config
120 .http_client()
121 .get(url)
122 .send()
123 .await
124 .map_err(SdkError::Http)?;
125 let status = resp.status();
126 let body = resp.text().await.map_err(SdkError::Http)?;
127 if !status.is_success() {
128 return Err(SdkError::Api { status, body });
129 }
130 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
131 }
132
133 pub async fn update_webhook(
140 &self,
141 id: &str,
142 params: &UpdateWebhookParams,
143 ) -> Result<Webhook, SdkError> {
144 let url = self
145 .config
146 .webhooks()
147 .base_url
148 .join(&format!("webhooks/{id}"))?;
149 let resp = self
150 .config
151 .http_client()
152 .patch(url)
153 .json(params)
154 .send()
155 .await
156 .map_err(SdkError::Http)?;
157 let status = resp.status();
158 let body = resp.text().await.map_err(SdkError::Http)?;
159 if !status.is_success() {
160 return Err(SdkError::Api { status, body });
161 }
162 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
163 }
164
165 pub async fn delete_webhook(&self, id: &str) -> Result<(), SdkError> {
167 let url = self
168 .config
169 .webhooks()
170 .base_url
171 .join(&format!("webhooks/{id}"))?;
172 let resp = self
173 .config
174 .http_client()
175 .delete(url)
176 .send()
177 .await
178 .map_err(SdkError::Http)?;
179 let status = resp.status();
180 if !status.is_success() {
181 let body = resp.text().await.map_err(SdkError::Http)?;
182 return Err(SdkError::Api { status, body });
183 }
184 Ok(())
185 }
186
187 pub async fn pause_webhook(&self, id: &str) -> Result<(), SdkError> {
189 let url = self
190 .config
191 .webhooks()
192 .base_url
193 .join(&format!("webhooks/{id}/pause"))?;
194 let resp = self
195 .config
196 .http_client()
197 .post(url)
198 .send()
199 .await
200 .map_err(SdkError::Http)?;
201 let status = resp.status();
202 if !status.is_success() {
203 let body = resp.text().await.map_err(SdkError::Http)?;
204 return Err(SdkError::Api { status, body });
205 }
206 Ok(())
207 }
208
209 pub async fn activate_webhook(
214 &self,
215 id: &str,
216 params: &ActivateWebhookParams,
217 ) -> Result<(), SdkError> {
218 let url = self
219 .config
220 .webhooks()
221 .base_url
222 .join(&format!("webhooks/{id}/activate"))?;
223 let resp = self
224 .config
225 .http_client()
226 .post(url)
227 .json(params)
228 .send()
229 .await
230 .map_err(SdkError::Http)?;
231 let status = resp.status();
232 if !status.is_success() {
233 let body = resp.text().await.map_err(SdkError::Http)?;
234 return Err(SdkError::Api { status, body });
235 }
236 Ok(())
237 }
238
239 pub async fn get_enabled_count(&self) -> Result<WebhookEnabledCountResponse, SdkError> {
242 let url = self
243 .config
244 .webhooks()
245 .base_url
246 .join("webhooks/enabled_count")?;
247 let resp = self
248 .config
249 .http_client()
250 .get(url)
251 .send()
252 .await
253 .map_err(SdkError::Http)?;
254 let status = resp.status();
255 let body = resp.text().await.map_err(SdkError::Http)?;
256 if !status.is_success() {
257 return Err(SdkError::Api { status, body });
258 }
259 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
260 }
261
262 pub async fn create_webhook_from_template(
270 &self,
271 params: &CreateWebhookFromTemplateParams,
272 ) -> Result<Webhook, SdkError> {
273 let template_id = params.template_args.tag().as_str();
274 let url = self
275 .config
276 .webhooks()
277 .base_url
278 .join(&format!("webhooks/template/{template_id}"))?;
279 let resp = self
280 .config
281 .http_client()
282 .post(url)
283 .json(params)
284 .send()
285 .await
286 .map_err(SdkError::Http)?;
287 let status = resp.status();
288 let body = resp.text().await.map_err(SdkError::Http)?;
289 if !status.is_success() {
290 return Err(SdkError::Api { status, body });
291 }
292 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
293 }
294
295 pub async fn update_webhook_template(
302 &self,
303 webhook_id: &str,
304 params: &UpdateWebhookTemplateParams,
305 ) -> Result<Webhook, SdkError> {
306 let template_id = params.template_args.tag().as_str();
307 let url = self
308 .config
309 .webhooks()
310 .base_url
311 .join(&format!("webhooks/{webhook_id}/template/{template_id}"))?;
312 let resp = self
313 .config
314 .http_client()
315 .patch(url)
316 .json(params)
317 .send()
318 .await
319 .map_err(SdkError::Http)?;
320 let status = resp.status();
321 let body = resp.text().await.map_err(SdkError::Http)?;
322 if !status.is_success() {
323 return Err(SdkError::Api { status, body });
324 }
325 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
326 }
327}
328
329#[cfg(test)]
332#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
333mod tests {
334 use super::*;
335 use crate::{QuicknodeSdk, SdkFullConfig, WebhooksConfig};
336 use wiremock::matchers::{method, path, path_regex};
337 use wiremock::{Mock, MockServer, ResponseTemplate};
338
339 fn make_sdk(base_url: String) -> QuicknodeSdk {
340 QuicknodeSdk::new(&SdkFullConfig {
341 api_key: "test-key".to_string(),
342 http: None,
343 admin: None,
344 streams: None,
345 webhooks: Some(WebhooksConfig {
346 base_url: Some(base_url),
347 }),
348 kvstore: None,
349 sql: None,
350 })
351 .unwrap()
352 }
353
354 fn webhook_response_json() -> serde_json::Value {
355 serde_json::json!({
356 "id": "wh-1234-5678",
357 "name": "test-webhook",
358 "status": "active",
359 "network": "ethereum-mainnet",
360 "created_at": "2026-03-19T12:00:00Z",
361 "updated_at": "2026-03-19T12:00:00Z"
362 })
363 }
364
365 #[tokio::test]
366 async fn list_webhooks_success() {
367 let server = MockServer::start().await;
368 let response = serde_json::json!({
369 "data": [webhook_response_json()],
370 "pageInfo": {
371 "limit": 20,
372 "offset": 0,
373 "total": 1,
374 }
375 });
376 Mock::given(method("GET"))
377 .and(path("/webhooks"))
378 .respond_with(ResponseTemplate::new(200).set_body_json(response))
379 .mount(&server)
380 .await;
381 let sdk = make_sdk(format!("{}/", server.uri()));
382 let resp = sdk
383 .webhooks
384 .list_webhooks(&GetWebhooksParams::default())
385 .await
386 .unwrap();
387 assert_eq!(resp.data.len(), 1);
388 assert_eq!(resp.data[0].id, "wh-1234-5678");
389 assert_eq!(resp.page_info.limit, 20);
390 assert_eq!(resp.page_info.offset, 0);
391 assert_eq!(resp.page_info.total, 1);
392 }
393
394 #[tokio::test]
395 async fn list_webhooks_api_error() {
396 let server = MockServer::start().await;
397 Mock::given(method("GET"))
398 .and(path("/webhooks"))
399 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
400 .mount(&server)
401 .await;
402 let sdk = make_sdk(format!("{}/", server.uri()));
403 let err = sdk
404 .webhooks
405 .list_webhooks(&GetWebhooksParams::default())
406 .await
407 .unwrap_err();
408 assert!(matches!(err, SdkError::Api { .. }));
409 }
410
411 #[tokio::test]
412 async fn list_webhooks_server_error() {
413 let server = MockServer::start().await;
414 Mock::given(method("GET"))
415 .and(path("/webhooks"))
416 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
417 .mount(&server)
418 .await;
419 let sdk = make_sdk(format!("{}/", server.uri()));
420 let err = sdk
421 .webhooks
422 .list_webhooks(&GetWebhooksParams::default())
423 .await
424 .unwrap_err();
425 assert!(matches!(err, SdkError::Api { .. }));
426 }
427
428 #[tokio::test]
429 async fn get_webhook_success() {
430 let server = MockServer::start().await;
431 Mock::given(method("GET"))
432 .and(path("/webhooks/test-id"))
433 .respond_with(ResponseTemplate::new(200).set_body_json(webhook_response_json()))
434 .mount(&server)
435 .await;
436 let sdk = make_sdk(format!("{}/", server.uri()));
437 let resp = sdk.webhooks.get_webhook("test-id").await.unwrap();
438 assert_eq!(resp.id, "wh-1234-5678");
439 }
440
441 #[tokio::test]
442 async fn get_webhook_api_error() {
443 let server = MockServer::start().await;
444 Mock::given(method("GET"))
445 .and(path("/webhooks/test-id"))
446 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
447 .mount(&server)
448 .await;
449 let sdk = make_sdk(format!("{}/", server.uri()));
450 let err = sdk.webhooks.get_webhook("test-id").await.unwrap_err();
451 assert!(matches!(err, SdkError::Api { .. }));
452 }
453
454 #[tokio::test]
455 async fn update_webhook_success() {
456 let server = MockServer::start().await;
457 let mut updated = webhook_response_json();
458 updated["name"] = serde_json::json!("updated-name");
459 Mock::given(method("PATCH"))
460 .and(path("/webhooks/test-id"))
461 .respond_with(ResponseTemplate::new(200).set_body_json(updated))
462 .mount(&server)
463 .await;
464 let sdk = make_sdk(format!("{}/", server.uri()));
465 let params = UpdateWebhookParams {
466 name: Some("updated-name".to_string()),
467 ..Default::default()
468 };
469 let resp = sdk
470 .webhooks
471 .update_webhook("test-id", ¶ms)
472 .await
473 .unwrap();
474 assert_eq!(resp.name, "updated-name");
475 }
476
477 #[tokio::test]
478 async fn update_webhook_api_error() {
479 let server = MockServer::start().await;
480 Mock::given(method("PATCH"))
481 .and(path("/webhooks/test-id"))
482 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
483 .mount(&server)
484 .await;
485 let sdk = make_sdk(format!("{}/", server.uri()));
486 let params = UpdateWebhookParams::default();
487 let err = sdk
488 .webhooks
489 .update_webhook("test-id", ¶ms)
490 .await
491 .unwrap_err();
492 assert!(matches!(err, SdkError::Api { .. }));
493 }
494
495 #[tokio::test]
496 async fn delete_webhook_success() {
497 let server = MockServer::start().await;
498 Mock::given(method("DELETE"))
499 .and(path("/webhooks/test-id"))
500 .respond_with(ResponseTemplate::new(200))
501 .mount(&server)
502 .await;
503 let sdk = make_sdk(format!("{}/", server.uri()));
504 sdk.webhooks.delete_webhook("test-id").await.unwrap();
505 }
506
507 #[tokio::test]
508 async fn delete_webhook_api_error() {
509 let server = MockServer::start().await;
510 Mock::given(method("DELETE"))
511 .and(path("/webhooks/test-id"))
512 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
513 .mount(&server)
514 .await;
515 let sdk = make_sdk(format!("{}/", server.uri()));
516 let err = sdk.webhooks.delete_webhook("test-id").await.unwrap_err();
517 assert!(matches!(err, SdkError::Api { .. }));
518 }
519
520 #[tokio::test]
521 async fn delete_all_webhooks_success() {
522 let server = MockServer::start().await;
523 Mock::given(method("DELETE"))
524 .and(path("/webhooks"))
525 .respond_with(ResponseTemplate::new(204))
526 .mount(&server)
527 .await;
528 let sdk = make_sdk(format!("{}/", server.uri()));
529 sdk.webhooks.delete_all_webhooks().await.unwrap();
530 }
531
532 #[tokio::test]
533 async fn delete_all_webhooks_api_error() {
534 let server = MockServer::start().await;
535 Mock::given(method("DELETE"))
536 .and(path("/webhooks"))
537 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
538 .mount(&server)
539 .await;
540 let sdk = make_sdk(format!("{}/", server.uri()));
541 let err = sdk.webhooks.delete_all_webhooks().await.unwrap_err();
542 assert!(matches!(err, SdkError::Api { .. }));
543 }
544
545 #[tokio::test]
546 async fn pause_webhook_success() {
547 let server = MockServer::start().await;
548 Mock::given(method("POST"))
549 .and(path("/webhooks/test-id/pause"))
550 .respond_with(ResponseTemplate::new(201))
551 .mount(&server)
552 .await;
553 let sdk = make_sdk(format!("{}/", server.uri()));
554 sdk.webhooks.pause_webhook("test-id").await.unwrap();
555 }
556
557 #[tokio::test]
558 async fn pause_webhook_api_error() {
559 let server = MockServer::start().await;
560 Mock::given(method("POST"))
561 .and(path("/webhooks/test-id/pause"))
562 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
563 .mount(&server)
564 .await;
565 let sdk = make_sdk(format!("{}/", server.uri()));
566 let err = sdk.webhooks.pause_webhook("test-id").await.unwrap_err();
567 assert!(matches!(err, SdkError::Api { .. }));
568 }
569
570 #[tokio::test]
571 async fn activate_webhook_success() {
572 let server = MockServer::start().await;
573 Mock::given(method("POST"))
574 .and(path("/webhooks/test-id/activate"))
575 .respond_with(ResponseTemplate::new(201))
576 .mount(&server)
577 .await;
578 let sdk = make_sdk(format!("{}/", server.uri()));
579 let params = ActivateWebhookParams {
580 start_from: WebhookStartFrom::Latest,
581 };
582 sdk.webhooks
583 .activate_webhook("test-id", ¶ms)
584 .await
585 .unwrap();
586 }
587
588 #[tokio::test]
589 async fn activate_webhook_api_error() {
590 let server = MockServer::start().await;
591 Mock::given(method("POST"))
592 .and(path("/webhooks/test-id/activate"))
593 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
594 .mount(&server)
595 .await;
596 let sdk = make_sdk(format!("{}/", server.uri()));
597 let params = ActivateWebhookParams {
598 start_from: WebhookStartFrom::Latest,
599 };
600 let err = sdk
601 .webhooks
602 .activate_webhook("test-id", ¶ms)
603 .await
604 .unwrap_err();
605 assert!(matches!(err, SdkError::Api { .. }));
606 }
607
608 #[tokio::test]
609 async fn get_enabled_count_success() {
610 let server = MockServer::start().await;
611 Mock::given(method("GET"))
612 .and(path("/webhooks/enabled_count"))
613 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"total": 5})))
614 .mount(&server)
615 .await;
616 let sdk = make_sdk(format!("{}/", server.uri()));
617 let resp = sdk.webhooks.get_enabled_count().await.unwrap();
618 assert_eq!(resp.total, 5);
619 }
620
621 #[tokio::test]
622 async fn get_enabled_count_api_error() {
623 let server = MockServer::start().await;
624 Mock::given(method("GET"))
625 .and(path("/webhooks/enabled_count"))
626 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
627 .mount(&server)
628 .await;
629 let sdk = make_sdk(format!("{}/", server.uri()));
630 let err = sdk.webhooks.get_enabled_count().await.unwrap_err();
631 assert!(matches!(err, SdkError::Api { .. }));
632 }
633
634 #[tokio::test]
635 async fn create_webhook_from_template_success() {
636 let server = MockServer::start().await;
637 Mock::given(method("POST"))
638 .and(path_regex("/webhooks/template/evmWalletFilter"))
639 .respond_with(ResponseTemplate::new(201).set_body_json(webhook_response_json()))
640 .mount(&server)
641 .await;
642 let sdk = make_sdk(format!("{}/", server.uri()));
643 let template_args =
644 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
645 wallets: vec!["0xabc".to_string()],
646 }));
647 let params = CreateWebhookFromTemplateParams {
648 name: "test-webhook".to_string(),
649 network: "ethereum-mainnet".to_string(),
650 notification_email: None,
651 destination_attributes: WebhookDestinationAttributes {
652 url: "https://example.com/hook".to_string(),
653 security_token: None,
654 compression: "none".to_string(),
655 },
656 template_args,
657 };
658 let resp = sdk
659 .webhooks
660 .create_webhook_from_template(¶ms)
661 .await
662 .unwrap();
663 assert_eq!(resp.id, "wh-1234-5678");
664 }
665
666 #[tokio::test]
667 async fn create_webhook_from_template_api_error() {
668 let server = MockServer::start().await;
669 Mock::given(method("POST"))
670 .and(path_regex("/webhooks/template/evmWalletFilter"))
671 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
672 .mount(&server)
673 .await;
674 let sdk = make_sdk(format!("{}/", server.uri()));
675 let template_args =
676 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
677 wallets: vec!["0xabc".to_string()],
678 }));
679 let params = CreateWebhookFromTemplateParams {
680 name: "test-webhook".to_string(),
681 network: "ethereum-mainnet".to_string(),
682 notification_email: None,
683 destination_attributes: WebhookDestinationAttributes {
684 url: "https://example.com/hook".to_string(),
685 security_token: None,
686 compression: "none".to_string(),
687 },
688 template_args,
689 };
690 let err = sdk
691 .webhooks
692 .create_webhook_from_template(¶ms)
693 .await
694 .unwrap_err();
695 assert!(matches!(err, SdkError::Api { .. }));
696 }
697
698 #[tokio::test]
699 async fn update_webhook_template_success() {
700 let server = MockServer::start().await;
701 Mock::given(method("PATCH"))
702 .and(path_regex("/webhooks/test-id/template/evmWalletFilter"))
703 .respond_with(ResponseTemplate::new(200).set_body_json(webhook_response_json()))
704 .mount(&server)
705 .await;
706 let sdk = make_sdk(format!("{}/", server.uri()));
707 let template_args =
708 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
709 wallets: vec!["0xabc".to_string()],
710 }));
711 let params = UpdateWebhookTemplateParams {
712 name: None,
713 notification_email: None,
714 destination_attributes: None,
715 template_args,
716 };
717 let resp = sdk
718 .webhooks
719 .update_webhook_template("test-id", ¶ms)
720 .await
721 .unwrap();
722 assert_eq!(resp.id, "wh-1234-5678");
723 }
724
725 #[tokio::test]
728 async fn update_webhook_template_wire_body_includes_name() {
729 use wiremock::matchers::body_partial_json;
730 let server = MockServer::start().await;
731 Mock::given(method("PATCH"))
732 .and(path_regex("/webhooks/test-id/template/evmWalletFilter"))
733 .and(body_partial_json(serde_json::json!({"name": "new-name"})))
734 .respond_with(ResponseTemplate::new(200).set_body_json(webhook_response_json()))
735 .mount(&server)
736 .await;
737 let sdk = make_sdk(format!("{}/", server.uri()));
738 let template_args =
739 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
740 wallets: vec!["0xabc".to_string()],
741 }));
742 let params = UpdateWebhookTemplateParams {
743 name: Some("new-name".to_string()),
744 notification_email: None,
745 destination_attributes: None,
746 template_args,
747 };
748 sdk.webhooks
749 .update_webhook_template("test-id", ¶ms)
750 .await
751 .unwrap();
752 }
753
754 #[tokio::test]
758 async fn create_webhook_from_template_wire_body_uses_camelcase_event_hashes() {
759 use wiremock::matchers::body_partial_json;
760 let server = MockServer::start().await;
761 Mock::given(method("POST"))
762 .and(path_regex("/webhooks/template/evmContractEvents"))
763 .and(body_partial_json(serde_json::json!({
764 "templateArgs": {
765 "eventHashes": ["0xabcd"],
766 }
767 })))
768 .respond_with(ResponseTemplate::new(201).set_body_json(webhook_response_json()))
769 .mount(&server)
770 .await;
771 let sdk = make_sdk(format!("{}/", server.uri()));
772 let template_args = TemplateArgs::EvmContractEvents(EvmContractEventsInput::Inline(
773 EvmContractEventsTemplate {
774 contracts: vec!["0xa0b8".to_string()],
775 event_hashes: vec!["0xabcd".to_string()],
776 },
777 ));
778 let params = CreateWebhookFromTemplateParams {
779 name: "test-webhook".to_string(),
780 network: "ethereum-mainnet".to_string(),
781 notification_email: None,
782 destination_attributes: WebhookDestinationAttributes {
783 url: "https://example.com/hook".to_string(),
784 security_token: None,
785 compression: "none".to_string(),
786 },
787 template_args,
788 };
789 sdk.webhooks
790 .create_webhook_from_template(¶ms)
791 .await
792 .unwrap();
793 }
794
795 #[tokio::test]
796 async fn update_webhook_template_api_error() {
797 let server = MockServer::start().await;
798 Mock::given(method("PATCH"))
799 .and(path_regex("/webhooks/test-id/template/evmWalletFilter"))
800 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
801 .mount(&server)
802 .await;
803 let sdk = make_sdk(format!("{}/", server.uri()));
804 let template_args =
805 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
806 wallets: vec!["0xabc".to_string()],
807 }));
808 let params = UpdateWebhookTemplateParams {
809 name: None,
810 notification_email: None,
811 destination_attributes: None,
812 template_args,
813 };
814 let err = sdk
815 .webhooks
816 .update_webhook_template("test-id", ¶ms)
817 .await
818 .unwrap_err();
819 assert!(matches!(err, SdkError::Api { .. }));
820 }
821}