Fee grant
Terra's fee grant module inherits from the Cosmos SDK's feegrant module. This document is a stub and mainly covers important Terra-specific notes on how it is used.
This module allows an account, the granter, to permit another account, the grantee, to pay for fees from the granter's account balance. Grantees will not need to maintain their own balance for paying fees.
Concepts
Grant
Grant is stored in the KVStore to record a grant with full context.
Every grant contains the following information:
- 
granter: The account address that gives permission to the grantee.
- 
grantee: The beneficiary account address.
- 
allowance: The type of fee allowance given to the grantee.Allowanceaccepts an interface that implementsFeeAllowanceIencoded asAnytype as shown in the following example:_3// allowance can be any of basic and filtered fee allowance._3google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"];_3}The following example shows FeeAllowanceI:_17type FeeAllowanceI interface {_17// Accept can use fee payment requested as well as timestamp of the current block_17// to determine whether or not to process this. This is checked in_17// Keeper.UseGrantedFees and the return values should match how it is handled there._17//_17// If it returns an error, the fee payment is rejected, otherwise it is accepted._17// The FeeAllowance implementation is expected to update it's internal state_17// and will be saved again after an acceptance._17//_17// If remove is true (regardless of the error), the FeeAllowance will be deleted from storage_17// (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees)_17Accept(ctx sdk.Context, fee sdk.Coins, msgs []sdk.Msg) (remove bool, err error)_17_17// ValidateBasic should evaluate this FeeAllowance for internal consistency._17// Don't allow negative amounts, or negative periods for example._17ValidateBasic() error_17}
Only one fee grant is allowed between a granter and a grantee. Self-grants are prohibited.
Fee allowance types
The following types of fee allowances can be granted.
BasicAllowance
BasicAllowance permits the grantee to pay fees by using funds from the granter's account. If the threshold for either spend_limit or expiration is met, the grant is removed from the state.
_14// BasicAllowance implements Allowance with a one-time grant of tokens_14// that optionally expires. The grantee can use up to SpendLimit to cover fees._14message BasicAllowance {_14  option (cosmos_proto.implements_interface) = "FeeAllowanceI";_14_14  // spend_limit specifies the maximum amount of tokens that can be spent_14  // by this allowance and will be updated as tokens are spent. If it is_14  // empty, there is no spend limit and any amount of coins can be spent._14  repeated cosmos.base.v1beta1.Coin spend_limit = 1_14      [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];_14_14  // expiration specifies an optional time when this allowance expires_14  google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true];_14}
- 
spend_limit: The amount of tokens from the granter's account that the grantee can spend. This value is optional. If it is blank, no spend limit is assigned and the grantee can spend any amount of tokens from the granter's account before the expiration is met.
- 
expiration: The date and time when the grant expires. This value is optional. If it is blank, the grant does not expire.
To restrict the grantee when values for spend_limit and expiration are blank, revoke the grant.
PeriodicAllowance
PeriodicAllowance is a repeating fee allowance for a specified period and for a specified maximum number of tokens that can be spent within that period.
 PeriodicAllowance code 
_46// PeriodicAllowance extends Allowance to allow for both a maximum cap_46// as well as a limit per time period._46message PeriodicAllowance {_46  option (cosmos_proto.implements_interface) = "FeeAllowanceI";_46_46  // basic specifies a struct of `BasicAllowance`_46  BasicAllowance basic = 1 [(gogoproto.nullable) = false];_46_46  // period specifies the time duration in which period_spend_limit coins can_46  // be spent before that allowance is reset_46  google.protobuf.Duration period = 2 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false];_46_46  // period_spend_limit specifies the maximum number of coins that can be spent_46  // in the period_46  repeated cosmos.base.v1beta1.Coin period_spend_limit = 3_46      [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];_46_46  // period_can_spend is the number of coins left to be spent before the period_reset time_46  repeated cosmos.base.v1beta1.Coin period_can_spend = 4_46      [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];_46_46  // period_reset is the time at which this period resets and a new one begins,_46  // it is calculated from the start time of the first transaction after the_46  // last period ended_46  google.protobuf.Timestamp period_reset = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];_46}_46_46// AllowedMsgAllowance creates allowance only for specified message types._46message AllowedMsgAllowance {_46  option (gogoproto.goproto_getters)         = false;_46  option (cosmos_proto.implements_interface) = "FeeAllowanceI";_46_46  // allowance can be any of basic and filtered fee allowance._46  google.protobuf.Any allowance = 1 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"];_46_46  // allowed_messages are the messages for which the grantee has the access._46  repeated string allowed_messages = 2;_46}_46_46// Grant is stored in the KVStore to record a grant with full context_46message Grant {_46  // granter is the address of the user granting an allowance of their funds._46  string              granter   = 1;_46_46  // grantee is the address of the user being granted an allowance of another user's funds._46  string              grantee   = 2;
- 
basic: The instance ofBasicAllowance. It is optional. If empty, the grant will not have aspend_limitorexpiration.
- 
period: The duration thatPeriodicAllowanceis granted. After each period expires,period_spend_limitis reset.
- 
period_spend_limit: The maximum number of tokens that the grantee is allowed to spend during the period.
- 
period_can_spend: The number of tokens remaining to be spent before the period_reset time.
- 
period_reset: The time when the period ends and a new period begins.
Fee account flag
To run transactions that use fee grant from the CLI, specify the FeeAccount flag followed by the granter's account address. When this flag is set, clientCtx appends the granter's account address.
 FeeAccount code 
_12if clientCtx.FeeGranter == nil || flagSet.Changed(flags.FlagFeeAccount) {_12  granter, _ := flagSet.GetString(flags.FlagFeeAccount)_12_12  if granter != "" {_12    granterAcc, err := sdk.AccAddressFromBech32(granter)_12    if err != nil {_12      return clientCtx, err_12    }_12_12    clientCtx = clientCtx.WithFeeGranterAddress(granterAcc)_12  }_12}
_475package tx_475_475import (_475	"bufio"_475	"errors"_475	"fmt"_475	"net/http"_475	"os"_475_475	"github.com/spf13/pflag"_475_475	"github.com/cosmos/cosmos-sdk/client"_475	"github.com/cosmos/cosmos-sdk/client/flags"_475	"github.com/cosmos/cosmos-sdk/client/input"_475	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"_475	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"_475	sdk "github.com/cosmos/cosmos-sdk/types"_475	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"_475	"github.com/cosmos/cosmos-sdk/types/rest"_475	"github.com/cosmos/cosmos-sdk/types/tx"_475	"github.com/cosmos/cosmos-sdk/types/tx/signing"_475	authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"_475	authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"_475)_475_475// GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction_475// or sign it and broadcast it returning an error upon failure._475func GenerateOrBroadcastTxCLI(clientCtx client.Context, flagSet *pflag.FlagSet, msgs ...sdk.Msg) error {_475	txf := NewFactoryCLI(clientCtx, flagSet)_475	return GenerateOrBroadcastTxWithFactory(clientCtx, txf, msgs...)_475}_475_475// GenerateOrBroadcastTxWithFactory will either generate and print an unsigned transaction_475// or sign it and broadcast it returning an error upon failure._475func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {_475	if clientCtx.GenerateOnly {_475		return GenerateTx(clientCtx, txf, msgs...)_475	}_475_475	return BroadcastTx(clientCtx, txf, msgs...)_475}_475_475// GenerateTx will generate an unsigned transaction and print it to the writer_475// specified by ctx.Output. If simulation was requested, the gas will be_475// simulated and also printed to the same writer before the transaction is_475// printed._475func GenerateTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {_475	if txf.SimulateAndExecute() {_475		if clientCtx.Offline {_475			return errors.New("cannot estimate gas in offline mode")_475		}_475_475		_, adjusted, err := CalculateGas(clientCtx.QueryWithData, txf, msgs...)_475		if err != nil {_475			return err_475		}_475_475		txf = txf.WithGas(adjusted)_475		_, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: txf.Gas()})_475	}_475_475	tx, err := BuildUnsignedTx(txf, msgs...)_475	if err != nil {_475		return err_475	}_475_475	json, err := clientCtx.TxConfig.TxJSONEncoder()(tx.GetTx())_475	if err != nil {_475		return err_475	}_475_475	return clientCtx.PrintString(fmt.Sprintf("%s\n", json))_475}_475_475// BroadcastTx attempts to generate, sign, and broadcast a transaction with the_475// given set of messages. It will also simulate gas requirements if necessary._475// It will return an error upon failure._475func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {_475	txf, err := PrepareFactory(clientCtx, txf)_475	if err != nil {_475		return err_475	}_475_475	if txf.SimulateAndExecute() || clientCtx.Simulate {_475		_, adjusted, err := CalculateGas(clientCtx.QueryWithData, txf, msgs...)_475		if err != nil {_475			return err_475		}_475_475		txf = txf.WithGas(adjusted)_475		_, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: txf.Gas()})_475	}_475_475	if clientCtx.Simulate {_475		return nil_475	}_475_475	tx, err := BuildUnsignedTx(txf, msgs...)_475	if err != nil {_475		return err_475	}_475_475	if !clientCtx.SkipConfirm {_475		out, err := clientCtx.TxConfig.TxJSONEncoder()(tx.GetTx())_475		if err != nil {_475			return err_475		}_475_475		_, _ = fmt.Fprintf(os.Stderr, "%s\n\n", out)_475_475		buf := bufio.NewReader(os.Stdin)_475		ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr)_475_475		if err != nil || !ok {_475			_, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction")_475			return err_475		}_475	}_475_475	tx.SetFeeGranter(clientCtx.GetFeeGranterAddress())_475	err = Sign(txf, clientCtx.GetFromName(), tx, true)_475	if err != nil {_475		return err_475	}_475_475	txBytes, err := clientCtx.TxConfig.TxEncoder()(tx.GetTx())_475	if err != nil {_475		return err_475	}_475_475	// broadcast to a Tendermint node_475	res, err := clientCtx.BroadcastTx(txBytes)_475	if err != nil {_475		return err_475	}_475_475	return clientCtx.PrintProto(res)_475}_475_475// WriteGeneratedTxResponse writes a generated unsigned transaction to the_475// provided http.ResponseWriter. It will simulate gas costs if requested by the_475// BaseReq. Upon any error, the error will be written to the http.ResponseWriter._475// Note that this function returns the legacy StdTx Amino JSON format for compatibility_475// with legacy clients._475func WriteGeneratedTxResponse(_475	ctx client.Context, w http.ResponseWriter, br rest.BaseReq, msgs ...sdk.Msg,_475) {_475	gasAdj, ok := rest.ParseFloat64OrReturnBadRequest(w, br.GasAdjustment, flags.DefaultGasAdjustment)_475	if !ok {_475		return_475	}_475_475	gasSetting, err := flags.ParseGasSetting(br.Gas)_475	if rest.CheckBadRequestError(w, err) {_475		return_475	}_475_475	txf := Factory{fees: br.Fees, gasPrices: br.GasPrices}._475		WithAccountNumber(br.AccountNumber)._475		WithSequence(br.Sequence)._475		WithGas(gasSetting.Gas)._475		WithGasAdjustment(gasAdj)._475		WithMemo(br.Memo)._475		WithChainID(br.ChainID)._475		WithSimulateAndExecute(br.Simulate)._475		WithTxConfig(ctx.TxConfig)._475		WithTimeoutHeight(br.TimeoutHeight)_475_475	if br.Simulate || gasSetting.Simulate {_475		if gasAdj < 0 {_475			rest.WriteErrorResponse(w, http.StatusBadRequest, sdkerrors.ErrorInvalidGasAdjustment.Error())_475			return_475		}_475_475		_, adjusted, err := CalculateGas(ctx.QueryWithData, txf, msgs...)_475		if rest.CheckInternalServerError(w, err) {_475			return_475		}_475_475		txf = txf.WithGas(adjusted)_475_475		if br.Simulate {_475			rest.WriteSimulationResponse(w, ctx.LegacyAmino, txf.Gas())_475			return_475		}_475	}_475_475	tx, err := BuildUnsignedTx(txf, msgs...)_475	if rest.CheckBadRequestError(w, err) {_475		return_475	}_475_475	stdTx, err := ConvertTxToStdTx(ctx.LegacyAmino, tx.GetTx())_475	if rest.CheckInternalServerError(w, err) {_475		return_475	}_475_475	output, err := ctx.LegacyAmino.MarshalJSON(stdTx)_475	if rest.CheckInternalServerError(w, err) {_475		return_475	}_475_475	w.Header().Set("Content-Type", "application/json")_475	w.WriteHeader(http.StatusOK)_475	_, _ = w.Write(output)_475}_475_475// BuildUnsignedTx builds a transaction to be signed given a set of messages. The_475// transaction is initially created via the provided factory's generator. Once_475// created, the fee, memo, and messages are set._475func BuildUnsignedTx(txf Factory, msgs ...sdk.Msg) (client.TxBuilder, error) {_475	if txf.chainID == "" {_475		return nil, fmt.Errorf("chain ID required but not specified")_475	}_475_475	fees := txf.fees_475_475	if !txf.gasPrices.IsZero() {_475		if !fees.IsZero() {_475			return nil, errors.New("cannot provide both fees and gas prices")_475		}_475_475		glDec := sdk.NewDec(int64(txf.gas))_475_475		// Derive the fees based on the provided gas prices where_475		// fee = ceil(gasPrice * gasLimit)._475		fees = make(sdk.Coins, len(txf.gasPrices))_475_475		for i, gp := range txf.gasPrices {_475			fee := gp.Amount.Mul(glDec)_475			fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())_475		}_475	}_475_475	tx := txf.txConfig.NewTxBuilder()_475_475	if err := tx.SetMsgs(msgs...); err != nil {_475		return nil, err_475	}_475_475	tx.SetMemo(txf.memo)_475	tx.SetFeeAmount(fees)_475	tx.SetGasLimit(txf.gas)_475	tx.SetTimeoutHeight(txf.TimeoutHeight())_475_475	return tx, nil_475}_475_475// BuildSimTx creates an unsigned tx with an empty single signature and returns_475// the encoded transaction or an error if the unsigned transaction cannot be_475// built._475func BuildSimTx(txf Factory, msgs ...sdk.Msg) ([]byte, error) {_475	txb, err := BuildUnsignedTx(txf, msgs...)_475	if err != nil {_475		return nil, err_475	}_475_475	// Create an empty signature literal as the ante handler will populate with a_475	// sentinel pubkey._475	sig := signing.SignatureV2{_475		PubKey: &secp256k1.PubKey{},_475		Data: &signing.SingleSignatureData{_475			SignMode: txf.signMode,_475		},_475		Sequence: txf.Sequence(),_475	}_475	if err := txb.SetSignatures(sig); err != nil {_475		return nil, err_475	}_475_475	protoProvider, ok := txb.(authtx.ProtoTxProvider)_475	if !ok {_475		return nil, fmt.Errorf("cannot simulate amino tx")_475	}_475	simReq := tx.SimulateRequest{Tx: protoProvider.GetProtoTx()}_475_475	return simReq.Marshal()_475}_475_475// CalculateGas simulates the execution of a transaction and returns the_475// simulation response obtained by the query and the adjusted gas amount._475func CalculateGas(_475	queryFunc func(string, []byte) ([]byte, int64, error), txf Factory, msgs ...sdk.Msg,_475) (tx.SimulateResponse, uint64, error) {_475	txBytes, err := BuildSimTx(txf, msgs...)_475	if err != nil {_475		return tx.SimulateResponse{}, 0, err_475	}_475_475	// https://github.com/cosmos/cosmos-sdk/issues/7726_475	bz, _, err := queryFunc("/cosmos.tx.v1beta1.Service/Simulate", txBytes)_475	if err != nil {_475		return tx.SimulateResponse{}, 0, err_475	}_475_475	var simRes tx.SimulateResponse_475_475	if err := simRes.Unmarshal(bz); err != nil {_475		return tx.SimulateResponse{}, 0, err_475	}_475_475	return simRes, uint64(txf.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil_475}_475_475// PrepareFactory ensures the account defined by ctx.GetFromAddress() exists and_475// if the account number and/or the account sequence number are zero (not set),_475// they will be queried for and set on the provided Factory. A new Factory with_475// the updated fields will be returned._475func PrepareFactory(clientCtx client.Context, txf Factory) (Factory, error) {_475	from := clientCtx.GetFromAddress()_475_475	if err := txf.accountRetriever.EnsureExists(clientCtx, from); err != nil {_475		return txf, err_475	}_475_475	initNum, initSeq := txf.accountNumber, txf.sequence_475	if initNum == 0 || initSeq == 0 {_475		num, seq, err := txf.accountRetriever.GetAccountNumberSequence(clientCtx, from)_475		if err != nil {_475			return txf, err_475		}_475_475		if initNum == 0 {_475			txf = txf.WithAccountNumber(num)_475		}_475_475		if initSeq == 0 {_475			txf = txf.WithSequence(seq)_475		}_475	}_475_475	return txf, nil_475}_475_475// SignWithPrivKey signs a given tx with the given private key, and returns the_475// corresponding SignatureV2 if the signing is successful._475func SignWithPrivKey(_475	signMode signing.SignMode, signerData authsigning.SignerData,_475	txBuilder client.TxBuilder, priv cryptotypes.PrivKey, txConfig client.TxConfig,_475	accSeq uint64,_475) (signing.SignatureV2, error) {_475	var sigV2 signing.SignatureV2_475_475	// Generate the bytes to be signed._475	signBytes, err := txConfig.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx())_475	if err != nil {_475		return sigV2, err_475	}_475_475	// Sign those bytes_475	signature, err := priv.Sign(signBytes)_475	if err != nil {_475		return sigV2, err_475	}_475_475	// Construct the SignatureV2 struct_475	sigData := signing.SingleSignatureData{_475		SignMode:  signMode,_475		Signature: signature,_475	}_475_475	sigV2 = signing.SignatureV2{_475		PubKey:   priv.PubKey(),_475		Data:     &sigData,_475		Sequence: accSeq,_475	}_475_475	return sigV2, nil_475}_475_475func checkMultipleSigners(mode signing.SignMode, tx authsigning.Tx) error {_475	if mode == signing.SignMode_SIGN_MODE_DIRECT &&_475		len(tx.GetSigners()) > 1 {_475		return sdkerrors.Wrap(sdkerrors.ErrNotSupported, "Signing in DIRECT mode is only supported for transactions with one signer only")_475	}_475	return nil_475}_475_475// Sign signs a given tx with a named key. The bytes signed over are canonical._475// The resulting signature will be added to the transaction builder overwriting the previous_475// ones if overwrite=true (otherwise, the signature will be appended)._475// Signing a transaction with mutltiple signers in the DIRECT mode is not supprted and will_475// return an error._475// An error is returned upon failure._475func Sign(txf Factory, name string, txBuilder client.TxBuilder, overwriteSig bool) error {_475	if txf.keybase == nil {_475		return errors.New("keybase must be set prior to signing a transaction")_475	}_475_475	signMode := txf.signMode_475	if signMode == signing.SignMode_SIGN_MODE_UNSPECIFIED {_475		// use the SignModeHandler's default mode if unspecified_475		signMode = txf.txConfig.SignModeHandler().DefaultMode()_475	}_475	if err := checkMultipleSigners(signMode, txBuilder.GetTx()); err != nil {_475		return err_475	}_475_475	key, err := txf.keybase.Key(name)_475	if err != nil {_475		return err_475	}_475	pubKey := key.GetPubKey()_475	signerData := authsigning.SignerData{_475		ChainID:       txf.chainID,_475		AccountNumber: txf.accountNumber,_475		Sequence:      txf.sequence,_475	}_475_475	// For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on_475	// TxBuilder under the hood, and SignerInfos is needed to generate the_475	// sign bytes. This is the reason for setting SetSignatures here, with a_475	// nil signature._475	//_475	// Note: this line is not needed for SIGN_MODE_LEGACY_AMINO, but providing it_475	// also doesn't affect its generated sign bytes, so for the sake of simplicity,_475	// it is placed here._475	sigData := signing.SingleSignatureData{_475		SignMode:  signMode,_475		Signature: nil,_475	}_475	sig := signing.SignatureV2{_475		PubKey:   pubKey,_475		Data:     &sigData,_475		Sequence: txf.Sequence(),_475	}_475	var prevSignatures []signing.SignatureV2_475	if !overwriteSig {_475		prevSignatures, err = txBuilder.GetTx().GetSignaturesV2()_475		if err != nil {_475			return err_475		}_475	}_475	if err := txBuilder.SetSignatures(sig); err != nil {_475		return err_475	}_475_475	// Generate the bytes to be signed._475	bytesToSign, err := txf.txConfig.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx())_475	if err != nil {_475		return err_475	}_475_475	// Sign those bytes_475	sigBytes, _, err := txf.keybase.Sign(name, bytesToSign)_475	if err != nil {_475		return err_475	}_475_475	// Construct the SignatureV2 struct_475	sigData = signing.SingleSignatureData{_475		SignMode:  signMode,_475		Signature: sigBytes,_475	}_475	sig = signing.SignatureV2{_475		PubKey:   pubKey,_475		Data:     &sigData,_475		Sequence: txf.Sequence(),_475	}_475_475	if overwriteSig {_475		return txBuilder.SetSignatures(sig)_475	}_475	prevSignatures = append(prevSignatures, sig)_475	return txBuilder.SetSignatures(prevSignatures...)_475}_475_475// GasEstimateResponse defines a response definition for tx gas estimation._475type GasEstimateResponse struct {_475	GasEstimate uint64 `json:"gas_estimate" yaml:"gas_estimate"`_475}_475_475func (gr GasEstimateResponse) String() string {_475	return fmt.Sprintf("gas estimate: %d", gr.GasEstimate)_475}
_10func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) {_10	if w.tx.AuthInfo.Fee == nil {_10		w.tx.AuthInfo.Fee = &tx.Fee{}_10	}_10_10	w.tx.AuthInfo.Fee.Granter = feeGranter.String()_10_10	// set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo_10	w.authInfoBz = nil_10}
_22// Fee includes the amount of coins paid in fees and the maximum_22// gas to be used by the transaction. The ratio yields an effective "gasprice",_22// which must be above some miminum to be accepted into the mempool._22message Fee {_22  // amount is the amount of coins to be paid as a fee_22  repeated cosmos.base.v1beta1.Coin amount = 1_22      [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];_22_22  // gas_limit is the maximum gas that can be used in transaction processing_22  // before an out of gas error occurs_22  uint64 gas_limit = 2;_22_22  // if unset, the first signer is responsible for paying the fees. If set, the specified account must pay the fees._22  // the payer must be a tx signer (and thus have signed this field in AuthInfo)._22  // setting this field does *not* change the ordering of required signers for the transaction._22  string payer = 3;_22_22  // if set, the fee payer (either the first signer or the value of the payer field) requests that a fee grant be used_22  // to pay fees instead of the fee payer's own balance. If an appropriate fee grant does not exist or the chain does_22  // not support fee grants, this will fail_22  string granter = 4;_22}
The following example shows a CLI command with the --fee-account flag:
_1./terrad tx gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --from validator-key --fee-account=terra1fmcjjt6yc9wqup2r06urnrd928jhrde6gcld6n --chain-id=testnet --fees="10uluna"
Granted fee deductions
Fees are deducted from grants in the auth ante handler.
Gas
To prevent DoS attacks, using a filtered feegrant incurs gas. To ensure that all the grantee's transactions conform to the filter set by the granter, the SDK iterates over the allowed messages in the filter and charges 10 gas per filtered message. Then, the SDK iterates over the messages sent by the grantee to ensure the messages adhere to the filter, which also charges 10 gas per message. If the SDK finds a message that does not conform to the filter, the SDK stops iterating, and the transaction fails.
Gas is charged against the granted allowance. Ensure all of your existing messages conform to the filter before you send transactions using your allowance.
State
FeeAllowance
Fee allowances are identified by combining Granter (the account address that grants permission to another account to spend its available tokens on fees) with Grantee (the account address that receives permission to spend the granter's tokens on fees).
The following example shows how a fee allowance is stored in the state:
Grant: 0x00 | grantee_addr_len (1 byte) | grantee_addr_bytes | granter_addr_len (1 byte) | granter_addr_bytes -> ProtocolBuffer(Grant)
_9// Grant is stored in the KVStore to record a grant with full context_9type Grant struct {_9	// granter is the address of the user granting an allowance of their funds._9	Granter string `protobuf:"bytes,1,opt,name=granter,proto3" json:"granter,omitempty"`_9	// grantee is the address of the user being granted an allowance of another user's funds._9	Grantee string `protobuf:"bytes,2,opt,name=grantee,proto3" json:"grantee,omitempty"`_9	// allowance can be any of basic and filtered fee allowance._9	Allowance *types1.Any `protobuf:"bytes,3,opt,name=allowance,proto3" json:"allowance,omitempty"`_9}
Message Types
MsgGrantAllowance
A fee allowance grant will be created with the MsgGrantAllowance message.
_12// MsgGrantAllowance adds permission for Grantee to spend up to Allowance_12// of fees from the account of Granter._12message MsgGrantAllowance {_12  // granter is the address of the user granting an allowance of their funds._12  string              granter   = 1;_12_12  // grantee is the address of the user being granted an allowance of another user's funds._12  string              grantee   = 2;_12_12  // allowance can be any of basic and filtered fee allowance._12  google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"];_12}
MsgRevokeAllowance
A fee allowance grant will be revoked with the MsgRevokeAllowance message.
_8// MsgRevokeAllowance removes any existing Allowance from Granter to Grantee._8message MsgRevokeAllowance {_8  // granter is the address of the user granting an allowance of their funds._8  string granter = 1;_8_8  // grantee is the address of the user being granted an allowance of another user's funds._8  string grantee = 2;_8}