Skip to content

FuturePass (Native API example) ​

Create a FuturePass, send an extrinsic through it, register a delegate and decode a proxy error. Reference: futurepass 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.

You don’t need a FuturePass

This page treats FuturePass as something to use. You don’t need one: where the page uses a FuturePass, use an account in your own wallet instead — any EIP-6963 wallet app, MetaMask preferred. If you have a FuturePass, you can move what’s in it to your wallet at any time: https://futurepass.trn.codeology.co.nz/wallet/futurepass. FuturePass explains how it works.

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-futurepass

# export all required environments
export CALLER_PRIVATE_KEY=

# creates new FPass account
pnpm call:createAccount

# call an extrinsic as FPass account
pnpm call:proxyExtrinsic

# decode error when using proxyExtrinsic
pnpm call:decodeProxyError

# register a delegate to FPass account
pnpm call:registerDelegate

Source files ​

src/createAccount.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";
import { Wallet } from "ethers";

/**
 * Use `futurepass.create` extrinsic to create a new FPass account
 *
 * Assumes the caller has XRP to pay for gas, and some ROOT to reserve the account
 */
withChainApi("porcini", async (api, caller, logger) => {
	const account = Wallet.createRandom();

	logger.info(
		{
			parameters: {
				account: account.address,
			},
		},
		`create a "futurepass.create" extrinsic`
	);
	const extrinsic = api.tx.futurepass.create(account.address);

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

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

src/decodeProxyError.ts ​

ts
import { BN, hexToU8a } 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";
import assert from "node:assert";

/**
 * Use `futurepass.proxyExtrinsic` to dispatch an extrinsic that is set to failed deliberately
 * to demostrate how to extract and decode the error.
 *
 * Assumes the FPass account has XRP to pay for gas.
 */
withChainApi("porcini", async (api, caller, logger) => {
	const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrapOr(undefined);
	assert(fpAccount);
	logger.info(
		{
			futurepass: {
				holder: caller.address,
				account: fpAccount.toString(),
			},
		},
		"futurepass details"
	);

	// force error as Asset ID 3 does not exist
	const call = api.tx.assets.transfer(3, fpAccount, 1);

	logger.info(
		{
			parameters: {
				fpAccount,
				call,
			},
		},
		`create a "futurepass.proxyExtrinsic" extrinsic`
	);
	const extrinsic = api.tx.futurepass.proxyExtrinsic(fpAccount, call);

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

	const { err } = executedEvent.event.data[0].toJSON() as {
		err: {
			module: {
				index: number;
				error: `0x${string}`;
			};
		};
	};

	const { section, name, docs } = api.registry.findMetaError({
		index: new BN(err.module.index),
		error: hexToU8a(err.module.error),
	});

	logger.info(
		{
			error: {
				code: `${section}.${name}`,
				docs,
			},
		},
		`proxy executed error`
	);

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

src/proxyExtrinsic.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";
import assert from "node:assert";

/**
 * Use `futurepass.proxyExtrinsic` to call `system.remarkWithEvent` and have FPasss account
 * to pay for gas.
 *
 * Assume FPass account of the caller has XRP to pay for gas.
 */
withChainApi("porcini", async (api, caller, logger) => {
	const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrapOr(undefined);
	assert(fpAccount);
	logger.info(
		{
			futurepass: {
				holder: caller.address,
				account: fpAccount.toString(),
			},
		},
		"futurepass details"
	);

	const call = api.tx.system.remarkWithEvent("Hello World");

	logger.info(
		{
			parameters: {
				fpAccount,
				call,
			},
		},
		`create a "futurepass.proxyExtrinsic" extrinsic`
	);
	const extrinsic = api.tx.futurepass.proxyExtrinsic(fpAccount, call);

	logger.info(`dispatch extrinsic from caller="${caller.address}"`);
	const { result, extrinsicId } = await sendExtrinsic(extrinsic, caller, { log: logger });
	const [proxyEvent, remarkEvent] = filterExtrinsicEvents(result.events, [
		"Futurepass.ProxyExecuted",
		// depending on what extrinsic call you have, filter out the right event here
		"System.Remarked",
	]);

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

src/registerDelegate.ts ​

ts
import { fetchFinalisedHead } from "@trne/utils/fetchFinalisedHead";
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";
import { getEthersProvider } from "@trne/utils/withEthersProvider";
import { utils as ethers, Wallet } from "ethers";
import assert from "node:assert";

enum ProxyType {
	NoPermission = 0,
	Any = 1,
}

withChainApi("porcini", async (api, caller, logger) => {
	const provider = getEthersProvider("porcini");
	const delegate = Wallet.createRandom().connect(provider);
	const proxyType = ProxyType.Any;
	// recommended low number, 75 blocks ~= 5 minutes
	const deadline = (await fetchFinalisedHead(api)) + 75;

	const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrapOr(undefined);
	assert(fpAccount);
	logger.info(
		{
			futurepass: {
				holder: caller.address,
				account: fpAccount.toString(),
			},
		},
		"futurepass details"
	);

	const message = ethers
		.solidityKeccak256(
			["address", "address", "uint8", "uint32"],
			[fpAccount, delegate.address, proxyType, deadline]
		)
		.substring(2);
	logger.info(
		{
			payload: {
				fpAccount,
				address: delegate.address,
				proxyType,
				deadline,
			},
		},
		"require delegate to sign a payload"
	);
	const signature = await delegate.signMessage(message);

	logger.info(
		{
			parameters: {
				fpAccount,
				address: delegate.address,
				proxyType,
				deadline,
				signature,
			},
		},
		`create a "futurepass.registerDelegateWithSignature" extrinsic`
	);
	const extrinsic = api.tx.futurepass.registerDelegateWithSignature(
		fpAccount, //        Futurepass account to register the account as delegate
		delegate.address, //          The delegated account for the futurepass
		proxyType, //         Delegate permission level
		deadline, //          Deadline for the signature
		signature //          Signature of the message parameters
	);

	const { result, extrinsicId } = await sendExtrinsic(extrinsic, caller, { log: logger });
	const [delegatedEvent] = filterExtrinsicEvents(result.events, ["Futurepass.DelegateRegistered"]);

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

package.json ​

json
{
  "scripts": {
    "call": "tsx --tsconfig ../../../tsconfig.json",
    "call:createAccount": "pnpm call src/createAccount.ts",
    "call:proxyExtrinsic": "pnpm call src/proxyExtrinsic.ts",
    "call:decodeProxyError": "pnpm call src/decodeProxyError.ts",
    "call:registerDelegate": "pnpm call src/registerDelegate.ts"
  }
}
Source: futureversecom/trn-examples, examples/substrate/use-futurepass (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.