Skip to main content

Claiming Basic 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.

Claiming might not be needed

Most basic outputs have no special unlock conditions and don't need to be claimed. They will simply be available as normal Coin<IOTA> objects for your account with no further action required. Manual claiming is only needed if special unlock conditions like unexpired timelocks apply.

As a user, you may own BasicOutput objects that need to be unlocked before you can claim them. This guide walks you through the process of evaluating the unlock conditions for a BasicOutput and the steps to claim the associated assets.

Assessing Unlock Conditions

To begin, you need to determine if your BasicOutput can be unlocked. You can achieve this by performing specific off-chain queries that will check the unlock conditions for the BasicOutput.

    // This object id was fetched manually. It refers to a Basic Output object that
// contains some Native Tokens.
const basicOutputObjectId = "0xde09139ed46b9f5f876671e4403f312fad867c5ae5d300a252e4b6a6f1fa1fbd";
// Get Basic Output object.
const basicOutputObject = await iotaClient.getObject({id: basicOutputObjectId, options: { showContent: true }});
if (!basicOutputObject) {
throw new Error("Basic output object not found");
}

// Extract contents of the BasicOutput object.
const moveObject = basicOutputObject.data?.content as IotaParsedData;
if (moveObject.dataType != "moveObject") {
throw new Error("BasicOutput is not a move object");
}

// Treat fields as key-value object.
const fields = moveObject.fields as Record<string, any>;
console.log(fields);

const storageDepositReturnUc = fields['storage_deposit_return_uc'];
if (storageDepositReturnUc) {
console.log(`Storage Deposit Return Unlock Condition info: ${storageDepositReturnUc}`);
}

const timeLockUc = fields['time_lock_uc'];
if (timeLockUc) {
console.log(`Timelocked until: ${timeLockUc}`);
}

const expirationUc = fields['expiration_uc'];
if (expirationUc) {
console.log(`Expiration Unlock Condition info: ${expirationUc}`);
}

Claim a Basic Output

Once you've confirmed that the BasicOutput can be unlocked, you can start the process of claiming its assets.

1. Retrieve the BasicOutput

The first step is to fetch the BasicOutput object that you intend to claim.

    // This object id was fetched manually. It refers to a Basic Output object that
// contains some Native Tokens.
const basicOutputObjectId = "0xde09139ed46b9f5f876671e4403f312fad867c5ae5d300a252e4b6a6f1fa1fbd";

// Get Basic Output object.
const basicOutputObject = await iotaClient.getObject({id: basicOutputObjectId, options: { showContent: true }});
if (!basicOutputObject) {
throw new Error("Basic output object not found");
}

// Extract contents of the BasicOutput object.
const moveObject = basicOutputObject.data?.content as IotaParsedData;
if (moveObject.dataType != "moveObject") {
throw new Error("BasicOutput is not a move object");
}

// Treat fields as key-value object.
const fields = moveObject.fields as Record<string, any>;

2. Check the Native Token Balance

After fetching the BasicOutput, you need to check for any native tokens that might be stored within it. These tokens are typically stored in a Bag. You'll need to obtain the dynamic field keys used as bag indexes to access the native tokens. For native tokens, these keys are strings representing the OTW used for the native token Coin.

    const nativeTokensBag = fields['native_tokens'];   // Bag field

// Extract the keys of the native_tokens bag if it is not empty; the keys
// are the type_arg of each native token, so they can be used later in the PTB.
const dfTypeKeys: string[] = [];
if (nativeTokensBag.fields.size > 0) {
// Get the dynamic fields owned by the native tokens bag.
const dynamicFieldPage = await iotaClient.getDynamicFields({
parentId: nativeTokensBag.fields.id.id
});

// Extract the dynamic fields keys, i.e., the native token type.
dynamicFieldPage.data.forEach(dynamicField => {
if (typeof dynamicField.name.value === 'string') {
dfTypeKeys.push(dynamicField.name.value);
} else {
throw new Error('Dynamic field key is not a string');
}
});
}

3. Construct the PTB

Finally, you can create a Programmable Transaction Block (PTB) using the basic_output as an input, along with the Bag keys to iterate over the extracted native tokens.

    // Create a PTB to claim the assets related to the basic output.
const tx = new Transaction();

////// Command #1: extract the base token and native tokens bag.
// Type argument for a Basic Output coming from the IOTA network, i.e., the IOTA
// token or Gas type tag
const gasTypeTag = "0x2::iota::IOTA";
// Then pass the basic output object as input.
const args = [tx.object(basicOutputObjectId)];
// Finally call the basic_output::extract_assets function.
const extractedBasicOutputAssets = tx.moveCall({
target: `${STARDUST_PACKAGE_ID}::basic_output::extract_assets`,
typeArguments: [gasTypeTag],
arguments: args,
});

// If the basic output can be unlocked, the command will be successful and will
// return a `base_token` (i.e., IOTA) balance and a `Bag` of native tokens.
const extractedBaseToken = extractedBasicOutputAssets[0];
let extractedNativeTokensBag: any = extractedBasicOutputAssets[1];

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

// Send back the base token coin to the user.
tx.transferObjects([iotaCoin], tx.pure.address(sender));

////// Extract the native tokens from the Bag and send them to sender.
for (const typeKey of dfTypeKeys) {
// Type argument for a Native Token contained in the basic output bag.
const typeArguments = [`0x${typeKey}`];
// Then pass the the bag and the receiver address as input.
const args = [extractedNativeTokensBag, tx.pure.address(sender)]

extractedNativeTokensBag = tx.moveCall({
target: `${STARDUST_PACKAGE_ID}::utilities::extract_and_send_to`,
typeArguments: typeArguments,
arguments: args,
});
}

// Cleanup the bag by destroying it.
tx.moveCall({
target: `0x2::bag::destroy_empty`,
typeArguments: [],
arguments: [extractedNativeTokensBag],
});