Vellar SDK
ExplorerGitHubOpen Vellar Wallet

Reference

Fees and Sponsorship

Every Vellar settlement charges the network fee to the facilitator's sponsor account, not the buyer. The buyer needs only the payment asset. This page explains what fees actually are, what they cost by payment type, and what happens when the ceiling is too low.

By the end of this page you will understand the difference between the fee bid, the fee charged, and the spend accounting estimate, know the real measured cost of each payment type in stroops, understand why policy-governed payments need a higher ceiling than the default, and know how the sponsor balance guards work.

The three numbers called "fee"

This is the most important thing on this page. Three different quantities all get called "the fee", and confusing them causes real debugging pain.

NameWhat it isWhere it appears
fee_chargedWhat the sponsor actually paid. The number on the receipt.fee_charged in any Horizon transaction response
bid (minResourceFee + BASE_FEE)What the facilitator offers before submission. Compared against MAX_TX_FEE_STROOPS. If the bid exceeds the ceiling, settlement is refused before submission and nothing is spent.Never visible externally
spend estimate500,000 stroops. A conservative constant used for budget accounting. Not a measurement, and not the ceiling.Internal accounting only

⚠️ The ceiling is compared against the bid, not the charge. A payment that bids 130,000 stroops is refused by a facilitator with a 100,000 stroop ceiling, even though the actual charge would have been only 86,000 stroops. The refusal happens before submission and costs nothing.

Real measured fee_charged values

These are confirmed on Horizon. They are what the sponsor actually paid, not estimates.

Payment typefee_chargedXLM equivalent
exact, classic keypair23,059 to 28,711 stroopsabout 0.0023 to 0.0029 XLM
exact, policy-governed smart account85,999 stroopsabout 0.0086 XLM
upto, smart account39,949 stroopsabout 0.0040 XLM

Verify any of these yourself:

# exact, keypair settlement
curl -s \
  "https://horizon-testnet.stellar.org/transactions/1da6f9e6a90b78da898c99dfefba8821b5f632b72f584968fb057fd8a298e039" \
  | python3 -c \
  "import json,sys; \
  d=json.load(sys.stdin); \
  print('fee_charged:', d['fee_charged']); \
  print('fee_account:', d['fee_account'])"
# fee_charged: 28711
# fee_account: GBUCR6H2... (the facilitator sponsor, not the buyer)

# exact, policy-governed smart account
curl -s \
  "https://horizon-testnet.stellar.org/transactions/a48818609704818b6e81c6c67c2e89bbace37d49b17819bf684eb6ad1da1d5a0" \
  | python3 -c \
  "import json,sys; \
  d=json.load(sys.stdin); \
  print('fee_charged:', d['fee_charged'])"
# fee_charged: 85999

Note: This settlement used an earlier sponsor account (GAJS3G2D...) from before the current channel-pool deployment. The fee payer is still the facilitator, not the buyer, which is the non-custodial property being demonstrated. Recent settlements are fee-bumped: the channel account appears as source_account and the sponsor GBUCR6H22CZC5OYHBJIEUS2JFZBOB63AHEGTCV6UEPMD2TMLKG2ZMIW4 appears as fee_account. Older settlements, from before the channel pool, show the sponsor in both fields. On either path the buyer's address appears in neither, and that is the non-custodial property to verify.

Neither source_account nor fee_account is ever the buyer. That is areFeesSponsored: true demonstrated on-chain rather than asserted.

Why policy-governed payments cost more

A policy-governed payment runs the spending-limit policy contract inside __check_auth during settlement, which adds Soroban compute cost on top of the base transfer.

Roughly:

  • Base transfer (keypair): 23,000 to 29,000 stroops
  • Policy execution overhead: 57,000 to 63,000 stroops
  • Total (policy-governed): about 86,000 stroops

That is roughly 3x a plain keypair settle, not the 7x to 9x that simulation estimates sometimes suggest. Simulation overbids to make sure the transaction is accepted; the actual charge comes in lower.

Note: The Vellar facilitator's default ceiling of 500,000 stroops is enough headroom for every measured settlement type. The reference x402.org facilitator defaults to 50,000 stroops, which is below the bid for any policy-governed payment. Such a payment sent to the reference facilitator is refused with fee_exceeds_maximum even though the payment is valid and the policy approved it. This is why the Vellar facilitator exists for agent payments.

The fee ceiling

MAX_TX_FEE_STROOPS (default 500,000) is compared against the bid before submission. If the bid exceeds it, the facilitator refuses the settlement without submitting.

The buyer is not charged on a ceiling refusal. Nothing is spent, and the error is fee_exceeds_maximum.

If you run your own facilitator and serve smart-account buyers, set MAX_TX_FEE_STROOPS to at least 200,000 to accommodate policy-governed payments. 500,000 gives comfortable headroom.

# In your .env or environment:
MAX_TX_FEE_STROOPS=500000

The sponsor balance guards

The facilitator monitors its sponsor account balance and refuses /settle before going on-chain when the balance is too low.

LevelThresholdAction
Soft floor25 XLM (250,000,000 stroops)Alerts, and settlement continues
Hard floor10 XLM (100,000,000 stroops)Refuses /settle with settlement_refused: sponsor_balance_low

The hard-floor refusal happens before submission, so nothing is spent. The buyer should retry once the operator has refunded the sponsor.

This protects against availability failures from an underfunded sponsor. Without it the facilitator would submit transactions that then fail on-chain with confusing errors.

⚠️ You cannot test this on testnet. Spend-control refusals, including sponsor_balance_low, are logged there but not enforced, so the settlement proceeds. They are enforced on pubnet.

Verifying fee sponsorship

Recent settlements are fee-bumped: the channel account appears as source_account and the sponsor GBUCR6H22CZC5OYHBJIEUS2JFZBOB63AHEGTCV6UEPMD2TMLKG2ZMIW4 appears as fee_account. Older settlements, from before the channel pool, show the sponsor in both fields. On either path the buyer's address appears in neither, and that is the non-custodial property to verify.

Print both fields, since checking only one tells you half the story:

curl -s \
  "https://horizon-testnet.stellar.org/transactions/b6712023355eaae20636da32a23909d0c74204ed0f6e46a6c6a10c06f4223ca4" \
  | python3 -c \
  "import json,sys; \
  d=json.load(sys.stdin); \
  print('source_account:', d['source_account']); \
  print('fee_account:', d['fee_account']); \
  print('successful:', d['successful'])"

Expected output:

source_account: GBG5UKF4EXHYOFQFHOO263NTZRFUSXKBRUOAPDZEKISA7CPLABH7ONV4
fee_account: GBUCR6H22CZC5OYHBJIEUS2JFZBOB63AHEGTCV6UEPMD2TMLKG2ZMIW4
successful: True

The first is a channel account, the second the facilitator's sponsor. If the buyer's address appears in either field, fee sponsorship is not working. Check that your facilitator's sponsor account is funded.

If you run your own facilitator

VariableDefaultWhat it controls
MAX_TX_FEE_STROOPS500,000Fee bid ceiling. Raise it if you serve smart-account buyers.
SPONSOR_SOFT_FLOOR_STROOPS250,000,000Alert threshold (25 XLM)
SPONSOR_HARD_FLOOR_STROOPS100,000,000Hard refusal threshold (10 XLM)
SPEND_CEILING_STROOPS50,000,000Global spend ceiling (5 XLM per window)
SPEND_WINDOW_MS60,000Spend window length in milliseconds

The spend ceiling and window are enforced on pubnet only. On testnet they are logged as would-reject and the settlement proceeds.

What the fee actually pays for

The Stellar network fee is charged to the facilitator's sponsor account, not the buyer, so the buyer needs only the payment asset such as USDC. The fee covers the ledger operation, and for a policy-governed payment that includes running __check_auth, which runs the spending-limit policy contract.

When it fails

ErrorCauseFix
fee_exceeds_maximumThe payment's bid exceeded MAX_TX_FEE_STROOPSRaise MAX_TX_FEE_STROOPS, or use the Vellar facilitator with its 500,000 default
settlement_refused: sponsor_balance_lowSponsor balance is below the hard floorFund the sponsor account with more XLM
Settlement succeeds but the buyer was charged XLMFee sponsorship is not workingCheck that the facilitator has a funded sponsor and advertises areFeesSponsored: true in /supported

Next steps