Skip to main content

gmsol_sdk/client/
pull_oracle.rs

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
15/// Build with pull oracle instructions.
16pub 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    /// Construct transactions with the given pull oracle and price updates.
27    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    /// Fetch the required price updates and use them to construct transactions.
36    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    /// Fetch the required price updates and use them to construct transactions.
54    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/// Feed IDs.
128#[derive(Debug, Clone)]
129pub struct FeedIds {
130    store: Pubkey,
131    tokens_with_feed: TokensWithFeed,
132}
133
134impl FeedIds {
135    /// Get the store address.
136    pub fn store(&self) -> &Pubkey {
137        &self.store
138    }
139
140    /// Create from `store` and `tokens_with_feed`.
141    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
157/// Pull Oracle.
158pub trait PullOracle {
159    /// Price Updates.
160    type PriceUpdates;
161
162    /// Fetch Price Update.
163    fn fetch_price_updates(
164        &self,
165        feed_ids: &FeedIds,
166        after: Option<OffsetDateTime>,
167    ) -> impl Future<Output = crate::Result<Self::PriceUpdates>>;
168}
169
170/// Price Update Instructions.
171pub 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    /// Create a new empty price update instructions.
178    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    /// Push a post price update instruction.
188    #[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    /// Get the mutable references to the [`BundleBuilder`]s.
198    pub fn split_mut(&mut self) -> (&mut BundleBuilder<'a, C>, &mut BundleBuilder<'a, C>) {
199        (&mut self.post, &mut self.close)
200    }
201
202    /// Returns the inner [`BundleBuilder`].
203    pub fn split(self) -> (BundleBuilder<'a, C>, BundleBuilder<'a, C>) {
204        (self.post, self.close)
205    }
206
207    /// Push a close instruction.
208    #[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    /// Merge.
218    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
225/// Post pull oracle price updates.
226pub trait PostPullOraclePrices<'a, C>: PullOracle {
227    /// Fetch instructions to post the price updates.
228    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
240/// Pull Oracle Price Consumer.
241pub trait PullOraclePriceConsumer {
242    /// Returns a reference to tokens and their associated feed IDs that require price updates.
243    fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>>;
244
245    /// Processes the feed address map returned from the pull oracle.
246    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}