approveOperationalFee
Grant operational-fee allowances to the gasless fee charger, as the paying sub-account.
import { approveOperationalFee, getOperationalFeeAllowance } from "@symmio/trading-core";
import { parseUnits } from "viem";
// A bounded fee budget of 5 collateral tokens, in 18-decimal Core units.
const hash = await approveOperationalFee(config, {
account: subAccount,
amounts: [parseUnits("5", 18)],
});
// Once the transaction has landed, read the allowance back before relying on it.
const { allowance } = await getOperationalFeeAllowance(config, { payer: subAccount });The diamond keys allowances by msg.sender, so the call is routed through the AccountLayer _call proxy — the diamond sees the sub-account as the payer. A direct wallet call would grant the allowance from the wrong account. The connected wallet must be the sub-account’s owner. Under the hood this encodes approveOperationalFeeWithMultiplier(chargers[], amounts[], feeMultipliers[]); chargers defaults to the chain’s gasless.gaslessLayerAddress and feeMultipliers to OPERATIONAL_FEE_LIST_PRICE_MULTIPLIER (10000n — list price, in basis points). Because the call always sets a multiplier, omitting feeMultipliers resets a previously set one to list price.
Amounts and what an approval does
- Amounts are 18-decimal Core units, the scale of the Core balance fees are drawn from, not the collateral token’s decimals. On a 6-decimal USDC chain
parseUnits("5", 18)is a 5 USDC budget, whileparseUnits("5", 6)would allow only 0.000000000005 USDC. To start from a token amount, convert it withcollateralToCore18. - Each amount replaces the current allowance. It works like ERC-20
approve, not an increment, and every relayed charge decrements it. Choose a bounded budget sized to the operations you intend to relay, notmaxUint256: a bounded allowance caps what the charger can ever draw from the payer in fees. - The charger is the GaslessLayer proxy (
gasless.gaslessLayerAddress), not the relayer’s executor address. An ERC-20approveof the collateral token is a different permission and grants nothing here. - Approving adds no balance. The fee is still drawn from the payer’s Core balance after the operation executes, so fund the sub-account as well as approving it.
Grant the allowance before relying on gasless execution. The service simulates every relay before accepting it, so
a payer without enough allowance is refused up front, with no request id. Today that arrives as HTTP 400
SIMULATION_REVERTED carrying the Core revert OperationalFee: Allowance exceeded in the vendor detail; a deployment
can also report the service code INSUFFICIENT_ALLOWANCE. Read either with
parseGaslessErrorDetail. A payer short of Core balance is refused the same way
with OperationalFee: Insufficient balance, which no approval fixes. This wallet-paid path is the one that always
works; the approval is itself relayable too (pass gasless: true once gasless execution is active). Reducing an
existing allowance can be timelocked on-chain — see
getOperationalFeeAllowance.
Parameters
accountAddressrequiredThe sub-account that pays operational fees; the connected wallet must be its owner.
amountsreadonly bigint[]requiredAllowance per charger, parallel to chargers, in 18-decimal Core units. Each value replaces the current
allowance. Use a bounded budget.
chargersreadonly Address[]optionalThe fee chargers to approve. Defaults to [gasless.gaslessLayerAddress], the GaslessLayer proxy.
feeMultipliersreadonly bigint[]optionalFee multiplier per charger in basis points; defaults to 10000n (list price) for every charger, replacing any
multiplier set before.
gaslessboolean | GaslessWriteOptionsoptionalRelay the approval itself through the gasless service — see Relayable writes.
chainIdnumberoptionalTarget chain id. Defaults to the config’s defaultChainId.
fromAddressoptionalSigner override for the wallet-client resolver.
simulateBeforeWritebooleanoptionalDry-run the _call before broadcasting; defaults to the config’s global setting.
Returns
Promise<Hash>Throws
SymmError("validation", "GASLESS_CHARGER_REQUIRED")— nochargersgiven and the chain has no gasless block to default from.SymmError("validation", "OPERATIONAL_FEE_APPROVAL_MISMATCH")—chargers/amounts/feeMultiplierslengths disagree.SymmErrorwhen the chain is unsupported or no wallet client is available; viem errors from the_callwrite.