Skip to content

SFT (Native API example) ​

Create and update an SFT collection and its tokens, mint and transfer. Reference: sft pallet.

Copied from Futureverse’s futureversecom/trn-examples, deleted with its GitHub organisation, at commit 44c9e16 of 2024-10-31. Apache-2.0: license. Before you run it, read Running the examples.

Offline destinations

Some places this page points to did not answer when last checked:

  • docs-beta.therootnetwork.com — does not resolve (checked 2026-10-18)
  • stackblitz.com/github/futureversecom — StackBlitz loads these projects from the deleted futureversecom organisation on GitHub (checked 2026-10-18)

Run in StackBlitz (repository deleted, checked 2026-10-18) · Pallet Documentation (offline, checked 2026-10-18)

IMPORTANT

Ensure the following ENV vars are available before running the examples

Examples ​

bash
# change your working directory to this example first
cd examples/substrate/use-sft

# export all required environments
export CALLER_PRIVATE_KEY=

# creates new collection
pnpm call:createCollection

# update collection info
pnpm call:updateCollection

# create new token for a collection
pnpm call:createToken

# update token info
pnpm call:updateToken

# mint token(s)
pnpm call:mint

# transfer tokens(s)
pnpm call:transfer

Source files ​

src/createCollection.ts ​

ts
import { stringToHex } from "@polkadot/util";
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";

/**
 * Use `sft.createCollection` extrinsic to create a new SFT collection.
 *
 * Assumes the caller has XRP to pay for gas.
 */
withChainApi("porcini", async (api, caller, logger) => {
	const collectionName = "MyCollection";
	const collectionOwner = caller.address;
	const metadataScheme = stringToHex("https://example.com/token/");
	const royaltiesSchedule = {
		entitlements: [[collectionOwner, 10_000 /* one percent */]],
	};

	logger.info(
		{
			parameters: {
				collectionName,
				collectionOwner,
				metadataScheme,
				royaltiesSchedule,
			},
		},
		`create a "sft.createCollection" extrinsic`
	);
	const extrinsic = api.tx.sft.createCollection(
		collectionName,
		collectionOwner,
		metadataScheme,
		royaltiesSchedule
	);

	logger.info(`dispatch extrinsic from caller="${caller.address}"`);
	const { result, extrinsicId } = await sendExtrinsic(extrinsic, caller, { log: logger });
	const [createEvent] = filterExtrinsicEvents(result.events, ["Sft.CollectionCreate"]);

	logger.info(
		{
			result: {
				extrinsicId,
				blockNumber: result.blockNumber,
				createEvent: formatEventData(createEvent.event),
			},
		},
		"receive result"
	);
});

src/createToken.ts ​

ts
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";

const COLLECTION_ID = 269412;

/**
 * Use `sft.createToken` extrinsic to create a new token for a collection.
 *
 * Assumes the caller has XRP to pay for gas.
 */
withChainApi("porcini", async (api, caller, logger) => {
	const tokenName = "MyToken";
	const initialIssuance = 0;
	const maxIssuance = null;
	const tokenOwner = caller.address;
	const collectionId = COLLECTION_ID;

	logger.info(
		{
			parameters: {
				collectionId,
				tokenName,
				initialIssuance,
				maxIssuance,
				tokenOwner,
			},
		},
		`create a "sft.createToken" extrinsic`
	);

	const extrinsic = api.tx.sft.createToken(
		collectionId,
		tokenName,
		initialIssuance,
		maxIssuance,
		tokenOwner
	);

	logger.info(`dispatch extrinsic from caller="${caller.address}"`);
	const { result, extrinsicId } = await sendExtrinsic(extrinsic, caller, { log: logger });
	const [createEvent] = filterExtrinsicEvents(result.events, ["Sft.TokenCreate"]);

	logger.info(
		{
			result: {
				extrinsicId,
				blockNumber: result.blockNumber,
				createEvent: formatEventData(createEvent.event),
			},
		},
		"receive result"
	);
});

src/mint.ts ​

ts
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";

const COLLECTION_ID = 269412;
const TOKEN_ID = 0;

/**
 * Use `nft.mint` extrinsic to mint new tokens.
 *
 * Assumes the caller is the owner of the collection, and has XRP to pay for gas.
 */

withChainApi("porcini", async (api, caller, logger) => {
	const collectionId = COLLECTION_ID;
	const tokenId = TOKEN_ID;
	const tokenOwner = caller.address;
	const quantity = 10;
	const serialNumbers = [[tokenId, quantity]] as unknown as [[number, number]];

	logger.info(
		{
			parameters: {
				collectionId,
				serialNumbers,
				tokenOwner,
			},
		},
		`create a "sft.mint" extrinsic`
	);
	const extrinsic = api.tx.sft.mint(collectionId, serialNumbers, tokenOwner);

	logger.info(`dispatch extrinsic from caller="${caller.address}"`);
	const { result, extrinsicId } = await sendExtrinsic(extrinsic, caller, { log: logger });
	const [mintEvent] = filterExtrinsicEvents(result.events, ["Sft.Mint"]);

	logger.info(
		{
			result: {
				extrinsicId,
				blockNumber: result.blockNumber,
				mintEvent: formatEventData(mintEvent.event),
			},
		},
		"receive result"
	);
});

src/transfer.ts ​

ts
import { BOB } from "@trne/utils/accounts";
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";

const COLLECTION_ID = 269412;
const TOKEN_ID = 0;

/**
 * Use `sft.transfer` extrinsic to transfer tokens to someone else.
 *
 * Assumes the caller is the owner of the transferring tokens, and has XRP to pay for gas.
 */
withChainApi("porcini", async (api, caller, logger) => {
	const collectionId = COLLECTION_ID;
	const tokenId = TOKEN_ID;
	const quantity = 1;
	const newOwner = BOB;
	const serialNumbers = [[tokenId, quantity]] as unknown as [[number, number]];

	logger.info(
		{
			parameters: {
				collectionId,
				serialNumbers,
				newOwner,
			},
		},
		`create a "sft.transfer" extrinsic`
	);
	const extrinsic = api.tx.sft.transfer(collectionId, serialNumbers, newOwner);

	logger.info(`dispatch extrinsic from caller="${caller.address}"`);
	const { result, extrinsicId } = await sendExtrinsic(extrinsic, caller, { log: logger });
	const [transferEvent] = filterExtrinsicEvents(result.events, ["Sft.Transfer"]);

	logger.info(
		{
			result: {
				extrinsicId,
				blockNumber: result.blockNumber,
				transferEvent: formatEventData(transferEvent.event),
			},
		},
		"receive result"
	);
});

src/updateCollection.ts ​

ts
import { stringToHex } from "@polkadot/util";
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";

const COLLECTION_ID = 269412;

/**
 * Use `sft.setBaseUri` extrinsic to update the collection `metadataSchheme`.
 *
 * Similar extrinsics are available to update different properties of a collection
 *  - setName
 *  - setOwner
 *  - setRoyaltiesSchedule
 *  Check out the pallet documentation (linked in README) for more details
 *
 * Assumes the caller is the owner of the collection, and has XRP to pay for gas.
 */
withChainApi("porcini", async (api, caller, logger) => {
	const baseUri = stringToHex("https://example.com/token/");
	const collectionId = COLLECTION_ID;

	logger.info(
		{
			parameters: {
				collectionId,
				baseUri,
			},
		},
		`create a "sft.setBaseUri" extrinsic`
	);
	const extrinsic = api.tx.sft.setBaseUri(collectionId, baseUri);

	logger.info(`dispatch extrinsic from caller="${caller.address}"`);
	const { result, extrinsicId } = await sendExtrinsic(extrinsic, caller, { log: logger });
	const [setEvent] = filterExtrinsicEvents(result.events, ["Sft.BaseUriSet"]);

	logger.info(
		{
			result: {
				extrinsicId,
				blockNumber: result.blockNumber,
				setEvent: formatEventData(setEvent.event),
			},
		},
		"receive result"
	);
});

src/updateToken.ts ​

ts
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";

const COLLECTION_ID = 269412;
const TOKEN_ID = 0;

/**
 * Use `sft.setMaxIssuance` extrinsic to update the token `maxIssuance`.
 *
 * Assumes the caller is the owner of the token, and has XRP to pay for gas.
 */
withChainApi("porcini", async (api, caller, logger) => {
	const maxIssuance = 1000;
	const tokenId = [COLLECTION_ID, TOKEN_ID] as [number, number];

	logger.info(
		{
			parameters: {
				tokenId,
				maxIssuance,
			},
		},
		`create a "sft.setMaxIssuance" extrinsic`
	);
	const extrinsic = api.tx.sft.setMaxIssuance(tokenId, maxIssuance);

	logger.info(`dispatch extrinsic from caller="${caller.address}"`);
	const { result, extrinsicId } = await sendExtrinsic(extrinsic, caller, { log: logger });
	const [setEvent] = filterExtrinsicEvents(result.events, ["Sft.MaxIssuanceSet"]);

	logger.info(
		{
			result: {
				extrinsicId,
				blockNumber: result.blockNumber,
				setEvent: formatEventData(setEvent.event),
			},
		},
		"receive result"
	);
});

package.json ​

json
{
  "scripts": {
    "call": "tsx --tsconfig ../../../tsconfig.json",
    "call:createCollection": "pnpm call src/createCollection.ts",
    "call:updateCollection": "pnpm call src/updateCollection.ts",
    "call:createToken": "pnpm call src/createToken.ts",
    "call:updateToken": "pnpm call src/updateToken.ts",
    "call:mint": "pnpm call src/mint.ts",
    "call:transfer": "pnpm call src/transfer.ts"
  }
}
Source: futureversecom/trn-examples, examples/substrate/use-sft (deleted) · commit 44c9e16, 2024-10-31 · Apache-2.0, license · part of Code examples

Curated independently. Source-attributed reference for The Root Network. Not affiliated with Futureverse / TRN Labs.