diff --git a/bindings/go/examples/custom_query/main.go b/bindings/go/examples/custom_query/main.go new file mode 100644 index 000000000..2a883a8ea --- /dev/null +++ b/bindings/go/examples/custom_query/main.go @@ -0,0 +1,62 @@ +// Copyright (c) 2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "log" + + sdk "bindings/iota_sdk_ffi" +) + +func main() { + client := sdk.GraphQlClientNewDevnet() + + queryEpochDataStr := ` + query MyQuery($id: UInt53) { + epoch(id: $id) { + epochId + referenceGasPrice + totalGasFees + totalCheckpoints + totalTransactions + } + }` + + queryEpochData := sdk.Query{ + Query: queryEpochDataStr, + } + res1, err := client.RunQuery(queryEpochData) + if err.(*sdk.SdkFfiError) != nil { + log.Fatalf("Failed to run a query: %v", err) + } + fmt.Println(res1) + + variablesJson := `{"id": 1}` + variables := string(variablesJson) + + queryEpochDataWithVariables := sdk.Query{ + Query: queryEpochDataStr, + Variables: &variables, + } + res2, err := client.RunQuery(queryEpochDataWithVariables) + if err.(*sdk.SdkFfiError) != nil { + log.Fatalf("Failed to run a query with variables: %v", err) + } + fmt.Println(res2) + + queryChainIdStr := ` + query MyQuery { + chainIdentifier + }` + queryChainId := sdk.Query{ + Query: queryChainIdStr, + } + res3, err := client.RunQuery(queryChainId) + if err.(*sdk.SdkFfiError) != nil { + log.Fatalf("Failed to run a query: %v", err) + } + fmt.Println(res3) + +} diff --git a/bindings/go/iota_sdk_ffi/iota_sdk_ffi.go b/bindings/go/iota_sdk_ffi/iota_sdk_ffi.go index 0f8f97833..af30504a2 100644 --- a/bindings/go/iota_sdk_ffi/iota_sdk_ffi.go +++ b/bindings/go/iota_sdk_ffi/iota_sdk_ffi.go @@ -1538,6 +1538,15 @@ func uniffiCheckChecksums() { } } { + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_run_query() + }) + if checksum != 54586 { + // If this happens try cleaning and rebuilding your project + panic("iota_sdk_ffi: uniffi_iota_sdk_ffi_checksum_method_graphqlclient_run_query: UniFFI API checksum mismatch") + } + } + { checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { return C.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_service_config() }) @@ -11367,6 +11376,8 @@ type GraphQlClientInterface interface { // This will return `Ok(None)` if the epoch requested is not available in // the GraphQL service (e.g., due to pruning). ReferenceGasPrice(epoch *uint64) (*uint64, error) + // Run a query. + RunQuery(query Query) (Value, error) // Get the GraphQL service configuration, including complexity limits, read // and mutation limits, supported versions, and others. ServiceConfig() (ServiceConfig, error) @@ -12585,6 +12596,38 @@ func (_self *GraphQlClient) ReferenceGasPrice(epoch *uint64) (*uint64, error) { return res, err } +// Run a query. +func (_self *GraphQlClient) RunQuery(query Query) (Value, error) { + _pointer := _self.ffiObject.incrementPointer("*GraphQlClient") + defer _self.ffiObject.decrementPointer() + res, err :=uniffiRustCallAsync[SdkFfiError]( + FfiConverterSdkFfiErrorINSTANCE, + // completeFn + func(handle C.uint64_t, status *C.RustCallStatus) RustBufferI { + res := C.ffi_iota_sdk_ffi_rust_future_complete_rust_buffer(handle, status) + return GoRustBuffer { + inner: res, + } + }, + // liftFn + func(ffi RustBufferI) Value { + return FfiConverterTypeValueINSTANCE.Lift(ffi) + }, + C.uniffi_iota_sdk_ffi_fn_method_graphqlclient_run_query( + _pointer,FfiConverterQueryINSTANCE.Lower(query)), + // pollFn + func (handle C.uint64_t, continuation C.UniffiRustFutureContinuationCallback, data C.uint64_t) { + C.ffi_iota_sdk_ffi_rust_future_poll_rust_buffer(handle, continuation, data) + }, + // freeFn + func (handle C.uint64_t) { + C.ffi_iota_sdk_ffi_rust_future_free_rust_buffer(handle) + }, + ) + + return res, err +} + // Get the GraphQL service configuration, including complexity limits, read // and mutation limits, supported versions, and others. func (_self *GraphQlClient) ServiceConfig() (ServiceConfig, error) { @@ -25914,6 +25957,45 @@ type FfiDestroyerProtocolConfigs struct {} func (_ FfiDestroyerProtocolConfigs) Destroy(value ProtocolConfigs) { value.Destroy() } +type Query struct { + Query string + Variables *Value +} + +func (r *Query) Destroy() { + FfiDestroyerString{}.Destroy(r.Query); + FfiDestroyerOptionalTypeValue{}.Destroy(r.Variables); +} + +type FfiConverterQuery struct {} + +var FfiConverterQueryINSTANCE = FfiConverterQuery{} + +func (c FfiConverterQuery) Lift(rb RustBufferI) Query { + return LiftFromRustBuffer[Query](c, rb) +} + +func (c FfiConverterQuery) Read(reader io.Reader) Query { + return Query { + FfiConverterStringINSTANCE.Read(reader), + FfiConverterOptionalTypeValueINSTANCE.Read(reader), + } +} + +func (c FfiConverterQuery) Lower(value Query) C.RustBuffer { + return LowerIntoRustBuffer[Query](c, value) +} + +func (c FfiConverterQuery) Write(writer io.Writer, value Query) { + FfiConverterStringINSTANCE.Write(writer, value.Query); + FfiConverterOptionalTypeValueINSTANCE.Write(writer, value.Variables); +} + +type FfiDestroyerQuery struct {} + +func (_ FfiDestroyerQuery) Destroy(value Query) { + value.Destroy() +} // Randomness update // // # BCS diff --git a/bindings/go/iota_sdk_ffi/iota_sdk_ffi.h b/bindings/go/iota_sdk_ffi/iota_sdk_ffi.h index b52ea62db..c2fffbd5e 100644 --- a/bindings/go/iota_sdk_ffi/iota_sdk_ffi.h +++ b/bindings/go/iota_sdk_ffi/iota_sdk_ffi.h @@ -1761,6 +1761,11 @@ uint64_t uniffi_iota_sdk_ffi_fn_method_graphqlclient_protocol_config(void* ptr, uint64_t uniffi_iota_sdk_ffi_fn_method_graphqlclient_reference_gas_price(void* ptr, RustBuffer epoch ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IOTA_SDK_FFI_FN_METHOD_GRAPHQLCLIENT_RUN_QUERY +#define UNIFFI_FFIDEF_UNIFFI_IOTA_SDK_FFI_FN_METHOD_GRAPHQLCLIENT_RUN_QUERY +uint64_t uniffi_iota_sdk_ffi_fn_method_graphqlclient_run_query(void* ptr, RustBuffer query +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IOTA_SDK_FFI_FN_METHOD_GRAPHQLCLIENT_SERVICE_CONFIG #define UNIFFI_FFIDEF_UNIFFI_IOTA_SDK_FFI_FN_METHOD_GRAPHQLCLIENT_SERVICE_CONFIG uint64_t uniffi_iota_sdk_ffi_fn_method_graphqlclient_service_config(void* ptr @@ -5738,6 +5743,12 @@ uint16_t uniffi_iota_sdk_ffi_checksum_method_graphqlclient_protocol_config(void #define UNIFFI_FFIDEF_UNIFFI_IOTA_SDK_FFI_CHECKSUM_METHOD_GRAPHQLCLIENT_REFERENCE_GAS_PRICE uint16_t uniffi_iota_sdk_ffi_checksum_method_graphqlclient_reference_gas_price(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IOTA_SDK_FFI_CHECKSUM_METHOD_GRAPHQLCLIENT_RUN_QUERY +#define UNIFFI_FFIDEF_UNIFFI_IOTA_SDK_FFI_CHECKSUM_METHOD_GRAPHQLCLIENT_RUN_QUERY +uint16_t uniffi_iota_sdk_ffi_checksum_method_graphqlclient_run_query(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_IOTA_SDK_FFI_CHECKSUM_METHOD_GRAPHQLCLIENT_SERVICE_CONFIG diff --git a/bindings/kotlin/build.gradle.kts b/bindings/kotlin/build.gradle.kts index b22da4bbd..d339111bf 100644 --- a/bindings/kotlin/build.gradle.kts +++ b/bindings/kotlin/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("jvm") version "1.9.24" + kotlin("plugin.serialization") version "1.9.24" application } @@ -11,6 +12,7 @@ repositories { mavenCentral() } dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") implementation("net.java.dev.jna:jna:5.13.0") } diff --git a/bindings/kotlin/examples/CustomQuery.kt b/bindings/kotlin/examples/CustomQuery.kt new file mode 100644 index 000000000..0fa08e9e4 --- /dev/null +++ b/bindings/kotlin/examples/CustomQuery.kt @@ -0,0 +1,45 @@ +// Copyright (c) 2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import iota_sdk.GraphQlClient +import iota_sdk.Query +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +fun main() = runBlocking { + val client = GraphQlClient.newDevnet() + + val queryEpochDataStr = + """ + query MyQuery(${'$'}id: UInt53) { + epoch(id: ${'$'}id) { + epochId + referenceGasPrice + totalGasFees + totalCheckpoints + totalTransactions + } + } + """.trimIndent() + + val queryEpochData = Query(queryEpochDataStr) + val res1 = client.runQuery(queryEpochData) + println(res1) + + val variables = mapOf("id" to 1) + val queryEpochDataWithVariables = Query(queryEpochDataStr, Json.encodeToString(variables)) + + val res2 = client.runQuery(queryEpochDataWithVariables) + println(res2) + + val queryChainIdStr = + """ + query MyQuery { + chainIdentifier + } + """.trimIndent() + val queryChainId = Query(queryChainIdStr) + val res3 = client.runQuery(queryChainId) + println(res3) +} diff --git a/bindings/kotlin/lib/iota_sdk/iota_sdk_ffi.kt b/bindings/kotlin/lib/iota_sdk/iota_sdk_ffi.kt index a9aa03eec..04949d9e0 100644 --- a/bindings/kotlin/lib/iota_sdk/iota_sdk_ffi.kt +++ b/bindings/kotlin/lib/iota_sdk/iota_sdk_ffi.kt @@ -2211,6 +2211,8 @@ internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { + + @@ -2491,6 +2493,8 @@ fun uniffi_iota_sdk_ffi_checksum_method_graphqlclient_protocol_config( ): Short fun uniffi_iota_sdk_ffi_checksum_method_graphqlclient_reference_gas_price( ): Short +fun uniffi_iota_sdk_ffi_checksum_method_graphqlclient_run_query( +): Short fun uniffi_iota_sdk_ffi_checksum_method_graphqlclient_service_config( ): Short fun uniffi_iota_sdk_ffi_checksum_method_graphqlclient_set_rpc_server( @@ -4114,6 +4118,8 @@ fun uniffi_iota_sdk_ffi_fn_method_graphqlclient_protocol_config(`ptr`: Pointer,` ): Long fun uniffi_iota_sdk_ffi_fn_method_graphqlclient_reference_gas_price(`ptr`: Pointer,`epoch`: RustBuffer.ByValue, ): Long +fun uniffi_iota_sdk_ffi_fn_method_graphqlclient_run_query(`ptr`: Pointer,`query`: RustBuffer.ByValue, +): Long fun uniffi_iota_sdk_ffi_fn_method_graphqlclient_service_config(`ptr`: Pointer, ): Long fun uniffi_iota_sdk_ffi_fn_method_graphqlclient_set_rpc_server(`ptr`: Pointer,`server`: RustBuffer.ByValue, @@ -5787,6 +5793,9 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { if (lib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_reference_gas_price() != 39065.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_run_query() != 54586.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_service_config() != 11931.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -18837,6 +18846,11 @@ public interface GraphQlClientInterface { */ suspend fun `referenceGasPrice`(`epoch`: kotlin.ULong? = null): kotlin.ULong? + /** + * Run a query. + */ + suspend fun `runQuery`(`query`: Query): Value + /** * Get the GraphQL service configuration, including complexity limits, read * and mutation limits, supported versions, and others. @@ -19883,6 +19897,30 @@ open class GraphQlClient: Disposable, AutoCloseable, GraphQlClientInterface } + /** + * Run a query. + */ + @Throws(SdkFfiException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `runQuery`(`query`: Query) : Value { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_iota_sdk_ffi_fn_method_graphqlclient_run_query( + thisPtr, + FfiConverterTypeQuery.lower(`query`), + ) + }, + { future, callback, continuation -> UniffiLib.INSTANCE.ffi_iota_sdk_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.INSTANCE.ffi_iota_sdk_ffi_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.INSTANCE.ffi_iota_sdk_ffi_rust_future_free_rust_buffer(future) }, + // lift function + { FfiConverterTypeValue.lift(it) }, + // Error FFI converter + SdkFfiException.ErrorHandler, + ) + } + + /** * Get the GraphQL service configuration, including complexity limits, read * and mutation limits, supported versions, and others. @@ -45475,6 +45513,38 @@ public object FfiConverterTypeProtocolConfigs: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Query { + return Query( + FfiConverterString.read(buf), + FfiConverterOptionalTypeValue.read(buf), + ) + } + + override fun allocationSize(value: Query) = ( + FfiConverterString.allocationSize(value.`query`) + + FfiConverterOptionalTypeValue.allocationSize(value.`variables`) + ) + + override fun write(value: Query, buf: ByteBuffer) { + FfiConverterString.write(value.`query`, buf) + FfiConverterOptionalTypeValue.write(value.`variables`, buf) + } +} + + + /** * Randomness update * diff --git a/bindings/python/examples/custom_query.py b/bindings/python/examples/custom_query.py new file mode 100644 index 000000000..5e48e6e87 --- /dev/null +++ b/bindings/python/examples/custom_query.py @@ -0,0 +1,50 @@ +# Copyright (c) 2025 IOTA Stiftung +# SPDX-License-Identifier: Apache-2.0 + +from lib.iota_sdk_ffi import * + +import asyncio +import json + + +async def main(): + client = GraphQlClient.new_devnet() + + query_epoch_data_str = """ + query MyQuery($id: UInt53) { + epoch(id: $id) { + epochId + referenceGasPrice + totalGasFees + totalCheckpoints + totalTransactions + } + } + """ + query_epoch_data = Query( + query=query_epoch_data_str, + ) + res = await client.run_query(query_epoch_data) + print(res) + + variables = {"id": 1} + query_epoch_data_with_variables = Query( + query=query_epoch_data_str, variables=json.dumps(variables) + ) + res = await client.run_query(query_epoch_data_with_variables) + print(res) + + query_chain_id_str = """ + query MyQuery { + chainIdentifier + } + """ + query_chain_id = Query( + query=query_chain_id_str, + ) + res = await client.run_query(query_chain_id) + print(res) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/bindings/python/lib/iota_sdk_ffi.py b/bindings/python/lib/iota_sdk_ffi.py index 6dac791af..5c6c9b2db 100644 --- a/bindings/python/lib/iota_sdk_ffi.py +++ b/bindings/python/lib/iota_sdk_ffi.py @@ -723,6 +723,8 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_reference_gas_price() != 39065: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_run_query() != 54586: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_service_config() != 11931: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_set_rpc_server() != 31958: @@ -3287,6 +3289,11 @@ class _UniffiForeignFutureStructVoid(ctypes.Structure): _UniffiRustBuffer, ) _UniffiLib.uniffi_iota_sdk_ffi_fn_method_graphqlclient_reference_gas_price.restype = ctypes.c_uint64 +_UniffiLib.uniffi_iota_sdk_ffi_fn_method_graphqlclient_run_query.argtypes = ( + ctypes.c_void_p, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_iota_sdk_ffi_fn_method_graphqlclient_run_query.restype = ctypes.c_uint64 _UniffiLib.uniffi_iota_sdk_ffi_fn_method_graphqlclient_service_config.argtypes = ( ctypes.c_void_p, ) @@ -6948,6 +6955,9 @@ class _UniffiForeignFutureStructVoid(ctypes.Structure): _UniffiLib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_reference_gas_price.argtypes = ( ) _UniffiLib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_reference_gas_price.restype = ctypes.c_uint16 +_UniffiLib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_run_query.argtypes = ( +) +_UniffiLib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_run_query.restype = ctypes.c_uint16 _UniffiLib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_service_config.argtypes = ( ) _UniffiLib.uniffi_iota_sdk_ffi_checksum_method_graphqlclient_service_config.restype = ctypes.c_uint16 @@ -12105,6 +12115,45 @@ def write(value, buf): _UniffiConverterSequenceTypeProtocolConfigAttr.write(value.configs, buf) +class Query: + query: "str" + variables: "typing.Optional[Value]" + def __init__(self, *, query: "str", variables: "typing.Optional[Value]" = _DEFAULT): + self.query = query + if variables is _DEFAULT: + self.variables = None + else: + self.variables = variables + + def __str__(self): + return "Query(query={}, variables={})".format(self.query, self.variables) + + def __eq__(self, other): + if self.query != other.query: + return False + if self.variables != other.variables: + return False + return True + +class _UniffiConverterTypeQuery(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return Query( + query=_UniffiConverterString.read(buf), + variables=_UniffiConverterOptionalTypeValue.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.query) + _UniffiConverterOptionalTypeValue.check_lower(value.variables) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.query, buf) + _UniffiConverterOptionalTypeValue.write(value.variables, buf) + + class RandomnessStateUpdate: """ Randomness update @@ -26168,6 +26217,12 @@ def reference_gas_price(self, epoch: "typing.Union[object, typing.Optional[int]] the GraphQL service (e.g., due to pruning). """ + raise NotImplementedError + def run_query(self, query: "Query"): + """ + Run a query. + """ + raise NotImplementedError def service_config(self, ): """ @@ -27404,6 +27459,31 @@ async def reference_gas_price(self, epoch: "typing.Union[object, typing.Optional + async def run_query(self, query: "Query") -> "Value": + """ + Run a query. + """ + + _UniffiConverterTypeQuery.check_lower(query) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_iota_sdk_ffi_fn_method_graphqlclient_run_query( + self._uniffi_clone_pointer(), + _UniffiConverterTypeQuery.lower(query) + ), + _UniffiLib.ffi_iota_sdk_ffi_rust_future_poll_rust_buffer, + _UniffiLib.ffi_iota_sdk_ffi_rust_future_complete_rust_buffer, + _UniffiLib.ffi_iota_sdk_ffi_rust_future_free_rust_buffer, + # lift function + _UniffiConverterTypeValue.lift, + + # Error FFI converter +_UniffiConverterTypeSdkFfiError, + + ) + + + async def service_config(self, ) -> "ServiceConfig": """ Get the GraphQL service configuration, including complexity limits, read @@ -38922,6 +39002,7 @@ def hex_encode(input: "bytes") -> "str": "ProtocolConfigAttr", "ProtocolConfigFeatureFlag", "ProtocolConfigs", + "Query", "RandomnessStateUpdate", "ServiceConfig", "SignedTransaction", diff --git a/crates/iota-graphql-client/src/lib.rs b/crates/iota-graphql-client/src/lib.rs index ac9fd6c02..42dacc531 100644 --- a/crates/iota-graphql-client/src/lib.rs +++ b/crates/iota-graphql-client/src/lib.rs @@ -403,6 +403,27 @@ impl Client { Ok(res) } + /// Run a JSON query on the GraphQL server and return the response. + /// This method expects a JSON map holding the GraphQL query string and + /// matching GraphQL variables. It returns a [`cynic::GraphQlResponse`] + /// wrapping a [`serde_json::Value`]. In general, it is recommended to use + /// [`run_query`](`Self::run_query`) which guarantees valid GraphQL + /// query syntax and returns a proper response type. + pub async fn run_query_from_json( + &self, + json: serde_json::Map, + ) -> Result> { + let res = self + .inner + .post(self.rpc_server().clone()) + .json(&json) + .send() + .await? + .json::>() + .await?; + Ok(res) + } + // =========================================================================== // Balance API // =========================================================================== diff --git a/crates/iota-sdk-ffi/Cargo.toml b/crates/iota-sdk-ffi/Cargo.toml index fba1aaf6b..1d078983b 100644 --- a/crates/iota-sdk-ffi/Cargo.toml +++ b/crates/iota-sdk-ffi/Cargo.toml @@ -20,6 +20,7 @@ derive_more = { version = "2.0", features = ["from", "deref", "display"] } hex = "0.4.3" rand = "0.8" roaring = { version = "0.11.2", default-features = false } +serde = { version = "1.0.144" } serde_json = "1.0.95" tokio = { version = "1.36.0", features = ["time"] } uniffi = { version = "0.29", features = ["cli", "tokio"] } diff --git a/crates/iota-sdk-ffi/src/graphql.rs b/crates/iota-sdk-ffi/src/graphql.rs index ffe5b2026..2d7b8eb91 100644 --- a/crates/iota-sdk-ffi/src/graphql.rs +++ b/crates/iota-sdk-ffi/src/graphql.rs @@ -1,17 +1,18 @@ // Copyright (c) 2025 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use iota_graphql_client::{ pagination::PaginationFilter, - query_types::{ProtocolConfigs, ServiceConfig}, + query_types::{ObjectKey, ProtocolConfigs, ServiceConfig}, }; -use iota_types::CheckpointSequenceNumber; +use iota_types::{CheckpointSequenceNumber, def_is}; use tokio::sync::RwLock; +use uniffi::deps::anyhow::anyhow; use crate::{ - error::Result, + error::{Result, SdkFfiError}, types::{ address::Address, checkpoint::CheckpointSummary, @@ -843,6 +844,22 @@ impl GraphQLClient { .into()) } + /// Run a query. + pub async fn run_query(&self, query: Query) -> Result { + self.0 + .read() + .await + .run_query_from_json( + serde_json::to_value(query)? + .as_object() + .ok_or_else(|| SdkFfiError::custom("invalid json; must be a map"))? + .clone(), + ) + .await? + .data + .ok_or_else(|| SdkFfiError::custom("query yielded no data")) + } + // =========================================================================== // Balance API // =========================================================================== @@ -859,3 +876,11 @@ impl GraphQLClient { Ok(self.0.read().await.balance(**address, coin_type).await?) } } + +#[derive(Debug, uniffi::Record, serde::Serialize)] +pub struct Query { + pub query: String, + #[uniffi(default = None)] + #[serde(default)] + pub variables: Option, +} diff --git a/crates/iota-sdk-types/src/crypto/zklogin.rs b/crates/iota-sdk-types/src/crypto/zklogin.rs index 943129e9b..40ceaacfa 100644 --- a/crates/iota-sdk-types/src/crypto/zklogin.rs +++ b/crates/iota-sdk-types/src/crypto/zklogin.rs @@ -258,7 +258,7 @@ impl ZkLoginClaim { /// Convert a bitarray (each bit is represented by a u8) to a byte array /// by taking each 8 bits as a byte in big-endian format. fn bitarray_to_bytearray(bits: &[u8]) -> Result, InvalidZkLoginAuthenticatorError> { - if bits.len() % 8 != 0 { + if !bits.len().is_multiple_of(8) { return Err(InvalidZkLoginAuthenticatorError::new( "bitarray_to_bytearray invalid input", )); diff --git a/crates/iota-sdk-types/src/transaction/fixtures/update-transaction-fixtures/Cargo.toml b/crates/iota-sdk-types/src/transaction/fixtures/update-transaction-fixtures/Cargo.toml index 4e44d57eb..47504c255 100644 --- a/crates/iota-sdk-types/src/transaction/fixtures/update-transaction-fixtures/Cargo.toml +++ b/crates/iota-sdk-types/src/transaction/fixtures/update-transaction-fixtures/Cargo.toml @@ -11,8 +11,6 @@ bcs = "0.1.4" fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "5f2c63266a065996d53f98156f0412782b468597" } futures = "0.3.28" iota-json-rpc-types = { path = "../../../../../../../iota/crates/iota-json-rpc-types" } -iota-keys = { path = "../../../../../../../iota/crates/iota-keys" } -iota-sdk = { path = "../../../../../../../iota/crates/iota-sdk" } iota-types = { path = "../../../../../../../iota/crates/iota-types" } test-cluster = { path = "../../../../../../../iota/crates/test-cluster" } tokio = "1.39.2"