use crate::error::OwsLibError;
use base64::Engine;
use std::process::Command;
fn near_rpc_call(
rpc_url: &str,
body: &serde_json::Value,
) -> Result<serde_json::Value, OwsLibError> {
let body_str = body.to_string();
let output = Command::new("curl")
.args([
"-fsSL",
"-X",
"POST",
"-H",
"Content-Type: application/json",
"-d",
&body_str,
rpc_url,
])
.output()
.map_err(|e| OwsLibError::BroadcastFailed(format!("failed to run curl: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(OwsLibError::BroadcastFailed(format!(
"NEAR RPC call failed: {stderr}"
)));
}
let resp_str = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&resp_str)?;
if let Some(error) = parsed.get("error") {
let msg = error
.get("data")
.and_then(|d| d.as_str())
.or_else(|| error.get("message").and_then(|m| m.as_str()))
.or_else(|| error.as_str())
.unwrap_or("unknown error");
return Err(OwsLibError::BroadcastFailed(format!(
"NEAR RPC error: {msg}"
)));
}
Ok(parsed)
}
pub fn broadcast_tx_commit(rpc_url: &str, signed_bytes: &[u8]) -> Result<String, OwsLibError> {
let signed_b64 = base64::engine::general_purpose::STANDARD.encode(signed_bytes);
let body = serde_json::json!({
"jsonrpc": "2.0",
"id": "ows",
"method": "broadcast_tx_commit",
"params": [signed_b64]
});
let resp = near_rpc_call(rpc_url, &body)?;
let hash = resp
.pointer("/result/transaction/hash")
.and_then(|h| h.as_str())
.or_else(|| {
resp.pointer("/result/transaction_outcome/id")
.and_then(|h| h.as_str())
})
.ok_or_else(|| {
OwsLibError::BroadcastFailed(
"broadcast_tx_commit response missing transaction hash".into(),
)
})?
.to_string();
Ok(hash)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_broadcast_body_shape() {
let signed = b"\x00\x01\x02\x03";
let b64 = base64::engine::general_purpose::STANDARD.encode(signed);
assert_eq!(b64, "AAECAw==");
}
}