ES

TransferFeeConfig (extension)

Mint-side Token-2022 extension that charges a percentage fee on every transfer. Fees accumulate as withheld balances on recipient TokenAccounts and are claimed later by the withdraw authority.

Token-2022 Program 108 bytes

Sample: PYUSD Mint (Token-2022)

(cached; refreshes hourly · mainnet only)

Mint Owner Amount Delegate Option Delegate (empty) State Is Native Native (reserved) Delegated Amount Close Authority Option Close Authority (empty) Account Type (Token-2022) MintCloseAuthority (header) Close Authority PermanentDelegate (header) Delegate TransferFeeConfig (header) Transfer Fee Config Authority Withdraw Withheld Authority Fee Config Data ConfidentialTransferMint (header) ConfidentialTransferMint ConfidentialTransferFee (header) ConfidentialTransferFee TransferHook (header) Hook Authority Hook Program ID MetadataPointer (header) Pointer Authority Metadata Address TokenMetadata (header) Update Authority Mint Name Symbol URI Data
0 1 2 3 4 5 6 7 8 9 A B C D E F
0000 01 00 00 00 6c 91 aa 49 65 90 12 f8 ab e9 61 ec
0010 24 b6 10 74 92 65 47 53 2f f0 94 ed 5b 69 39 82
0020 db 98 73 13 12 cd 38 46 01 a4 02 00 06 01 01 00
0030 00 00 17 85 32 61 ef 6a b8 53 2a 67 f0 53 86 5a
0040 ad 31 29 3f cf 07 cf 12 0a b5 b9 a1 57 06 54 8d
0050 c0 2b 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00a0 00 00 00 00 00 01 03 00 20 00 17 85 32 61 ef 6a
00b0 b8 53 2a 67 f0 53 86 5a ad 31 29 3f cf 07 cf 12
00c0 0a b5 b9 a1 57 06 54 8d c0 2b 0c 00 20 00 17 85
00d0 32 61 ef 6a b8 53 2a 67 f0 53 86 5a ad 31 29 3f
00e0 cf 07 cf 12 0a b5 b9 a1 57 06 54 8d c0 2b 01 00
00f0 6c 00 17 85 32 61 ef 6a b8 53 2a 67 f0 53 86 5a
0100 ad 31 29 3f cf 07 cf 12 0a b5 b9 a1 57 06 54 8d
0110 c0 2b 17 85 32 61 ef 6a b8 53 2a 67 f0 53 86 5a
0120 ad 31 29 3f cf 07 cf 12 0a b5 b9 a1 57 06 54 8d
0130 c0 2b 00 00 00 00 00 00 00 00 5d 02 00 00 00 00
0140 00 00 00 00 00 00 00 00 00 00 00 00 5d 02 00 00
0150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 00
0160 41 00 17 85 32 61 ef 6a b8 53 2a 67 f0 53 86 5a
0170 ad 31 29 3f cf 07 cf 12 0a b5 b9 a1 57 06 54 8d
0180 c0 2b 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01a0 00 00 00 10 00 81 00 17 85 32 61 ef 6a b8 53 2a
01b0 67 f0 53 86 5a ad 31 29 3f cf 07 cf 12 0a b5 b9
01c0 a1 57 06 54 8d c0 2b 1c 37 e6 43 3b 73 04 dd 82
01d0 73 7a e4 0d 9b 8b f3 c4 9f 5b 0e 6c 49 a8 d5 33
01e0 28 b3 e5 06 90 1c 57 01 00 00 00 00 00 00 00 00
01f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0200 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0220 00 00 00 00 00 00 00 00 0e 00 40 00 17 85 32 61
0230 ef 6a b8 53 2a 67 f0 53 86 5a ad 31 29 3f cf 07
0240 cf 12 0a b5 b9 a1 57 06 54 8d c0 2b 00 00 00 00
0250 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0260 00 00 00 00 00 00 00 00 00 00 00 00 12 00 40 00
0270 17 85 32 61 ef 6a b8 53 2a 67 f0 53 86 5a ad 31
0280 29 3f cf 07 cf 12 0a b5 b9 a1 57 06 54 8d c0 2b
0290 17 92 48 3b 6c 8a 2a 87 b7 47 1d 81 4f 95 91 f9
02a0 39 5c 84 0a 9c e3 d9 f4 d5 ba 7d 3a 4b 8a 74 9e
02b0 13 00 ae 00 17 85 32 61 ef 6a b8 53 2a 67 f0 53
02c0 86 5a ad 31 29 3f cf 07 cf 12 0a b5 b9 a1 57 06
02d0 54 8d c0 2b 17 92 48 3b 6c 8a 2a 87 b7 47 1d 81
02e0 4f 95 91 f9 39 5c 84 0a 9c e3 d9 f4 d5 ba 7d 3a
02f0 4b 8a 74 9e 0a 00 00 00 50 61 79 50 61 6c 20 55
0300 53 44 05 00 00 00 50 59 55 53 44 4f 00 00 00 68
0310 74 74 70 73 3a 2f 2f 74 6f 6b 65 6e 2d 6d 65 74
0320 61 64 61 74 61 2e 70 61 78 6f 73 2e 63 6f 6d 2f
0330 70 79 75 73 64 5f 6d 65 74 61 64 61 74 61 2f 70
0340 72 6f 64 2f 73 6f 6c 61 6e 61 2f 70 79 75 73 64
0350 5f 6d 65 74 61 64 61 74 61 2e 6a 73 6f 6e 00 00
0360 00 00

What it is

TransferFeeConfig is a Token-2022 Mint extension that imposes a percentage-based fee on every transfer of the token. Fees don’t flow to a treasury at transfer time — they accumulate as withheld balances inside the recipient’s TokenAccount (via the paired TransferFeeAmount extension), and the withdraw_withheld_authority claims them later.

Why it exists

SPL Token has no native fees. Protocols (DEXes, payment apps, NFT royalty enforcers) charge fees by routing through an extra wallet at the application layer — lossy, hard to audit, easy to bypass. Token-2022 lets the Mint itself enforce a fee policy, so every transfer of the token pays the fee whether it’s going through a DEX, a wallet send, or a CPI from another protocol.

Byte layout

This is the payload of a TransferFeeConfig TLV entry (extension_type = 1, length = 108). The full on-chain entry is 4 bytes of TLV header (see the TLV layout primer) plus this 108-byte payload.

Offset Length Field Type Notes
0 32 transfer_fee_config_authority OptionalNonZeroPubkey Updates fee config. All-zero pubkey means None (disabled — fees frozen forever). No tag byte.
32 32 withdraw_withheld_authority OptionalNonZeroPubkey Claims withheld fees from TokenAccounts. All-zero means None (fees uncollectable).
64 8 withheld_amount u64 LE Total fees withheld at the Mint level (separate from per-account withheld amounts).
72 18 older_transfer_fee TransferFee struct Pre-update fee config — active until newer.epoch lands.
90 18 newer_transfer_fee TransferFee struct Active fee config from newer.epoch onwards.

The nested TransferFee struct is 18 bytes:

Offset Length Field Type Notes
0 8 epoch u64 LE Epoch this fee config became (or becomes) active.
8 8 maximum_fee u64 LE Absolute cap in atomic units (per transfer).
16 2 transfer_fee_basis_points u16 LE Fee rate in basis points (100 = 1%).

Total payload: 108 bytes.

Why two fee configs

Fee changes don’t take effect immediately. When the authority sets a new fee via SetTransferFee, the value goes into newer_transfer_fee with epoch set to two epochs ahead; until then, the network keeps charging older_transfer_fee. The two-config + epoch model gives holders a window to react before the new rate kicks in.

A decoder displaying the current fee must read the cluster’s current epoch and pick newer if current_epoch >= newer.epoch, otherwise older. Showing only newer.transfer_fee_basis_points is the canonical bug here.

Where you see it

PYUSD (PayPal’s stablecoin) carries this extension with a 0 basis-point config — present for compliance auditability without actually charging. Real-fee deployments are rarer but growing: revenue-sharing community tokens, RWA tokens with transfer royalties, some experimental memecoins that route fees back to a creator wallet.

Common gotchas

  • Token-2022 extensions use a third optional encoding: OptionalNonZeroPubkey. Same 32-byte size as a plain Pubkey with no tag byte — all-zero bytes mean None. Distinct from both SPL’s COption<Pubkey> (4-byte tag + 32 = 36 bytes) and Borsh’s Option<Pubkey> (1-byte tag + 32 = 33 bytes). Three optional encodings coexist in a single Token-2022 account.
  • Fees withhold, they don’t burn. The fee subtracts from the sender’s balance and lands in the recipient’s per-account TransferFeeAmount.withheld_amount extension. The withdraw_withheld_authority then sweeps these via WithdrawWithheldTokensFromAccounts and WithdrawWithheldTokensFromMint.
  • maximum_fee is per-transfer, not per-period. A 1% fee on a 1,000,000 USDC transfer with maximum_fee = 1000 caps at $0.001 (atomic units = 1000), not the $10,000 the basis points would imply on an uncapped fee.
  • Two-epoch update delay. Setting a new fee doesn’t take effect until next-next epoch — roughly four to five days in production. Plan UI copy and notifications around that lag.
  • Reading the active fee requires the cluster’s current epoch. Don’t display newer_transfer_fee parameters as “the fee” unless the epoch has rolled; otherwise users see a rate they aren’t actually paying yet.

Last verified: 2026-05-19