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 rpc: None,
351 })
352 .unwrap()
353 }
354
355 fn webhook_response_json() -> serde_json::Value {
356 serde_json::json!({
357 "id": "wh-1234-5678",
358 "name": "test-webhook",
359 "status": "active",
360 "network": "ethereum-mainnet",
361 "created_at": "2026-03-19T12:00:00Z",
362 "updated_at": "2026-03-19T12:00:00Z"
363 })
364 }
365
366 #[tokio::test]
367 async fn list_webhooks_success() {
368 let server = MockServer::start().await;
369 let response = serde_json::json!({
370 "data": [webhook_response_json()],
371 "pageInfo": {
372 "limit": 20,
373 "offset": 0,
374 "total": 1,
375 }
376 });
377 Mock::given(method("GET"))
378 .and(path("/webhooks"))
379 .respond_with(ResponseTemplate::new(200).set_body_json(response))
380 .mount(&server)
381 .await;
382 let sdk = make_sdk(format!("{}/", server.uri()));
383 let resp = sdk
384 .webhooks
385 .list_webhooks(&GetWebhooksParams::default())
386 .await
387 .unwrap();
388 assert_eq!(resp.data.len(), 1);
389 assert_eq!(resp.data[0].id, "wh-1234-5678");
390 assert_eq!(resp.page_info.limit, 20);
391 assert_eq!(resp.page_info.offset, 0);
392 assert_eq!(resp.page_info.total, 1);
393 }
394
395 #[tokio::test]
396 async fn list_webhooks_api_error() {
397 let server = MockServer::start().await;
398 Mock::given(method("GET"))
399 .and(path("/webhooks"))
400 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
401 .mount(&server)
402 .await;
403 let sdk = make_sdk(format!("{}/", server.uri()));
404 let err = sdk
405 .webhooks
406 .list_webhooks(&GetWebhooksParams::default())
407 .await
408 .unwrap_err();
409 assert!(matches!(err, SdkError::Api { .. }));
410 }
411
412 #[tokio::test]
413 async fn list_webhooks_server_error() {
414 let server = MockServer::start().await;
415 Mock::given(method("GET"))
416 .and(path("/webhooks"))
417 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
418 .mount(&server)
419 .await;
420 let sdk = make_sdk(format!("{}/", server.uri()));
421 let err = sdk
422 .webhooks
423 .list_webhooks(&GetWebhooksParams::default())
424 .await
425 .unwrap_err();
426 assert!(matches!(err, SdkError::Api { .. }));
427 }
428
429 #[tokio::test]
430 async fn get_webhook_success() {
431 let server = MockServer::start().await;
432 Mock::given(method("GET"))
433 .and(path("/webhooks/test-id"))
434 .respond_with(ResponseTemplate::new(200).set_body_json(webhook_response_json()))
435 .mount(&server)
436 .await;
437 let sdk = make_sdk(format!("{}/", server.uri()));
438 let resp = sdk.webhooks.get_webhook("test-id").await.unwrap();
439 assert_eq!(resp.id, "wh-1234-5678");
440 }
441
442 #[tokio::test]
443 async fn get_webhook_api_error() {
444 let server = MockServer::start().await;
445 Mock::given(method("GET"))
446 .and(path("/webhooks/test-id"))
447 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
448 .mount(&server)
449 .await;
450 let sdk = make_sdk(format!("{}/", server.uri()));
451 let err = sdk.webhooks.get_webhook("test-id").await.unwrap_err();
452 assert!(matches!(err, SdkError::Api { .. }));
453 }
454
455 #[tokio::test]
456 async fn update_webhook_success() {
457 let server = MockServer::start().await;
458 let mut updated = webhook_response_json();
459 updated["name"] = serde_json::json!("updated-name");
460 Mock::given(method("PATCH"))
461 .and(path("/webhooks/test-id"))
462 .respond_with(ResponseTemplate::new(200).set_body_json(updated))
463 .mount(&server)
464 .await;
465 let sdk = make_sdk(format!("{}/", server.uri()));
466 let params = UpdateWebhookParams {
467 name: Some("updated-name".to_string()),
468 ..Default::default()
469 };
470 let resp = sdk
471 .webhooks
472 .update_webhook("test-id", ¶ms)
473 .await
474 .unwrap();
475 assert_eq!(resp.name, "updated-name");
476 }
477
478 #[tokio::test]
479 async fn update_webhook_api_error() {
480 let server = MockServer::start().await;
481 Mock::given(method("PATCH"))
482 .and(path("/webhooks/test-id"))
483 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
484 .mount(&server)
485 .await;
486 let sdk = make_sdk(format!("{}/", server.uri()));
487 let params = UpdateWebhookParams::default();
488 let err = sdk
489 .webhooks
490 .update_webhook("test-id", ¶ms)
491 .await
492 .unwrap_err();
493 assert!(matches!(err, SdkError::Api { .. }));
494 }
495
496 #[tokio::test]
497 async fn delete_webhook_success() {
498 let server = MockServer::start().await;
499 Mock::given(method("DELETE"))
500 .and(path("/webhooks/test-id"))
501 .respond_with(ResponseTemplate::new(200))
502 .mount(&server)
503 .await;
504 let sdk = make_sdk(format!("{}/", server.uri()));
505 sdk.webhooks.delete_webhook("test-id").await.unwrap();
506 }
507
508 #[tokio::test]
509 async fn delete_webhook_api_error() {
510 let server = MockServer::start().await;
511 Mock::given(method("DELETE"))
512 .and(path("/webhooks/test-id"))
513 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
514 .mount(&server)
515 .await;
516 let sdk = make_sdk(format!("{}/", server.uri()));
517 let err = sdk.webhooks.delete_webhook("test-id").await.unwrap_err();
518 assert!(matches!(err, SdkError::Api { .. }));
519 }
520
521 #[tokio::test]
522 async fn delete_all_webhooks_success() {
523 let server = MockServer::start().await;
524 Mock::given(method("DELETE"))
525 .and(path("/webhooks"))
526 .respond_with(ResponseTemplate::new(204))
527 .mount(&server)
528 .await;
529 let sdk = make_sdk(format!("{}/", server.uri()));
530 sdk.webhooks.delete_all_webhooks().await.unwrap();
531 }
532
533 #[tokio::test]
534 async fn delete_all_webhooks_api_error() {
535 let server = MockServer::start().await;
536 Mock::given(method("DELETE"))
537 .and(path("/webhooks"))
538 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
539 .mount(&server)
540 .await;
541 let sdk = make_sdk(format!("{}/", server.uri()));
542 let err = sdk.webhooks.delete_all_webhooks().await.unwrap_err();
543 assert!(matches!(err, SdkError::Api { .. }));
544 }
545
546 #[tokio::test]
547 async fn pause_webhook_success() {
548 let server = MockServer::start().await;
549 Mock::given(method("POST"))
550 .and(path("/webhooks/test-id/pause"))
551 .respond_with(ResponseTemplate::new(201))
552 .mount(&server)
553 .await;
554 let sdk = make_sdk(format!("{}/", server.uri()));
555 sdk.webhooks.pause_webhook("test-id").await.unwrap();
556 }
557
558 #[tokio::test]
559 async fn pause_webhook_api_error() {
560 let server = MockServer::start().await;
561 Mock::given(method("POST"))
562 .and(path("/webhooks/test-id/pause"))
563 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
564 .mount(&server)
565 .await;
566 let sdk = make_sdk(format!("{}/", server.uri()));
567 let err = sdk.webhooks.pause_webhook("test-id").await.unwrap_err();
568 assert!(matches!(err, SdkError::Api { .. }));
569 }
570
571 #[tokio::test]
572 async fn activate_webhook_success() {
573 let server = MockServer::start().await;
574 Mock::given(method("POST"))
575 .and(path("/webhooks/test-id/activate"))
576 .respond_with(ResponseTemplate::new(201))
577 .mount(&server)
578 .await;
579 let sdk = make_sdk(format!("{}/", server.uri()));
580 let params = ActivateWebhookParams {
581 start_from: WebhookStartFrom::Latest,
582 };
583 sdk.webhooks
584 .activate_webhook("test-id", ¶ms)
585 .await
586 .unwrap();
587 }
588
589 #[tokio::test]
590 async fn activate_webhook_api_error() {
591 let server = MockServer::start().await;
592 Mock::given(method("POST"))
593 .and(path("/webhooks/test-id/activate"))
594 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
595 .mount(&server)
596 .await;
597 let sdk = make_sdk(format!("{}/", server.uri()));
598 let params = ActivateWebhookParams {
599 start_from: WebhookStartFrom::Latest,
600 };
601 let err = sdk
602 .webhooks
603 .activate_webhook("test-id", ¶ms)
604 .await
605 .unwrap_err();
606 assert!(matches!(err, SdkError::Api { .. }));
607 }
608
609 #[tokio::test]
610 async fn get_enabled_count_success() {
611 let server = MockServer::start().await;
612 Mock::given(method("GET"))
613 .and(path("/webhooks/enabled_count"))
614 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"total": 5})))
615 .mount(&server)
616 .await;
617 let sdk = make_sdk(format!("{}/", server.uri()));
618 let resp = sdk.webhooks.get_enabled_count().await.unwrap();
619 assert_eq!(resp.total, 5);
620 }
621
622 #[tokio::test]
623 async fn get_enabled_count_api_error() {
624 let server = MockServer::start().await;
625 Mock::given(method("GET"))
626 .and(path("/webhooks/enabled_count"))
627 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
628 .mount(&server)
629 .await;
630 let sdk = make_sdk(format!("{}/", server.uri()));
631 let err = sdk.webhooks.get_enabled_count().await.unwrap_err();
632 assert!(matches!(err, SdkError::Api { .. }));
633 }
634
635 #[tokio::test]
636 async fn create_webhook_from_template_success() {
637 let server = MockServer::start().await;
638 Mock::given(method("POST"))
639 .and(path_regex("/webhooks/template/evmWalletFilter"))
640 .respond_with(ResponseTemplate::new(201).set_body_json(webhook_response_json()))
641 .mount(&server)
642 .await;
643 let sdk = make_sdk(format!("{}/", server.uri()));
644 let template_args =
645 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
646 wallets: vec!["0xabc".to_string()],
647 }));
648 let params = CreateWebhookFromTemplateParams {
649 name: "test-webhook".to_string(),
650 network: "ethereum-mainnet".to_string(),
651 notification_email: None,
652 destination_attributes: WebhookDestinationAttributes {
653 url: "https://example.com/hook".to_string(),
654 security_token: None,
655 compression: "none".to_string(),
656 },
657 template_args,
658 };
659 let resp = sdk
660 .webhooks
661 .create_webhook_from_template(¶ms)
662 .await
663 .unwrap();
664 assert_eq!(resp.id, "wh-1234-5678");
665 }
666
667 #[tokio::test]
668 async fn create_webhook_from_template_api_error() {
669 let server = MockServer::start().await;
670 Mock::given(method("POST"))
671 .and(path_regex("/webhooks/template/evmWalletFilter"))
672 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
673 .mount(&server)
674 .await;
675 let sdk = make_sdk(format!("{}/", server.uri()));
676 let template_args =
677 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
678 wallets: vec!["0xabc".to_string()],
679 }));
680 let params = CreateWebhookFromTemplateParams {
681 name: "test-webhook".to_string(),
682 network: "ethereum-mainnet".to_string(),
683 notification_email: None,
684 destination_attributes: WebhookDestinationAttributes {
685 url: "https://example.com/hook".to_string(),
686 security_token: None,
687 compression: "none".to_string(),
688 },
689 template_args,
690 };
691 let err = sdk
692 .webhooks
693 .create_webhook_from_template(¶ms)
694 .await
695 .unwrap_err();
696 assert!(matches!(err, SdkError::Api { .. }));
697 }
698
699 #[tokio::test]
700 async fn update_webhook_template_success() {
701 let server = MockServer::start().await;
702 Mock::given(method("PATCH"))
703 .and(path_regex("/webhooks/test-id/template/evmWalletFilter"))
704 .respond_with(ResponseTemplate::new(200).set_body_json(webhook_response_json()))
705 .mount(&server)
706 .await;
707 let sdk = make_sdk(format!("{}/", server.uri()));
708 let template_args =
709 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
710 wallets: vec!["0xabc".to_string()],
711 }));
712 let params = UpdateWebhookTemplateParams {
713 name: None,
714 notification_email: None,
715 destination_attributes: None,
716 template_args,
717 };
718 let resp = sdk
719 .webhooks
720 .update_webhook_template("test-id", ¶ms)
721 .await
722 .unwrap();
723 assert_eq!(resp.id, "wh-1234-5678");
724 }
725
726 #[tokio::test]
729 async fn update_webhook_template_wire_body_includes_name() {
730 use wiremock::matchers::body_partial_json;
731 let server = MockServer::start().await;
732 Mock::given(method("PATCH"))
733 .and(path_regex("/webhooks/test-id/template/evmWalletFilter"))
734 .and(body_partial_json(serde_json::json!({"name": "new-name"})))
735 .respond_with(ResponseTemplate::new(200).set_body_json(webhook_response_json()))
736 .mount(&server)
737 .await;
738 let sdk = make_sdk(format!("{}/", server.uri()));
739 let template_args =
740 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
741 wallets: vec!["0xabc".to_string()],
742 }));
743 let params = UpdateWebhookTemplateParams {
744 name: Some("new-name".to_string()),
745 notification_email: None,
746 destination_attributes: None,
747 template_args,
748 };
749 sdk.webhooks
750 .update_webhook_template("test-id", ¶ms)
751 .await
752 .unwrap();
753 }
754
755 #[tokio::test]
759 async fn create_webhook_from_template_wire_body_uses_camelcase_event_hashes() {
760 use wiremock::matchers::body_partial_json;
761 let server = MockServer::start().await;
762 Mock::given(method("POST"))
763 .and(path_regex("/webhooks/template/evmContractEvents"))
764 .and(body_partial_json(serde_json::json!({
765 "templateArgs": {
766 "eventHashes": ["0xabcd"],
767 }
768 })))
769 .respond_with(ResponseTemplate::new(201).set_body_json(webhook_response_json()))
770 .mount(&server)
771 .await;
772 let sdk = make_sdk(format!("{}/", server.uri()));
773 let template_args = TemplateArgs::EvmContractEvents(EvmContractEventsInput::Inline(
774 EvmContractEventsTemplate {
775 contracts: vec!["0xa0b8".to_string()],
776 event_hashes: vec!["0xabcd".to_string()],
777 },
778 ));
779 let params = CreateWebhookFromTemplateParams {
780 name: "test-webhook".to_string(),
781 network: "ethereum-mainnet".to_string(),
782 notification_email: None,
783 destination_attributes: WebhookDestinationAttributes {
784 url: "https://example.com/hook".to_string(),
785 security_token: None,
786 compression: "none".to_string(),
787 },
788 template_args,
789 };
790 sdk.webhooks
791 .create_webhook_from_template(¶ms)
792 .await
793 .unwrap();
794 }
795
796 #[tokio::test]
797 async fn update_webhook_template_api_error() {
798 let server = MockServer::start().await;
799 Mock::given(method("PATCH"))
800 .and(path_regex("/webhooks/test-id/template/evmWalletFilter"))
801 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
802 .mount(&server)
803 .await;
804 let sdk = make_sdk(format!("{}/", server.uri()));
805 let template_args =
806 TemplateArgs::EvmWalletFilter(EvmWalletFilterInput::Inline(EvmWalletFilterTemplate {
807 wallets: vec!["0xabc".to_string()],
808 }));
809 let params = UpdateWebhookTemplateParams {
810 name: None,
811 notification_email: None,
812 destination_attributes: None,
813 template_args,
814 };
815 let err = sdk
816 .webhooks
817 .update_webhook_template("test-id", ¶ms)
818 .await
819 .unwrap_err();
820 assert!(matches!(err, SdkError::Api { .. }));
821 }
822}