diff --git a/.gitignore b/.gitignore index 040d4c2079e..1ad0c648f99 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,5 @@ book/book/ # gRPC coverage report grpc-coverage-report.txt + +__pycache__/ diff --git a/CLAUDE.md b/CLAUDE.md index 7c105372ee0..fbd47f0476a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -110,7 +110,7 @@ yarn configure:tests:network **DPP** (`packages/rs-dpp`, `packages/wasm-dpp`): Dash Platform Protocol implementation that defines data structures and validation rules. -**SDK** (`packages/js-dash-sdk`, `packages/rs-sdk`): Client libraries providing high-level interfaces for building applications on Dash Platform. +**SDK** (`packages/js-dash-sdk`, `packages/rs-sdk`): Client libraries providing high-level interfaces for building applications on Dash Platform. See [packages/rs-sdk/README.md](packages/rs-sdk/README.md) for the Rust SDK checklist for implementing new `Fetch`/`FetchMany` queries. **WASM SDK** (`packages/wasm-sdk`): WebAssembly bindings for browser-based applications. See [AI_REFERENCE.md](packages/wasm-sdk/AI_REFERENCE.md) for comprehensive API documentation. diff --git a/packages/dapi-grpc/build.rs b/packages/dapi-grpc/build.rs index 1b4c8cbe4d6..e0ea14fb1c2 100644 --- a/packages/dapi-grpc/build.rs +++ b/packages/dapi-grpc/build.rs @@ -84,7 +84,7 @@ fn configure_platform(mut platform: MappingConfig) -> MappingConfig { // Derive features for versioned messages // // "GetConsensusParamsRequest" is excluded as this message does not support proofs - const VERSIONED_REQUESTS: [&str; 55] = [ + const VERSIONED_REQUESTS: [&str; 56] = [ "GetDataContractHistoryRequest", "GetDataContractRequest", "GetDataContractsRequest", @@ -123,6 +123,7 @@ fn configure_platform(mut platform: MappingConfig) -> MappingConfig { "GetTokenDirectPurchasePricesRequest", "GetTokenContractInfoRequest", "GetTokenStatusesRequest", + "GetTokenPreProgrammedDistributionsRequest", "GetTokenTotalSupplyRequest", "GetGroupInfoRequest", "GetGroupInfosRequest", @@ -160,7 +161,7 @@ fn configure_platform(mut platform: MappingConfig) -> MappingConfig { // - "GetIdentityByNonUniquePublicKeyHashResponse" // // "GetEvonodesProposedEpochBlocksResponse" is used for 2 Requests - const VERSIONED_RESPONSES: [&str; 53] = [ + const VERSIONED_RESPONSES: [&str; 54] = [ "GetDataContractHistoryResponse", "GetDataContractResponse", "GetDataContractsResponse", @@ -197,6 +198,7 @@ fn configure_platform(mut platform: MappingConfig) -> MappingConfig { "GetTokenDirectPurchasePricesResponse", "GetTokenContractInfoResponse", "GetTokenStatusesResponse", + "GetTokenPreProgrammedDistributionsResponse", "GetTokenTotalSupplyResponse", "GetGroupInfoResponse", "GetGroupInfosResponse", diff --git a/packages/rs-dapi-client/src/transport/grpc.rs b/packages/rs-dapi-client/src/transport/grpc.rs index a9f85020f3c..3b9aa9eed5b 100644 --- a/packages/rs-dapi-client/src/transport/grpc.rs +++ b/packages/rs-dapi-client/src/transport/grpc.rs @@ -650,6 +650,15 @@ impl_transport_request_grpc!( get_token_statuses ); +// rpc getTokenPreProgrammedDistributions(GetTokenPreProgrammedDistributionsRequest) returns (GetTokenPreProgrammedDistributionsResponse); +impl_transport_request_grpc!( + platform_proto::GetTokenPreProgrammedDistributionsRequest, + platform_proto::GetTokenPreProgrammedDistributionsResponse, + PlatformGrpcClient, + RequestSettings::default(), + get_token_pre_programmed_distributions +); + // rpc getTokenTotalSupply(GetTokenTotalSupplyRequest) returns (GetTokenTotalSupplyResponse); impl_transport_request_grpc!( platform_proto::GetTokenTotalSupplyRequest, diff --git a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs index 8a2301b9438..87a62abc548 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs @@ -8,6 +8,7 @@ use dpp::data_contract::associated_token::token_configuration_convention::v0::To use dpp::data_contract::associated_token::token_configuration_localization::v0::TokenConfigurationLocalizationV0; use dpp::data_contract::associated_token::token_configuration_localization::TokenConfigurationLocalization; use dpp::data_contract::associated_token::token_distribution_rules::v0::TokenDistributionRulesV0; +use dpp::data_contract::associated_token::token_distribution_rules::TokenDistributionRules; use dpp::data_contract::associated_token::token_keeps_history_rules::v0::TokenKeepsHistoryRulesV0; use dpp::data_contract::associated_token::token_marketplace_rules::v0::TokenMarketplaceRulesV0; use dpp::data_contract::associated_token::token_perpetual_distribution::distribution_function::DistributionFunction; @@ -15,6 +16,8 @@ use dpp::data_contract::associated_token::token_perpetual_distribution::distribu use dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_type::RewardDistributionType; use dpp::data_contract::associated_token::token_perpetual_distribution::v0::TokenPerpetualDistributionV0; use dpp::data_contract::associated_token::token_perpetual_distribution::TokenPerpetualDistribution; +use dpp::data_contract::associated_token::token_pre_programmed_distribution::v0::TokenPreProgrammedDistributionV0; +use dpp::data_contract::associated_token::token_pre_programmed_distribution::TokenPreProgrammedDistribution; use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use dpp::data_contract::change_control_rules::v0::ChangeControlRulesV0; use dpp::data_contract::config::DataContractConfig; @@ -39,6 +42,7 @@ use dpp::version::PlatformVersion; use drive::grovedb::TransactionArg; use rand::rngs::StdRng; use rand::SeedableRng; +use std::collections::BTreeMap; use std::sync::LazyLock; const IDENTITY_ID_1: Identifier = Identifier::new([1; 32]); @@ -380,10 +384,31 @@ impl Platform { }), ); + let mut token_configuration_2 = token_configuration.clone(); + if let TokenConfiguration::V0(ref mut cfg) = token_configuration_2 { + if let TokenDistributionRules::V0(ref mut rules) = cfg.distribution_rules { + rules.pre_programmed_distribution = Some(TokenPreProgrammedDistribution::V0( + TokenPreProgrammedDistributionV0 { + distributions: BTreeMap::from([ + ( + 1000, + BTreeMap::from([(IDENTITY_ID_1, 500), (IDENTITY_ID_2, 300)]), + ), + (5000, BTreeMap::from([(IDENTITY_ID_1, 1000)])), + ( + 10000, + BTreeMap::from([(IDENTITY_ID_2, 750), (IDENTITY_ID_3, 250)]), + ), + ]), + }, + )); + } + } + let tokens = [ (0, token_configuration.clone()), - (1, token_configuration.clone()), - (2, token_configuration), + (1, token_configuration), + (2, token_configuration_2), ] .into(); diff --git a/packages/rs-drive-proof-verifier/src/proof.rs b/packages/rs-drive-proof-verifier/src/proof.rs index f7d8567f2c3..0699fe77ea3 100644 --- a/packages/rs-drive-proof-verifier/src/proof.rs +++ b/packages/rs-drive-proof-verifier/src/proof.rs @@ -4,6 +4,7 @@ pub mod token_contract_info; pub mod token_direct_purchase; pub mod token_info; pub mod token_perpetual_distribution_last_claim; +pub mod token_pre_programmed_distributions; pub mod token_status; pub mod token_total_supply; diff --git a/packages/rs-drive-proof-verifier/src/proof/token_pre_programmed_distributions.rs b/packages/rs-drive-proof-verifier/src/proof/token_pre_programmed_distributions.rs new file mode 100644 index 00000000000..041de4ca662 --- /dev/null +++ b/packages/rs-drive-proof-verifier/src/proof/token_pre_programmed_distributions.rs @@ -0,0 +1,106 @@ +use crate::error::MapGroveDbError; +use crate::verify::verify_tenderdash_proof; +use crate::{types::TokenPreProgrammedDistributions, ContextProvider, Error}; +use dapi_grpc::platform::v0::{ + get_token_pre_programmed_distributions_request, GetTokenPreProgrammedDistributionsRequest, + GetTokenPreProgrammedDistributionsResponse, Proof, ResponseMetadata, +}; +use dapi_grpc::platform::VersionedGrpcResponse; +use dpp::dashcore::Network; +use dpp::prelude::Identifier; +use dpp::version::PlatformVersion; +use drive::drive::tokens::distribution::queries::QueryPreProgrammedDistributionStartAt; +use drive::drive::Drive; + +use super::FromProof; + +impl FromProof for TokenPreProgrammedDistributions { + type Request = GetTokenPreProgrammedDistributionsRequest; + type Response = GetTokenPreProgrammedDistributionsResponse; + + fn maybe_from_proof_with_metadata<'a, I: Into, O: Into>( + request: I, + response: O, + _network: Network, + platform_version: &PlatformVersion, + provider: &'a dyn ContextProvider, + ) -> Result<(Option, ResponseMetadata, Proof), Error> + where + Self: Sized + 'a, + { + let request: Self::Request = request.into(); + let response: Self::Response = response.into(); + + let get_token_pre_programmed_distributions_request::Version::V0(req_v0) = + request.version.ok_or(Error::EmptyVersion)?; + + let token_id: [u8; 32] = + req_v0 + .token_id + .as_slice() + .try_into() + .map_err(|_| Error::RequestError { + error: "token_id must be 32 bytes".into(), + })?; + + let start_at = match req_v0.start_at_info { + Some(start_at_info) => { + let start_at_recipient = match start_at_info.start_recipient { + Some(recipient_bytes) => { + let recipient_id = + Identifier::from_bytes(&recipient_bytes).map_err(|_| { + Error::RequestError { + error: "start_recipient must be 32 bytes".into(), + } + })?; + // Default to inclusive: if omitted the start recipient is included. + let included = start_at_info.start_recipient_included.unwrap_or(true); + Some((recipient_id, included)) + } + None => None, + }; + + Some(QueryPreProgrammedDistributionStartAt { + start_at_time: start_at_info.start_time_ms, + start_at_recipient, + }) + } + None => None, + }; + + let limit = req_v0 + .limit + .map(|l| { + u16::try_from(l).map_err(|_| Error::RequestError { + error: "limit exceeds u16::MAX".into(), + }) + }) + .transpose()?; + + let metadata = response + .metadata() + .or(Err(Error::EmptyResponseMetadata))? + .clone(); + + let proof = response.proof_owned().or(Err(Error::NoProofInResult))?; + + let (root_hash, result): ([u8; 32], TokenPreProgrammedDistributions) = + Drive::verify_token_pre_programmed_distributions( + &proof.grovedb_proof, + token_id, + start_at, + limit, + false, + platform_version, + ) + .map_drive_error(&proof, &metadata)?; + + verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?; + + if result.0.is_empty() { + Ok((None, metadata, proof)) + } else { + Ok((Some(result), metadata, proof)) + } + } +} diff --git a/packages/rs-drive-proof-verifier/src/types.rs b/packages/rs-drive-proof-verifier/src/types.rs index 27f824260a7..654245d8348 100644 --- a/packages/rs-drive-proof-verifier/src/types.rs +++ b/packages/rs-drive-proof-verifier/src/types.rs @@ -663,6 +663,37 @@ pub struct ProposerBlockCountById(pub u64); /// Prices for direct purchase of tokens. Retrieved by [TokenPricingSchedule::fetch_many()]. pub type TokenDirectPurchasePrices = RetrievedObjects; +/// Pre-programmed token distributions grouped by timestamp. +/// +/// Each entry maps a timestamp (in milliseconds) to a collection of +/// `(Identifier, Credits)` pairs representing the recipients and their token amounts in credits. +#[derive(Debug, Clone, Default, derive_more::From)] +#[cfg_attr( + feature = "mocks", + derive(Encode, Decode, PlatformSerialize, PlatformDeserialize), + platform_serialize(unversioned) +)] +pub struct TokenPreProgrammedDistributions( + pub BTreeMap>, +); + +impl TokenPreProgrammedDistributions { + /// Get the inner map. + pub fn into_inner(self) -> BTreeMap> { + self.0 + } +} + +impl FromIterator<(TimestampMillis, BTreeMap)> + for TokenPreProgrammedDistributions +{ + fn from_iter)>>( + iter: T, + ) -> Self { + Self(iter.into_iter().collect()) + } +} + /// Address balance changes for a single block. #[derive(Debug, Clone)] #[cfg_attr( diff --git a/packages/rs-drive-proof-verifier/src/types/evonode_status.rs b/packages/rs-drive-proof-verifier/src/types/evonode_status.rs index e465bbebf26..0edc6bb95f5 100644 --- a/packages/rs-drive-proof-verifier/src/types/evonode_status.rs +++ b/packages/rs-drive-proof-verifier/src/types/evonode_status.rs @@ -40,7 +40,6 @@ pub struct EvoNodeStatus { derive(Encode, Decode, PlatformSerialize, PlatformDeserialize), platform_serialize(unversioned) )] - /// Information about protocol and software components versions. pub struct Version { /// Information about software components versions. @@ -96,7 +95,6 @@ pub struct TenderdashProtocol { derive(Encode, Decode, PlatformSerialize, PlatformDeserialize), platform_serialize(unversioned) )] - /// Drive protocol versions. pub struct DriveProtocol { /// Latest version supported by the node. @@ -112,7 +110,6 @@ pub struct DriveProtocol { derive(Encode, Decode, PlatformSerialize, PlatformDeserialize), platform_serialize(unversioned) )] - /// Information about current time used by the node. pub struct Time { /// Local time of the node. Unix timestamp since epoch. @@ -131,7 +128,6 @@ pub struct Time { derive(Encode, Decode, PlatformSerialize, PlatformDeserialize), platform_serialize(unversioned) )] - /// Evo node identification information. pub struct Node { /// Node ID @@ -145,7 +141,6 @@ pub struct Node { derive(Encode, Decode, PlatformSerialize, PlatformDeserialize), platform_serialize(unversioned) )] - /// Layer 2 blockchain information pub struct Chain { /// Whether the node is catching up with the network. diff --git a/packages/rs-sdk-ffi/src/token/queries/mod.rs b/packages/rs-sdk-ffi/src/token/queries/mod.rs index 1552a547f73..36a8a253946 100644 --- a/packages/rs-sdk-ffi/src/token/queries/mod.rs +++ b/packages/rs-sdk-ffi/src/token/queries/mod.rs @@ -22,6 +22,6 @@ pub use identity_balances::dash_sdk_identity_fetch_token_balances; pub use identity_token_infos::dash_sdk_identity_fetch_token_infos; pub use info::dash_sdk_token_get_identity_infos; pub use perpetual_distribution_last_claim::dash_sdk_token_get_perpetual_distribution_last_claim; -// pub use pre_programmed_distributions::dash_sdk_token_get_pre_programmed_distributions; // TODO: Not yet implemented +pub use pre_programmed_distributions::dash_sdk_token_get_pre_programmed_distributions; pub use status::dash_sdk_token_get_statuses; pub use total_supply::dash_sdk_token_get_total_supply; diff --git a/packages/rs-sdk-ffi/src/token/queries/pre_programmed_distributions.rs b/packages/rs-sdk-ffi/src/token/queries/pre_programmed_distributions.rs index 57e9a85fcd6..f3ab2dcaaea 100644 --- a/packages/rs-sdk-ffi/src/token/queries/pre_programmed_distributions.rs +++ b/packages/rs-sdk-ffi/src/token/queries/pre_programmed_distributions.rs @@ -1,34 +1,36 @@ -// TODO: GetTokenPreProgrammedDistributionsRequest is not yet exposed in the SDK -// This function is temporarily disabled until the SDK adds support for it -/* -use crate::types::SDKHandle; -use crate::{DashSDKError, DashSDKResult, DashSDKResultDataType, DashSDKErrorCode, FFIError}; -use dash_sdk::dapi_grpc::platform::v0::{ - get_token_pre_programmed_distributions_request::{ - get_token_pre_programmed_distributions_request_v0::StartAtInfo, - GetTokenPreProgrammedDistributionsRequestV0, - }, - GetTokenPreProgrammedDistributionsRequest, GetTokenPreProgrammedDistributionsResponse, +//! Token pre-programmed distributions query operations + +use dash_sdk::dpp::platform_value::string_encoding::Encoding; +use dash_sdk::dpp::prelude::Identifier; +use dash_sdk::platform::tokens::token_pre_programmed_distributions::{ + TokenPreProgrammedDistributions, TokenPreProgrammedDistributionsQuery, + TokenPreProgrammedDistributionsStartAtInfo, }; -use dash_sdk::dapi_client::{transport::TransportRequest, DapiRequest, RequestSettings}; -use std::ffi::{c_char, c_void, CStr, CString}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +use crate::sdk::SDKWrapper; +use crate::types::SDKHandle; +use crate::{DashSDKError, DashSDKErrorCode, DashSDKResult, FFIError}; -/// Fetches pre-programmed distributions for a token +/// Fetches pre-programmed distributions for a token. /// /// # Parameters /// * `sdk_handle` - Handle to the SDK instance /// * `token_id` - Base58-encoded token identifier /// * `start_time_ms` - Starting time in milliseconds (optional, 0 for no start time) -/// * `start_recipient` - Base58-encoded starting recipient ID (optional) +/// * `start_recipient` - Base58-encoded starting recipient ID (optional, null for none) /// * `start_recipient_included` - Whether to include the start recipient /// * `limit` - Maximum number of distributions to return (optional, 0 for default limit) /// /// # Returns /// * JSON array of pre-programmed distributions or null if not found -/// * Error message if operation fails /// /// # Safety -/// This function is unsafe because it handles raw pointers from C +/// - `sdk_handle` must be a valid pointer to an initialized SDKHandle. +/// - `token_id` must be a valid pointer to a NUL-terminated C string. +/// - `start_recipient` may be null; if non-null, must be a valid NUL-terminated C string. +/// - The returned C string pointer (on success) must be freed by the caller. #[no_mangle] pub unsafe extern "C" fn dash_sdk_token_get_pre_programmed_distributions( sdk_handle: *const SDKHandle, @@ -38,203 +40,152 @@ pub unsafe extern "C" fn dash_sdk_token_get_pre_programmed_distributions( start_recipient_included: bool, limit: u32, ) -> DashSDKResult { - match get_token_pre_programmed_distributions( - sdk_handle, - token_id, - start_time_ms, - start_recipient, - start_recipient_included, - limit, - ) { - Ok(Some(json)) => { - let c_str = match CString::new(json) { - Ok(s) => s, - Err(e) => { - return DashSDKResult { - data_type: DashSDKResultDataType::NoData, - data: std::ptr::null_mut(), - error: Box::into_raw(Box::new(DashSDKError::new( - DashSDKErrorCode::InternalError, - format!("Failed to create CString: {}", e) - ))), - } - } - }; - DashSDKResult { - data_type: DashSDKResultDataType::String, - data: c_str.into_raw() as *mut c_void, - error: std::ptr::null_mut(), - } - } - Ok(None) => DashSDKResult { - data_type: DashSDKResultDataType::NoData, - data: std::ptr::null_mut(), - error: std::ptr::null_mut(), - }, - Err(e) => DashSDKResult { - data_type: DashSDKResultDataType::NoData, - data: std::ptr::null_mut(), - error: Box::into_raw(Box::new(DashSDKError::new( - DashSDKErrorCode::InternalError, - e - ))), - }, + if sdk_handle.is_null() || token_id.is_null() { + return DashSDKResult::error(DashSDKError::new( + DashSDKErrorCode::InvalidParameter, + "SDK handle or token ID is null".to_string(), + )); } -} -fn get_token_pre_programmed_distributions( - sdk_handle: *const SDKHandle, - token_id: *const c_char, - start_time_ms: u64, - start_recipient: *const c_char, - start_recipient_included: bool, - limit: u32, -) -> Result, String> { - // Check for null pointers - if sdk_handle.is_null() { - return Err("SDK handle is null".to_string()); - } - if token_id.is_null() { - return Err("Token ID is null".to_string()); - } + let wrapper = &*(sdk_handle as *const SDKWrapper); - let rt = tokio::runtime::Runtime::new() - .map_err(|e| format!("Failed to create Tokio runtime: {}", e))?; + let token_id_str = match CStr::from_ptr(token_id).to_str() { + Ok(s) => s, + Err(e) => return DashSDKResult::error(FFIError::from(e).into()), + }; - let token_id_str = unsafe { - CStr::from_ptr(token_id) - .to_str() - .map_err(|e| format!("Invalid UTF-8 in token ID: {}", e))? + let token_id = match Identifier::from_string(token_id_str, Encoding::Base58) { + Ok(id) => id, + Err(e) => { + return DashSDKResult::error(DashSDKError::new( + DashSDKErrorCode::InvalidParameter, + format!("Invalid token ID: {}", e), + )) + } }; - let wrapper = unsafe { &*(sdk_handle as *const crate::sdk::SDKWrapper) }; - let sdk = wrapper.sdk.clone(); - - rt.block_on(async move { - let token_id_bytes = bs58::decode(token_id_str) - .into_vec() - .map_err(|e| format!("Failed to decode token ID: {}", e))?; - - let token_id: [u8; 32] = token_id_bytes - .try_into() - .map_err(|_| "Token ID must be exactly 32 bytes".to_string())?; - - let start_at_info = if start_time_ms > 0 { - let start_recipient_bytes = if start_recipient.is_null() { - None - } else { - let start_recipient_str = unsafe { - CStr::from_ptr(start_recipient) - .to_str() - .map_err(|e| format!("Invalid UTF-8 in start recipient: {}", e))? - }; - let recipient_bytes = bs58::decode(start_recipient_str) - .into_vec() - .map_err(|e| format!("Failed to decode start recipient: {}", e))?; - let recipient_id: [u8; 32] = recipient_bytes - .try_into() - .map_err(|_| "Start recipient must be exactly 32 bytes".to_string())?; - Some(recipient_id.to_vec()) - }; - Some(StartAtInfo { - start_time_ms, - start_recipient: start_recipient_bytes, - start_recipient_included: Some(start_recipient_included), - }) + let start_at_info = if start_time_ms > 0 { + let recipient = if !start_recipient.is_null() { + let recipient_str = match CStr::from_ptr(start_recipient).to_str() { + Ok(s) => s, + Err(e) => return DashSDKResult::error(FFIError::from(e).into()), + }; + match Identifier::from_string(recipient_str, Encoding::Base58) { + Ok(id) => Some(id), + Err(e) => { + return DashSDKResult::error(DashSDKError::new( + DashSDKErrorCode::InvalidParameter, + format!("Invalid start recipient: {}", e), + )) + } + } } else { None }; - let request = GetTokenPreProgrammedDistributionsRequest { - version: Some(dash_sdk::dapi_grpc::platform::v0::get_token_pre_programmed_distributions_request::Version::V0(GetTokenPreProgrammedDistributionsRequestV0 { - token_id: token_id.to_vec(), - start_at_info, - limit: if limit > 0 { Some(limit) } else { None }, - prove: true, - })), - }; - - // Execute the request directly since this isn't exposed in the SDK yet - let result = request - .execute(&sdk, RequestSettings::default()) - .await - .map_err(|e| format!("Failed to execute request: {}", e))?; + Some(TokenPreProgrammedDistributionsStartAtInfo { + start_time_ms, + start_recipient: recipient, + start_recipient_included, + }) + } else { + if !start_recipient.is_null() { + return DashSDKResult::error(DashSDKError::new( + DashSDKErrorCode::InvalidParameter, + "start_recipient provided but start_time_ms is zero".to_string(), + )); + } + None + }; - // Parse the response using the SDK's proof verification - let response: GetTokenPreProgrammedDistributionsResponse = result.inner; + let query = TokenPreProgrammedDistributionsQuery { + token_id, + start_at_info, + limit: if limit > 0 { Some(limit) } else { None }, + }; - match response.version { - Some(dash_sdk::dapi_grpc::platform::v0::get_token_pre_programmed_distributions_response::Version::V0(v0)) => { - match v0.result { - Some(dash_sdk::dapi_grpc::platform::v0::get_token_pre_programmed_distributions_response::get_token_pre_programmed_distributions_response_v0::Result::TokenDistributions(distributions)) => { - if distributions.token_distributions.is_empty() { - return Ok(None); - } + let result: Result, FFIError> = wrapper.runtime.block_on(async { + use dash_sdk::platform::Fetch; - let distributions_json: Vec = distributions - .token_distributions + let distributions = TokenPreProgrammedDistributions::fetch(&wrapper.sdk, query) + .await + .map_err(|e| { + FFIError::InternalError(format!( + "Failed to fetch token pre-programmed distributions: {}", + e + )) + })?; + + match distributions { + Some(dists) => { + let distributions_json: Vec = dists + .0 + .iter() + .map(|(timestamp, recipients)| { + let recipients_json: Vec = recipients .iter() - .map(|timed_distribution| { - let distributions_for_time_json: Vec = timed_distribution - .distributions - .iter() - .map(|distribution| { - format!( - r#"{{"recipient_id":"{}","amount":{}}}"#, - bs58::encode(&distribution.recipient_id).into_string(), - distribution.amount - ) - }) - .collect(); - + .map(|(recipient_id, amount)| { format!( - r#"{{"timestamp":{},"distributions":[{}]}}"#, - timed_distribution.timestamp, - distributions_for_time_json.join(",") + r#"{{"recipient_id":"{}","amount":{}}}"#, + recipient_id, amount ) }) .collect(); - Ok(Some(format!("[{}]", distributions_json.join(",")))) - } - Some(dash_sdk::dapi_grpc::platform::v0::get_token_pre_programmed_distributions_response::get_token_pre_programmed_distributions_response_v0::Result::Proof(_proof)) => { - // For now, return empty result for proof responses - // TODO: Implement proper proof verification when SDK supports it - Ok(None) - } - None => Ok(None), - } + format!( + r#"{{"timestamp":{},"distributions":[{}]}}"#, + timestamp, + recipients_json.join(",") + ) + }) + .collect(); + + Ok(Some(format!("[{}]", distributions_json.join(",")))) } - None => Err("Invalid response format".to_string()), + None => Ok(None), + } + }); + + match result { + Ok(Some(json_str)) => { + let c_str = match CString::new(json_str) { + Ok(s) => s, + Err(e) => { + return DashSDKResult::error( + FFIError::InternalError(format!("Failed to create CString: {}", e)).into(), + ) + } + }; + DashSDKResult::success_string(c_str.into_raw()) } - }) + Ok(None) => DashSDKResult::success(std::ptr::null_mut()), + Err(e) => DashSDKResult::error(e.into()), + } } -*/ -/* #[cfg(test)] mod tests { use super::*; use crate::test_utils::test_utils::create_mock_sdk_handle; - use std::ffi::CString; #[test] - fn test_get_token_pre_programmed_distributions_null_handle() { + fn test_null_handle() { unsafe { + let token_id = std::ffi::CString::new("test").unwrap(); let result = dash_sdk_token_get_pre_programmed_distributions( std::ptr::null(), - CString::new("test").unwrap().as_ptr(), + token_id.as_ptr(), 0, std::ptr::null(), false, - 10, + 0, ); assert!(!result.error.is_null()); } } #[test] - fn test_get_token_pre_programmed_distributions_null_token_id() { + fn test_null_token_id() { let handle = create_mock_sdk_handle(); unsafe { let result = dash_sdk_token_get_pre_programmed_distributions( @@ -243,11 +194,10 @@ mod tests { 0, std::ptr::null(), false, - 10, + 0, ); assert!(!result.error.is_null()); crate::test_utils::test_utils::destroy_mock_sdk_handle(handle); } } } -*/ diff --git a/packages/rs-sdk-ffi/tests/integration_tests/token.rs b/packages/rs-sdk-ffi/tests/integration_tests/token.rs index 5f00d41b99d..67ece97dcb4 100644 --- a/packages/rs-sdk-ffi/tests/integration_tests/token.rs +++ b/packages/rs-sdk-ffi/tests/integration_tests/token.rs @@ -16,6 +16,14 @@ fn token0_id_b58() -> String { token_id.to_string(Encoding::Base58) } +fn token2_id_b58() -> String { + // Matches rs-sdk vectors: token id 2 for data contract id [3;32] + let data_contract_id = Identifier::new([3u8; 32]); + let token_bytes = calculate_token_id(&data_contract_id.to_buffer(), 2); + let token_id = Identifier::new(token_bytes); + token_id.to_string(Encoding::Base58) +} + // Pruned: token info test lacks rs-sdk vectors and is outdated // Pruned: token contract info not backed by rs-sdk vectors @@ -174,6 +182,103 @@ fn test_token_direct_purchase_prices() { destroy_test_sdk_handle(handle); } +/// Test fetching pre-programmed distributions for a token +#[test] +fn test_token_pre_programmed_distributions() { + setup_logs(); + + let handle = create_test_sdk_handle("test_token_pre_programmed_distributions_present"); + let token_contract_id = to_c_string(&token2_id_b58()); + + unsafe { + let result = dash_sdk_token_get_pre_programmed_distributions( + handle, + token_contract_id.as_ptr(), + 0, + std::ptr::null(), + false, + 0, + ); + + match parse_string_result(result) { + Ok(Some(json_str)) => { + let json = parse_json_result(&json_str).expect("valid JSON"); + assert!(json.is_array(), "Expected array, got: {:?}", json); + let arr = json.as_array().unwrap(); + assert_eq!( + arr.len(), + 3, + "Expected 3 distribution timestamps, got {}", + arr.len() + ); + // Verify at least the first entry has the expected structure. + // Test data: timestamp 1000 with 2 recipients, timestamp 5000 with 1, timestamp 10000 with 2. + let first = &arr[0]; + assert!( + first.get("timestamp").is_some(), + "Entry should have a 'timestamp' field: {:?}", + first + ); + assert!( + first + .get("distributions") + .map(|d| d.is_array()) + .unwrap_or(false), + "Entry should have a 'distributions' array: {:?}", + first + ); + } + Ok(None) => { + // Vectors not yet generated; absent distributions is also acceptable. + } + Err(e) => panic!("Unexpected error: {}", e), + } + } + + destroy_test_sdk_handle(handle); +} + +/// Test that TOKEN_ID_0 has no pre-programmed distributions (returns null data) +#[test] +fn test_token_pre_programmed_distributions_absent() { + setup_logs(); + + let handle = create_test_sdk_handle("test_token_pre_programmed_distributions_absent"); + let token_contract_id = to_c_string(&token0_id_b58()); + + unsafe { + let result = dash_sdk_token_get_pre_programmed_distributions( + handle, + token_contract_id.as_ptr(), + 0, + std::ptr::null(), + false, + 0, + ); + + match parse_string_result(result) { + Ok(None) => { + // Expected: TOKEN_ID_0 has no pre-programmed distributions. + } + Ok(Some(json_str)) => { + // If vectors happen to return data, the token should have zero entries. + let json = parse_json_result(&json_str).expect("valid JSON"); + let arr = json + .as_array() + .expect("Expected array for absent distributions"); + assert!( + arr.is_empty(), + "TOKEN_ID_0 should have no distributions, got: {:?}", + arr + ); + } + Err(e) => panic!("Unexpected error: {}", e), + } + } + + destroy_test_sdk_handle(handle); +} + /// Test fetching token info for multiple identities #[test] fn test_token_identities_token_infos() { diff --git a/packages/rs-sdk/README.md b/packages/rs-sdk/README.md index 090f002daea..7a2372e0d4e 100644 --- a/packages/rs-sdk/README.md +++ b/packages/rs-sdk/README.md @@ -83,10 +83,28 @@ Core RPC on the remote host. Refer to rich comments / help in the forementioned scripts for more details. +### SDK test data + +When starting the local devnet with `SDK_TEST_DATA=true yarn start`, the `create_sdk_test_data` cfg flag +activates creation of deterministic test data in genesis state. This data is defined in +`packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/`. + +Current test data includes 3 identities, a data contract with 3 tokens, and address balances. +Token configuration: + +| Token | Config | +|-------|--------| +| `TOKEN_ID_0` | base_supply=100000, frozen for IDENTITY_ID_2, no pricing, no pre-programmed distributions | +| `TOKEN_ID_1` | base_supply=100000, paused, single price=25, no pre-programmed distributions | +| `TOKEN_ID_2` | base_supply=100000, pricing schedule (10 levels), pre-programmed distributions at timestamps 1000, 5000, 10000 | + +When adding a new query type, add corresponding test data to the files in `create_genesis_state/test/` +and reference it in `packages/rs-sdk/tests/fetch/generated_data.rs`. + ### Generating test vectors -To generate test vectors for offline testing, you need to have access acredentials to Dash Platform instance, either by -specifying configuration manually in `packages/rs-sdk/tests/.env`. or starting a local devnet. +To generate test vectors for offline testing, you need to have access to a Dash Platform instance, either by +specifying configuration manually in `packages/rs-sdk/tests/.env` or starting a local devnet. The `.env` file is automatically generated during `yarn setup` or `yarn reset`, using `platform/scripts/configure_dotenv.sh` script. See [Dash Platform documentation](../../README.md) for more details about starting and using local devnet. To generate test vectors: @@ -132,14 +150,27 @@ in `packages/rs-dapi-client/src/transport/grpc.rs`. If you intend to implement `FetchMany`, you should define type returned by `fetch_many()` using `RetrievedObjects` that will store collection of returned objects, indexed by some key. 5. [ ] Implement `FromProof` trait for the `Object` (or type defined in `types.rs`) in `packages/rs-drive-proof-verifier/src/proof.rs`. -6. [ ] Implement `Query` trait for the `Request` in `packages/rs-sdk/src/platform/query.rs`. +6. [ ] Implement `Query` trait for the `Request` and `Fetch` (or `FetchMany`) trait for the `Object`. + Create a dedicated module under the appropriate subdirectory + (e.g., `packages/rs-sdk/src/platform/tokens/my_query.rs`) and add `Query` + `Fetch`/`FetchMany` impls there. + **Deprecated:** older code placed these in central files (`query.rs`, `fetch.rs`, `fetch_many.rs`) — do not follow that pattern for new queries. 7. [ ] Implement `MockResponse` for `Object` in `packages/rs-sdk/src/mock/requests.rs`. -8. [ ] Implement `Fetch` trait for the `Object` (or type defined in `types.rs`), with inner type Request = `Request`, - in `packages/rs-sdk/src/platform/fetch.rs`. -9. [ ] Implement `FetchMany\` trait for the `Object` (or type defined in `types.rs`), - with inner type Request = `Request`, in `packages/rs-sdk/src/platform/fetch_many.rs`. -10. [ ] Add `mod ...;` clause to `packages/rs-sdk/tests/fetch/main.rs` -11. [ ] Implement unit tests in `packages/rs-sdk/tests/fetch/*object*.rs` -12. [ ] Add name of request type to match clause in `packages/rs-sdk/src/mock/sdk.rs` : `load_expectations()` -13. [ ] Start local devnet with `yarn reset && SDK_TEST_DATA=true yarn start` -14. [ ] Generate test vectors with script `packages/rs-sdk/scripts/generate_test_vectors.sh` +8. [ ] Implement `FetchMany\` trait for the `Object` (or type defined in `types.rs`), + with inner type Request = `Request`, if the query returns a collection of objects. + Skip if the query returns a single result and only `Fetch` is needed. +9. [ ] Add `mod ...;` clause to `packages/rs-sdk/tests/fetch/main.rs` +10. [ ] Implement unit tests in `packages/rs-sdk/tests/fetch/*object*.rs`. + Tests must compile but will **fail** at this point due to missing test vectors — that is expected. + The vector generation script (step 13) runs these tests with `--features generate-test-vectors` + against a live devnet to record responses as test vectors. After vectors are generated, + re-run `cargo test -p dash-sdk` to verify they pass in offline mode. +11. [ ] Add name of request type to match clause in `packages/rs-sdk/src/mock/sdk.rs` : `load_expectations()` +12. [ ] (Optional) If not already configured, run `yarn setup` (fresh checkout) or `yarn reset` (reconfigure existing environment). + **Warning:** both commands rebuild everything and reset data — do not run if your environment is already working. + This configures the `.env` file in `packages/rs-sdk/tests/` needed by the tests. +13. [ ] Start local devnet with `SDK_TEST_DATA=true yarn start`. +14. [ ] Generate test vectors by running `packages/rs-sdk/scripts/generate_test_vectors.sh test_name` + where `test_name` matches only the new tests (e.g., `test_token_pre_programmed_distributions`). + Running without arguments regenerates **all** vectors — avoid this unless intentional. + The script executes matching tests with `--features generate-test-vectors` against the running devnet, + saving responses as test vectors in `packages/rs-sdk/tests/vectors/`. diff --git a/packages/rs-sdk/src/mock/requests.rs b/packages/rs-sdk/src/mock/requests.rs index 115ad66f658..401fba3c728 100644 --- a/packages/rs-sdk/src/mock/requests.rs +++ b/packages/rs-sdk/src/mock/requests.rs @@ -39,7 +39,7 @@ use drive_proof_verifier::types::{ PlatformAddressTrunkState, PrefundedSpecializedBalance, ProposerBlockCounts, RecentAddressBalanceChanges, RecentCompactedAddressBalanceChanges, MostRecentShieldedAnchor, RecentCompactedNullifierChanges, RecentNullifierChanges, - RetrievedValues, ShieldedAnchors, + RetrievedValues, ShieldedAnchors, TokenPreProgrammedDistributions, ShieldedEncryptedNote, ShieldedEncryptedNotes, ShieldedNullifierStatus, ShieldedNullifierStatuses, ShieldedPoolState, TotalCreditsInPlatform, VotePollsGroupedByTimestamp, Voters, @@ -507,6 +507,7 @@ impl_mock_response!(CurrentQuorumsInfo); impl_mock_response!(Group); impl_mock_response!(TokenPricingSchedule); impl_mock_response!(RewardDistributionMoment); +impl_mock_response!(TokenPreProgrammedDistributions); impl_mock_response!(PlatformAddress); impl_mock_response!(AddressInfo); impl_mock_response!(RecentAddressBalanceChanges); diff --git a/packages/rs-sdk/src/mock/sdk.rs b/packages/rs-sdk/src/mock/sdk.rs index a86fada03b4..9e52c297d24 100644 --- a/packages/rs-sdk/src/mock/sdk.rs +++ b/packages/rs-sdk/src/mock/sdk.rs @@ -244,6 +244,11 @@ impl MockDashPlatformSdk { &mut dapi, filename, )? } + "GetTokenPreProgrammedDistributionsRequest" => { + load_expectation::( + &mut dapi, filename, + )? + } "GetAddressesTrunkStateRequest" => { load_expectation::(&mut dapi, filename)? } diff --git a/packages/rs-sdk/src/platform/tokens/mod.rs b/packages/rs-sdk/src/platform/tokens/mod.rs index d8bc6b10d7a..aabb61599ed 100644 --- a/packages/rs-sdk/src/platform/tokens/mod.rs +++ b/packages/rs-sdk/src/platform/tokens/mod.rs @@ -5,6 +5,8 @@ pub mod identity_token_balances; pub mod token_contract_info; /// Token info queries pub mod token_info; +/// Token pre-programmed distributions query +pub mod token_pre_programmed_distributions; /// Token status query pub mod token_status; /// Token total supply query diff --git a/packages/rs-sdk/src/platform/tokens/token_pre_programmed_distributions.rs b/packages/rs-sdk/src/platform/tokens/token_pre_programmed_distributions.rs new file mode 100644 index 00000000000..e79de6e588b --- /dev/null +++ b/packages/rs-sdk/src/platform/tokens/token_pre_programmed_distributions.rs @@ -0,0 +1,71 @@ +use crate::platform::{Fetch, Query}; +use crate::Error; +use dapi_grpc::platform::v0::get_token_pre_programmed_distributions_request::get_token_pre_programmed_distributions_request_v0::StartAtInfo; +use dapi_grpc::platform::v0::get_token_pre_programmed_distributions_request::GetTokenPreProgrammedDistributionsRequestV0; +use dapi_grpc::platform::v0::{ + get_token_pre_programmed_distributions_request, GetTokenPreProgrammedDistributionsRequest, +}; +use dpp::prelude::Identifier; +pub use drive_proof_verifier::types::TokenPreProgrammedDistributions; + +/// Query parameters for fetching pre-programmed token distributions. +#[derive(Debug, Clone)] +pub struct TokenPreProgrammedDistributionsQuery { + /// Token identifier. + pub token_id: Identifier, + /// Optional pagination start point. When `None`, results start from the earliest timestamp. + pub start_at_info: Option, + /// Optional limit on the number of results. + pub limit: Option, +} + +/// Start-at pagination info for pre-programmed distributions queries. +#[derive(Debug, Clone)] +pub struct TokenPreProgrammedDistributionsStartAtInfo { + /// Timestamp in milliseconds to start from. + pub start_time_ms: u64, + /// Optional recipient identifier to start from within the timestamp. + pub start_recipient: Option, + /// Whether to include the start recipient in results. Defaults to `true` when absent in proof + /// verification, so omitting this is equivalent to an inclusive range start. + pub start_recipient_included: bool, +} + +impl Query for TokenPreProgrammedDistributionsQuery { + fn query(self, prove: bool) -> Result { + if !prove { + unimplemented!("queries without proofs are not supported yet"); + } + + let start_at_info = self.start_at_info.map(|info| { + let has_recipient = info.start_recipient.is_some(); + StartAtInfo { + start_time_ms: info.start_time_ms, + start_recipient: info.start_recipient.map(|id| id.to_vec()), + // Only set when a start_recipient is provided; meaningless otherwise. + start_recipient_included: if has_recipient { + Some(info.start_recipient_included) + } else { + None + }, + } + }); + + let request = GetTokenPreProgrammedDistributionsRequest { + version: Some(get_token_pre_programmed_distributions_request::Version::V0( + GetTokenPreProgrammedDistributionsRequestV0 { + token_id: self.token_id.to_vec(), + start_at_info, + limit: self.limit, + prove, + }, + )), + }; + + Ok(request) + } +} + +impl Fetch for TokenPreProgrammedDistributions { + type Request = GetTokenPreProgrammedDistributionsRequest; +} diff --git a/packages/rs-sdk/tests/fetch/generated_data.rs b/packages/rs-sdk/tests/fetch/generated_data.rs index 4046e576c73..9db93f65f66 100644 --- a/packages/rs-sdk/tests/fetch/generated_data.rs +++ b/packages/rs-sdk/tests/fetch/generated_data.rs @@ -40,7 +40,8 @@ pub static TOKEN_ID_0: LazyLock = /// See `/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/mod.rs#L49` pub static TOKEN_ID_1: LazyLock = LazyLock::new(|| Identifier::new(calculate_token_id(&DATA_CONTRACT_ID.to_buffer(), 1))); -/// The third token ID from the data contract above created by init chain for testing +/// The third token ID from the data contract above created by init chain for testing. +/// Has a pricing schedule and pre-programmed distributions (3 timestamps). /// See `/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/mod.rs#L49` pub static TOKEN_ID_2: LazyLock = LazyLock::new(|| Identifier::new(calculate_token_id(&DATA_CONTRACT_ID.to_buffer(), 2))); diff --git a/packages/rs-sdk/tests/fetch/tokens/mod.rs b/packages/rs-sdk/tests/fetch/tokens/mod.rs index 14f980c3a5d..7bc1e8e97b7 100644 --- a/packages/rs-sdk/tests/fetch/tokens/mod.rs +++ b/packages/rs-sdk/tests/fetch/tokens/mod.rs @@ -1,5 +1,6 @@ mod identity_token_balances; mod token_contract_info; mod token_info; +mod token_pre_programmed_distributions; mod token_status; mod token_total_supply; diff --git a/packages/rs-sdk/tests/fetch/tokens/token_pre_programmed_distributions.rs b/packages/rs-sdk/tests/fetch/tokens/token_pre_programmed_distributions.rs new file mode 100644 index 00000000000..5ec7a4fdca9 --- /dev/null +++ b/packages/rs-sdk/tests/fetch/tokens/token_pre_programmed_distributions.rs @@ -0,0 +1,93 @@ +// TODO: Generate test vectors by running against a devnet: +// yarn reset && SDK_TEST_DATA=true yarn start +// ./packages/rs-sdk/scripts/generate_test_vectors.sh test_token_pre_programmed_distributions + +use crate::fetch::common::setup_logs; +use crate::fetch::config::Config; +use crate::fetch::generated_data::*; +use dash_sdk::dpp::prelude::Identifier; +use dash_sdk::platform::tokens::token_pre_programmed_distributions::{ + TokenPreProgrammedDistributions, TokenPreProgrammedDistributionsQuery, +}; +use dash_sdk::platform::Fetch; + +/// TOKEN_ID_2 has pre-programmed distributions configured with 3 timestamps. +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_token_pre_programmed_distributions_present() { + setup_logs(); + + let cfg = Config::new(); + let sdk = cfg + .setup_api("test_token_pre_programmed_distributions_present") + .await; + + let query = TokenPreProgrammedDistributionsQuery { + token_id: *TOKEN_ID_2, + start_at_info: None, + limit: None, + }; + + let distributions = TokenPreProgrammedDistributions::fetch(&sdk, query) + .await + .expect("fetch token pre-programmed distributions"); + + let distributions = distributions.expect("TOKEN_ID_2 should have pre-programmed distributions"); + assert_eq!( + distributions.0.len(), + 3, + "expected 3 distribution timestamps" + ); + + // Verify timestamp 1000 has 2 recipients with correct amounts + let ts1000 = distributions + .0 + .get(&1000) + .expect("timestamp 1000 should be present"); + assert_eq!(ts1000.len(), 2, "timestamp 1000 should have 2 recipients"); + assert_eq!( + ts1000.get(&Identifier::new([1u8; 32])).copied(), + Some(500), + "IDENTITY_ID_1 should receive 500 at timestamp 1000" + ); + assert_eq!( + ts1000.get(&Identifier::new([2u8; 32])).copied(), + Some(300), + "IDENTITY_ID_2 should receive 300 at timestamp 1000" + ); + + // Verify remaining timestamps exist + assert!( + distributions.0.contains_key(&5000), + "timestamp 5000 should be present" + ); + assert!( + distributions.0.contains_key(&10000), + "timestamp 10000 should be present" + ); +} + +/// TOKEN_ID_0 has no pre-programmed distributions; query should return None. +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_token_pre_programmed_distributions_absent() { + setup_logs(); + + let cfg = Config::new(); + let sdk = cfg + .setup_api("test_token_pre_programmed_distributions_absent") + .await; + + let query = TokenPreProgrammedDistributionsQuery { + token_id: *TOKEN_ID_0, + start_at_info: None, + limit: None, + }; + + let distributions = TokenPreProgrammedDistributions::fetch(&sdk, query) + .await + .expect("fetch token pre-programmed distributions"); + + assert!( + distributions.is_none(), + "expected no pre-programmed distributions for TOKEN_ID_0" + ); +} diff --git a/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_absent/.gitkeep b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_absent/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_absent/msg_GetTokenPreProgrammedDistributionsRequest_a46a647398eedeed648eafe389472c682c883ab61c852a35528493ea2680909b.json b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_absent/msg_GetTokenPreProgrammedDistributionsRequest_a46a647398eedeed648eafe389472c682c883ab61c852a35528493ea2680909b.json new file mode 100644 index 00000000000..3c066e3fec0 Binary files /dev/null and b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_absent/msg_GetTokenPreProgrammedDistributionsRequest_a46a647398eedeed648eafe389472c682c883ab61c852a35528493ea2680909b.json differ diff --git a/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_absent/quorum_pubkey-106-7e46a98df86489ce0e3845110ce59d247f99d4333a771efb4bc554e4575d59c9.json b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_absent/quorum_pubkey-106-7e46a98df86489ce0e3845110ce59d247f99d4333a771efb4bc554e4575d59c9.json new file mode 100644 index 00000000000..798d07653a3 --- /dev/null +++ b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_absent/quorum_pubkey-106-7e46a98df86489ce0e3845110ce59d247f99d4333a771efb4bc554e4575d59c9.json @@ -0,0 +1 @@ +8652a5e411b09ed8f445379612c5fb302f6f14e403e735f8fe9c0af4e68502c3a14a262cac04611f598dee968d46b7a7 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_present/.gitkeep b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_present/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_present/msg_GetTokenPreProgrammedDistributionsRequest_70451dbd24fcfcbf1831ab10f5ae02962217f1ae14791d7fbee47d4e01696b52.json b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_present/msg_GetTokenPreProgrammedDistributionsRequest_70451dbd24fcfcbf1831ab10f5ae02962217f1ae14791d7fbee47d4e01696b52.json new file mode 100644 index 00000000000..32a1e337c18 Binary files /dev/null and b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_present/msg_GetTokenPreProgrammedDistributionsRequest_70451dbd24fcfcbf1831ab10f5ae02962217f1ae14791d7fbee47d4e01696b52.json differ diff --git a/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_present/quorum_pubkey-106-7e46a98df86489ce0e3845110ce59d247f99d4333a771efb4bc554e4575d59c9.json b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_present/quorum_pubkey-106-7e46a98df86489ce0e3845110ce59d247f99d4333a771efb4bc554e4575d59c9.json new file mode 100644 index 00000000000..798d07653a3 --- /dev/null +++ b/packages/rs-sdk/tests/vectors/test_token_pre_programmed_distributions_present/quorum_pubkey-106-7e46a98df86489ce0e3845110ce59d247f99d4333a771efb4bc554e4575d59c9.json @@ -0,0 +1 @@ +8652a5e411b09ed8f445379612c5fb302f6f14e403e735f8fe9c0af4e68502c3a14a262cac04611f598dee968d46b7a7 \ No newline at end of file