Skip to main content

Claiming Foundry Outputs

Exchanges and dApp Devs Only

The migration documentation describes the processes needed to claim and migrate output types manually; However, for the average user, this knowledge is not needed and is abstracted in the wallet web application (dashboard). The specific migration knowledge described here is unnecessary for people using a regular wallet.

In the Move-based IOTA ledger, Foundry Outputs don't have a direct equivalent representation. Instead, claiming a Foundry Output involves extracting a CoinManagerTreasuryCap from the AliasOutput that originally controlled the Foundry in Stardust. This capability allows you to manage the supply of the Coin created during migration to represent the native token controlled by the Foundry.

Steps to Claim a Foundry Output

1. Fetch the AliasOutput

The first step is to retrieve the AliasOutput object that you intend to claim.

    // This object id was fetched manually. It refers to an Alias Output object that
// contains a CoinManagerTreasuryCap (i.e., a Foundry representation).
let alias_output_object_id = ObjectID::from_hex_literal(
"0xa58e9b6b85863e2fa50710c4594f701b2f5e2c6ff5e3c2b10cf09e6b18d740da",
)?;
let alias_output_object = iota_client
.read_api()
.get_object_with_options(
alias_output_object_id,
IotaObjectDataOptions::new().with_bcs(),
)
.await?
.data
.ok_or(anyhow!("alias output not found"))?;
let alias_output_object_ref = alias_output_object.object_ref();

2. Gather the Alias Object

Next, use the dynamic field function with the "alias" dynamic field key filter to obtain the Alias object itself.

    // Get the dynamic field owned by the Alias Output, i.e., only the Alias
// object.
// The dynamic field name for the Alias object is "alias", of type vector<u8>
let df_name = DynamicFieldName {
type_: TypeTag::Vector(Box::new(TypeTag::U8)),
value: serde_json::Value::String("alias".to_string()),
};
let alias_object = iota_client
.read_api()
.get_dynamic_field_object(alias_output_object_id, df_name)
.await?
.data
.ok_or(anyhow!("alias not found"))?;
let alias_object_ref = alias_object.object_ref();

3. Filter Owned Objects by Type

The Alias object may own various other objects (for more details, refer to the Output Unlockable by an Alias/Nft Address page). In this step, you filter these objects by type, specifically looking for the CoinManagerTreasuryCap type.

    // Get the objects owned by the alias object and filter in the ones with
// CoinManagerTreasuryCap as type.
let alias_owned_objects_page = iota_client
.read_api()
.get_owned_objects(
alias_object_ref.0.into(),
Some(IotaObjectResponseQuery::new_with_options(
IotaObjectDataOptions::new().with_bcs().with_type(),
)),
None,
None,
)
.await?;
// Only one page should exist.
assert!(!alias_owned_objects_page.has_next_page);
// Get the CoinManagerTreasuryCaps from the query
let owned_coin_manager_treasury_caps = alias_owned_objects_page
.data
.into_iter()
.filter(|object| {
CoinManagerTreasuryCap::is_coin_manager_treasury_cap(
&object
.data
.as_ref()
.expect("the query should request the data")
.object_type()
.expect("should contain the type")
.try_into()
.expect("should convert into a struct tag"),
)
})
.collect::<Vec<_>>();

// Get only the first coin manager treasury cap
let coin_manager_treasury_cap_object = owned_coin_manager_treasury_caps
.into_iter()
.next()
.ok_or(anyhow!("no coin manager treasury caps found"))?
.data
.ok_or(anyhow!("coin manager treasury cap data not found"))?;
let coin_manager_treasury_cap_object_ref = coin_manager_treasury_cap_object.object_ref();

4. Extract the One-Time Witness (OTW)

Since each native token is tied to its own package, a Foundry's native token has a specific OTW. Here, you will extract the OTW from the CoinManagerTreasuryCap object.

    // Extract the foundry token type from the type parameters of the coin manager
// treasury cap object
let foundry_token_type_struct_tag: StructTag = coin_manager_treasury_cap_object
.object_type()
.expect("should contain the type")
.try_into()?;
let foundry_token_type = foundry_token_type_struct_tag
.type_params
.first()
.expect("should contain the type param");

5. Create the PTB to Claim the CoinManagerTreasuryCap

Finally, you should create a Programmable Transaction Block (PTB) that claims the CoinManagerTreasuryCap associated with the Foundry Output from the AliasOutput using the unlock_alias_address_owned_coinmanager_treasury function.

    // Create a PTB to claim the CoinManagerTreasuryCap related to the foundry
// output from the alias output.
let pt = {
// Init a programmable transaction builder.
let mut builder = ProgrammableTransactionBuilder::new();

// Type argument for an AliasOutput coming from the IOTA network, i.e., the
// IOTA token or the Gas type tag.
let type_arguments = vec![GAS::type_tag()];
// Then pass the AliasOutput object as an input.
let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(alias_output_object_ref))?];
// Finally call the alias_output::extract_assets function.
if let Argument::Result(extracted_assets) = builder.programmable_move_call(
STARDUST_ADDRESS.into(),
ident_str!("alias_output").to_owned(),
ident_str!("extract_assets").to_owned(),
type_arguments,
arguments,
) {
// The alias output can always be unlocked by the governor address. So the
// command will be successful and will return a `base_token` (i.e., IOTA)
// balance, a `Bag` of the related native tokens and the related Alias object.
let extracted_base_token = Argument::NestedResult(extracted_assets, 0);
let extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1);
let extracted_alias = Argument::NestedResult(extracted_assets, 2);

// Extract the IOTA balance.
let type_arguments = vec![GAS::type_tag()];
let arguments = vec![extracted_base_token];
let iota_coin = builder.programmable_move_call(
IOTA_FRAMEWORK_ADDRESS.into(),
ident_str!("coin").to_owned(),
ident_str!("from_balance").to_owned(),
type_arguments,
arguments,
);

// Transfer the IOTA balance to the sender.
builder.transfer_arg(sender, iota_coin);

// In this example the native tokens bag is empty, so it can be destroyed.
let arguments = vec![extracted_native_tokens_bag];
builder.programmable_move_call(
IOTA_FRAMEWORK_ADDRESS.into(),
ident_str!("bag").to_owned(),
ident_str!("destroy_empty").to_owned(),
vec![],
arguments,
);

// Extract the CoinManagerTreasuryCap
let type_arguments = vec![foundry_token_type.clone()];
let arguments = vec![
extracted_alias,
builder.obj(ObjectArg::Receiving(coin_manager_treasury_cap_object_ref))?,
];
let coin_manager_treasury_cap = builder.programmable_move_call(
STARDUST_ADDRESS.into(),
ident_str!("address_unlock_condition").to_owned(),
ident_str!("unlock_alias_address_owned_coinmanager_treasury").to_owned(),
type_arguments,
arguments,
);

// Transfer the coin manager treasury cap.
builder.transfer_arg(sender, coin_manager_treasury_cap);

// Transfer the alias asset.
builder.transfer_arg(sender, extracted_alias);
}
builder.finish()
};