Skip to main content
Every action you take on Confiroll is gas-free. Confiroll achieves that with a fee-bump: you sign the inner transaction, and a Confiroll-operated sponsor wraps it in a CAP-15 fee-bump and pays the network fee. The sponsor never needs your secret. It only pays the outer fee, and on-chain your XLM balance does not move.

You sign

You keep custody. The inner transaction is sourced by your own account and signed on your device with signTransaction.

Sponsor pays

A separate hot key builds and signs the CAP-15 outer envelope and pays the fee. It cannot read the amount or move your balance.

0 XLM for you

On-chain, fee_account is the sponsor and your XLM delta is exactly 0. A zero-XLM account can still transact.

The fee-bump path

1

Client builds and signs the inner transaction

The browser builds a confidential_transfer with the employer as tx source and signs the envelope with signTransaction (Fork B). The amount is inside the proof; the secret never leaves the device.
2

POST /transfer { signedXDR }

The browser sends the signed inner transaction to payroll-api with a Bearer session.
3

API binds and validates

The API rejects the request unless the session is a Stellar (SEP-10) session and the inner transaction’s source equals the session’s account. You can only fee-bump your own transaction (otherwise 403).
4

Sponsor validates, then fee-bumps

The sponsor runs its guard-rails before the key is touched, wraps the inner tx via buildFeeBumpTransaction (outer fee = inner * 2), signs the bump with the sponsor key, submits over Soroban RPC, and polls to SUCCESS.
5

Result

. On-chain, fee_account is the sponsor and the employer’s XLM delta is 0.
A minimal client call looks like this:

Guard-rails

The sponsor is a guard-railed relay, not an open one. The sponsor enforces each rail before it signs:

Contract allow-list

Every invokeHostFunction op’s target contract must be in the configured allow-list. An empty allow-list refuses to even construct the sponsor: “that would be an open relay.”

Fee cap

The inner fee must be under a maximum (default 0.5 XLM / 5,000,000 stroops). The sponsor refuses anything above, capping its exposure per transaction.

Per-account quota

A per-source counter (default 200 per process lifetime) limits how many transactions any one account can have sponsored, so a single account can’t drain the sponsor.

Key separation

The sponsor’s hot signing key must hold no other authority. Construction fails if the sponsor pubkey is on the forbidden list (e.g. the deployer or a vault owner). The signing key sits behind a narrow interface that never exposes the secret to the caller.

Guard-rail reference

Additional checks: the sponsor refuses transactions sourced by itself, requires at least one operation, and only sponsors invokeHostFunction (and allow-listed-asset changeTrust) op types: anything else is “not sponsorable.” The sponsor increments the per-account quota counter only after an on-chain SUCCESS, so refused or failed attempts never consume your quota.

The CAP-15 outer fee

CAP-15 lets an outer envelope pay the fee for an inner transaction it wraps. Confiroll sets the outer fee to inner fee * 2. The doubling gives the fee-bump headroom over the inner transaction’s own declared fee, which the network requires for the bump to be strictly higher.
Worked example: if the inner confidential_transfer declares a fee of 1,000,000 stroops (0.1 XLM), the sponsor builds an outer envelope with a fee of 2,000,000 stroops (0.2 XLM) and signs only that outer envelope. Your inner transaction is left byte-for-byte as you signed it, so its signature stays valid. The sponsor pays the 2,000,000 stroops; your account pays 0.

Errors on /transfer

The API returns the shape { "error": string }. The status codes you can see:
The API binds tx.source == session.sub, so it rejects foreign-source fee-bumps with 403 before the sponsor ever sees them. A Privy (email) session cannot transfer: /transfer returns 501 for it, and a Stellar wallet (SEP-10) session is required.

The two fee-bump routes

There are two ways a signed transaction reaches the chain fee-sponsored:

Fork B /transfer (deployed)

The API sponsor fee-bumps the browser-signed transfer directly, using the guard-rails above. This is the real, non-custodial write path, proven end-to-end on testnet.

SDP queue relay

The relay inserts a row into SDP’s submission queue and its worker fee-bumps from a channel account. The relay submits it through SDP with channel-account fee-bumps.
  1. Fork B /transfer (the deployed non-custodial path). The API sponsor fee-bumps the browser-signed transfer directly, using the guard-rails above. This is proven end-to-end on testnet. Its request and error shapes are in the API reference.
  2. The headless SDP relay (the batch path). The relay inserts signed operations into SDP’s submission queue, and its worker fee-bumps them from a channel account. The relay submits it through SDP with channel-account fee-bumps. See SDP integration.
Both make your action cost 0 XLM; Fork B is the real, non-custodial write path.

FAQ

No. You sign the inner transaction on your device, and the sponsor signs only the outer CAP-15 envelope with its own separate hot key. The two signatures are independent. Confiroll holds zero user keys, so it cannot move your balance or read your amount.
Nothing. The per-account quota counter increments only after an on-chain SUCCESS. A guard-rail refusal (422), a foreign-source rejection (403), or a transaction that fails on-chain does not consume any of your 200-per-process quota.
A Privy (email) session is not bound to a Stellar signing key, so it cannot produce the self-signed inner transaction that /transfer fee-bumps. Only a Stellar (SEP-10) session, where sub is your G-address, can transfer. Log in with a Stellar wallet to use /transfer.