Skip to content

Commit

Permalink
transaction endpoints yo (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
Chuck authored Aug 21, 2023
1 parent b9d99e8 commit 2d3df7b
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,3 @@ impl Pagination {
}
}
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct Return<P> {
/// The returned data.
pub data: P,
/// Pagination information (if any)
#[serde(skip_serializing_if = "Option::is_none", default)]
pub pagination: Option<Pagination>,
}
16 changes: 13 additions & 3 deletions crates/explorer-client/src/endpoints/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
pub mod tx;
pub mod vaa;

use std::{fmt::Debug, future::Future, pin::Pin};

use tracing::Instrument;

use crate::Client;
use crate::{error::ApiError, Client};

/// A call to the [Wormhole Explorer API].
///
Expand Down Expand Up @@ -48,9 +49,18 @@ pub trait ApiCall: Send + Sync + Debug {
tracing::debug!("sending request");
let resp = fut.await?;
let text = resp.text().await?;
tracing::debug!(text, "received response");
tracing::debug!("received response");
tracing::trace!(text);

serde_json::from_str::<Self::Return>(&text).map_err(Into::into)
let res = serde_json::from_str::<Self::Return>(&text).map_err(Into::into);
// if the res is an error, try to deser the text as an API error object.
if res.is_err() {
if let Ok(err) = serde_json::from_str::<ApiError>(&text) {
return Err(err.into());
}
tracing::error!(text, "unknown error response from server");
}
res
}
.in_current_span(),
)
Expand Down
95 changes: 95 additions & 0 deletions crates/explorer-client/src/endpoints/tx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::collections::HashMap;

use alloy_primitives::{FixedBytes, U256};

use crate::ApiCall;

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ExplorerTxResponse {
pub transactions: Vec<ExplorerTx>,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ExplorerTx {
pub id: String,
pub emitter_chain: u16,
pub emitter_address: FixedBytes<32>,

pub tx_hash: Option<String>,

pub emitter_native_addr: Option<String>,
pub global_tx: Option<GlobalTx>,
pub symbol: Option<String>,
pub timestamp: Option<String>,
pub token_amount: Option<String>,
pub usd_amount: Option<String>,

#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub payload: HashMap<String, serde_json::Value>,

#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub standardized_properties: HashMap<String, serde_json::Value>,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GlobalTx {
pub destination_tx: Option<DestinationTx>,
pub id: String,
pub origin_tx: OriginTx,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DestinationTx {
pub block_number: U256,
pub chain_id: u16,
pub from: String,
pub method: String,
pub status: String,
pub timestamp: String,
pub to: String,
pub tx_hash: String,
pub updated_at: String,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OriginTx {
pub from: String,
pub status: String,
pub tx_hash: String,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AllTxnsRequest;

impl ApiCall for AllTxnsRequest {
type Return = ExplorerTxResponse;

fn endpoint(&self) -> String {
"/api/v1/transactions/".to_string()
}
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SingleTxRequest {
pub chain_id: u16,
pub emitter: FixedBytes<32>,
pub sequence: u64,
}

impl ApiCall for SingleTxRequest {
type Return = ExplorerTx;

fn endpoint(&self) -> String {
format!(
"/api/v1/transactions/{}/{}/{}",
self.chain_id, self.emitter, self.sequence
)
}
}
43 changes: 26 additions & 17 deletions crates/explorer-client/src/endpoints/vaa.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
use alloy_primitives::FixedBytes;
use wormhole_vaas::{Readable, Vaa};

use crate::{ApiCall, Result};
use crate::{ApiCall, Pagination, Result};

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct ExplorerVaaResponse {
/// The returned data.
pub data: Vec<ExplorerVaa>,
/// Pagination information (if any)
#[serde(skip_serializing_if = "Option::is_none", default)]
pub pagination: Option<Pagination>,
}

#[derive(Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ExplorerVaaResp {
sequence: u32,
id: String,
version: u8,
emitter_chain: u16,
emitter_addr: FixedBytes<32>,
emitter_native_addr: String,
pub struct ExplorerVaa {
pub sequence: u32,
pub id: String,
pub version: u8,
pub emitter_chain: u16,
pub emitter_addr: FixedBytes<32>,
pub emitter_native_addr: String,
#[serde(with = "base64")]
vaa: Vec<u8>,
timestamp: String,
updated_at: String,
indexed_at: String,
tx_hash: Option<String>,
pub vaa: Vec<u8>,
pub timestamp: String,
pub updated_at: String,
pub indexed_at: String,
pub tx_hash: Option<String>,
}

impl std::fmt::Debug for ExplorerVaaResp {
impl std::fmt::Debug for ExplorerVaa {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExplorerVaaResp")
f.debug_struct("ExplorerVaa")
.field("sequence", &self.sequence)
.field("id", &self.id)
.field("version", &self.version)
Expand All @@ -38,7 +47,7 @@ impl std::fmt::Debug for ExplorerVaaResp {
}
}

impl ExplorerVaaResp {
impl ExplorerVaa {
pub fn deser_vaa(&self) -> Result<Vaa> {
Vaa::read(&mut self.vaa.as_slice()).map_err(Into::into)
}
Expand All @@ -52,7 +61,7 @@ pub struct VaaRequest {
}

impl ApiCall for VaaRequest {
type Return = crate::returns::Return<Vec<ExplorerVaaResp>>;
type Return = ExplorerVaaResponse;

fn endpoint(&self) -> String {
let stem = "/api/v1/vaas";
Expand Down
27 changes: 27 additions & 0 deletions crates/explorer-client/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashMap;

pub type Result<T> = std::result::Result<T, Error>;

#[derive(thiserror::Error, Debug)]
Expand All @@ -13,4 +15,29 @@ pub enum Error {
/// IO
#[error(transparent)]
Io(#[from] std::io::Error),

/// Api
#[error("Internal API Error. Hint: this usually means a misformatted URL")]
ApiError(#[from] ApiError),
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiError {
pub code: u32,
pub message: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub details: Vec<HashMap<String, serde_json::Value>>,
}

impl std::fmt::Display for ApiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ApiError")
.field("code", &self.code)
.field("message", &self.message)
.field("details", &self.details)
.finish()
}
}

impl std::error::Error for ApiError {}
4 changes: 2 additions & 2 deletions crates/explorer-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ pub use endpoints::{vaa::VaaRequest, ApiCall};
mod error;
pub use error::{Error, Result};

mod returns;
pub use returns::Return;
mod common;
pub use common::Pagination;

mod client;
pub use client::Client;
41 changes: 41 additions & 0 deletions crates/explorer-client/tests/tx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#![allow(dead_code)]
use wormhole_explorer_client::{
endpoints::tx::{AllTxnsRequest, SingleTxRequest},
Client,
};

use hex_literal::hex;

// #[tokio::test]
async fn retrieve_txs() {
let req = AllTxnsRequest;

let client = Client::new(
"https://api.wormholescan.io/".parse().unwrap(),
Default::default(),
);

let resp = client.send(&req).await;
dbg!(&resp);

assert!(resp.is_ok());
}

// #[tokio::test]
async fn retrieve_eth_token_bridge() {
let req = SingleTxRequest {
chain_id: 2,
emitter: hex!("0000000000000000000000003ee18B2214AFF97000D974cf647E7C347E8fa585").into(),
sequence: 5,
};

let client = Client::new(
"https://api.wormholescan.io/".parse().unwrap(),
Default::default(),
);

let resp = client.send(&req).await;

let tx = resp.unwrap();
dbg!(&tx);
}

0 comments on commit 2d3df7b

Please sign in to comment.