1use std::{collections::HashMap, future::Future, ops::Deref};
2
3use gmsol_solana_utils::{
4 bundle_builder::{BundleBuilder, BundleOptions},
5 make_bundle_builder::{MakeBundleBuilder, SetExecutionFee},
6 transaction_builder::TransactionBuilder,
7};
8
9use gmsol_utils::{oracle::PriceProviderKind, token_config::TokensWithFeed};
10use solana_sdk::{pubkey::Pubkey, signer::Signer};
11use time::OffsetDateTime;
12
13use super::{feeds_parser::FeedAddressMap, Client};
14
15pub struct WithPullOracle<O, T>
17where
18 O: PullOracle,
19{
20 builder: T,
21 pull_oracle: O,
22 price_updates: O::PriceUpdates,
23}
24
25impl<O: PullOracle, T> WithPullOracle<O, T> {
26 pub fn with_price_updates(pull_oracle: O, builder: T, price_updates: O::PriceUpdates) -> Self {
28 Self {
29 builder,
30 pull_oracle,
31 price_updates,
32 }
33 }
34
35 pub async fn from_consumer(
37 pull_oracle: O,
38 mut builder: T,
39 after: Option<OffsetDateTime>,
40 ) -> crate::Result<(Self, FeedIds)>
41 where
42 T: PullOraclePriceConsumer,
43 {
44 let feed_ids = builder.feed_ids().await?;
45 let price_updates = pull_oracle.fetch_price_updates(&feed_ids, after).await?;
46
47 Ok((
48 Self::with_price_updates(pull_oracle, builder, price_updates),
49 feed_ids,
50 ))
51 }
52
53 pub async fn new(
55 pull_oracle: O,
56 builder: T,
57 after: Option<OffsetDateTime>,
58 ) -> crate::Result<Self>
59 where
60 T: PullOraclePriceConsumer,
61 {
62 Ok(Self::from_consumer(pull_oracle, builder, after).await?.0)
63 }
64}
65
66impl<'a, C: Deref<Target = impl Signer> + Clone, O, T> MakeBundleBuilder<'a, C>
67 for WithPullOracle<O, T>
68where
69 O: PostPullOraclePrices<'a, C>,
70 T: PullOraclePriceConsumer + MakeBundleBuilder<'a, C>,
71{
72 async fn build_with_options(
73 &mut self,
74 options: BundleOptions,
75 ) -> gmsol_solana_utils::Result<BundleBuilder<'a, C>> {
76 let (instructions, map) = self
77 .pull_oracle
78 .fetch_price_update_instructions(&self.price_updates, options.clone())
79 .await
80 .map_err(gmsol_solana_utils::Error::custom)?;
81
82 for (kind, map) in map {
83 self.builder
84 .process_feeds(kind, map)
85 .map_err(gmsol_solana_utils::Error::custom)?;
86 }
87
88 let consume = self.builder.build_with_options(options).await?;
89
90 let PriceUpdateInstructions {
91 post: mut tx,
92 close,
93 } = instructions;
94
95 tx.append(consume, false)?;
96 tx.append(close, true)?;
97
98 Ok(tx)
99 }
100}
101
102impl<O: PullOracle, T: SetExecutionFee> SetExecutionFee for WithPullOracle<O, T> {
103 fn is_execution_fee_estimation_required(&self) -> bool {
104 self.builder.is_execution_fee_estimation_required()
105 }
106
107 fn set_execution_fee(&mut self, lamports: u64) -> &mut Self {
108 self.builder.set_execution_fee(lamports);
109 self
110 }
111}
112
113impl<O: PullOracle, T: PullOraclePriceConsumer> PullOraclePriceConsumer for WithPullOracle<O, T> {
114 fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>> {
115 self.builder.feed_ids()
116 }
117
118 fn process_feeds(
119 &mut self,
120 provider: PriceProviderKind,
121 map: FeedAddressMap,
122 ) -> crate::Result<()> {
123 self.builder.process_feeds(provider, map)
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct FeedIds {
130 store: Pubkey,
131 tokens_with_feed: TokensWithFeed,
132}
133
134impl FeedIds {
135 pub fn store(&self) -> &Pubkey {
137 &self.store
138 }
139
140 pub fn new(store: Pubkey, tokens_with_feed: TokensWithFeed) -> Self {
142 Self {
143 store,
144 tokens_with_feed,
145 }
146 }
147}
148
149impl Deref for FeedIds {
150 type Target = TokensWithFeed;
151
152 fn deref(&self) -> &Self::Target {
153 &self.tokens_with_feed
154 }
155}
156
157pub trait PullOracle {
159 type PriceUpdates;
161
162 fn fetch_price_updates(
164 &self,
165 feed_ids: &FeedIds,
166 after: Option<OffsetDateTime>,
167 ) -> impl Future<Output = crate::Result<Self::PriceUpdates>>;
168}
169
170pub struct PriceUpdateInstructions<'a, C> {
172 post: BundleBuilder<'a, C>,
173 close: BundleBuilder<'a, C>,
174}
175
176impl<'a, C: Deref<Target = impl Signer> + Clone> PriceUpdateInstructions<'a, C> {
177 pub fn new(client: &'a Client<C>, options: BundleOptions) -> Self {
179 Self {
180 post: client.bundle_with_options(options.clone()),
181 close: client.bundle_with_options(options),
182 }
183 }
184}
185
186impl<'a, C: Deref<Target = impl Signer> + Clone> PriceUpdateInstructions<'a, C> {
187 #[allow(clippy::result_large_err)]
189 pub fn try_push_post(
190 &mut self,
191 instruction: TransactionBuilder<'a, C>,
192 ) -> Result<(), (TransactionBuilder<'a, C>, gmsol_solana_utils::Error)> {
193 self.post.try_push(instruction)?;
194 Ok(())
195 }
196
197 pub fn split_mut(&mut self) -> (&mut BundleBuilder<'a, C>, &mut BundleBuilder<'a, C>) {
199 (&mut self.post, &mut self.close)
200 }
201
202 pub fn split(self) -> (BundleBuilder<'a, C>, BundleBuilder<'a, C>) {
204 (self.post, self.close)
205 }
206
207 #[allow(clippy::result_large_err)]
209 pub fn try_push_close(
210 &mut self,
211 instruction: TransactionBuilder<'a, C>,
212 ) -> Result<(), (TransactionBuilder<'a, C>, gmsol_solana_utils::Error)> {
213 self.close.try_push(instruction)?;
214 Ok(())
215 }
216
217 pub fn merge(&mut self, other: Self) -> gmsol_solana_utils::Result<()> {
219 self.post.append(other.post, false)?;
220 self.close.append(other.close, false)?;
221 Ok(())
222 }
223}
224
225pub trait PostPullOraclePrices<'a, C>: PullOracle {
227 fn fetch_price_update_instructions(
229 &self,
230 price_updates: &Self::PriceUpdates,
231 options: BundleOptions,
232 ) -> impl Future<
233 Output = crate::Result<(
234 PriceUpdateInstructions<'a, C>,
235 HashMap<PriceProviderKind, FeedAddressMap>,
236 )>,
237 >;
238}
239
240pub trait PullOraclePriceConsumer {
242 fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>>;
244
245 fn process_feeds(
247 &mut self,
248 provider: PriceProviderKind,
249 map: FeedAddressMap,
250 ) -> crate::Result<()>;
251}
252
253impl<T: PullOraclePriceConsumer> PullOraclePriceConsumer for &mut T {
254 fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>> {
255 (**self).feed_ids()
256 }
257
258 fn process_feeds(
259 &mut self,
260 provider: PriceProviderKind,
261 map: FeedAddressMap,
262 ) -> crate::Result<()> {
263 (**self).process_feeds(provider, map)
264 }
265}