Skip to content

feat(sdk): token purchase and set price transitions #2613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ pub mod destroy;
pub mod emergency_action;
pub mod freeze;
pub mod mint;
pub mod purchase;
pub mod set_price;
pub mod transfer;
pub mod unfreeze;
141 changes: 141 additions & 0 deletions packages/rs-sdk/src/platform/transition/fungible_tokens/purchase.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use crate::platform::transition::put_settings::PutSettings;
use crate::platform::Identifier;
use crate::{Error, Sdk};
use dpp::balances::credits::TokenAmount;
use dpp::data_contract::accessors::v0::DataContractV0Getters;
use dpp::data_contract::{DataContract, TokenContractPosition};
use dpp::fee::Credits;
use dpp::identity::signer::Signer;
use dpp::identity::IdentityPublicKey;
use dpp::prelude::UserFeeIncrease;
use dpp::state_transition::batch_transition::methods::v1::DocumentsBatchTransitionMethodsV1;
use dpp::state_transition::batch_transition::methods::StateTransitionCreationOptions;
use dpp::state_transition::batch_transition::BatchTransition;
use dpp::state_transition::StateTransition;
use dpp::tokens::calculate_token_id;
use dpp::version::PlatformVersion;

/// A builder to configure and broadcast token purchase transitions
pub struct TokenDirectPurchaseTransitionBuilder<'a> {
data_contract: &'a DataContract,
token_position: TokenContractPosition,
actor_id: Identifier,
amount: TokenAmount,
total_agreed_price: Credits,
settings: Option<PutSettings>,
user_fee_increase: Option<UserFeeIncrease>,
}

impl<'a> TokenDirectPurchaseTransitionBuilder<'a> {
/// Start building a purchase tokens request for the provided DataContract.
///
/// # Arguments
///
/// * `data_contract` - A reference to the data contract
/// * `token_position` - The position of the token in the contract
/// * `issuer_id` - The identifier of the issuer
/// * `amount` - The amount of tokens to purchase
///
/// # Returns
///
/// * `Self` - The new builder instance
pub fn new(
data_contract: &'a DataContract,
token_position: TokenContractPosition,
actor_id: Identifier,
amount: TokenAmount,
total_agreed_price: Credits,
) -> Self {
// TODO: Validate token position

Self {
data_contract,
token_position,
actor_id,
amount,
total_agreed_price,
settings: None,
user_fee_increase: None,
}
}

/// Adds a user fee increase to the token purchase transition
///
/// # Arguments
///
/// * `user_fee_increase` - The user fee increase to add
///
/// # Returns
///
/// * `Self` - The updated builder
pub fn with_user_fee_increase(mut self, user_fee_increase: UserFeeIncrease) -> Self {
self.user_fee_increase = Some(user_fee_increase);
self
}

/// Adds settings to the token purchase transition
///
/// # Arguments
///
/// * `settings` - The settings to add
///
/// # Returns
///
/// * `Self` - The updated builder
pub fn with_settings(mut self, settings: PutSettings) -> Self {
self.settings = Some(settings);
self
}

/// Signs the token purchase transition
///
/// # Arguments
///
/// * `sdk` - The SDK instance
/// * `identity_public_key` - The public key of the identity
/// * `signer` - The signer instance
/// * `platform_version` - The platform version
///
/// # Returns
///
/// * `Result<StateTransition, Error>` - The signed state transition or an error
pub async fn sign(
&self,
sdk: &Sdk,
identity_public_key: &IdentityPublicKey,
signer: &impl Signer,
platform_version: &PlatformVersion,
options: Option<StateTransitionCreationOptions>,
) -> Result<StateTransition, Error> {
let token_id = Identifier::from(calculate_token_id(
self.data_contract.id().as_bytes(),
self.token_position,
));

Comment on lines +110 to +114
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

No check that token_id actually belongs to data_contract
Although calculate_token_id(...) is called, we don’t ensure the resulting token_id is present in the contract’s token definitions. An invalid position would silently create an incoherent transition.
Consider validating:

if self.token_position >= self.data_contract.tokens().len() {
    return Err(Error::InvalidTokenPosition(self.token_position));
}
🤖 Prompt for AI Agents
In packages/rs-sdk/src/platform/transition/fungible_tokens/purchase.rs around
lines 110 to 114, the code calculates a token_id from a token_position without
verifying that the position is valid within the data_contract's tokens. To fix
this, add a check before calculating token_id to ensure self.token_position is
less than the length of self.data_contract.tokens(). If the position is invalid,
return an appropriate error such as Error::InvalidTokenPosition with the invalid
position value.

let identity_contract_nonce = sdk
.get_identity_contract_nonce(
self.actor_id,
self.data_contract.id(),
true,
self.settings,
)
.await?;

let state_transition = BatchTransition::new_token_direct_purchase_transition(
token_id,
self.actor_id,
self.data_contract.id(),
self.token_position,
self.amount,
self.total_agreed_price,
identity_public_key,
identity_contract_nonce,
self.user_fee_increase.unwrap_or_default(),
signer,
platform_version,
options,
)?;

Ok(state_transition)
}
}
174 changes: 174 additions & 0 deletions packages/rs-sdk/src/platform/transition/fungible_tokens/set_price.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use crate::platform::transition::put_settings::PutSettings;
use crate::platform::Identifier;
use crate::{Error, Sdk};
use dpp::data_contract::accessors::v0::DataContractV0Getters;
use dpp::data_contract::{DataContract, TokenContractPosition};
use dpp::group::GroupStateTransitionInfoStatus;
use dpp::identity::signer::Signer;
use dpp::identity::IdentityPublicKey;
use dpp::prelude::UserFeeIncrease;
use dpp::state_transition::batch_transition::methods::v1::DocumentsBatchTransitionMethodsV1;
use dpp::state_transition::batch_transition::methods::StateTransitionCreationOptions;
use dpp::state_transition::batch_transition::BatchTransition;
use dpp::state_transition::StateTransition;
use dpp::tokens::calculate_token_id;
use dpp::tokens::token_pricing_schedule::TokenPricingSchedule;
use dpp::version::PlatformVersion;

/// A builder to configure and broadcast token change direct purchase price transitions
pub struct TokenChangeDirectPurchasePriceTransitionBuilder<'a> {
data_contract: &'a DataContract,
token_position: TokenContractPosition,
actor_id: Identifier,
token_pricing_schedule: Option<TokenPricingSchedule>,
public_note: Option<String>,
settings: Option<PutSettings>,
user_fee_increase: Option<UserFeeIncrease>,
using_group_info: Option<GroupStateTransitionInfoStatus>,
}

impl<'a> TokenChangeDirectPurchasePriceTransitionBuilder<'a> {
/// Start building a change direct purchase price tokens request for the provided DataContract.
///
/// # Arguments
///
/// * `data_contract` - A reference to the data contract
/// * `token_position` - The position of the token in the contract
/// * `issuer_id` - The identifier of the issuer
/// * `amount` - The amount of tokens to change direct purchase price
///
/// # Returns
///
/// * `Self` - The new builder instance
pub fn new(
data_contract: &'a DataContract,
token_position: TokenContractPosition,
issuer_id: Identifier,
token_pricing_schedule: Option<TokenPricingSchedule>,
) -> Self {
// TODO: Validate token position

Self {
data_contract,
token_position,
actor_id: issuer_id,
token_pricing_schedule,
public_note: None,
settings: None,
user_fee_increase: None,
using_group_info: None,
}
}

/// Adds a public note to the token change direct purchase price transition
///
/// # Arguments
///
/// * `note` - The public note to add
///
/// # Returns
///
/// * `Self` - The updated builder
pub fn with_public_note(mut self, note: String) -> Self {
self.public_note = Some(note);
self
}

/// Adds a user fee increase to the token change direct purchase price transition
///
/// # Arguments
///
/// * `user_fee_increase` - The user fee increase to add
///
/// # Returns
///
/// * `Self` - The updated builder
pub fn with_user_fee_increase(mut self, user_fee_increase: UserFeeIncrease) -> Self {
self.user_fee_increase = Some(user_fee_increase);
self
}

/// Adds group information to the token change direct purchase price transition
///
/// # Arguments
///
/// * `group_info` - The group information to add
///
/// # Returns
///
/// * `Self` - The updated builder
pub fn with_using_group_info(mut self, group_info: GroupStateTransitionInfoStatus) -> Self {
self.using_group_info = Some(group_info);

// TODO: Simplify group actions automatically find position if group action is required

self
}

/// Adds settings to the token change direct purchase price transition
///
/// # Arguments
///
/// * `settings` - The settings to add
///
/// # Returns
///
/// * `Self` - The updated builder
pub fn with_settings(mut self, settings: PutSettings) -> Self {
self.settings = Some(settings);
self
}

/// Signs the token change direct purchase price transition
///
/// # Arguments
///
/// * `sdk` - The SDK instance
/// * `identity_public_key` - The public key of the identity
/// * `signer` - The signer instance
/// * `platform_version` - The platform version
///
/// # Returns
///
/// * `Result<StateTransition, Error>` - The signed state transition or an error
pub async fn sign(
&self,
sdk: &Sdk,
identity_public_key: &IdentityPublicKey,
signer: &impl Signer,
platform_version: &PlatformVersion,
options: Option<StateTransitionCreationOptions>,
) -> Result<StateTransition, Error> {
let token_id = Identifier::from(calculate_token_id(
self.data_contract.id().as_bytes(),
self.token_position,
));

let identity_contract_nonce = sdk
.get_identity_contract_nonce(
self.actor_id,
self.data_contract.id(),
true,
self.settings,
)
.await?;

let state_transition = BatchTransition::new_token_change_direct_purchase_price_transition(
token_id,
self.actor_id,
self.data_contract.id(),
self.token_position,
self.token_pricing_schedule.clone(),
self.public_note.clone(),
self.using_group_info,
identity_public_key,
identity_contract_nonce,
self.user_fee_increase.unwrap_or_default(),
signer,
platform_version,
options,
)?;

Ok(state_transition)
}
}
Loading