This guide demonstrates how to implement gas sponsorship on Cosmos networks using fee grants. Unlike EVM chains that use account abstraction for gasless transactions, Cosmos networks natively support gas sponsorship through fee grants, allowing a grantor address to pay for another account’s transaction fees.

Prerequisites

Para with CosmJS Setup

Understanding Fee Grants

Fee grants in Cosmos allow one account (grantor) to pay transaction fees for another account (grantee). This mechanism provides native gas sponsorship without requiring smart contracts or account abstraction. Key concepts:
  • Grantor: The account that pays for gas fees
  • Grantee: The account whose transactions are sponsored
  • Allowance: Defines spending limits and expiration for the grant
Only one fee grant is allowed per granter-grantee pair. Self-grants are not permitted.

Creating a Basic Fee Grant

Create a basic allowance to grant gas sponsorship to another address:
import { MsgGrantAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/tx";
import { BasicAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant";

const granterAddress = paraSigner.address; // From your Para signer setup
const granteeAddress = "cosmos1grantee..."; // Replace with actual grantee address

// Create basic allowance with spending limits
const basicAllowance = BasicAllowance.fromPartial({
  spendLimit: [{
    denom: "uatom",
    amount: "1000000" // 1 ATOM limit
  }],
  expiration: {
    seconds: BigInt(Math.floor(Date.now() / 1000) + 86400), // 24 hours from now
    nanos: 0
  }
});

// Create the grant message
const grantMsg = {
  typeUrl: "/cosmos.feegrant.v1beta1.MsgGrantAllowance",
  value: MsgGrantAllowance.fromPartial({
    granter: granterAddress,
    grantee: granteeAddress,
    allowance: {
      typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
      value: BasicAllowance.encode(basicAllowance).finish()
    }
  })
};

// Sign and broadcast the grant transaction
const result = await client.signAndBroadcast(
  granterAddress,
  [grantMsg],
  "auto", // Let the client estimate gas
  "Granting fee allowance"
);

Using Fee Grants as a Grantee

Once a fee grant is established, the grantee can perform transactions with sponsored gas fees:
// Create a signer for the grantee
const granteeSigner = new ParaProtoSigner(granteeParaInstance, "cosmos");
const granteeClient = await SigningStargateClient.connectWithSigner(rpcUrl, granteeSigner);

// Send tokens with sponsored gas
const result = await granteeClient.sendTokens(
  granteeAddress,
  "cosmos1recipient...",
  [{ denom: "uatom", amount: "100000" }], // 0.1 ATOM
  {
    amount: [{ denom: "uatom", amount: "5000" }],
    gas: "200000",
    granter: granterAddress // This tells the network to use the fee grant
  }
);

Querying Fee Grants

Check existing grants before creating new ones:
// Query grants for a specific grantee
const grantsByGrantee = await fetch(
  `${restUrl}/cosmos/feegrant/v1beta1/allowances/${granteeAddress}`
).then(res => res.json());

// Query all grants by a specific granter
const grantsByGranter = await fetch(
  `${restUrl}/cosmos/feegrant/v1beta1/issued/${granterAddress}`
).then(res => res.json());

// Query a specific grant
const specificGrant = await fetch(
  `${restUrl}/cosmos/feegrant/v1beta1/allowance/${granterAddress}/${granteeAddress}`
).then(res => res.json());

Other Allowance Types

Periodic Allowance

Resets spending limits periodically:
import { PeriodicAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant";

const periodicAllowance = PeriodicAllowance.fromPartial({
  basic: {
    spendLimit: [{
      denom: "uatom",
      amount: "10000000" // 10 ATOM total limit
    }],
    expiration: null // No expiration
  },
  period: { seconds: BigInt(86400), nanos: 0 }, // 24 hours
  periodSpendLimit: [{
    denom: "uatom",
    amount: "1000000" // 1 ATOM per period
  }]
});

Allowed Message Allowance

Restricts which message types can be sponsored:
import { AllowedMsgAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant";

const allowedMsgAllowance = AllowedMsgAllowance.fromPartial({
  allowance: {
    typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
    value: BasicAllowance.encode(basicAllowance).finish()
  },
  allowedMessages: [
    "/cosmos.bank.v1beta1.MsgSend",
    "/cosmos.staking.v1beta1.MsgDelegate"
  ]
});

Revoking Fee Grants

Remove a fee grant when it’s no longer needed:
import { MsgRevokeAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/tx";

const revokeMsg = {
  typeUrl: "/cosmos.feegrant.v1beta1.MsgRevokeAllowance",
  value: MsgRevokeAllowance.fromPartial({
    granter: granterAddress,
    grantee: granteeAddress
  })
};

const result = await client.signAndBroadcast(
  granterAddress,
  [revokeMsg],
  "auto"
);

Advanced: Server-Controlled Gas Sponsorship

For production applications, use server-side controlled wallets as grantors while allowing users to authenticate client-side as grantees. This pattern uses Para’s pregenerated wallets to create an app-controlled grantor wallet.

Wallet Pregeneration Guide

Server-Side Setup

Create and manage a grantor wallet on your server:
// server.ts
import { Para } from "@getpara/server-sdk@alpha";
import { SigningStargateClient } from "@cosmjs/stargate";
import { ParaProtoSigner } from "@getpara/cosmjs-adapter@alpha";

const serverPara = new Para(process.env.PARA_API_KEY);

// Create a pregen wallet for gas sponsorship
const grantorWallet = await serverPara.createPregenWallet({
  type: 'COSMOS',
  pregenId: { customId: "app-gas-sponsor-wallet" }
});

// Store the user share securely
const userShare = await serverPara.getUserShare();
// Store userShare in your secure database

API Endpoint for Creating Grants

// POST /api/create-fee-grant
async function createFeeGrantForUser(userAddress: string) {
  // Load the grantor wallet
  await serverPara.setUserShare(storedUserShare);
  
  // Set up Cosmos client
  const granterSigner = new ParaProtoSigner(serverPara, "cosmos");
  const client = await SigningStargateClient.connectWithSigner(rpcUrl, granterSigner);
  
  // Check if grant already exists
  const existingGrant = await fetch(
    `${restUrl}/cosmos/feegrant/v1beta1/allowance/${granterSigner.address}/${userAddress}`
  ).then(res => res.json());
  
  if (existingGrant.allowance) {
    // Revoke existing grant first
    const revokeMsg = {
      typeUrl: "/cosmos.feegrant.v1beta1.MsgRevokeAllowance",
      value: MsgRevokeAllowance.fromPartial({
        granter: granterSigner.address,
        grantee: userAddress
      })
    };
    
    await client.signAndBroadcast(
      granterSigner.address,
      [revokeMsg],
      "auto"
    );
  }
  
  // Create new grant with daily limit
  const basicAllowance = BasicAllowance.fromPartial({
    spendLimit: [{
      denom: "uatom",
      amount: "1000000" // 1 ATOM daily limit
    }],
    expiration: {
      seconds: BigInt(Math.floor(Date.now() / 1000) + 86400),
      nanos: 0
    }
  });

  const grantMsg = {
    typeUrl: "/cosmos.feegrant.v1beta1.MsgGrantAllowance",
    value: MsgGrantAllowance.fromPartial({
      granter: granterSigner.address,
      grantee: userAddress,
      allowance: {
        typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
        value: BasicAllowance.encode(basicAllowance).finish()
      }
    })
  };

  const result = await client.signAndBroadcast(
    granterSigner.address,
    [grantMsg],
    "auto"
  );
  
  return {
    transactionHash: result.transactionHash,
    granterAddress: granterSigner.address
  };
}

Client-Side Integration

// client.tsx
import { useParaWallet } from "@getpara/react-sdk@alpha";
import { SigningStargateClient } from "@cosmjs/stargate";
import { ParaProtoSigner } from "@getpara/cosmjs-adapter@alpha";

function MyApp() {
  const { para, address } = useParaWallet();
  const [granterAddress, setGranterAddress] = useState<string>();
  
  const setupGasSponsorship = async () => {
    if (!address) return;
    
    const response = await fetch('/api/create-fee-grant', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userAddress: address })
    });
    
    const { granterAddress } = await response.json();
    setGranterAddress(granterAddress);
  };
  
  const performSponsoredTransaction = async (recipientAddress: string, amount: string) => {
    if (!para || !address || !granterAddress) return;
    
    const signer = new ParaProtoSigner(para, "cosmos");
    const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer);
    
    const result = await client.sendTokens(
      address,
      recipientAddress,
      [{ denom: "uatom", amount }],
      {
        amount: [{ denom: "uatom", amount: "5000" }],
        gas: "200000",
        granter: granterAddress // App pays the gas fees
      }
    );
    
    return result.transactionHash;
  };
  
  return (
    <>
      <button onClick={setupGasSponsorship}>
        Activate Gas Sponsorship
      </button>
      <button onClick={() => performSponsoredTransaction("cosmos1...", "100000")}>
        Send Sponsored Transaction
      </button>
    </>
  );
}

Complete Server Implementation

// Complete server implementation with grant management
class FeeGrantService {
  private serverPara: Para;
  private granterAddress?: string;
  
  constructor() {
    this.serverPara = new Para(process.env.PARA_API_KEY);
  }
  
  async initialize() {
    // Load or create grantor wallet
    const userShare = await loadUserShareFromDatabase();
    await this.serverPara.setUserShare(userShare);
    
    const signer = new ParaProtoSigner(this.serverPara, "cosmos");
    this.granterAddress = signer.address;
  }
  
  async createGrant(userAddress: string, limitAmount: string, periodSeconds: number) {
    const signer = new ParaProtoSigner(this.serverPara, "cosmos");
    const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer);
    
    const allowance = BasicAllowance.fromPartial({
      spendLimit: [{
        denom: "uatom",
        amount: limitAmount
      }],
      expiration: {
        seconds: BigInt(Math.floor(Date.now() / 1000) + periodSeconds),
        nanos: 0
      }
    });
    
    const msg = {
      typeUrl: "/cosmos.feegrant.v1beta1.MsgGrantAllowance",
      value: MsgGrantAllowance.fromPartial({
        granter: this.granterAddress,
        grantee: userAddress,
        allowance: {
          typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
          value: BasicAllowance.encode(allowance).finish()
        }
      })
    };
    
    return client.signAndBroadcast(this.granterAddress, [msg], "auto");
  }
  
  async revokeGrant(userAddress: string) {
    const signer = new ParaProtoSigner(this.serverPara, "cosmos");
    const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer);
    
    const msg = {
      typeUrl: "/cosmos.feegrant.v1beta1.MsgRevokeAllowance",
      value: MsgRevokeAllowance.fromPartial({
        granter: this.granterAddress,
        grantee: userAddress
      })
    };
    
    return client.signAndBroadcast(this.granterAddress, [msg], "auto");
  }
}

Best Practices

  • Set appropriate spending limits based on expected transaction volume
  • Use expiration times to automatically clean up unused grants
  • Monitor grant usage to control costs and detect abuse
  • Consider using periodic allowances for regular users
  • Use allowed message allowances to restrict transaction types
  • Remember that creating and revoking grants also incur gas costs
Fee grants provide native gas sponsorship on Cosmos networks without requiring smart contracts, making them more efficient than EVM account abstraction solutions.