FuturePass (EVM example)
Create a FuturePass, make a call through it and register a delegate. Reference: FuturePass precompile.
Copied from Futureverse’s
futureversecom/trn-examples, deleted with its GitHub organisation, at commit44c9e16of 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) · Precompile Documentation (offline, checked 2026-10-18)
IMPORTANT
Ensure the following ENV vars are available before running the examples
CALLER_PRIVATE_KEY- Private key of an account that submits the transaction. Follow this guide to create and fund an account with some test tokens on Porcini (testnet) if you don't have one yet.
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 a call as FPass account
pnpm call:proxyCall
# register a delegate to FPass account
pnpm call:registerDelegateSource files
src/createAccount.ts
ts
import { FUTUREPASS_REGISTRAR_PRECOMPILE_ABI } from "@therootnetwork/evm";
import { filterTransactionEvents } from "@trne/utils/filterTransactionEvents";
import { getFuturepassRegistrarContract } from "@trne/utils/getFuturepassRegistrarContract";
import { withEthersProvider } from "@trne/utils/withEthersProvider";
import { ContractReceipt, Wallet } from "ethers";
/**
* Use `registrar.create` call to create a FPass account.
*
* Assumes the caller has XRP to pay for gas.
*/
withEthersProvider("porcini", async (provider, wallet, logger) => {
const owner = Wallet.createRandom();
const registrar = getFuturepassRegistrarContract().connect(wallet);
logger.info(
{
parameters: {
contractAddress: registrar.address,
owner: owner.address,
},
},
`create "registrar.create" call`
);
logger.info(`dispatch transaction from wallet=${wallet.address}`);
const tx = await registrar.create(owner.address);
const receipt = (await tx.wait()) as unknown as ContractReceipt;
const [createEvent] = filterTransactionEvents(FUTUREPASS_REGISTRAR_PRECOMPILE_ABI, receipt.logs, [
"FuturepassCreated",
]);
logger.info(
{
result: {
transactionHash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
createEvent: {
name: createEvent.name,
args: createEvent.args,
},
},
},
"receive result"
);
});src/proxyCall.ts
ts
import { ERC20_ABI, FUTUREPASS_PRECOMPILE_ABI } from "@therootnetwork/evm";
import { ALICE } from "@trne/utils/accounts";
import { filterTransactionEvents } from "@trne/utils/filterTransactionEvents";
import { getERC20Contract } from "@trne/utils/getERC20Contract";
import { getFuturepassContract } from "@trne/utils/getFuturepassContract";
import { getFuturepassRegistrarContract } from "@trne/utils/getFuturepassRegistrarContract";
import { ASTO_ASSET_ID } from "@trne/utils/porcini-assets";
import { withEthersProvider } from "@trne/utils/withEthersProvider";
import { ContractReceipt, utils } from "ethers";
import assert from "node:assert";
const CALL_TYPE = {
StaticCall: 0,
Call: 1,
DelegateCall: 2,
Create: 3,
Create2: 4,
};
/**
* Use `futurepass.proxyCall` call to call `erc20.transfer` and have the FPass account transfers
* the amount
*
* Assume wallet has XRP to pay for gas.
*/
withEthersProvider("porcini", async (provider, wallet, logger) => {
const registrar = getFuturepassRegistrarContract().connect(provider);
const fpAccount = await registrar.futurepassOf(wallet.address);
assert(fpAccount);
logger.info(
{
futurepass: {
holder: wallet.address,
account: fpAccount.toString(),
},
},
"futurepass details"
);
const fp = getFuturepassContract(fpAccount).connect(wallet);
const transferAmount = utils.parseUnits("0.1", 18); // 0.1 ASTO
const transferToken = getERC20Contract(ASTO_ASSET_ID);
const transferCalldata = new utils.Interface(ERC20_ABI).encodeFunctionData("transfer", [
ALICE,
transferAmount,
]);
logger.info(
{
parameters: {
callType: CALL_TYPE.Call,
callTarget: transferToken.address,
value: "0",
callData: transferCalldata,
},
},
`create a "futurepass.proxyCall" call`
);
logger.info(`dispatch transaction from wallet=${wallet.address}`);
const tx = await fp.proxyCall(CALL_TYPE.Call, transferToken.address, "0", transferCalldata);
const receipt = (await tx.wait()) as unknown as ContractReceipt;
const [transferEvent, executedEvent] = filterTransactionEvents(
[...ERC20_ABI, ...FUTUREPASS_PRECOMPILE_ABI],
receipt.logs,
["Transfer", "Executed"]
);
logger.info(
{
result: {
transactionHash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
transferEvent: {
name: transferEvent.name,
args: transferEvent.args,
},
executedEvent: {
name: executedEvent.name,
args: executedEvent.args,
},
},
},
"receive result"
);
});src/registerDelegate.ts
ts
import { FUTUREPASS_PRECOMPILE_ABI } from "@therootnetwork/evm";
import { filterTransactionEvents } from "@trne/utils/filterTransactionEvents";
import { getFuturepassContract } from "@trne/utils/getFuturepassContract";
import { getFuturepassRegistrarContract } from "@trne/utils/getFuturepassRegistrarContract";
import { withEthersProvider } from "@trne/utils/withEthersProvider";
import { ContractReceipt, utils as ethers, Wallet } from "ethers";
import assert from "node:assert";
enum ProxyType {
NoPermission = 0,
Any = 1,
}
/**
* Use `futurepass.registerDelegateWithSignature` call to add delegate to an existing FPass account
*
* Assumes wallet has XRP to pay for gas.
*/
withEthersProvider("porcini", async (provider, wallet, logger) => {
const delegate = Wallet.createRandom();
const proxyType = ProxyType.Any;
// recommended low number, 75 blocks ~= 5 minutes
const deadline = (await provider.getBlockNumber()) + 75;
const registrar = getFuturepassRegistrarContract().connect(provider);
const fpAccount = await registrar.futurepassOf(wallet.address);
assert(fpAccount);
logger.info(
{
futurepass: {
holder: wallet.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" call`
);
const fp = getFuturepassContract(fpAccount).connect(wallet);
logger.info(`dispatch transaction from wallet=${wallet.address}`);
const tx = await fp.registerDelegateWithSignature(
delegate.address,
proxyType,
deadline,
signature
);
const receipt = (await tx.wait()) as unknown as ContractReceipt;
const [delegatedEvent] = filterTransactionEvents(FUTUREPASS_PRECOMPILE_ABI, receipt.logs, [
"FuturepassDelegateRegistered",
]);
logger.info(
{
result: {
transactionHash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
delegatedEvent: {
name: delegatedEvent.name,
args: delegatedEvent.args,
},
},
},
"receive result"
);
});package.json
json
{
"scripts": {
"call": "tsx --tsconfig ../../../tsconfig.json",
"call:createAccount": "pnpm call src/createAccount.ts",
"call:proxyCall": "pnpm call src/proxyCall.ts",
"call:registerDelegate": "pnpm call src/registerDelegate.ts"
}
}Source:
futureversecom/trn-examples, examples/evm/use-futurepass (deleted) · commit 44c9e16, 2024-10-31 · Apache-2.0, license · part of Code examples