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