use std::{collections::HashMap, future::Future, ops::Deref};
use gmsol_solana_utils::{
bundle_builder::{BundleBuilder, BundleOptions},
make_bundle_builder::{MakeBundleBuilder, SetExecutionFee},
transaction_builder::TransactionBuilder,
};
use gmsol_utils::{oracle::PriceProviderKind, token_config::TokensWithFeed};
use solana_sdk::{pubkey::Pubkey, signer::Signer};
use time::OffsetDateTime;
use super::{feeds_parser::FeedAddressMap, Client};
pub struct WithPullOracle<O, T>
where
O: PullOracle,
{
builder: T,
pull_oracle: O,
price_updates: O::PriceUpdates,
}
impl<O: PullOracle, T> WithPullOracle<O, T> {
pub fn with_price_updates(pull_oracle: O, builder: T, price_updates: O::PriceUpdates) -> Self {
Self {
builder,
pull_oracle,
price_updates,
}
}
pub async fn from_consumer(
pull_oracle: O,
mut builder: T,
after: Option<OffsetDateTime>,
) -> crate::Result<(Self, FeedIds)>
where
T: PullOraclePriceConsumer,
{
let feed_ids = builder.feed_ids().await?;
let price_updates = pull_oracle.fetch_price_updates(&feed_ids, after).await?;
Ok((
Self::with_price_updates(pull_oracle, builder, price_updates),
feed_ids,
))
}
pub async fn new(
pull_oracle: O,
builder: T,
after: Option<OffsetDateTime>,
) -> crate::Result<Self>
where
T: PullOraclePriceConsumer,
{
Ok(Self::from_consumer(pull_oracle, builder, after).await?.0)
}
}
impl<'a, C: Deref<Target = impl Signer> + Clone, O, T> MakeBundleBuilder<'a, C>
for WithPullOracle<O, T>
where
O: PostPullOraclePrices<'a, C>,
T: PullOraclePriceConsumer + MakeBundleBuilder<'a, C>,
{
async fn build_with_options(
&mut self,
options: BundleOptions,
) -> gmsol_solana_utils::Result<BundleBuilder<'a, C>> {
let (instructions, map) = self
.pull_oracle
.fetch_price_update_instructions(&self.price_updates, options.clone())
.await
.map_err(gmsol_solana_utils::Error::custom)?;
for (kind, map) in map {
self.builder
.process_feeds(kind, map)
.map_err(gmsol_solana_utils::Error::custom)?;
}
let consume = self.builder.build_with_options(options).await?;
let PriceUpdateInstructions {
post: mut tx,
close,
} = instructions;
tx.append(consume, false)?;
tx.append(close, true)?;
Ok(tx)
}
}
impl<O: PullOracle, T: SetExecutionFee> SetExecutionFee for WithPullOracle<O, T> {
fn is_execution_fee_estimation_required(&self) -> bool {
self.builder.is_execution_fee_estimation_required()
}
fn set_execution_fee(&mut self, lamports: u64) -> &mut Self {
self.builder.set_execution_fee(lamports);
self
}
}
impl<O: PullOracle, T: PullOraclePriceConsumer> PullOraclePriceConsumer for WithPullOracle<O, T> {
fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>> {
self.builder.feed_ids()
}
fn process_feeds(
&mut self,
provider: PriceProviderKind,
map: FeedAddressMap,
) -> crate::Result<()> {
self.builder.process_feeds(provider, map)
}
}
#[derive(Debug, Clone)]
pub struct FeedIds {
store: Pubkey,
tokens_with_feed: TokensWithFeed,
}
impl FeedIds {
pub fn store(&self) -> &Pubkey {
&self.store
}
pub fn new(store: Pubkey, tokens_with_feed: TokensWithFeed) -> Self {
Self {
store,
tokens_with_feed,
}
}
}
impl Deref for FeedIds {
type Target = TokensWithFeed;
fn deref(&self) -> &Self::Target {
&self.tokens_with_feed
}
}
pub trait PullOracle {
type PriceUpdates;
fn fetch_price_updates(
&self,
feed_ids: &FeedIds,
after: Option<OffsetDateTime>,
) -> impl Future<Output = crate::Result<Self::PriceUpdates>>;
}
pub struct PriceUpdateInstructions<'a, C> {
post: BundleBuilder<'a, C>,
close: BundleBuilder<'a, C>,
}
impl<'a, C: Deref<Target = impl Signer> + Clone> PriceUpdateInstructions<'a, C> {
pub fn new(client: &'a Client<C>, options: BundleOptions) -> Self {
Self {
post: client.bundle_with_options(options.clone()),
close: client.bundle_with_options(options),
}
}
}
impl<'a, C: Deref<Target = impl Signer> + Clone> PriceUpdateInstructions<'a, C> {
#[allow(clippy::result_large_err)]
pub fn try_push_post(
&mut self,
instruction: TransactionBuilder<'a, C>,
) -> Result<(), (TransactionBuilder<'a, C>, gmsol_solana_utils::Error)> {
self.post.try_push(instruction)?;
Ok(())
}
pub fn split_mut(&mut self) -> (&mut BundleBuilder<'a, C>, &mut BundleBuilder<'a, C>) {
(&mut self.post, &mut self.close)
}
pub fn split(self) -> (BundleBuilder<'a, C>, BundleBuilder<'a, C>) {
(self.post, self.close)
}
#[allow(clippy::result_large_err)]
pub fn try_push_close(
&mut self,
instruction: TransactionBuilder<'a, C>,
) -> Result<(), (TransactionBuilder<'a, C>, gmsol_solana_utils::Error)> {
self.close.try_push(instruction)?;
Ok(())
}
pub fn merge(&mut self, other: Self) -> gmsol_solana_utils::Result<()> {
self.post.append(other.post, false)?;
self.close.append(other.close, false)?;
Ok(())
}
}
pub trait PostPullOraclePrices<'a, C>: PullOracle {
fn fetch_price_update_instructions(
&self,
price_updates: &Self::PriceUpdates,
options: BundleOptions,
) -> impl Future<
Output = crate::Result<(
PriceUpdateInstructions<'a, C>,
HashMap<PriceProviderKind, FeedAddressMap>,
)>,
>;
}
pub trait PullOraclePriceConsumer {
fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>>;
fn process_feeds(
&mut self,
provider: PriceProviderKind,
map: FeedAddressMap,
) -> crate::Result<()>;
}
impl<T: PullOraclePriceConsumer> PullOraclePriceConsumer for &mut T {
fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>> {
(**self).feed_ids()
}
fn process_feeds(
&mut self,
provider: PriceProviderKind,
map: FeedAddressMap,
) -> crate::Result<()> {
(**self).process_feeds(provider, map)
}
}