Skip to main content

iota_sdk_graphql_client/
transaction_builder_client.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Implementation of the transaction builder client traits for the GraphQL
5//! [`GraphQLClient`].
6
7use iota_transaction_builder::{
8    ObjectsPage, ProtocolConfig, TransactionBuilder, TransactionBuilderClientBase,
9    TransactionBuilderExecutionClient, TransactionBuilderLedgerClient,
10    TransactionBuilderSimulationClient, WaitForTransaction,
11};
12use iota_types::{
13    Address, Object, ObjectId, StructTag, Transaction, TransactionDigest, TransactionEffects,
14    UserSignature, Version,
15};
16
17use crate::{
18    DryRunResult, GraphQLClient,
19    pagination::{Direction, PaginationFilter},
20    query_types::ObjectFilter,
21};
22
23impl GraphQLClient {
24    /// Create a new [`TransactionBuilder`] with the given sender address.
25    pub fn transaction_builder(&self, sender: Address) -> TransactionBuilder<&Self> {
26        TransactionBuilder::new(sender).with_client(self)
27    }
28}
29
30impl TransactionBuilderClientBase for GraphQLClient {
31    type Error = crate::error::GraphQLError;
32}
33
34impl TransactionBuilderLedgerClient for GraphQLClient {
35    async fn object(
36        &self,
37        object_id: ObjectId,
38        version: impl Into<Option<Version>>,
39    ) -> Result<Option<Object>, Self::Error> {
40        let mut query = self.object(object_id);
41        if let Some(version) = version.into() {
42            query = query.version(version);
43        }
44        query.await
45    }
46
47    async fn objects(
48        &self,
49        struct_tag: Option<StructTag>,
50        owner: Address,
51        cursor: Option<Vec<u8>>,
52        limit: Option<usize>,
53    ) -> Result<ObjectsPage, Self::Error> {
54        // GraphQL cursors are base64 ASCII, so round-tripping through
55        // Vec<u8> is lossless. Caller-supplied cursors must come from a
56        // prior call to this method; anything else is rejected here
57        // rather than panicked on.
58        let cursor = cursor.map(String::from_utf8).transpose()?;
59        let page = self
60            .objects()
61            .filter(ObjectFilter {
62                type_tag: struct_tag.map(|tag| tag.to_string()),
63                owner: Some(owner),
64                object_ids: None,
65            })
66            .pagination(PaginationFilter {
67                direction: Direction::Forward,
68                cursor,
69                limit: limit.map(|v| v as _),
70            })
71            .await?;
72        let (page_info, data) = page.into_parts();
73        let next_cursor = page_info
74            .has_next_page
75            .then_some(page_info.end_cursor)
76            .flatten()
77            .map(String::into_bytes);
78        Ok(ObjectsPage { data, next_cursor })
79    }
80
81    async fn protocol_config(&self) -> Result<ProtocolConfig, Self::Error> {
82        let cfg = self.protocol_config().await?;
83        let attributes = cfg
84            .configs
85            .into_iter()
86            .filter_map(|attr| attr.value.map(|v| (attr.key, v)))
87            .collect();
88        Ok(ProtocolConfig::new(attributes))
89    }
90
91    async fn reference_gas_price(
92        &self,
93        epoch: impl Into<Option<u64>>,
94    ) -> Result<Option<u64>, Self::Error> {
95        let mut query = self.reference_gas_price();
96        if let Some(epoch) = epoch.into() {
97            query = query.epoch_number(epoch);
98        }
99        query.await
100    }
101}
102
103impl TransactionBuilderSimulationClient for GraphQLClient {
104    type DryRunResult = DryRunResult;
105
106    async fn estimate_transaction_budget(
107        &self,
108        transaction: &Transaction,
109    ) -> Result<Option<u64>, Self::Error> {
110        let res = self
111            .dry_run_transaction(transaction)
112            .skip_checks(true)
113            .await?;
114        Ok(res.effects.map(|effects| match effects {
115            TransactionEffects::V1(v1) => v1.gas_cost_summary.gas_used(),
116            _ => unimplemented!(
117                "a new TransactionEffects enum variant was added and needs to be handled"
118            ),
119        }))
120    }
121
122    async fn dry_run_transaction(
123        &self,
124        transaction: &Transaction,
125        skip_checks: bool,
126    ) -> Result<Self::DryRunResult, Self::Error> {
127        self.dry_run_transaction(transaction)
128            .skip_checks(skip_checks)
129            .await
130    }
131}
132
133impl TransactionBuilderExecutionClient for GraphQLClient {
134    async fn execute_transaction(
135        &self,
136        signatures: &[UserSignature],
137        transaction: &Transaction,
138        wait_for: impl Into<Option<WaitForTransaction>>,
139    ) -> Result<TransactionEffects, Self::Error> {
140        let mut query = self.execute_transaction(signatures, transaction);
141        if let Some(wait_for) = wait_for.into() {
142            query = query.wait_for(wait_for);
143        }
144        query.await
145    }
146
147    async fn wait_for_transaction(
148        &self,
149        digest: TransactionDigest,
150        wait_for: WaitForTransaction,
151    ) -> Result<(), Self::Error> {
152        self.wait_for_transaction(digest, wait_for).await
153    }
154
155    async fn transaction_effects(
156        &self,
157        digest: TransactionDigest,
158    ) -> Result<Option<TransactionEffects>, Self::Error> {
159        self.transaction_effects(digest).await
160    }
161}