Self-Sponsoring Iota Assets Claims
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.
If you own Iota assets but don't have any IOTA coins to cover transaction fees, self-sponsorship can help you claim those assets. In this process, a sponsor (an IOTA address) pays the transaction gas fees for another address (the user). When claiming assets, a sponsor can cover the gas fees required for the transaction.
Claim an Iota Basic Output with Self-Sponsorship
1. Identify the Self-Sponsoring Address
Use the IOTA coin_type
to derive the sponsor and sender addresses.
- TypeScript
- Rust
// For this example we need to derive addresses that are at different
// indexes and coin_types, one for sponsoring with IOTA coin type and one for
// claiming the Basic Output with Iota coin type.
const sponsorDerivationPath = "m/44'/4218'/0'/0'/5'"
const senderDerivationPath = "m/44'/4218'/0'/0'/50'"
// For this example we need to derive addresses that are at different
// indexes and coin_types, one for sponsoring with IOTA coin type and one for
// claiming the Basic Output with Iota coin type.
let sponsor_derivation_path =
DerivationPath::from_str(format!("m/44'/{IOTA_COIN_TYPE}'/0'/0'/5'").as_str())?;
let sender_derivation_path =
DerivationPath::from_str(format!("m/44'/{IOTA_COIN_TYPE}'/0'/0'/50'").as_str())?;
2. Create the PTB for Claiming
Next, create a Programmable Transaction Block (PTB)
to claim a BasicOutput
owned by the derived Iota address.
This process is similar to the one outlined in the Basic Output guide.
- TypeScript
- Rust
// Create a PTB to claim the assets related to the basic output.
const tx = new Transaction();
// Extract the base token and native tokens bag.
// Type argument for a Basic Output holding IOTA coin.
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` balance and a `Bag` of native tokens.
const extractedBaseToken = extractedBasicOutputAssets[0];
const extractedNativeTokensBag = extractedBasicOutputAssets[1];
// Delete the empty native tokens bag.
tx.moveCall({
target: `0x2::bag::destroy_empty`,
typeArguments: [],
arguments: [extractedNativeTokensBag],
});
// Create a coin from the extracted 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));
// Create a PTB to claim the assets related to the basic output.
let pt = {
// Init the builder
let mut builder = ProgrammableTransactionBuilder::new();
////// Command #1: extract the base token and native tokens bag.
// Type argument for a Basic Output holding IOTA coin
let type_arguments = vec![GAS::type_tag()];
// Then pass the basic output object as input
let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(basic_output_object_ref))?];
// Finally call the basic_output::extract_assets function
if let Argument::Result(extracted_assets) = builder.programmable_move_call(
STARDUST_ADDRESS.into(),
ident_str!("basic_output").to_owned(),
ident_str!("extract_assets").to_owned(),
type_arguments,
arguments,
) {
// If the basic output can be unlocked, the command will be successful and will
// return a `base_token` balance and a `Bag` of native tokens
let extracted_base_token = Argument::NestedResult(extracted_assets, 0);
let extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1);
////// Command #2: delete the empty native tokens 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,
);
////// Command #3: create a coin from the extracted IOTA balance
// Type argument for the IOTA coin
let type_arguments = vec![GAS::type_tag()];
let arguments = vec![extracted_base_token];
let new_iota_coin = builder.programmable_move_call(
IOTA_FRAMEWORK_ADDRESS.into(),
ident_str!("coin").to_owned(),
ident_str!("from_balance").to_owned(),
type_arguments,
arguments,
);
////// Command #5: send back the base token coin to the user.
builder.transfer_arg(sender, new_iota_coin)
}
builder.finish()
};
3. Sign the Transaction
For this transaction, both the sender address (the object's owner) and the sponsor address must sign the transaction.
- TypeScript
- Rust
// Get a gas coin belonging to the sponsor.
const sponsorGasObjects = await iotaClient.getCoins({ owner: sponsor });
const sponsorGasCoin = sponsorGasObjects.data?.[0];
if (!sponsorGasCoin) {
throw new Error('No coins found for sponsor');
}
tx.setSender(sender);
tx.setGasOwner(sponsor);
// Set sponsor’s gas object to cover fees.
tx.setGasPayment([{
objectId: sponsorGasCoin.coinObjectId,
version: sponsorGasCoin.version,
digest: sponsorGasCoin.digest
}]);
tx.setGasBudget(10_000_000);
// Sign the transaction with the sponsor and sender keypairs.
const sponsorSignedTransaction = await tx.sign({ client: iotaClient, signer: sponsorKeypair });
const senderSignedTransaction = await tx.sign({ client: iotaClient, signer: senderKeypair });
// Build the transaction and execute it.
const builtTransaction = await tx.build({ client: iotaClient });
const result = await iotaClient.executeTransactionBlock({
transactionBlock: builtTransaction,
signature: [sponsorSignedTransaction.signature, senderSignedTransaction.signature]
});
// Get the response of the transaction.
const response = await iotaClient.waitForTransaction({ digest: result.digest });
console.log(`Transaction digest: ${response.digest}`);
// Create the transaction data that will be sent to the network and allow
// sponsoring
let tx_data = TransactionData::new_programmable_allow_sponsor(
sender,
vec![gas_coin.object_ref()],
pt,
gas_budget,
gas_price,
sponsor,
);
// Sender signs the transaction
let sender_signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?;
// Sponsor signs the transaction
let sponsor_signature = keystore.sign_secure(&sponsor, &tx_data, Intent::iota_transaction())?;
// Execute transaction; the transaction data is created using the signature of
// the sender and of the sponsor.
let transaction_response = iota_client
.quorum_driver_api()
.execute_transaction_block(
Transaction::from_data(tx_data, vec![sender_signature, sponsor_signature]),
IotaTransactionBlockResponseOptions::full_content(),
Some(ExecuteTransactionRequestType::WaitForLocalExecution),
)
.await?;
println!("Transaction digest: {}", transaction_response.digest);