use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LeaderHint {
pub timestamp: u64,
pub slot: u64,
pub expires_at_slot: u64,
pub preferred_region: String,
pub backup_regions: Vec<String>,
pub confidence: u32,
pub leader_pubkey: String,
pub metadata: LeaderHintMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LeaderHintMetadata {
pub tpu_rtt_ms: u32,
pub region_score: f64,
#[serde(default)]
pub leader_tpu_address: Option<String>,
#[serde(default)]
pub region_rtt_ms: Option<std::collections::HashMap<String, u32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TipInstruction {
pub timestamp: u64,
pub sender: String,
pub sender_name: String,
pub tip_wallet_address: String,
pub tip_amount_sol: f64,
pub tip_tier: String,
pub expected_latency_ms: u32,
pub confidence: u32,
pub valid_until_slot: u64,
#[serde(default)]
pub alternative_senders: Vec<AlternativeSender>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AlternativeSender {
pub sender: String,
pub tip_amount_sol: f64,
pub confidence: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriorityFee {
pub timestamp: u64,
pub speed: String,
pub compute_unit_price: u64,
pub compute_unit_limit: u32,
pub estimated_cost_sol: f64,
pub landing_probability: u32,
pub network_congestion: String,
pub recent_success_rate: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LatestBlockhash {
pub blockhash: String,
pub last_valid_block_height: u64,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LatestSlot {
pub slot: u64,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionResult {
pub request_id: String,
pub transaction_id: String,
pub signature: Option<String>,
pub status: TransactionStatus,
pub slot: Option<u64>,
pub slot_sent: Option<u64>,
pub slot_accepted: Option<u64>,
pub slot_landed: Option<u64>,
pub slot_delta: Option<u64>,
#[serde(default)]
pub commitment_level: Option<String>,
#[serde(default)]
pub confirmations: Option<u64>,
#[serde(default)]
pub slot_processed: Option<u64>,
#[serde(default)]
pub slot_confirmed: Option<u64>,
#[serde(default)]
pub slot_finalized: Option<u64>,
pub timestamp: u64,
pub routing: Option<RoutingInfo>,
pub error: Option<TransactionError>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TransactionStatus {
Pending,
Processing,
Sent,
Confirmed,
Failed,
Duplicate,
RateLimited,
InsufficientTokens,
}
impl TransactionStatus {
pub fn is_terminal(&self) -> bool {
matches!(
self,
TransactionStatus::Confirmed
| TransactionStatus::Failed
| TransactionStatus::Duplicate
| TransactionStatus::RateLimited
| TransactionStatus::InsufficientTokens
)
}
pub fn is_success(&self) -> bool {
matches!(self, TransactionStatus::Confirmed)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct RoutingInfo {
pub region: String,
pub sender: String,
pub routing_latency_ms: u32,
pub sender_latency_ms: u32,
pub total_latency_ms: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionError {
pub code: String,
pub message: String,
pub details: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RetryOptions {
#[serde(default = "default_max_retries")]
pub max_retries: u32,
#[serde(default = "default_backoff_base_ms")]
pub backoff_base_ms: u64,
#[serde(default)]
pub cross_sender_retry: bool,
}
fn default_backoff_base_ms() -> u64 { 100 }
impl Default for RetryOptions {
fn default() -> Self {
Self {
max_retries: default_max_retries(),
backoff_base_ms: 100,
cross_sender_retry: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmitOptions {
#[serde(default)]
pub broadcast_mode: bool,
#[serde(default)]
pub preferred_sender: Option<String>,
#[serde(default = "default_max_retries")]
pub max_retries: u32,
#[serde(default = "default_timeout_ms")]
pub timeout_ms: u64,
#[serde(default)]
pub dedup_id: Option<String>,
#[serde(default)]
pub retry: Option<RetryOptions>,
#[serde(default)]
pub tpu_submission: bool,
}
impl Default for SubmitOptions {
fn default() -> Self {
Self {
broadcast_mode: false,
preferred_sender: None,
max_retries: default_max_retries(),
timeout_ms: default_timeout_ms(),
dedup_id: None,
retry: None,
tpu_submission: false,
}
}
}
fn default_max_retries() -> u32 {
2
}
fn default_timeout_ms() -> u64 {
30_000
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConnectionInfo {
pub session_id: String,
pub protocol: String,
pub region: Option<String>,
pub server_time: u64,
pub features: Vec<String>,
pub rate_limit: RateLimitInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RateLimitInfo {
pub rps: u32,
pub burst: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PingResult {
pub seq: u32,
pub rtt_ms: u64,
pub clock_offset_ms: i64,
pub server_time: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Protocol {
Quic,
Grpc,
WebSocket,
Http,
}
impl Protocol {
pub fn fallback_order() -> &'static [Protocol] {
&[Protocol::Quic, Protocol::Grpc, Protocol::WebSocket, Protocol::Http]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerEndpoint {
pub id: String,
pub region: String,
pub quic: Option<String>,
pub grpc: Option<String>,
pub websocket: Option<String>,
pub http: Option<String>,
}
impl WorkerEndpoint {
pub fn new(id: &str, region: &str, ip: &str) -> Self {
Self {
id: id.to_string(),
region: region.to_string(),
quic: Some(format!("{}:4433", ip)),
grpc: Some(format!("http://{}:10000", ip)),
websocket: Some(format!("ws://{}:9000/ws", ip)),
http: Some(format!("http://{}:9000", ip)),
}
}
pub fn with_ports(
id: &str,
region: &str,
ip: &str,
quic_port: u16,
grpc_port: u16,
ws_port: u16,
http_port: u16,
) -> Self {
Self {
id: id.to_string(),
region: region.to_string(),
quic: Some(format!("{}:{}", ip, quic_port)),
grpc: Some(format!("http://{}:{}", ip, grpc_port)),
websocket: Some(format!("ws://{}:{}/ws", ip, ws_port)),
http: Some(format!("http://{}:{}", ip, http_port)),
}
}
pub fn with_endpoints(
id: &str,
region: &str,
quic: Option<String>,
grpc: Option<String>,
websocket: Option<String>,
http: Option<String>,
) -> Self {
Self {
id: id.to_string(),
region: region.to_string(),
quic,
grpc,
websocket,
http,
}
}
pub fn get_endpoint(&self, protocol: Protocol) -> Option<&str> {
match protocol {
Protocol::Quic => self.quic.as_deref(),
Protocol::Grpc => self.grpc.as_deref(),
Protocol::WebSocket => self.websocket.as_deref(),
Protocol::Http => self.http.as_deref(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_leader_hint_deserialize() {
let json = r#"{
"timestamp": 1706011200000,
"slot": 12345678,
"expiresAtSlot": 12345682,
"preferredRegion": "us-west",
"backupRegions": ["eu-central"],
"confidence": 87,
"leaderPubkey": "Vote111111111111111111111111111111111111111",
"metadata": {
"tpuRttMs": 12,
"regionScore": 0.85
}
}"#;
let hint: LeaderHint = serde_json::from_str(json).unwrap();
assert_eq!(hint.preferred_region, "us-west");
assert_eq!(hint.confidence, 87);
assert_eq!(hint.leader_pubkey, "Vote111111111111111111111111111111111111111");
assert_eq!(hint.metadata.tpu_rtt_ms, 12);
assert!(hint.metadata.leader_tpu_address.is_none());
assert!(hint.metadata.region_rtt_ms.is_none());
}
#[test]
fn test_leader_hint_with_extended_metadata() {
let json = r#"{
"timestamp": 1706011200000,
"slot": 12345678,
"expiresAtSlot": 12345682,
"preferredRegion": "us-west",
"backupRegions": ["eu-central", "asia-east"],
"confidence": 92,
"leaderPubkey": "Vote111111111111111111111111111111111111111",
"metadata": {
"tpuRttMs": 8,
"regionScore": 0.92,
"leaderTpuAddress": "192.168.1.100:8004",
"regionRttMs": {"us-west": 8, "eu-central": 45, "asia-east": 120}
}
}"#;
let hint: LeaderHint = serde_json::from_str(json).unwrap();
assert_eq!(hint.preferred_region, "us-west");
assert_eq!(hint.confidence, 92);
assert_eq!(hint.leader_pubkey, "Vote111111111111111111111111111111111111111");
assert_eq!(hint.metadata.leader_tpu_address, Some("192.168.1.100:8004".to_string()));
let region_rtt = hint.metadata.region_rtt_ms.unwrap();
assert_eq!(region_rtt.get("us-west"), Some(&8));
assert_eq!(region_rtt.get("eu-central"), Some(&45));
}
#[test]
fn test_tip_instruction_deserialize() {
let json = r#"{
"timestamp": 1706011200000,
"sender": "0slot",
"senderName": "0Slot",
"tipWalletAddress": "So11111111111111111111111111111111111111112",
"tipAmountSol": 0.0001,
"tipTier": "standard",
"expectedLatencyMs": 100,
"confidence": 95,
"validUntilSlot": 12345700,
"alternativeSenders": []
}"#;
let tip: TipInstruction = serde_json::from_str(json).unwrap();
assert_eq!(tip.sender, "0slot");
assert_eq!(tip.tip_amount_sol, 0.0001);
}
#[test]
fn test_transaction_status() {
assert!(TransactionStatus::Confirmed.is_terminal());
assert!(TransactionStatus::Failed.is_terminal());
assert!(!TransactionStatus::Processing.is_terminal());
assert!(TransactionStatus::Confirmed.is_success());
assert!(!TransactionStatus::Failed.is_success());
}
#[test]
fn test_submit_options_default() {
let options = SubmitOptions::default();
assert!(!options.broadcast_mode);
assert_eq!(options.max_retries, 2);
assert_eq!(options.timeout_ms, 30_000);
}
#[test]
fn test_latest_blockhash_deserialize() {
let json = r#"{
"blockhash": "7Xq3JcEBR1sVmAHGgn3Dz3C96DRfz7RgXWbvJqLbMp3",
"lastValidBlockHeight": 12345700,
"timestamp": 1706011200000
}"#;
let bh: LatestBlockhash = serde_json::from_str(json).unwrap();
assert_eq!(bh.blockhash, "7Xq3JcEBR1sVmAHGgn3Dz3C96DRfz7RgXWbvJqLbMp3");
assert_eq!(bh.last_valid_block_height, 12345700);
assert_eq!(bh.timestamp, 1706011200000);
}
#[test]
fn test_latest_slot_deserialize() {
let json = r#"{
"slot": 12345678,
"timestamp": 1706011200000
}"#;
let slot: LatestSlot = serde_json::from_str(json).unwrap();
assert_eq!(slot.slot, 12345678);
assert_eq!(slot.timestamp, 1706011200000);
}
#[test]
fn test_routing_recommendation_deserialize() {
let json = r#"{
"bestRegion": "us-west",
"leaderPubkey": "Vote111111111111111111111111111111111111111",
"slot": 12345678,
"confidence": 85,
"expectedRttMs": 12,
"fallbackRegions": ["eu-central", "asia-east"],
"fallbackStrategy": "sequential",
"validForMs": 400
}"#;
let rec: RoutingRecommendation = serde_json::from_str(json).unwrap();
assert_eq!(rec.best_region, "us-west");
assert_eq!(rec.confidence, 85);
assert_eq!(rec.fallback_strategy, FallbackStrategy::Sequential);
assert_eq!(rec.fallback_regions.len(), 2);
}
#[test]
fn test_multi_region_config_default() {
let config = MultiRegionConfig::default();
assert!(config.auto_follow_leader);
assert_eq!(config.min_switch_confidence, 60);
assert_eq!(config.switch_cooldown_ms, 500);
assert!(!config.broadcast_high_priority);
assert_eq!(config.max_broadcast_regions, 3);
}
#[test]
fn test_fallback_strategy_default() {
let strategy = FallbackStrategy::default();
assert_eq!(strategy, FallbackStrategy::Sequential);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RoutingRecommendation {
pub best_region: String,
pub leader_pubkey: String,
pub slot: u64,
pub confidence: u32,
pub expected_rtt_ms: Option<u32>,
pub fallback_regions: Vec<String>,
pub fallback_strategy: FallbackStrategy,
pub valid_for_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FallbackStrategy {
Sequential,
Broadcast,
Retry,
None,
}
impl Default for FallbackStrategy {
fn default() -> Self {
FallbackStrategy::Sequential
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MultiRegionConfig {
#[serde(default = "default_auto_follow")]
pub auto_follow_leader: bool,
#[serde(default = "default_min_confidence")]
pub min_switch_confidence: u32,
#[serde(default = "default_switch_cooldown")]
pub switch_cooldown_ms: u64,
#[serde(default)]
pub broadcast_high_priority: bool,
#[serde(default = "default_max_broadcast_regions")]
pub max_broadcast_regions: usize,
}
fn default_auto_follow() -> bool {
true
}
fn default_min_confidence() -> u32 {
60
}
fn default_switch_cooldown() -> u64 {
500
}
fn default_max_broadcast_regions() -> usize {
3
}
impl Default for MultiRegionConfig {
fn default() -> Self {
Self {
auto_follow_leader: default_auto_follow(),
min_switch_confidence: default_min_confidence(),
switch_cooldown_ms: default_switch_cooldown(),
broadcast_high_priority: false,
max_broadcast_regions: default_max_broadcast_regions(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegionInfo {
pub region_id: String,
pub display_name: String,
pub endpoint: String,
pub geolocation: Option<Geolocation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Geolocation {
pub lat: f64,
pub lon: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SenderInfo {
pub sender_id: String,
pub display_name: String,
pub tip_wallets: Vec<String>,
pub tip_tiers: Vec<TipTier>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TipTier {
pub name: String,
pub amount_sol: f64,
pub expected_latency_ms: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegionStatus {
pub region_id: String,
pub available: bool,
pub latency_ms: Option<u32>,
pub leader_rtt_ms: Option<u32>,
pub score: Option<f64>,
pub worker_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct Balance {
pub balance_sol: f64,
pub balance_tokens: i64,
pub balance_lamports: i64,
pub grace_remaining_tokens: i64,
#[serde(default)]
pub tier: Option<String>,
#[serde(default)]
pub free_tier_usage: Option<FreeTierUsage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct TopUpInfo {
pub deposit_wallet: String,
pub min_amount_sol: f64,
pub min_amount_lamports: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct UsageEntry {
pub timestamp: u64,
pub tx_type: String,
pub amount_lamports: i64,
pub balance_after_lamports: i64,
pub description: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct UsageHistoryOptions {
pub limit: Option<u32>,
pub offset: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DepositEntry {
pub signature: String,
pub amount_lamports: i64,
pub amount_sol: f64,
pub usd_value: Option<f64>,
pub sol_usd_price: Option<f64>,
pub credited: bool,
pub credited_at: Option<String>,
pub slot: i64,
pub detected_at: String,
pub block_time: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct DepositHistoryOptions {
pub limit: Option<u32>,
pub offset: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FreeTierUsage {
pub used: u32,
pub remaining: u32,
pub limit: u32,
pub resets_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingDeposit {
pub pending_lamports: i64,
pub pending_sol: f64,
pub pending_count: i64,
pub minimum_deposit_usd: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriorityFeeConfig {
pub enabled: bool,
pub speed: PriorityFeeSpeed,
pub max_tip: Option<f64>,
}
impl Default for PriorityFeeConfig {
fn default() -> Self {
Self {
enabled: false,
speed: PriorityFeeSpeed::Fast,
max_tip: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PriorityFeeSpeed {
Slow,
Fast,
UltraFast,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConnectionStatus {
pub state: ConnectionState,
pub protocol: Protocol,
pub latency_ms: u64,
pub region: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConnectionState {
Disconnected,
Connecting,
Connected,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BackoffStrategy {
Linear,
Exponential,
}
impl Default for BackoffStrategy {
fn default() -> Self {
BackoffStrategy::Exponential
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PerformanceMetrics {
pub transactions_submitted: u64,
pub transactions_confirmed: u64,
pub average_latency_ms: f64,
pub success_rate: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum WebhookEvent {
#[serde(rename = "transaction.sent")]
TransactionSent,
#[serde(rename = "transaction.confirmed")]
TransactionConfirmed,
#[serde(rename = "transaction.failed")]
TransactionFailed,
#[serde(rename = "bundle.sent")]
BundleSent,
#[serde(rename = "bundle.confirmed")]
BundleConfirmed,
#[serde(rename = "bundle.failed")]
BundleFailed,
#[serde(rename = "billing.low_balance")]
BillingLowBalance,
#[serde(rename = "billing.depleted")]
BillingDepleted,
#[serde(rename = "billing.deposit_received")]
BillingDepositReceived,
}
impl WebhookEvent {
pub fn as_str(&self) -> &'static str {
match self {
Self::TransactionSent => "transaction.sent",
Self::TransactionConfirmed => "transaction.confirmed",
Self::TransactionFailed => "transaction.failed",
Self::BundleSent => "bundle.sent",
Self::BundleConfirmed => "bundle.confirmed",
Self::BundleFailed => "bundle.failed",
Self::BillingLowBalance => "billing.low_balance",
Self::BillingDepleted => "billing.depleted",
Self::BillingDepositReceived => "billing.deposit_received",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WebhookNotificationLevel {
All,
Final,
Confirmed,
}
impl Default for WebhookNotificationLevel {
fn default() -> Self {
Self::Final
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebhookConfig {
pub id: String,
pub url: String,
pub secret: Option<String>,
pub events: Vec<String>,
pub notification_level: String,
pub is_active: bool,
pub created_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LandingRateStats {
pub period: LandingRatePeriod,
pub total_sent: i64,
pub total_landed: i64,
pub landing_rate: f64,
pub by_sender: Vec<SenderLandingRate>,
pub by_region: Vec<RegionLandingRate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LandingRatePeriod {
pub start: String,
pub end: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SenderLandingRate {
pub sender: String,
pub total_sent: i64,
pub total_landed: i64,
pub landing_rate: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegionLandingRate {
pub region: String,
pub total_sent: i64,
pub total_landed: i64,
pub landing_rate: f64,
}
#[derive(Debug, Clone, Default)]
pub struct LandingRateOptions {
pub start: Option<String>,
pub end: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BundleResult {
pub bundle_id: String,
pub accepted: bool,
pub signatures: Vec<String>,
pub sender_id: Option<String>,
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcResponse {
pub jsonrpc: String,
pub id: serde_json::Value,
#[serde(default)]
pub result: Option<serde_json::Value>,
#[serde(default)]
pub error: Option<RpcError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcError {
pub code: i64,
pub message: String,
#[serde(default)]
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimulationResult {
#[serde(default)]
pub err: Option<serde_json::Value>,
#[serde(default)]
pub logs: Vec<String>,
#[serde(default, rename = "unitsConsumed")]
pub units_consumed: u64,
#[serde(default, rename = "returnData")]
pub return_data: Option<serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct TransactionBuilder {
tip_wallet: Option<String>,
tip_lamports: u64,
compute_unit_price: Option<u64>,
compute_unit_limit: Option<u32>,
preferred_sender: Option<String>,
broadcast_mode: bool,
dedup_id: Option<String>,
max_retries: u32,
timeout_ms: u64,
}
impl Default for TransactionBuilder {
fn default() -> Self {
Self::new()
}
}
impl TransactionBuilder {
pub fn new() -> Self {
Self {
tip_wallet: None,
tip_lamports: 0,
compute_unit_price: None,
compute_unit_limit: None,
preferred_sender: None,
broadcast_mode: false,
dedup_id: None,
max_retries: 2,
timeout_ms: 30_000,
}
}
pub fn tip_wallet(mut self, wallet: &str) -> Self {
self.tip_wallet = Some(wallet.to_string());
self
}
pub fn tip_lamports(mut self, lamports: u64) -> Self {
self.tip_lamports = lamports;
self
}
pub fn tip_sol(mut self, sol: f64) -> Self {
self.tip_lamports = (sol * 1_000_000_000.0) as u64;
self
}
pub fn from_tip_instruction(mut self, tip: &TipInstruction) -> Self {
self.tip_wallet = Some(tip.tip_wallet_address.clone());
self.tip_lamports = (tip.tip_amount_sol * 1_000_000_000.0) as u64;
self.preferred_sender = Some(tip.sender.clone());
self
}
pub fn compute_unit_price(mut self, price: u64) -> Self {
self.compute_unit_price = Some(price);
self
}
pub fn compute_unit_limit(mut self, limit: u32) -> Self {
self.compute_unit_limit = Some(limit);
self
}
pub fn from_priority_fee(mut self, fee: &PriorityFee) -> Self {
self.compute_unit_price = Some(fee.compute_unit_price);
self.compute_unit_limit = Some(fee.compute_unit_limit);
self
}
pub fn preferred_sender(mut self, sender: &str) -> Self {
self.preferred_sender = Some(sender.to_string());
self
}
pub fn broadcast(mut self, enabled: bool) -> Self {
self.broadcast_mode = enabled;
self
}
pub fn dedup_id(mut self, id: &str) -> Self {
self.dedup_id = Some(id.to_string());
self
}
pub fn max_retries(mut self, retries: u32) -> Self {
self.max_retries = retries;
self
}
pub fn timeout_ms(mut self, ms: u64) -> Self {
self.timeout_ms = ms;
self
}
pub fn tip_info(&self) -> Option<(&str, u64)> {
self.tip_wallet.as_deref().map(|w| (w, self.tip_lamports))
}
pub fn fee_info(&self) -> Option<(u64, u32)> {
match (self.compute_unit_price, self.compute_unit_limit) {
(Some(price), Some(limit)) => Some((price, limit)),
(Some(price), None) => Some((price, 200_000)),
_ => None,
}
}
pub fn build_options(&self) -> SubmitOptions {
SubmitOptions {
broadcast_mode: self.broadcast_mode,
preferred_sender: self.preferred_sender.clone(),
max_retries: self.max_retries,
timeout_ms: self.timeout_ms,
dedup_id: self.dedup_id.clone(),
retry: None,
tpu_submission: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegisterWebhookRequest {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub events: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notification_level: Option<String>,
}