Skip to main content

Claiming an Output unlockable by an Alias/Nft Address

In Stardust outputs presented an Address Unlock Condition or similarly, in the case of the Alias Output, a Governor Address Unlock Condition. In the new ledger, this mechanism is represented as an address owning the associated Output object. Most of the times the address is directly managed through a keypair by a user, but sometimes this address could represent another object. In this case, that object owns the interested Output object. Coming from the Stardust migration, only Alias and Nft objects can own other Output objects.

Claim of an Output owned by another Alias/Nft object

For this example, we're using an AliasOutput to extract an Alias object that owns an NftOutput.

  1. The first step is to fetch the AliasOutput object that is needed for claiming the NftOutput.
        .into_iter()
.next()
.ok_or(anyhow!("No coins found"))?;

// This object id was fetched manually. It refers to an Alias Output object that
// owns a NftOutput.
let alias_output_object_id = ObjectID::from_hex_literal(
"0x3b35e67750b8e4ccb45b2fc4a6a26a6d97e74c37a532f17177e6324ab93eaca6",
)?;

let alias_output_object = iota_client
.read_api()
.get_object_with_options(
alias_output_object_id,
IotaObjectDataOptions::new().with_bcs(),
)
.await?
.data
.into_iter()
  1. By using the dynamic field function with the "alias" dynamic field key filter, we gather the Alias object itself.
        .ok_or(anyhow!("Alias output not found"))?;

let alias_output_object_ref = alias_output_object.object_ref();

// 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)
  1. Some objects are owned by the Alias object. In this case we filter them by type using the NftOutput type tag. Applying the filter to get NftOutputs owned by the Alias.
        .data
.ok_or(anyhow!("alias not found"))?;
let alias_object_address = alias_object.object_ref().0;

// Some objects are owned by the Alias object. In this case we filter them by
// type using the NftOutput type.
let owned_objects_query_filter =
IotaObjectDataFilter::StructType(NftOutput::tag(GAS::type_tag()));
let owned_objects_query = IotaObjectResponseQuery::new(Some(owned_objects_query_filter), None);

// Get the first NftOutput found
let nft_output_object_owned_by_alias = iota_client
.read_api()
.get_owned_objects(
alias_object_address.into(),
Some(owned_objects_query),
None,
None,
)
.await?
.data
.into_iter()
.next()
.ok_or(anyhow!("Owned nft outputs not found"))?
  1. Create PTB that firstly extracts the assets from the AliasOutput and then it uses the extracted Alias to "address unlock" the NftOutput using the funсtion unlock_alias_address_owned_nft.
        .ok_or(anyhow!("Nft output data not found"))?;

let nft_output_object_ref = nft_output_object_owned_by_alias.object_ref();

let pt = {
let mut builder = ProgrammableTransactionBuilder::new();

// Extract alias output assets
let type_arguments = vec![GAS::type_tag()];
let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(alias_output_object_ref))?];
if let Argument::Result(extracted_alias_output_assets) = builder.programmable_move_call(
STARDUST_ADDRESS.into(),
ident_str!("alias_output").to_owned(),
ident_str!("extract_assets").to_owned(),
type_arguments,
arguments,
) {
let extracted_base_token = Argument::NestedResult(extracted_alias_output_assets, 0);
let extracted_native_tokens_bag =
Argument::NestedResult(extracted_alias_output_assets, 1);
let alias = Argument::NestedResult(extracted_alias_output_assets, 2);

let type_arguments = vec![GAS::type_tag()];
let arguments = vec![extracted_base_token];

// Extract the IOTA balance.
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);

// Cleanup the bag.
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,
);

// Unlock the nft output.
let type_arguments = vec![GAS::type_tag()];
let arguments = vec![
alias,
builder.obj(ObjectArg::Receiving(nft_output_object_ref))?,
];

let nft_output = builder.programmable_move_call(
STARDUST_ADDRESS.into(),
ident_str!("address_unlock_condition").to_owned(),
ident_str!("unlock_alias_address_owned_nft").to_owned(),
type_arguments,
arguments,
);

// Transferring alias asset
builder.transfer_arg(sender, alias);

// Extract nft assets(base token, native tokens bag, nft asset itself).
let type_arguments = vec![GAS::type_tag()];
let arguments = vec![nft_output];
// Finally call the nft_output::extract_assets function
if let Argument::Result(extracted_assets) = builder.programmable_move_call(
STARDUST_ADDRESS.into(),
ident_str!("nft_output").to_owned(),
ident_str!("extract_assets").to_owned(),
type_arguments,
arguments,
) {
// If the nft output can be unlocked, the command will be succesful and will
// return a `base_token` (i.e., IOTA) balance and a `Bag` of native tokens and
// related nft object.
let extracted_base_token = Argument::NestedResult(extracted_assets, 0);
let extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1);
let nft_asset = Argument::NestedResult(extracted_assets, 2);

let type_arguments = vec![GAS::type_tag()];
let arguments = vec![extracted_base_token];

// Extract the IOTA balance.
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);

// Cleanup the bag because it is empty.
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,
);

// Transferring nft asset
builder.transfer_arg(sender, nft_asset);