var img = document.createElement('img'); img.src = "https://terradocs.matomo.cloud//piwik.php?idsite=1&rec=1&url=https://docs.terra.money" + location.pathname; img.style = "border:0"; img.alt = "tracker"; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(img,s);
Skip to main content

Fee grant

ℹ️info

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. Allowance accepts an interface that implements FeeAllowanceI encoded as Any type as shown in the following example:


    _3
    // allowance can be any of basic and filtered fee allowance.
    _3
    google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"];
    _3
    }

    The following example shows FeeAllowanceI:


    _17
    type 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)
    _17
    Accept(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.
    _17
    ValidateBasic() 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.
_14
message 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.
_46
message 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.
_46
message 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
_46
message 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 of BasicAllowance. It is optional. If empty, the grant will not have a spend_limit or expiration.

  • period: The duration that PeriodicAllowance is granted. After each period expires, period_spend_limit is 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


_12
if 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
}


_475
package tx
_475
_475
import (
_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.
_475
func 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.
_475
func 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.
_475
func 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.
_475
func 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.
_475
func 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.
_475
func 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.
_475
func 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.
_475
func 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.
_475
func 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.
_475
func 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
_475
func 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.
_475
func 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.
_475
type GasEstimateResponse struct {
_475
GasEstimate uint64 `json:"gas_estimate" yaml:"gas_estimate"`
_475
}
_475
_475
func (gr GasEstimateResponse) String() string {
_475
return fmt.Sprintf("gas estimate: %d", gr.GasEstimate)
_475
}


_10
func (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.
_22
message 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.

☢️caution

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
_9
type 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.
_12
message 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.
_8
message 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
}