Skip to main content

sof_solana_compat/
submit_ext.rs

1use async_trait::async_trait;
2use bincode::serialize;
3use solana_signer::signers::Signers;
4use solana_transaction::versioned::VersionedTransaction;
5use thiserror::Error;
6
7use crate::{BuilderError, TxBuilder};
8
9/// Error returned by the Solana-coupled submission helpers.
10#[derive(Debug, Error)]
11pub enum SolanaCompatSubmitError {
12    /// Transaction building or signing failed before submission.
13    #[error("failed to build/sign transaction: {source}")]
14    Build {
15        /// Underlying builder/signing error.
16        source: BuilderError,
17    },
18    /// Core byte-oriented submit path failed.
19    #[error(transparent)]
20    Submit {
21        /// Underlying core submit error.
22        source: sof_tx::SubmitError,
23    },
24    /// Could not encode the signed transaction into bytes.
25    #[error("failed to encode signed transaction bytes: {message}")]
26    Encode {
27        /// Encoder error details.
28        message: String,
29    },
30}
31
32/// Solana-coupled convenience methods layered on top of `sof-tx` core byte submission.
33#[async_trait]
34pub trait TxSubmitClientSolanaExt {
35    /// Builds, signs, and submits a transaction through one route plan.
36    async fn submit_unsigned_via<T>(
37        &mut self,
38        builder: TxBuilder,
39        signers: &T,
40        plan: sof_tx::SubmitPlan,
41    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>
42    where
43        T: Signers + Sync + ?Sized;
44
45    /// Builds, signs, and submits a transaction in one API call.
46    async fn submit_unsigned<T>(
47        &mut self,
48        builder: TxBuilder,
49        signers: &T,
50        mode: sof_tx::SubmitMode,
51    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>
52    where
53        T: Signers + Sync + ?Sized;
54
55    /// Builds, signs, and submits a transaction with explicit toxic-flow context and route plan.
56    async fn submit_unsigned_with_context_via<T>(
57        &mut self,
58        builder: TxBuilder,
59        signers: &T,
60        plan: sof_tx::SubmitPlan,
61        context: sof_tx::TxSubmitContext,
62    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>
63    where
64        T: Signers + Sync + ?Sized;
65
66    /// Builds, signs, and submits a transaction with explicit toxic-flow context.
67    async fn submit_unsigned_with_context<T>(
68        &mut self,
69        builder: TxBuilder,
70        signers: &T,
71        mode: sof_tx::SubmitMode,
72        context: sof_tx::TxSubmitContext,
73    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>
74    where
75        T: Signers + Sync + ?Sized;
76
77    /// Submits one signed `VersionedTransaction` through one route plan.
78    async fn submit_transaction_via(
79        &mut self,
80        tx: VersionedTransaction,
81        plan: sof_tx::SubmitPlan,
82    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>;
83
84    /// Submits one signed `VersionedTransaction`.
85    async fn submit_transaction(
86        &mut self,
87        tx: VersionedTransaction,
88        mode: sof_tx::SubmitMode,
89    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>;
90
91    /// Submits one signed `VersionedTransaction` with explicit toxic-flow context and route plan.
92    async fn submit_transaction_with_context_via(
93        &mut self,
94        tx: VersionedTransaction,
95        plan: sof_tx::SubmitPlan,
96        context: sof_tx::TxSubmitContext,
97    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>;
98
99    /// Submits one signed `VersionedTransaction` with explicit toxic-flow context.
100    async fn submit_transaction_with_context(
101        &mut self,
102        tx: VersionedTransaction,
103        mode: sof_tx::SubmitMode,
104        context: sof_tx::TxSubmitContext,
105    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>;
106}
107
108#[async_trait]
109impl TxSubmitClientSolanaExt for sof_tx::TxSubmitClient {
110    async fn submit_unsigned_via<T>(
111        &mut self,
112        builder: TxBuilder,
113        signers: &T,
114        plan: sof_tx::SubmitPlan,
115    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>
116    where
117        T: Signers + Sync + ?Sized,
118    {
119        self.submit_unsigned_with_context_via(
120            builder,
121            signers,
122            plan,
123            sof_tx::TxSubmitContext::default(),
124        )
125        .await
126    }
127
128    async fn submit_unsigned<T>(
129        &mut self,
130        builder: TxBuilder,
131        signers: &T,
132        mode: sof_tx::SubmitMode,
133    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>
134    where
135        T: Signers + Sync + ?Sized,
136    {
137        self.submit_unsigned_with_context_via(
138            builder,
139            signers,
140            sof_tx::SubmitPlan::from(mode),
141            sof_tx::TxSubmitContext::default(),
142        )
143        .await
144    }
145
146    async fn submit_unsigned_with_context_via<T>(
147        &mut self,
148        builder: TxBuilder,
149        signers: &T,
150        plan: sof_tx::SubmitPlan,
151        context: sof_tx::TxSubmitContext,
152    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>
153    where
154        T: Signers + Sync + ?Sized,
155    {
156        let blockhash = self
157            .refresh_latest_blockhash_bytes()
158            .await
159            .map_err(|source| SolanaCompatSubmitError::Submit {
160                source: sof_tx::SubmitError::Rpc { source },
161            })?
162            .ok_or(SolanaCompatSubmitError::Submit {
163                source: sof_tx::SubmitError::MissingRecentBlockhash,
164            })?;
165        let tx = builder
166            .build_and_sign(blockhash, signers)
167            .map_err(|source| SolanaCompatSubmitError::Build { source })?;
168        self.submit_transaction_with_context_via(tx, plan, context)
169            .await
170    }
171
172    async fn submit_unsigned_with_context<T>(
173        &mut self,
174        builder: TxBuilder,
175        signers: &T,
176        mode: sof_tx::SubmitMode,
177        context: sof_tx::TxSubmitContext,
178    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError>
179    where
180        T: Signers + Sync + ?Sized,
181    {
182        self.submit_unsigned_with_context_via(
183            builder,
184            signers,
185            sof_tx::SubmitPlan::from(mode),
186            context,
187        )
188        .await
189    }
190
191    async fn submit_transaction_via(
192        &mut self,
193        tx: VersionedTransaction,
194        plan: sof_tx::SubmitPlan,
195    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError> {
196        self.submit_transaction_with_context_via(tx, plan, sof_tx::TxSubmitContext::default())
197            .await
198    }
199
200    async fn submit_transaction(
201        &mut self,
202        tx: VersionedTransaction,
203        mode: sof_tx::SubmitMode,
204    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError> {
205        self.submit_transaction_with_context_via(
206            tx,
207            sof_tx::SubmitPlan::from(mode),
208            sof_tx::TxSubmitContext::default(),
209        )
210        .await
211    }
212
213    async fn submit_transaction_with_context_via(
214        &mut self,
215        tx: VersionedTransaction,
216        plan: sof_tx::SubmitPlan,
217        context: sof_tx::TxSubmitContext,
218    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError> {
219        let tx_bytes = serialize(&tx).map_err(|error| SolanaCompatSubmitError::Encode {
220            message: error.to_string(),
221        })?;
222        self.submit_signed_with_context_via(
223            sof_tx::SignedTx::VersionedTransactionBytes(tx_bytes),
224            plan,
225            context,
226        )
227        .await
228        .map_err(|source| SolanaCompatSubmitError::Submit { source })
229    }
230
231    async fn submit_transaction_with_context(
232        &mut self,
233        tx: VersionedTransaction,
234        mode: sof_tx::SubmitMode,
235        context: sof_tx::TxSubmitContext,
236    ) -> Result<sof_tx::SubmitResult, SolanaCompatSubmitError> {
237        self.submit_transaction_with_context_via(tx, sof_tx::SubmitPlan::from(mode), context)
238            .await
239    }
240}