How to use nesting and bundling

The nesting feature allows you to create nested tokens. Thus, the topmost token will have a wallet address as its owner, while the owner of tokens nested in it will be the token above it. The entire chain of nested tokens will be called a bundle. If you transfer a top-level token to another owner, all tokens in the bundle will go with it.

In this guide, we will go through the entire process of creating nested token using the Unique Network SDK.

To create nested tokens and collections, we need install the following packages:

  1. @unique-nft/sdkopen in new window
  2. @unique-nft/accountsopen in new window

Steps

Prepare

Install the above dependencies in the project

Create js or ts file and write the following inside:

// Required imports
import { Sdk } from '@unique-nft/sdk';
import { KeyringProvider } from '@unique-nft/accounts/keyring';

// Test mnemonic
const mnemonic =
  'bus ahead nation nice damp recall place dance guide media clap language';

// BaseUrl for test
const baseUrl = 'https://rest.unique.network/opal/v1';

Create Sdk

function createSdk(account: any) {
  const options = {
    baseUrl,
    signer: account,
  };
  return new Sdk(options);
}

Creating a collection

⚠️ You need to set the permission field correctly in the collection. Otherwise, if the collection is not allowed to be nested, you won't be able to create a bundle of nested tokens in it.

export async function createCollection(sdk, address) {
  const { parsed, error } = await sdk.collections.creation.submitWaitResult({
    address,
    name: 'Test collection',
    description: 'My test collection',
    tokenPrefix: 'TST',
    permissions: {
      nesting: {
        tokenOwner: true,
        collectionAdmin: true,
      },
    },
  });

  if (error) {
    console.log('create collection error', error);
    process.exit();
  }

  const { collectionId } = parsed;

  return sdk.collections.get({ collectionId });
}

Token creation

export async function createToken(sdk, address, collectionId) {
  const { parsed, error } = await sdk.tokens.create.submitWaitResult({
    address,
    collectionId,
  });

  if (error) {
    console.log('create token error', error);
    process.exit();
  }

  const { tokenId } = parsed;

  return sdk.tokens.get({ collectionId, tokenId });
}

Creating a nested token

export async function createNestedToken(sdk, nestedArgs) {
  const { address, parent, nested } = nestedArgs;
  const { parsed, error } = await sdk.tokens.nest.submitWaitResult({
    address,
    parent,
    nested,
  });

  if (error) {
    console.log('create token error', error);
    process.exit();
  }

  const { collectionId, tokenId } = parsed;

  console.log(`Token ${tokenId} from collection ${collectionId} successfully nested`);

  return sdk.tokens.get({ collectionId, tokenId });
}

Example execute function

Now let's wrap all our steps in an asynchronous function and run it, watching the result in the console

async function main() {

  // get signer
  const signer = await KeyringProvider.fromMnemonic(mnemonic);
  const address = signer.instance.address;

  // initialize sdk
  const sdk = createSdk(signer);

  // create a collection
  const collection = await createCollection(sdk, address);
  console.log('collection', collection);

  // token creating 
  const token = await createToken(sdk, address, collection.id);
  console.log('token', token);
  const token2 = await createToken(sdk, address, collection.id);
  console.log('token2', token2);

  // nesting (put tokenId 2 in tokenId 1)
  const nestedToken = await createNestedToken(sdk, {
    address,
    parent: {
      collectionId: collection.id,
      tokenId: token.tokenId,
    },
    nested: {
      collectionId: collection.id,
      tokenId: token2.tokenId,
    },
  });

  console.log('nestedToken', nestedToken);
}

main();

IsBundle and getBundle

Let's look at some more functions provided by our library: IsBundle and getBundle. Add the following code inside the main function

  const isBundle = await sdk.tokens.isBundle({
    collectionId: collection.id,
    tokenId: 1,
  });

  const isBundle2 = await sdk.tokens.isBundle({
    collectionId: collection.id,
    tokenId: 2,
  });

  console.log('token 1 isBundle?', isBundle);
  console.log('token 2 isBundle?', isBundle2);

  const result: any = await sdk.tokens.getBundle({
    collectionId: collection.id,
    tokenId: 2,
  });

  console.log('getBundle', result);

Running the whole process, you will see that both the top-level and the nested token are part of the bundle.

Unnest

The reverse process is called unnest, when a token no longer belongs to any token and its owner becomes the wallet address.

Add to your js file unnesting function

export async function createUnNestedToken(sdk: any, nestedArgs: any) {
  const { address, parent, nested } = nestedArgs;
  const { parsed, error } = await sdk.tokens.unnest.submitWaitResult({
    address,
    parent,
    nested,
  });

  if (error) {
    console.log('create token error', error);
    process.exit();
  }

  const { collectionId, tokenId } = parsed;

  console.log(`Token ${tokenId} from collection ${collectionId} successfully unnested`);

  return sdk.tokens.get({ collectionId, tokenId });
}

and add its call to our functionmain

const unNestedToken = await createUnNestedToken(sdk, {
    address,
    parent: {
      collectionId: collection.id,
      tokenId: token.tokenId,
    },
    nested: {
      collectionId: collection.id,
      tokenId: token2.tokenId,
    },
  });

  console.log('unNestedToken', unNestedToken);

  const isBundleAfterUnnest = await sdk.tokens.isBundle({
    collectionId: collection.id,
    tokenId: 2,
  });

  console.log('token 2 isBundle?', isBundleAfterUnnest);

after unnesting you will see that the token with tokenId=2 is no longer part of the bundle. Neither is the token with tokenId=1 - because it no longer has any attached tokens.

Read more about this and other nesting functions here