2.41 FeeProxy (Native API example) At a glance What it is Pay gas in a token other than XRP, around a remark, a batch, an EVM call and a FuturePass proxy call. Futureverse’s Native API example for The Root Network, with its README and source. Where it sits Part II — Building · Chapter 2.41 Covers Examples · Source files Source github.com/futureversecom/trn-examples/tree/main/examples/substrate/use-feeProxy
| At a glance | |
|---|---|
| What it is | Pay gas in a token other than XRP, around a remark, a batch, an EVM call and a FuturePass proxy call. Futureverse’s Native API example for The Root Network, with its README and source. |
| Where it sits | Part II — Building · Chapter 2.41 |
| Covers | Examples · Source files |
| Source | github.com/futureversecom/trn-examples/tree/main/examples/substrate/use-feeProxy |
Pay gas in a token other than XRP, around a remark, a batch, an EVM call and a FuturePass proxy call. Reference: feeProxy pallet.
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://root.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
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.
2.41.1 Examples
bash
# change your working directory to this example first
cd examples/substrate/use-feeProxy
# export all required environments
export CALLER_PRIVATE_KEY=
# `feeProxy.callWithFeePreferences` that wraps around `system.remarkWithEvent`
pnpm call:callSystemRemark
# `feeProxy.callWithFeePreferences` that wraps around `utility.batchAll`
pnpm call:callBatchAll
# `feeProxy.callWithFeePreferences` that wraps around `evm.call`
pnpm call:callEVMCall
# `feeProxy.callWithFeePreferences` that wraps around `futurepass.proxyExtrinsic`
pnpm call:callProxyExtrinsic
# `feeProxy.callWithFeePreferences` that wraps around `futurepass.proxyExtrinsic` then `emv.call`
pnpm call:callProxyExtrinsicEVMCall2.41.2 Source files
src/callBatchAll.ts2.41.2.1
ts
import { ALICE, BOB, CHARLIE } from "@trne/utils/accounts";
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { ASTO_ASSET_ID, XRP_ASSET_ID } from "@trne/utils/porcini-assets";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";
interface AmountsIn {
Ok: [number, number];
}
/**
* Use `feeProxy.callWithFeePreferences` to trigger `utility.batchAll` call, which contain multiple
* calls of `assets.trafer`, and pay gas in ASTO token.
*
* Assumes the caller has ASTO balance.
*/
withChainApi("porcini", async (api, caller, logger) => {
/**
* 1. Create `utility.batchAll` call that batches 3 transfer calls to ALICE, BOB & CHARLIE
*/
const oneASTO = 1 * Math.pow(10, 18); // 1 ASTO in `wei` unit
const transferToAliceCall = api.tx.assets.transfer(ASTO_ASSET_ID, ALICE, oneASTO.toString());
const transferToBobCall = api.tx.assets.transfer(ASTO_ASSET_ID, BOB, oneASTO.toString());
const transferToCharlieCall = api.tx.assets.transfer(ASTO_ASSET_ID, CHARLIE, oneASTO.toString());
logger.info(
{
parameters: [
transferToAliceCall.toJSON(),
transferToBobCall.toJSON(),
transferToCharlieCall.toJSON(),
],
},
`create a "system.remarkWithEvent"`
);
const batchAllCall = api.tx.utility.batchAll([
transferToAliceCall,
transferToBobCall,
transferToCharlieCall,
]);
/**
* 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote
*/
// we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation
const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
0,
batchAllCall
);
const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address);
const estimatedFee = paymentInfo.partialFee.toString();
// query the the `dex` to determine the `maxPayment` you are willing to pay
const {
Ok: [amountIn],
} = (await api.rpc.dex.getAmountsIn(estimatedFee, [
ASTO_ASSET_ID,
XRP_ASSET_ID,
])) as unknown as AmountsIn;
// allow a buffer to avoid slippage, 5%
const maxPayment = Number(amountIn * 1.05).toFixed();
/**
* 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic
*/
logger.info(
{
parameters: {
paymentAsset: ASTO_ASSET_ID,
maxPayment,
call: batchAllCall.toJSON(),
},
},
`create a "feeProxy.callWithFeePreferences"`
);
const feeProxyCall = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
maxPayment,
batchAllCall
);
logger.info(`dispatch extrinsic from caller="${caller.address}"`);
const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger });
const [proxyEvent, batchEvent, aliceTransferEvent, bobTransferEvent, charlieTransferEvent] =
filterExtrinsicEvents(result.events, [
"FeeProxy.CallWithFeePreferences",
"Utility.BatchCompleted",
{ name: "Assets.Transferred", key: "to", data: { value: ALICE, type: "T::AccountId" } },
{ name: "Assets.Transferred", key: "to", data: { value: BOB, type: "T::AccountId" } },
{ name: "Assets.Transferred", key: "to", data: { value: CHARLIE, type: "T::AccountId" } },
]);
logger.info(
{
result: {
extrinsicId,
blockNumber: result.blockNumber,
proxyEvent: formatEventData(proxyEvent.event),
batchEvent: formatEventData(batchEvent.event),
aliceTransferEvent: formatEventData(aliceTransferEvent.event),
bobTransferEvent: formatEventData(bobTransferEvent.event),
charlieTransferEvent: formatEventData(charlieTransferEvent.event),
},
},
"receive result"
);
});src/callEVMCall.ts2.41.2.2
ts
import { ApiPromise } from "@polkadot/api";
import { ERC20_ABI } from "@therootnetwork/evm";
import { ALICE } from "@trne/utils/accounts";
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { getERC20Contract } from "@trne/utils/getERC20Contract";
import { getFeeProxyPricePair } from "@trne/utils/getFeeProxyPricePair";
import { Logger } from "@trne/utils/getLogger";
import { ASTO_ASSET_ID, SYLO_ASSET_ID, XRP_ASSET_ID } from "@trne/utils/porcini-assets";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";
import { provideEthersProvider } from "@trne/utils/withEthersProvider";
import { BigNumber, utils } from "ethers";
interface AmountsIn {
Ok: [number, number];
}
/**
* Use `feeProxy.callWithFeePreferencs` to trigger `evm.call`
*
* Assumes the caller has ASTO balance to pay for gas and SYLO balance to demonstrate
* the transfer
*/
withChainApi("porcini", async (api, caller, logger) => {
/**
* 1. Create `emv.call` call
*/
const { call: evmCall, estimateGasCost } = await createEVMCall(caller.address, api, logger);
/**
* 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote
*/
// we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation
const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
0,
evmCall
);
const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address);
// we need to add the actual estimate cost for the EVM layer call as part of the estimation
const estimatedFee = BigNumber.from(paymentInfo.partialFee.toString())
.add(estimateGasCost)
.toString();
// query the the `dex` to determine the `maxPayment` you are willing to pay
const {
Ok: [amountIn],
} = (await api.rpc.dex.getAmountsIn(estimatedFee, [
ASTO_ASSET_ID,
XRP_ASSET_ID,
])) as unknown as AmountsIn;
// allow a buffer to avoid slippage, 5%
const maxPayment = Number(amountIn * 1.05).toFixed();
/**
* 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic
*/
logger.info(
{
parameters: {
paymentAsset: ASTO_ASSET_ID,
maxPayment,
call: evmCall.toJSON(),
},
},
`create a "feeProxy.callWithFeePreferences"`
);
const feeProxyCall = api.tx.feeProxy.callWithFeePreferences(ASTO_ASSET_ID, maxPayment, evmCall);
logger.info(`dispatch extrinsic from caller="${caller.address}"`);
const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger });
const [proxyEvent, evmLogEvent, aliceTransferEvent] = filterExtrinsicEvents(result.events, [
"FeeProxy.CallWithFeePreferences",
"Evm.Log",
{ name: "Assets.Transferred", key: "to", data: { value: ALICE, type: "T::AccountId" } },
]);
logger.info(
{
result: {
extrinsicId,
blockNumber: result.blockNumber,
proxyEvent: formatEventData(proxyEvent.event),
evmLogEvent: formatEventData(evmLogEvent.event),
aliceTransferEvent: formatEventData(aliceTransferEvent.event),
},
},
"receive result"
);
});
async function createEVMCall(caller: string, api: ApiPromise, logger: Logger) {
// setup the actual EVM transaction, as interface
const { provider, wallet } = await provideEthersProvider("porcini");
const transferToken = getERC20Contract(SYLO_ASSET_ID).connect(wallet);
const transferAmount = utils.parseUnits("0.1", 18); // 0.1 SYLO
const transferInput = new utils.Interface(ERC20_ABI).encodeFunctionData("transfer", [
ALICE,
transferAmount,
]);
const transferEstimate = await transferToken.estimateGas.transfer(ALICE, transferAmount);
const { maxFeePerGas, estimateGasCost } = await getFeeProxyPricePair(
provider,
transferEstimate,
ASTO_ASSET_ID,
0.05
);
logger.info(
{
parameters: {
source: caller,
target: transferToken.address,
input: transferInput,
gasLimit: transferEstimate.toString(),
maxFeePerGas: maxFeePerGas.toString(),
},
},
`create an "emv.call"`
);
return {
call: api.tx.evm.call(
caller,
transferToken.address,
transferInput,
0,
transferEstimate.toString(),
maxFeePerGas.toString(),
0,
null,
[]
),
estimateGasCost,
};
}src/callProxyExtrinsic.ts2.41.2.3
ts
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { ASTO_ASSET_ID, XRP_ASSET_ID } from "@trne/utils/porcini-assets";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";
import assert from "node:assert";
interface AmountsIn {
Ok: [number, number];
}
/**
* Use `feeProxy.callWithFeePreferences` to trigger `system.remarkWithEvent` call via
* `futurepass.proxyExtrinsic`, and have Futurepass account pays gas in ASTO.
*
* Assumes the caller has a valid Futurepass account and ASTO balance to pay for gas.
*/
withChainApi("porcini", async (api, caller, logger) => {
/**
* 1. Create `futurepass.proxyExtrinsic` call that wraps around `system.remarkWithEvent` call
*/
const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrap();
logger.info(
{
futurepass: {
holder: caller.address,
account: fpAccount.toString(),
},
},
"futurepass details"
);
assert(fpAccount);
// can be any extrinsic, using `system.remarkWithEvent` for simplicity
const remarkCall = api.tx.system.remarkWithEvent("Hello World");
logger.info(
{
parameters: {
futurepass: fpAccount,
call: remarkCall.toJSON(),
},
},
`create a "futurepass.proxyExtrinsic"`
);
const futurepassCall = api.tx.futurepass.proxyExtrinsic(fpAccount, remarkCall);
/**
* 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote
*/
// we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation
const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
0,
futurepassCall
);
const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address);
const estimatedFee = paymentInfo.partialFee.toString();
// query the the `dex` to determine the `maxPayment` you are willing to pay
const {
Ok: [amountIn],
} = (await api.rpc.dex.getAmountsIn(estimatedFee, [
ASTO_ASSET_ID,
XRP_ASSET_ID,
])) as unknown as AmountsIn;
// allow a buffer to avoid slippage, 5%
const maxPayment = Number(amountIn * 1.05).toFixed();
/**
* 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic
*/
logger.info(
{
parameters: {
paymentAsset: ASTO_ASSET_ID,
maxPayment,
call: futurepassCall.toJSON(),
},
},
`create a "feeProxy.callWithFeePreferences"`
);
const feeProxyCall = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
maxPayment,
futurepassCall
);
logger.info(`dispatch extrinsic from caller="${caller.address}"`);
const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger });
const [proxyEvent, futurepassEvent, remarkEvent] = filterExtrinsicEvents(result.events, [
"FeeProxy.CallWithFeePreferences",
"Futurepass.ProxyExecuted",
"System.Remarked",
]);
logger.info(
{
result: {
extrinsicId,
blockNumber: result.blockNumber,
proxyEvent: formatEventData(proxyEvent.event),
futurepassEvent: formatEventData(futurepassEvent.event),
remarkEvent: formatEventData(remarkEvent.event),
},
},
"receive result"
);
});src/callProxyExtrinsicEVMCall.ts2.41.2.4
ts
import { ApiPromise } from "@polkadot/api";
import { ERC20_ABI } from "@therootnetwork/evm";
import { ALICE } from "@trne/utils/accounts";
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { getERC20Contract } from "@trne/utils/getERC20Contract";
import { getFeeProxyPricePair } from "@trne/utils/getFeeProxyPricePair";
import { Logger } from "@trne/utils/getLogger";
import { ASTO_ASSET_ID, SYLO_ASSET_ID, XRP_ASSET_ID } from "@trne/utils/porcini-assets";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";
import { provideEthersProvider } from "@trne/utils/withEthersProvider";
import { BigNumber, utils } from "ethers";
import assert from "node:assert";
interface AmountsIn {
Ok: [number, number];
}
/**
* Use `feeProxy.callWithFeePreferences` to trigger `emv.call` call via
* `futurepass.proxyExtrinsic`, and have Futurepass account pays gas in ASTO.
*
* Assumes the caller has a valid Futurepass account, ASTO balance to pay for gas and
* SYLO balance to demonstrate the transfer
*/
withChainApi("porcini", async (api, caller, logger) => {
/**
* 1. Create `futurepass.proxyExtrinsic` call that wraps around `evm.call`
*/
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: evmCall, estimateGasCost } = await createEVMCall(fpAccount.toString(), api, logger);
logger.info(
{
parameters: {
futurepass: fpAccount,
call: evmCall.toJSON(),
},
},
`create a "futurepass.proxyExtrinsic"`
);
const futurepassCall = api.tx.futurepass.proxyExtrinsic(fpAccount, evmCall);
/**
* 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote
*/
// we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation
const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
0,
futurepassCall
);
const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address);
// we need to add the actual estimate cost for the EVM layer call as part of the estimation
const estimatedFee = BigNumber.from(paymentInfo.partialFee.toString())
.add(estimateGasCost)
.toString();
// query the the `dex` to determine the `maxPayment` you are willing to pay
const {
Ok: [amountIn],
} = (await api.rpc.dex.getAmountsIn(estimatedFee, [
ASTO_ASSET_ID,
XRP_ASSET_ID,
])) as unknown as AmountsIn;
// allow a buffer to avoid slippage, 5%
const maxPayment = Number(amountIn * 1.05).toFixed();
/**
* 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic
*/
logger.info(
{
parameters: {
paymentAsset: ASTO_ASSET_ID,
maxPayment,
call: futurepassCall.toJSON(),
},
},
`create a "feeProxy.callWithFeePreferences"`
);
const feeProxyCall = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
maxPayment,
futurepassCall
);
logger.info(`dispatch extrinsic from caller="${caller.address}"`);
const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger });
const [proxyEvent, futurepassEvent, evmLogEvent, aliceTransferEvent] = filterExtrinsicEvents(
result.events,
[
"FeeProxy.CallWithFeePreferences",
"Futurepass.ProxyExecuted",
"Evm.Log",
{ name: "Assets.Transferred", key: "to", data: { value: ALICE, type: "T::AccountId" } },
]
);
logger.info(
{
result: {
extrinsicId,
blockNumber: result.blockNumber,
proxyEvent: formatEventData(proxyEvent.event),
futurepassEvent: formatEventData(futurepassEvent.event),
evmLogEvent: formatEventData(evmLogEvent.event),
aliceTransferEvent: formatEventData(aliceTransferEvent.event),
},
},
"receive result"
);
});
async function createEVMCall(caller: string, api: ApiPromise, logger: Logger) {
// setup the actual EVM transaction, as interface
const { provider, wallet } = await provideEthersProvider("porcini");
const transferToken = getERC20Contract(SYLO_ASSET_ID).connect(wallet);
const transferAmount = utils.parseUnits("0.1", 18); // 0.1 SYLO
const transferInput = new utils.Interface(ERC20_ABI).encodeFunctionData("transfer", [
ALICE,
transferAmount,
]);
const transferEstimate = await transferToken.estimateGas.transfer(ALICE, transferAmount);
const { maxFeePerGas, estimateGasCost } = await getFeeProxyPricePair(
provider,
transferEstimate,
ASTO_ASSET_ID,
0.05
);
logger.info(
{
parameters: {
source: caller,
target: transferToken.address,
input: transferInput,
gasLimit: transferEstimate.toString(),
maxFeePerGas: maxFeePerGas.toString(),
},
},
`create an "emv.call"`
);
return {
call: api.tx.evm.call(
caller,
transferToken.address,
transferInput,
0,
transferEstimate.toString(),
maxFeePerGas.toString(),
0,
null,
[]
),
estimateGasCost,
};
}src/callSystemRemark.ts2.41.2.5
ts
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { formatEventData } from "@trne/utils/formatEventData";
import { ASTO_ASSET_ID, XRP_ASSET_ID } from "@trne/utils/porcini-assets";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";
interface AmountsIn {
Ok: [number, number];
}
/**
* Use `feeProxy.callWithFeePreferences` to trigger `system.remarkWithEvent` call, and pay gas
* in ASTO token.
*
* Assumes the caller has ASTO balance.
*/
withChainApi("porcini", async (api, caller, logger) => {
/**
* 1. Create `system.remarkWithEvent` call
*/
// can be any extrinsic, using `system.remarkWithEvent` for simplicity
logger.info(
{
parameters: {
remark: "Hello World",
},
},
`create a "system.remarkWithEvent"`
);
const remarkCall = api.tx.system.remarkWithEvent("Hello World");
/**
* 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote
*/
// we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation
const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
0,
remarkCall
);
const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address);
const estimatedFee = paymentInfo.partialFee.toString();
// query the the `dex` to determine the `maxPayment` you are willing to pay
const {
Ok: [amountIn],
} = (await api.rpc.dex.getAmountsIn(estimatedFee, [
ASTO_ASSET_ID,
XRP_ASSET_ID,
])) as unknown as AmountsIn;
// allow a buffer to avoid slippage, 5%
const maxPayment = Number(amountIn * 1.05).toFixed();
/**
* 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic
*/
logger.info(
{
parameters: {
paymentAsset: ASTO_ASSET_ID,
maxPayment,
call: remarkCall.toJSON(),
},
},
`create a "feeProxy.callWithFeePreferences"`
);
const feeProxyCall = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
maxPayment,
remarkCall
);
logger.info(`dispatch extrinsic from caller="${caller.address}"`);
const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger });
const [proxyEvent, remarkEvent] = filterExtrinsicEvents(result.events, [
"FeeProxy.CallWithFeePreferences",
"System.Remarked",
]);
logger.info(
{
result: {
extrinsicId,
blockNumber: result.blockNumber,
proxyEvent: formatEventData(proxyEvent.event),
remarkEvent: formatEventData(remarkEvent.event),
},
},
"receive result"
);
});package.json2.41.2.6
json
{
"scripts": {
"call": "tsx --tsconfig ../../../tsconfig.json",
"call:callSystemRemark": "pnpm call src/callSystemRemark.ts",
"call:callProxyExtrinsic": "pnpm call src/callProxyExtrinsic.ts",
"call:callBatchAll": "pnpm call src/callBatchAll.ts",
"call:callEVMCall": "pnpm call src/callEVMCall.ts",
"call:callProxyExtrinsicEVMCall": "pnpm call src/callProxyExtrinsicEVMCall.ts"
}
}Source:
futureversecom/trn-examples, examples/substrate/use-feeProxy (deleted) · commit 44c9e16, 2024-10-31 · Apache-2.0, license · part of Code examplesAssociated docs
Where else you might go from here. Following one is optional — this chapter stands without them.
- Any-token Gas
See also— Users can pay gas fees using any approved token or have them covered by developers.- Back matter — read in order
See also— The whole of Back matter as one continuous page, reprinted from its chapters without change.- Code examples
See also— Futureverse’s code examples for The Root Network: scripts for the native API and the EVM, and two contract projects, each with its README and source.- Contents
See also— Every chapter in this book, each with the one line that tells you what you get by reading it.- FeeProxy (pallet)
See also— Call an internal call with specified gas token payment_asset: The token to be used for paying gas fees.- Front matter — read in order
See also— The whole of Front matter as one continuous page, reprinted from its chapters without change.- FuturePass
See also— FuturePass, the on-chain identity + proxy account model Futureverse built on The Root Network: what it is, where it lives on chain, and how a holder moves what's in it to their own wallet.- Guides for the code examples
See also— How Futureverse’s TRN code examples get a test account: create one in MetaMask and fund it from the Porcini faucet.