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).
const aliasOutputObjectId = "0xa58e9b6b85863e2fa50710c4594f701b2f5e2c6ff5e3c2b10cf09e6b18d740da";
const aliasOutputObject = await iotaClient.getObject({id: aliasOutputObjectId});
if (!aliasOutputObject) {
throw new Error("Alias output object not found");
}

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>.
const dfName = {
type: bcs.TypeTag.serialize({
vector: {
u8: true,
},
}).parse(),
value: "alias"
};

const aliasObject = await iotaClient.getDynamicFieldObject({ parentId: aliasOutputObjectId, name: dfName});
if (!aliasObject) {
throw new Error("Alias object not found");
}

// Get the object id of the Alias object.
const aliasObjectId = aliasObject.data?.objectId;

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.
const aliasOwnedObjects = await iotaClient.getOwnedObjects({
owner: aliasObjectId ? aliasObjectId.toString() : "",
options: {
showBcs: true,
showType: true
},
});

// Only one page should exist.
assert.ok(!aliasOwnedObjects.hasNextPage, "Only one page should exist");

// Get the CoinManagerTreasuryCaps from the query.
const ownedCoinManagerTreasuryCaps = aliasOwnedObjects.data
.filter(object => {
return isCoinManagerTreasuryCap(object.data as IotaObjectData);
});

// Get only the first coin manager treasury cap.
const coinManagerTreasuryCap = ownedCoinManagerTreasuryCaps[0]?.data;
if (!coinManagerTreasuryCap) {
throw new Error("CoinManagerTreasuryCap not found");
}

const coinManagerTreasuryCapId = coinManagerTreasuryCap.objectId;

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.
const foundryTokenTypeStructTag = coinManagerTreasuryCap.type
const foundryTokenType = foundryTokenTypeStructTag?.split("<")[1].split(">")[0] || "";

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.
const tx = new Transaction();
// Type argument for an AliasOutput coming from the IOTA network, i.e., the
// IOTA token or the Gas type tag.
const gasTypeTag = "0x2::iota::IOTA";
// Then pass the AliasOutput object as an input.
const args = [tx.object(aliasOutputObjectId)];
// Finally call the alias_output::extract_assets function.
const extractedAliasOutputAssets = tx.moveCall({
target: `${STARDUST_PACKAGE_ID}::alias_output::extract_assets`,
typeArguments: [gasTypeTag],
arguments: args,
});

// 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.
const extractedBaseToken = extractedAliasOutputAssets[0];
const extractedNativeTokensBag = extractedAliasOutputAssets[1];
const alias = extractedAliasOutputAssets[2];

// Extract the IOTA balance.
const iotaCoin = tx.moveCall({
target: '0x2::coin::from_balance',
typeArguments: [gasTypeTag],
arguments: [extractedBaseToken],
});

// Transfer the IOTA balance to the sender.
tx.transferObjects([iotaCoin], tx.pure.address(sender));

// In this example the native tokens bag is empty, so it can be destroyed.
tx.moveCall({
target: '0x2::bag::destroy_empty',
typeArguments: [],
arguments: [extractedNativeTokensBag],
});

// Extract the CoinManagerTreasuryCap.
const coinManagerTreasuryCapObject = tx.moveCall({
target: `${STARDUST_PACKAGE_ID}::address_unlock_condition::unlock_alias_address_owned_coinmanager_treasury`,
typeArguments: [foundryTokenType],
arguments: [alias, tx.object(coinManagerTreasuryCapId)],
});

// Transfer the coin manager treasury cap.
tx.transferObjects([coinManagerTreasuryCapObject], tx.pure.address(sender));

// Transfer the alias asset.
tx.transferObjects([alias], tx.pure.address(sender));