Skip to content

Commit

Permalink
Add foundry-output-claim typescript sdk example
Browse files Browse the repository at this point in the history
  • Loading branch information
nonast committed Oct 28, 2024
1 parent d822e2a commit a86f16c
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/examples/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"alias-migration": "ts-node src/stardust/alias-migration.ts",
"alias-output-claim": "ts-node src/stardust/alias-output-claim.ts",
"basic-output-claim": "ts-node src/stardust/basic-output-claim.ts",
"check-basic-output-unlock-conditions": "ts-node src/stardust/check-basic-output-unlock-conditions.ts"
"check-basic-output-unlock-conditions": "ts-node src/stardust/check-basic-output-unlock-conditions.ts",
"foundry-output-claim": "ts-node src/stardust/foundry-output-claim.ts"
},
"keywords": [],
"author": "",
Expand Down
158 changes: 158 additions & 0 deletions docs/examples/typescript/src/stardust/foundry-output-claim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/** Copyright (c) 2024 IOTA Stiftung
* SPDX-License-Identifier: Apache-2.0
*
* Example demonstrating the claim of a CoinManagerTreasuryCap related to a
* foundry output. In order to work, it requires a network with test objects
* generated from iota-genesis-builder/src/stardust/test_outputs.
*/
import {getFullnodeUrl, IotaClient, IotaObjectData} from "@iota/iota-sdk/client";
import {Ed25519Keypair} from "@iota/iota-sdk/keypairs/ed25519";
import {fundAddress} from "../utils";
import {bcs} from "@iota/iota-sdk/bcs";
import * as assert from "node:assert";
import {Transaction} from "@iota/iota-sdk/transactions";

const MAIN_ADDRESS_MNEMONIC = "few hood high omit camp keep burger give happy iron evolve draft few dawn pulp jazz box dash load snake gown bag draft car";
const STARDUST_PACKAGE_ID = "0x107a";
const COIN_MANAGER_MODULE_NAME = "coin_manager";
const COIN_MANAGER_TREASURY_CAP_STRUCT_NAME = "CoinManagerTreasuryCap";
const IOTA_FRAMEWORK_ADDRESS = "0x2";

async function main() {
// Build a client to connect to the local IOTA network.
const iotaClient = new IotaClient({url: getFullnodeUrl('localnet')});

// Derive keypair from mnemonic.
const keypair = Ed25519Keypair.deriveKeypair(MAIN_ADDRESS_MNEMONIC);
const sender = keypair.toIotaAddress();
console.log(`Sender address: ${sender}`);

// Fund the address.
await fundAddress(iotaClient, sender);

// Fetch the alias output object.
const aliasOutputObjectId = "0xa58e9b6b85863e2fa50710c4594f701b2f5e2c6ff5e3c2b10cf09e6b18d740da";
const aliasOutputObject = await iotaClient.getObject({id: aliasOutputObjectId});
if (!aliasOutputObject) {
throw new Error("Alias output object not found");
}

// 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;

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

// Create a PTB to claim the CoinManagerTreasuryCap related to the foundry
// output from the alias output.
const tx = new Transaction();
const gasTypeTag = "0x2::iota::IOTA";
const args = [tx.object(aliasOutputObjectId)];
const extractedAliasOutputAssets = tx.moveCall({
target: `${STARDUST_PACKAGE_ID}::alias_output::extract_assets`,
typeArguments: [gasTypeTag],
arguments: args,
});

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],
});

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));

// Set the gas budget for the transaction.
tx.setGasBudget(10_000_000);

// Sign and execute the transaction.
const result = await iotaClient.signAndExecuteTransaction({ signer: keypair, transaction: tx });

// Get the response of the transaction.
const response = await iotaClient.waitForTransaction({ digest: result.digest });
console.log(`Transaction digest: ${response.digest}`);
}

/**
* Check if the object is a CoinManagerTreasuryCap.
*/
function isCoinManagerTreasuryCap(object: IotaObjectData): boolean {
const splitType = object.type?.split("::");
return splitType !== undefined &&
splitType[0] === IOTA_FRAMEWORK_ADDRESS &&
splitType[1] === COIN_MANAGER_MODULE_NAME &&
splitType[2].split("<")[0] === COIN_MANAGER_TREASURY_CAP_STRUCT_NAME;
}

main().catch(error => {
console.error(`Error: ${error.message}`);
});

0 comments on commit a86f16c

Please sign in to comment.