Skip to main content

Token Gating (Beta)

Overview

Token Gating in STABILITY refers to the system that allows transaction fees to be paid in multiple ERC-20 tokens instead of a single native coin. Unlike most blockchains, STABILITY has no native gas token — it doesn’t issue a built-in currency for fees. Instead, users can choose a supported ERC-20 token to pay fees, and validators choose which of those tokens they will accept as payment. This design effectively “decentralizes” the native token, making approved tokens function as gas currency.

The economic rationale is to improve usability and composability: projects can use their own token or stablecoins to cover transaction fees, eliminating the need for a separate gas coin. This enables a form of native token abstraction—from a user’s perspective, they pay gas in a token they already hold (e.g., USDC), rather than acquiring a separate coin or interfacing with STABILITY directly.

Despite the flexibility of multi-token fees, STABILITY still supports a public mempool. This is made possible by globally whitelisting the tokens validators can accept for fees—nodes only propagate transactions that use approved tokens. By gating the mempool to accepted tokens, STABILITY prevents spam transactions using worthless tokens while still offering flexibility to users and dApps.

In short, token gating allows public EVM transactions to occur without a native coin by enabling users to pay in whitelisted tokens and aligning validator incentives around those tokens. Validators earn fees in those tokens, and users benefit from a seamless experience.

Notably, users leveraging the Zero Gas Transaction or ZKT framework can bypass the use of gas tokens altogether.

How It Works

The set of tokens that can be used to pay fees is controlled by a whitelist called the Supported Tokens List. Only tokens on this approved list will be recognized for gas payment. If a token is not whitelisted, users cannot select it for fees. Any transaction attempting to use an unapproved token will be rejected or never included in a block. This ensures all fee tokens have been vetted by the STABILITY team. The list is updated by admins via the SupportedTokensManager precompile (Code) to add or remove allowed tokens​. A default token is designated on this list – if a user does not explicitly choose a fee token, the network will use the default​ for ATM transactions.


Example: Token Conversion Fee

For example, suppose the network's internal gas unit is notionally priced at 1 gwei, and the validator's conversion rate for USDC is set to 5x. This 5x represents a price of 5 gwei. In this case, the effective gas price in USDC would be calculated as: gas_price_token = 1 * 5 / 1e18 ≈ 5e-8 USDC per gas unit​. Let's assume USDC has 18 decimals, the transaction used 100,000 gas. The transaction contained no priority fee, as it is always ignored.

total_fee = gas_used * base_gas_price * conversion_rate
= 100,000 * 1 gwei * 5 / 1e18
= 100,000 * (5e-9 USDC)
= 0.0005 USDC.

Let's assume we want to use a token that is of a higher value. Let's assume we want to use MUFFIN token. Validators are willing to process transactions with a conversion rate of 0.1x. Again, this 0.1x represents a price of 0.1 gwei. Let's use the same parameters - we'll assume MUFFIN has 18 decimals and the transaction used 100,000 gas. The priority fee is always ignored.

total_fee = gas_used * base_gas_price * conversion_rate
= 100,000 * 1 gwei * 0.1 / 1e18
= 100,000 * (0.1e-9 MUFFIN)
= 0.00001 MUFFIN.

Fee Distribution and BSR

After fee conversion, STABILITY's fee distribution logic kicks in. The fee amount in the token is deducted from the user’s balance. The fee is split between the validator and the smart contract (dApp) that was called, according to the Business Share Revenue (BSR) rules. In practice, the validator’s portion of the fee goes to the validator’s account, and the dApp’s portion is deposited into the BSR vault to be claimed by the contract owner in the same token that was used to pay​ gas fees.

For example, if a user pays 0.05 USDC in gas fees on a swap contract and the BSR split is 50/50, the validator gets 0.025 USDC and the contract’s BSR vault accrues 0.025 USDC for the dApp developer. The fee token gating system works seamlessly with BSR: the vault holds balances in multiple tokens. Developers can query and claim rewards per token using getClaimableReward(dapp, token) and claimReward(dapp, token) on the FeeRewardsVault precompile (Code).

Whatever token the user pays in, that same token is distributed as a reward — no currency conversion is performed.

Mempool Inclusion Dynamics

Because not all validators may accept every token, a user’s chosen fee token can affect how quickly their transaction gets picked up. If a token is universally accepted, every block producer will include transactions with that token if the transaction is not underpriced, so the experience is similar to a normal gas coin.

If a token is less widely accepted – say only 20% of validators opt in – then a transaction paying with that token will only be included by those 20% of blocks. This means an effective slowdown in confirmation time. In this example, if blocks come every 2 seconds normally, a 20% acceptance rate would mean a maximum wait of 5x, or ~10 seconds for a transaction to be included​.

The more validators support a token, the smoother and faster the user’s transactions will be. This also puts downward market pressure on the conversion rate of the token. If no active validator supports the token, the transaction will never be executed. The transaction will remain pending until it times out or the user cancels it by changing fee token, underscoring the importance of using well-supported tokens.

In other words, the token gating system involves a handshake between user preferences and validator policies: users pick a token from the allowed list and validators indicate which tokens they’ll take and what rate they will accept it. The network only processes the transaction when there’s a match, then uses the validator’s conversion rate to charge the fee in that token. All of this occurs at the protocol level, allowing a user to effectively use a preferred token as the “native gas” for that transaction.


How to Use - Developers

Overview

Developers will be primarily interacting with the following three contracts when utilizing token gating on STABILITY -

ContractAddressDescriptionCode
SupportedTokensManager0x0000000000000000000000000000000000000801To query which fee tokens are supported network-wide (and what the default token is).Code
FeeTokenSelector0x0000000000000000000000000000000000000803For users (EOAs or contracts paying fees) to set or get their selected fee token.Code
ValidatorFeeTokenSelector0x0000000000000000000000000000000000000802For validators to configure accepted tokens and conversion rate controllers.Code

These precompiles are built into the runtime at a fixed address. They can be called like any smart contract, using a web3 library or via Solidity interfaces. Below is a developer guide for typical use-cases.

Examples

Let's say you can you want to check what tokens are currently whitelisted by the ATM to be used as gas fees, if a validator accepts.

All three functions below are view calls. You can interact with the Supported Token Manager precompile at 0x0000000000000000000000000000000000000801.

FunctionReturn TypeDescription
SupportedTokensManager.supportedTokens()address[]Returns the list of all ERC-20 token addresses that are currently whitelisted as fee tokens. A dApp or wallet can call this to present users with the available options. The order of the array is unspecified, but one of them will be the default token.
SupportedTokensManager.isTokenSupported(address token)boolQuick check if a given token address is in the whitelist. Returns true if the token can be used for fees.
SupportedTokensManager.defaultToken()addressReturns the address of the current default fee token. This is the token that will be used for fees if a user has not set any preference. By default, this might be a stablecoin or another primary token chosen by the network. Admins can update it via updateDefaultToken(address token), but as a developer you can treat it as a network constant between governance changes.

Ethers Example Code in Typescript

async function getSupportedFeeTokens() {
const provider = new ethers.JsonRpcProvider(/ YOUR RPC URL HERE /);
const supportedTokensManagerAddr =
"0x0000000000000000000000000000000000000801";
const abi = [
"function supportedTokens() view returns (address[])",
"function defaultToken() view returns (address)",
];
const stm = new ethers.Contract(supportedTokensManagerAddr, abi, provider);
const tokens = await stm.supportedTokens();
const decodedTokens = tokens.map((token: string) => ethers.getAddress(token));
console.log("Whitelisted fee tokens:", decodedTokens);
const defaultToken = await stm.defaultToken();
console.log("Default fee token:", defaultToken);
}

This would give you an array of allowed fee token addresses and the default token as a string.