A guide to using fee channels the right way
If you’ve built a Stellar application which creates or manages Stellar accounts there’s one slightly grimy issue that pops up for every single Stellar transaction. Fees. Who’s responsible for them? Where do they come from? How do you explain them in a way that makes sense?
Imagine if every time you spent money your bank took out a small fee. Sure, it’s only a tiny fraction of a penny, but still. You had $10 but now you have $9.99999. You deserve $10, you earned $10, you aren’t paying the bank to take out fees, so why are you losing money every time you utilize their services?
Most Stellar apps will justify the fees by claiming they are super low or that it’s an incredibly small price to pay for their services. Still though, for me, at the end of the day if a user has $10 they should see $10.
The obvious solution to this issue is that someone else will have to pay for the fees, namely you. You’ll need to find an alternative to pulling the fees from their account to pulling from one of your own accounts. Within Stellar this concept already exists and is actually incredibly easy to implement and several applications are already doing this. It’s through the concept of the transaction’s source account.
Transactions are made up of operations. Those operations are bundled together in order and run from a sending account. What most developers aren’t considering though is that there are two “fees” being consumed each time a transaction is submitted to the network. The first obvious one is the fees for the operations. Operations each take 0.00001 XLM (100 stroops), those fees are paid for by the sending account of the entire transaction. The second less obvious fee is the sequence number for the sending account. Knowing this will become essential very soon as we explore why the current method of utilizing a “fee channel” doesn’t work very well.
Fee channels are essentially allowing child accounts can issue operations but those operations will be bundled and sent from a singular “fee account” source which will consume any fees generated by the operations.
The issue with this is that if you don’t have control over the flow or timing of issuance for the transactions you’re going to very quickly run into issues with the sequence number for the fee account. If two child accounts both issue requests to the network at the same time they will both issue with the same sequence number but only one of those transactions will succeed as the sequence number, true to form, must progress sequentially. Controlling the flow of transaction issuance, while theoretically possible, is a fools errand as any successful implementation will greatly reduce the speed at which you can respond to requests. As your business succeeds you will be punished with slower and slower response times as requests have to be bundled and sent all from a single fee account.
These issues are not new and the way some are getting around this is by creating a fee account pair for each child account, but this has the obvious drawbacks that you’re potentially tying up loads of money into both fee and child accounts which could be used for just creating new child accounts. You also have to keep tabs on hundreds, thousands, (millions?) of fee accounts and ensure they stay topped up to meet the demands of your customers.
Fret not though, there is an incredibly simple solution which avoids all of these issues. The best of all the worlds with zero compromise on either the fee channel or the user side. Speedy, easy and affordable.
To explain my proposal I’m going to walk through performing all the actions on the Stellar Laboratory so you can see exactly what I’m doing.
To begin we’ll need to create both a master and a child account. Since the master account will create the child account let’s start there. We tackle that by simply creating a new testnet account and funding it via the friendbot.
Perfect! GC…43 will be our master account. Make note of the public and private keys for this master account and then generate another keypair for the child.
Nice! GD…FO it is. We won’t fund this child account via the friendbot though, for that we’ll head over to “Transaction Builder” and issue a “Create Account” operation sent from the master account.
Why a Starting Balance of 1.001 you ask? Well 1 XLM is the minimum account balance to even open a new account and 0.001 is to cover a single transaction with the maximum of 100 operations. While the fees will be repaid as one of the operations in any given transaction we’ll need enough to cover the validation check before a transaction is allowed to be submitted to the network. The program knows it will cost {x} amount to submit but it doesn’t know you plan on paying the source back as one of the operations of the transaction /shrug. Alright, so now we just need to sign and submit that transaction with the master account’s secret key.
Great! Now account GD…FO is funded with the minimum account balance. Just for kicks let’s go and submit a single operation transaction just to show that fees are being taken out of the child account for now. A simple dummy Bump Sequence operation should be innocent enough.
Make sure to sign with the child account’s secret key this time.
Finally let’s check the account balance to see that it’s sitting at 1.001 minus the 0.00001 fee. If my math is good it should be 1.00099.
And so it is, the native asset_type balance in the balances array is 1.0009900. Perfect, just what we were expecting. From now on any transaction we push, as long as we use my proposed fee channel method, should never drop that balance below 1.0009900.
So how can we perform actions consuming this account’s sequence number without consuming fees from this account’s balance? Simple! Add a repayment operation from a fee account as an operation to every transaction. So for example considering our last transaction let’s add a repayment operation from the master account and see if we get the desired outcome.
Notice the Source Account for the Payment operation is the master account and the Destination is the child account. Also notice the Amount is 0.0000200 not 0.0000100. We have to repay 1 stoop (0.0000100 XLM) for every single operation in the transaction including the repayment operation itself. Very important.
Next we sign. Don’t forget to sign with both the child and master account secret keys as we are performing actions from both of those sources. If this becomes an issue you could add either account as a signer to the other account and only include the one signer but for our purposes and most use cases including both signers will be ideal.
After signing we simply submit the transaction and then check to ensure that the child account balance is still sitting pretty at that 1.0009900 while the sequence number should have bumped from 1040155179745281 to 1040155179745282.
Score! That’s really all there is to it. Child accounts can be their own transaction issuers while not draining their own balance from fees. If you were to inspect the master account GC…43 you would find that its sequence number hasn’t budged since that initial account creation transaction. Awesome Mr. McPossum!