fuel_core/schema/
coins.rsuse crate::{
coins_query::{
random_improve,
SpendQuery,
},
fuel_core_graphql_api::{
query_costs,
IntoApiResult,
},
graphql_api::api_service::ConsensusProvider,
query::asset_query::AssetSpendTarget,
schema::{
scalars::{
Address,
AssetId,
Nonce,
UtxoId,
U16,
U32,
U64,
},
ReadViewProvider,
},
};
use async_graphql::{
connection::{
Connection,
EmptyFields,
},
Context,
};
use fuel_core_types::{
entities::{
coins,
coins::{
coin::Coin as CoinModel,
message_coin::MessageCoin as MessageCoinModel,
},
},
fuel_tx,
};
use itertools::Itertools;
use tokio_stream::StreamExt;
pub struct Coin(pub(crate) CoinModel);
#[async_graphql::Object]
impl Coin {
async fn utxo_id(&self) -> UtxoId {
self.0.utxo_id.into()
}
async fn owner(&self) -> Address {
self.0.owner.into()
}
async fn amount(&self) -> U64 {
self.0.amount.into()
}
async fn asset_id(&self) -> AssetId {
self.0.asset_id.into()
}
async fn block_created(&self) -> U32 {
u32::from(self.0.tx_pointer.block_height()).into()
}
async fn tx_created_idx(&self) -> U16 {
self.0.tx_pointer.tx_index().into()
}
}
pub struct MessageCoin(pub(crate) MessageCoinModel);
#[async_graphql::Object]
impl MessageCoin {
async fn sender(&self) -> Address {
self.0.sender.into()
}
async fn recipient(&self) -> Address {
self.0.recipient.into()
}
async fn nonce(&self) -> Nonce {
self.0.nonce.into()
}
async fn amount(&self) -> U64 {
self.0.amount.into()
}
#[graphql(complexity = "query_costs().storage_read")]
async fn asset_id(&self, ctx: &Context<'_>) -> AssetId {
let params = ctx
.data_unchecked::<ConsensusProvider>()
.latest_consensus_params();
let base_asset_id = *params.base_asset_id();
base_asset_id.into()
}
async fn da_height(&self) -> U64 {
self.0.da_height.0.into()
}
}
#[derive(async_graphql::Union)]
pub enum CoinType {
Coin(Coin),
MessageCoin(MessageCoin),
}
#[derive(async_graphql::InputObject)]
struct CoinFilterInput {
owner: Address,
asset_id: Option<AssetId>,
}
#[derive(async_graphql::InputObject)]
pub struct SpendQueryElementInput {
asset_id: AssetId,
amount: U64,
max: Option<U32>,
}
#[derive(async_graphql::InputObject)]
pub struct ExcludeInput {
utxos: Vec<UtxoId>,
messages: Vec<Nonce>,
}
#[derive(Default)]
pub struct CoinQuery;
#[async_graphql::Object]
impl CoinQuery {
#[graphql(complexity = "query_costs().storage_read + child_complexity")]
async fn coin(
&self,
ctx: &Context<'_>,
#[graphql(desc = "The ID of the coin")] utxo_id: UtxoId,
) -> async_graphql::Result<Option<Coin>> {
let query = ctx.read_view()?;
query.coin(utxo_id.0).into_api_result()
}
#[graphql(complexity = "{\
query_costs().storage_iterator\
+ (query_costs().storage_read + first.unwrap_or_default() as usize) * child_complexity \
+ (query_costs().storage_read + last.unwrap_or_default() as usize) * child_complexity\
}")]
async fn coins(
&self,
ctx: &Context<'_>,
filter: CoinFilterInput,
first: Option<i32>,
after: Option<String>,
last: Option<i32>,
before: Option<String>,
) -> async_graphql::Result<Connection<UtxoId, Coin, EmptyFields, EmptyFields>> {
let query = ctx.read_view()?;
let owner: fuel_tx::Address = filter.owner.into();
crate::schema::query_pagination(after, before, first, last, |start, direction| {
let coins = query
.owned_coins(&owner, (*start).map(Into::into), direction)
.filter_map(|result| {
if let (Ok(coin), Some(filter_asset_id)) = (&result, &filter.asset_id)
{
if coin.asset_id != filter_asset_id.0 {
return None
}
}
Some(result)
})
.map(|res| res.map(|coin| (coin.utxo_id.into(), coin.into())));
Ok(coins)
})
.await
}
#[graphql(complexity = "query_costs().coins_to_spend")]
async fn coins_to_spend(
&self,
ctx: &Context<'_>,
#[graphql(desc = "The `Address` of the coins owner.")] owner: Address,
#[graphql(desc = "\
The list of requested assets` coins with asset ids, `target` amount the user wants \
to reach, and the `max` number of coins in the selection. Several entries with the \
same asset id are not allowed. The result can't contain more coins than `max_inputs`.")]
mut query_per_asset: Vec<SpendQueryElementInput>,
#[graphql(desc = "The excluded coins from the selection.")] excluded_ids: Option<
ExcludeInput,
>,
) -> async_graphql::Result<Vec<Vec<CoinType>>> {
let params = ctx
.data_unchecked::<ConsensusProvider>()
.latest_consensus_params();
let max_input = params.tx_params().max_inputs();
query_per_asset.truncate(max_input as usize);
let owner: fuel_tx::Address = owner.0;
let query_per_asset = query_per_asset
.into_iter()
.map(|e| {
AssetSpendTarget::new(
e.asset_id.0,
e.amount.0,
e.max
.and_then(|max| u16::try_from(max.0).ok())
.unwrap_or(max_input)
.min(max_input),
)
})
.collect_vec();
let excluded_ids: Option<Vec<_>> = excluded_ids.map(|exclude| {
let utxos = exclude
.utxos
.into_iter()
.map(|utxo| coins::CoinId::Utxo(utxo.into()));
let messages = exclude
.messages
.into_iter()
.map(|message| coins::CoinId::Message(message.into()));
utxos.chain(messages).collect()
});
let base_asset_id = params.base_asset_id();
let spend_query =
SpendQuery::new(owner, &query_per_asset, excluded_ids, *base_asset_id)?;
let query = ctx.read_view()?;
let coins = random_improve(query.as_ref(), &spend_query)
.await?
.into_iter()
.map(|coins| {
coins
.into_iter()
.map(|coin| match coin {
coins::CoinType::Coin(coin) => CoinType::Coin(coin.into()),
coins::CoinType::MessageCoin(coin) => {
CoinType::MessageCoin(coin.into())
}
})
.collect_vec()
})
.collect();
Ok(coins)
}
}
impl From<CoinModel> for Coin {
fn from(value: CoinModel) -> Self {
Coin(value)
}
}
impl From<MessageCoinModel> for MessageCoin {
fn from(value: MessageCoinModel) -> Self {
MessageCoin(value)
}
}
impl From<coins::CoinType> for CoinType {
fn from(value: coins::CoinType) -> Self {
match value {
coins::CoinType::Coin(coin) => CoinType::Coin(coin.into()),
coins::CoinType::MessageCoin(coin) => CoinType::MessageCoin(coin.into()),
}
}
}