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