Skip to Content
Fermah Pay Authorising and submitting

Authorising and submitting are different acts

Everything in Fermah Pay follows from one split. Authorising a payment and submitting it to the blockchain are separate acts, and different parties can perform them.

The user authorises. Fermah submits. Authorising produces a signed message, which is free, needs no XLM, and touches no ledger. Submitting is a transaction that costs fees, and Fermah pays it.

The mechanism is Soroban authorization entries, defined in CAP-0046-11. It is not a Fermah invention and not a workaround: it exists in the Stellar protocol precisely so that the party who owns the funds and the party who pays for the transaction can be different.


How Soroban authorisation works

A Soroban contract function that moves a user’s funds calls require_auth(user). The Stellar protocol requires that the transaction carry an authorisation entry signed by the user’s key, covering the exact invocation: the contract address, the function name, and every argument. No XLM is needed from the user. The transaction’s fee-payer is whoever submits it.

This is structurally different from the EVM model in two ways that matter. First, the authorisation entry covers the full invocation tree — not just from, to, value, but the specific contract call and its arguments. If the contract function takes a slippage parameter, that parameter is inside the signed entry. The submitter cannot change it. Second, fee-bump transactions are a protocol-level primitive on Stellar: the operator wraps a signed transaction in a fee-bump envelope and pays the fee without any smart-contract infrastructure. No paymaster contract, no bundler, no entry-point contract.

Fermah Pay splits the work into three acts, held by three different parties. The user authorises the call with an entry. A submitter account signs and sequences the transaction that carries it. A separate fee account pays, through a fee bump. The fee-bump signature grants no contract authority, and the user’s entry does not cover who submits it or what it costs, so the user’s signature commits to exactly one call and nothing else.


What the user signs

The user’s wallet signs a Soroban authorisation entry. This is the whole of it:

SorobanAuthorizationEntry { credentials: SorobanCredentials::Address { address: // the user's Stellar address nonce: // unique, prevents replay signature_expiration_ledger // entry dies after this ledger signature // over the entry, by the address's key } root_invocation: { contract_id: // the target contract function_name: // the function being called args: // every argument, bound sub_invocations: // nested calls, also bound } }

The signature covers the entire invocation tree. Producing it happens inside the wallet: it costs nothing, requires no XLM, and does not reach the chain. The wallet might be Freighter, Albedo, a mobile wallet, or an embedded wallet created at sign-up that they operate by logging in. It can also be a contract account, a smart wallet whose own __check_auth decides what counts as its signature: a passkey, several signers, a spending policy. Fermah Pay accepts both kinds of address.

The credentials Fermah Pay prepares are AddressV2, available from protocol 27, which also bind the signed payload to the signer’s address.

The nonce is unique per authorisation. Stellar records consumed nonces as TEMPORARY contract data entries, which the ledger garbage-collects after their TTL expires. A nonce that has been consumed cannot be reused, and the signature_expiration_ledger bounds how long an unconsumed authorisation remains valid.


Why Fermah cannot alter it

A signature covers a specific sequence of bytes. Change one and the signature no longer corresponds to the message, and there is no way to edit a signed statement into a different valid one without the private key.

Holding a signed authorisation entry, Fermah has two options: submit it as signed, or do nothing. It cannot change the contract address, the function name, or any argument. It cannot extend signature_expiration_ledger or replay a consumed nonce. That is not a restraint Fermah’s code imposes on itself, which is the important part. Soroban’s runtime verifies the signature against the authorisation entry and checks the nonce against its own on-chain storage. Fermah’s software being wrong does not weaken any of it.


One payment, end to end

USER FERMAH PAY STELLAR | | | | 1. signs Soroban auth | | | entry (free, off- | | | chain) | | |-------------------------->| | | | 2. verify auth entry, | | | destination, window, | | | spend limits | | 3. immediate | | | acknowledgement | | |<--------------------------| | | | 4. wraps in fee-bump tx | | | + submits | | |-------------------------->| | | | 5. Soroban | | | verifies | | | auth entry, | | | checks nonce, | | | moves the | | | funds | | 6. decides the outcome | | | from the ledger | | |<--------------------------| | 7. the product reads the | | | final state | | |<--------------------------| | | | |

Step 2 is Fermah’s own work, and it happens before a unit of Fermah’s fees is committed. The gateway prepared the entry itself, for the buyer’s linked wallet and the seller’s own contract, so it checks what comes back against what it prepared: the same account, a byte-identical invocation tree, an expiration ledger still live and no more than about an hour ahead, and a valid signature by the account’s own key. A forged, altered or mistyped entry, or one signed for another contract, never reaches the chain at Fermah’s expense, and a product holding valid credentials cannot attribute somebody else’s signature to a buyer of its choosing. For a contract account, whose signature only its own code can interpret, the gateway goes further: it runs the exact call in enforcing simulation, as the network will, and refuses what the network would refuse or what would cost more than a set bound. And the request counts against the quotas in what the system cannot do. A request failing any of these is refused with nothing reserved.

Step 3 is why the product feels responsive. The caller is acknowledged once the request is validated and durably recorded, not once the chain confirms. Confirmation takes five to six seconds on Stellar, and a user waiting on every one of them feels every one of them, so the gateway writes down a durable intent, answers, and submits in the background.

Steps 6 and 7 are the price of that choice. Because the answer came early, the authoritative result arrives later. The worker decides it from the ledger itself, the transaction’s hash and the contract’s own records, never from the submission’s reply, and the product reads the final state of each deposit, charge and withdrawal through the same API. The software chapter explains why the Stellar implementation answers by query rather than by webhook.


Fermah pays the fees, and that has to be bounded

Fees come from a Stellar account the operator funds and controls. The fee-bump envelope’s fee_source is that account, so the fee is charged to the operator, not the user. Nothing reimburses it on-chain. It is a cost of running the product.

Which makes the third problem from the last chapter live, so it is bounded in several places. The contract refuses deposits below a minimum, so a deposit’s fee stays a small share of it. Every request that will cost a fee counts against daily quotas, per buyer and per seller deployment, counted in the same database transaction as the insert, with the buyer’s row locked, so N simultaneous requests cannot all pass a quota that fits one of them. No buyer’s transaction is sent if its resources would cost more than a set bound. And below a floor on the fee account’s balance, nothing new is sent. All are in what the system cannot do.


What the user ends up holding

That depends on which arrangement the product uses, and the difference is not cosmetic.

In the prepaid arrangement the user’s USDC moves once into the seller’s treasury, and in the same invocation the ledger contract credits their account, after which the product draws that balance down as the user consumes paid work. The user holds a claim on a balance, recorded on-chain, which they can withdraw back to their wallet.

In the recurring arrangement the USDC stays in the user’s wallet until each period is charged, and the user holds a mandate that bounds every charge and can revoke it at any moment.

Both are the next chapter.


Last updated on