Skip to content

Commit 213aa9e

Browse files
committed
feat: add asset config transactions to utils FFI
Add AssetCreateParams, AssetReconfigureParams, and AssetDestroyParams with corresponding Composer methods. Includes metadata hash validation, optional address parsing helper, and comprehensive test TODOs for asset lifecycle.
1 parent e38f254 commit 213aa9e

File tree

7 files changed

+761
-0
lines changed

7 files changed

+761
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
use crate::transactions::common::UtilsError;
2+
3+
use super::common::CommonParams;
4+
use algokit_utils::transactions::{
5+
AssetCreateParams as RustAssetCreateParams, AssetDestroyParams as RustAssetDestroyParams,
6+
AssetReconfigureParams as RustAssetReconfigureParams,
7+
};
8+
9+
// Helper function to parse optional address strings
10+
fn parse_optional_address(
11+
addr_opt: Option<String>,
12+
field_name: &str,
13+
) -> Result<Option<algokit_transact::Address>, UtilsError> {
14+
match addr_opt {
15+
Some(addr_str) => {
16+
let addr = addr_str.parse().map_err(|e| UtilsError::UtilsError {
17+
message: format!("Failed to parse {} address: {}", field_name, e),
18+
})?;
19+
Ok(Some(addr))
20+
}
21+
None => Ok(None),
22+
}
23+
}
24+
25+
#[derive(uniffi::Record)]
26+
pub struct AssetCreateParams {
27+
/// Common transaction parameters.
28+
pub common_params: CommonParams,
29+
30+
/// The total amount of the smallest divisible (decimal) unit to create.
31+
///
32+
/// For example, if creating an asset with 2 decimals and wanting a total supply of 100 units, this value should be 10000.
33+
pub total: u64,
34+
35+
/// The amount of decimal places the asset should have.
36+
///
37+
/// If unspecified then the asset will be in whole units (i.e. `0`).
38+
/// * If 0, the asset is not divisible;
39+
/// * If 1, the base unit of the asset is in tenths;
40+
/// * If 2, the base unit of the asset is in hundredths;
41+
/// * If 3, the base unit of the asset is in thousandths;
42+
///
43+
/// and so on up to 19 decimal places.
44+
pub decimals: Option<u32>,
45+
46+
/// Whether the asset is frozen by default for all accounts.
47+
/// Defaults to `false`.
48+
///
49+
/// If `true` then for anyone apart from the creator to hold the
50+
/// asset it needs to be unfrozen per account using an asset freeze
51+
/// transaction from the `freeze` account, which must be set on creation.
52+
pub default_frozen: Option<bool>,
53+
54+
/// The optional name of the asset.
55+
///
56+
/// Max size is 32 bytes.
57+
pub asset_name: Option<String>,
58+
59+
/// The optional name of the unit of this asset (e.g. ticker name).
60+
///
61+
/// Max size is 8 bytes.
62+
pub unit_name: Option<String>,
63+
64+
/// Specifies an optional URL where more information about the asset can be retrieved (e.g. metadata).
65+
///
66+
/// Max size is 96 bytes.
67+
pub url: Option<String>,
68+
69+
/// 32-byte hash of some metadata that is relevant to your asset and/or asset holders.
70+
///
71+
/// The format of this metadata is up to the application.
72+
pub metadata_hash: Option<Vec<u8>>,
73+
74+
/// The address of the optional account that can manage the configuration of the asset and destroy it.
75+
///
76+
/// The configuration fields it can change are `manager`, `reserve`, `clawback`, and `freeze`.
77+
///
78+
/// If not set or set to the Zero address the asset becomes permanently immutable.
79+
pub manager: Option<String>,
80+
81+
/// The address of the optional account that holds the reserve (uncirculated supply) units of the asset.
82+
///
83+
/// This address has no specific authority in the protocol itself and is informational only.
84+
///
85+
/// Some standards like [ARC-19](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0019.md)
86+
/// rely on this field to hold meaningful data.
87+
///
88+
/// It can be used in the case where you want to signal to holders of your asset that the uncirculated units
89+
/// of the asset reside in an account that is different from the default creator account.
90+
///
91+
/// If not set or set to the Zero address is permanently empty.
92+
pub reserve: Option<String>,
93+
94+
/// The address of the optional account that can be used to freeze or unfreeze holdings of this asset for any account.
95+
///
96+
/// If empty, freezing is not permitted.
97+
///
98+
/// If not set or set to the Zero address is permanently empty.
99+
pub freeze: Option<String>,
100+
101+
/// The address of the optional account that can clawback holdings of this asset from any account.
102+
///
103+
/// **This field should be used with caution** as the clawback account has the ability to **unconditionally take assets from any account**.
104+
///
105+
/// If empty, clawback is not permitted.
106+
///
107+
/// If not set or set to the Zero address is permanently empty.
108+
pub clawback: Option<String>,
109+
}
110+
111+
#[derive(uniffi::Record)]
112+
pub struct AssetReconfigureParams {
113+
/// Common transaction parameters.
114+
pub common_params: CommonParams,
115+
116+
/// ID of the existing asset to be reconfigured.
117+
pub asset_id: u64,
118+
119+
/// The address of the optional account that can manage the configuration of the asset and destroy it.
120+
///
121+
/// The configuration fields it can change are `manager`, `reserve`, `clawback`, and `freeze`.
122+
///
123+
/// If not set or set to the Zero address the asset becomes permanently immutable.
124+
pub manager: Option<String>,
125+
126+
/// The address of the optional account that holds the reserve (uncirculated supply) units of the asset.
127+
///
128+
/// This address has no specific authority in the protocol itself and is informational only.
129+
///
130+
/// Some standards like [ARC-19](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0019.md)
131+
/// rely on this field to hold meaningful data.
132+
///
133+
/// It can be used in the case where you want to signal to holders of your asset that the uncirculated units
134+
/// of the asset reside in an account that is different from the default creator account.
135+
///
136+
/// If not set or set to the Zero address is permanently empty.
137+
pub reserve: Option<String>,
138+
139+
/// The address of the optional account that can be used to freeze or unfreeze holdings of this asset for any account.
140+
///
141+
/// If empty, freezing is not permitted.
142+
///
143+
/// If not set or set to the Zero address is permanently empty.
144+
pub freeze: Option<String>,
145+
146+
/// The address of the optional account that can clawback holdings of this asset from any account.
147+
///
148+
/// **This field should be used with caution** as the clawback account has the ability to **unconditionally take assets from any account**.
149+
///
150+
/// If empty, clawback is not permitted.
151+
///
152+
/// If not set or set to the Zero address is permanently empty.
153+
pub clawback: Option<String>,
154+
}
155+
156+
#[derive(uniffi::Record)]
157+
pub struct AssetDestroyParams {
158+
/// Common transaction parameters.
159+
pub common_params: CommonParams,
160+
161+
/// ID of the existing asset to be destroyed.
162+
pub asset_id: u64,
163+
}
164+
165+
impl TryFrom<AssetCreateParams> for RustAssetCreateParams {
166+
type Error = UtilsError;
167+
168+
fn try_from(params: AssetCreateParams) -> Result<Self, Self::Error> {
169+
let common_params = params.common_params.try_into()?;
170+
171+
// Convert metadata_hash if present
172+
let metadata_hash = match params.metadata_hash {
173+
Some(hash_vec) => {
174+
if hash_vec.len() != 32 {
175+
return Err(UtilsError::UtilsError {
176+
message: format!(
177+
"metadata_hash must be exactly 32 bytes, got {}",
178+
hash_vec.len()
179+
),
180+
});
181+
}
182+
let mut hash_array = [0u8; 32];
183+
hash_array.copy_from_slice(&hash_vec);
184+
Some(hash_array)
185+
}
186+
None => None,
187+
};
188+
189+
Ok(RustAssetCreateParams {
190+
common_params,
191+
total: params.total,
192+
decimals: params.decimals,
193+
default_frozen: params.default_frozen,
194+
asset_name: params.asset_name,
195+
unit_name: params.unit_name,
196+
url: params.url,
197+
metadata_hash,
198+
manager: parse_optional_address(params.manager, "manager")?,
199+
reserve: parse_optional_address(params.reserve, "reserve")?,
200+
freeze: parse_optional_address(params.freeze, "freeze")?,
201+
clawback: parse_optional_address(params.clawback, "clawback")?,
202+
})
203+
}
204+
}
205+
206+
impl TryFrom<AssetReconfigureParams> for RustAssetReconfigureParams {
207+
type Error = UtilsError;
208+
209+
fn try_from(params: AssetReconfigureParams) -> Result<Self, Self::Error> {
210+
let common_params = params.common_params.try_into()?;
211+
212+
Ok(RustAssetReconfigureParams {
213+
common_params,
214+
asset_id: params.asset_id,
215+
manager: parse_optional_address(params.manager, "manager")?,
216+
reserve: parse_optional_address(params.reserve, "reserve")?,
217+
freeze: parse_optional_address(params.freeze, "freeze")?,
218+
clawback: parse_optional_address(params.clawback, "clawback")?,
219+
})
220+
}
221+
}
222+
223+
impl TryFrom<AssetDestroyParams> for RustAssetDestroyParams {
224+
type Error = UtilsError;
225+
226+
fn try_from(params: AssetDestroyParams) -> Result<Self, Self::Error> {
227+
let common_params = params.common_params.try_into()?;
228+
Ok(RustAssetDestroyParams {
229+
common_params,
230+
asset_id: params.asset_id,
231+
})
232+
}
233+
}

crates/algokit_utils_ffi/src/transactions/composer.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,42 @@ impl Composer {
157157
})
158158
}
159159

160+
pub fn add_asset_create(
161+
&self,
162+
params: super::asset_config::AssetCreateParams,
163+
) -> Result<(), UtilsError> {
164+
let mut composer = self.inner_composer.blocking_lock();
165+
composer
166+
.add_asset_create(params.try_into()?)
167+
.map_err(|e| UtilsError::UtilsError {
168+
message: e.to_string(),
169+
})
170+
}
171+
172+
pub fn add_asset_reconfigure(
173+
&self,
174+
params: super::asset_config::AssetReconfigureParams,
175+
) -> Result<(), UtilsError> {
176+
let mut composer = self.inner_composer.blocking_lock();
177+
composer
178+
.add_asset_reconfigure(params.try_into()?)
179+
.map_err(|e| UtilsError::UtilsError {
180+
message: e.to_string(),
181+
})
182+
}
183+
184+
pub fn add_asset_destroy(
185+
&self,
186+
params: super::asset_config::AssetDestroyParams,
187+
) -> Result<(), UtilsError> {
188+
let mut composer = self.inner_composer.blocking_lock();
189+
composer
190+
.add_asset_destroy(params.try_into()?)
191+
.map_err(|e| UtilsError::UtilsError {
192+
message: e.to_string(),
193+
})
194+
}
195+
160196
pub async fn send(&self) -> Result<Vec<String>, UtilsError> {
161197
let mut composer = self.inner_composer.blocking_lock();
162198
let result = composer

crates/algokit_utils_ffi/src/transactions/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod asset_config;
12
pub mod asset_freeze;
23
pub mod asset_transfer;
34
pub mod common;

0 commit comments

Comments
 (0)