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.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.
- 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. - 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.
FAQ
Does the sponsor ever hold my secret key?
Does the sponsor ever hold my secret key?
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.
What happens to my quota if a fee-bump fails?
What happens to my quota if a fee-bump fails?
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.Why does my Privy login get a 501 on /transfer?
Why does my Privy login get a 501 on /transfer?
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.