dpp/tokens/token_payment_info/
mod.rs

1//! Token payment metadata and helpers.
2//!
3//! This module defines the versioned `TokenPaymentInfo` wrapper used to describe how a
4//! client intends to pay with tokens for an operation (for example, creating,
5//! transferring, purchasing, or updating the price of a document/NFT).
6//! It captures which token to use, optional price bounds, and who covers gas fees.
7//!
8//! The enum is versioned to allow future evolution without breaking callers. The
9//! current implementation is [`v0::TokenPaymentInfoV0`]. Accessors are provided via
10//! [`v0::v0_accessors::TokenPaymentInfoAccessorsV0`], and convenience methods (such
11//! as `token_id()` and `is_valid_for_required_cost()`) are available through
12//! [`methods::v0::TokenPaymentInfoMethodsV0`].
13//!
14//! Typical usage:
15//!
16//! ```ignore
17//! use dpp::tokens::gas_fees_paid_by::GasFeesPaidBy;
18//! use dpp::data_contract::TokenContractPosition;
19//! use dpp::tokens::token_payment_info::{TokenPaymentInfo, v0::TokenPaymentInfoV0};
20//!
21//! // Client indicates payment preferences for a transition
22//! let info: TokenPaymentInfo = TokenPaymentInfoV0 {
23//!     // `None` => use a token defined on the current contract
24//!     payment_token_contract_id: None,
25//!     // Which token (by position/index) on the contract to use
26//!     token_contract_position: 0u16,
27//!     // Optional bounds to guard against unexpected price changes
28//!     minimum_token_cost: None,
29//!     maximum_token_cost: Some(1_000u64.into()),
30//!     // Who pays gas: user, contract owner, or prefer contract owner
31//!     gas_fees_paid_by: GasFeesPaidBy::DocumentOwner,
32//! }.into();
33//! ```
34//!
35//! Deserialization from a platform `BTreeMap<String, Value>` requires a
36//! `$formatVersion` key. For V0 the map may contain:
37//! - `paymentTokenContractId` (`Identifier` as bytes)
38//! - `tokenContractPosition` (`u16`)
39//! - `minimumTokenCost` (`u64`)
40//! - `maximumTokenCost` (`u64`)
41//! - `gasFeesPaidBy` (one of: `"DocumentOwner"`, `"ContractOwner"`, `"PreferContractOwner"`)
42//!
43//! Unknown `$formatVersion` values yield an `UnknownVersionMismatch` error.
44//!
45use crate::balances::credits::TokenAmount;
46use crate::data_contract::TokenContractPosition;
47use crate::tokens::gas_fees_paid_by::GasFeesPaidBy;
48use crate::tokens::token_payment_info::methods::v0::TokenPaymentInfoMethodsV0;
49use crate::tokens::token_payment_info::v0::v0_accessors::TokenPaymentInfoAccessorsV0;
50use crate::tokens::token_payment_info::v0::TokenPaymentInfoV0;
51use crate::ProtocolError;
52use bincode::{Decode, Encode};
53use derive_more::{Display, From};
54use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
55use platform_value::btreemap_extensions::BTreeValueMapHelper;
56#[cfg(feature = "value-conversion")]
57use platform_value::Error;
58use platform_value::{Identifier, Value};
59#[cfg(any(
60    feature = "serde-conversion",
61    all(feature = "serde-conversion", feature = "serde-conversion"),
62))]
63use serde::{Deserialize, Serialize};
64use std::collections::BTreeMap;
65
66pub mod methods;
67pub mod v0;
68
69#[derive(
70    Debug,
71    Clone,
72    Copy,
73    Encode,
74    Decode,
75    PlatformDeserialize,
76    PlatformSerialize,
77    PartialEq,
78    Display,
79    From,
80)]
81#[cfg_attr(
82    any(
83        feature = "serde-conversion",
84        all(feature = "serde-conversion", feature = "serde-conversion"),
85    ),
86    derive(Serialize, Deserialize),
87    serde(tag = "$formatVersion")
88)]
89/// Versioned container describing how a client intends to pay with tokens.
90///
91/// The `TokenPaymentInfo` enum allows the protocol to evolve the underlying structure
92/// across versions while keeping a stable API for callers. Use the accessor trait
93/// [`v0::v0_accessors::TokenPaymentInfoAccessorsV0`] to read or update fields, and
94/// [`methods::v0::TokenPaymentInfoMethodsV0`] for helpers like `token_id()` and
95/// `is_valid_for_required_cost()`.
96///
97/// See [`v0::TokenPaymentInfoV0`] for the current set of fields and semantics.
98pub enum TokenPaymentInfo {
99    #[display("V0({})", "_0")]
100    #[cfg_attr(
101        any(
102            feature = "serde-conversion",
103            all(feature = "serde-conversion", feature = "serde-conversion"),
104        ),
105        serde(rename = "0")
106    )]
107    V0(TokenPaymentInfoV0),
108}
109
110impl TokenPaymentInfoMethodsV0 for TokenPaymentInfo {}
111
112impl TokenPaymentInfoAccessorsV0 for TokenPaymentInfo {
113    // Getters
114    fn payment_token_contract_id(&self) -> Option<Identifier> {
115        match self {
116            TokenPaymentInfo::V0(v0) => v0.payment_token_contract_id(),
117        }
118    }
119
120    fn payment_token_contract_id_ref(&self) -> &Option<Identifier> {
121        match self {
122            TokenPaymentInfo::V0(v0) => v0.payment_token_contract_id_ref(),
123        }
124    }
125
126    fn token_contract_position(&self) -> TokenContractPosition {
127        match self {
128            TokenPaymentInfo::V0(v0) => v0.token_contract_position(),
129        }
130    }
131
132    fn minimum_token_cost(&self) -> Option<TokenAmount> {
133        match self {
134            TokenPaymentInfo::V0(v0) => v0.minimum_token_cost(),
135        }
136    }
137
138    fn maximum_token_cost(&self) -> Option<TokenAmount> {
139        match self {
140            TokenPaymentInfo::V0(v0) => v0.maximum_token_cost(),
141        }
142    }
143
144    fn gas_fees_paid_by(&self) -> GasFeesPaidBy {
145        match self {
146            TokenPaymentInfo::V0(v0) => v0.gas_fees_paid_by(),
147        }
148    }
149
150    // Setters
151    fn set_payment_token_contract_id(&mut self, id: Option<Identifier>) {
152        match self {
153            TokenPaymentInfo::V0(v0) => v0.set_payment_token_contract_id(id),
154        }
155    }
156
157    fn set_token_contract_position(&mut self, position: TokenContractPosition) {
158        match self {
159            TokenPaymentInfo::V0(v0) => v0.set_token_contract_position(position),
160        }
161    }
162
163    fn set_minimum_token_cost(&mut self, cost: Option<TokenAmount>) {
164        match self {
165            TokenPaymentInfo::V0(v0) => v0.set_minimum_token_cost(cost),
166        }
167    }
168
169    fn set_maximum_token_cost(&mut self, cost: Option<TokenAmount>) {
170        match self {
171            TokenPaymentInfo::V0(v0) => v0.set_maximum_token_cost(cost),
172        }
173    }
174
175    fn set_gas_fees_paid_by(&mut self, payer: GasFeesPaidBy) {
176        match self {
177            TokenPaymentInfo::V0(v0) => v0.set_gas_fees_paid_by(payer),
178        }
179    }
180}
181
182impl TryFrom<BTreeMap<String, Value>> for TokenPaymentInfo {
183    type Error = ProtocolError;
184
185    fn try_from(map: BTreeMap<String, Value>) -> Result<Self, Self::Error> {
186        // Expect a `$formatVersion` discriminator and dispatch to the
187        // corresponding versioned structure. This allows backward-compatible
188        // support for older serialized payloads.
189        let format_version = map.get_str("$formatVersion")?;
190        match format_version {
191            "0" => {
192                let token_payment_info: TokenPaymentInfoV0 = map.try_into()?;
193
194                Ok(token_payment_info.into())
195            }
196            version => Err(ProtocolError::UnknownVersionMismatch {
197                method: "TokenPaymentInfo::from_value".to_string(),
198                known_versions: vec![0],
199                received: version
200                    .parse()
201                    .map_err(|_| ProtocolError::Generic("Conversion error".to_string()))?,
202            }),
203        }
204    }
205}
206
207#[cfg(feature = "value-conversion")]
208impl TryFrom<TokenPaymentInfo> for Value {
209    type Error = Error;
210    /// Serialize the versioned token payment info into a platform `Value`.
211    ///
212    /// This mirrors the map format accepted by `TryFrom<BTreeMap<String, Value>>`,
213    /// including the `$formatVersion` discriminator.
214    fn try_from(value: TokenPaymentInfo) -> Result<Self, Self::Error> {
215        platform_value::to_value(value)
216    }
217}