How to Set Up Gasless Transactions

Joan Alavedra8 min read
How to Set Up Gasless Transactions

Gas fees break onboarding. A user signs up, creates a wallet, tries to interact with your app — and hits a wall because they don't have ETH. They leave. Most of them don't come back.

For AI agents the problem is structural: autonomous systems executing transactions across multiple chains can't be expected to manually source native tokens on each one.

Gasless transactions fix both problems. Your application sponsors gas costs on behalf of users and agents, removing the friction entirely. This guide covers the full setup on Ethereum/EVM and Solana — from your first gas policy to production best practices.

How Gas Sponsorship Works

The mechanics differ by chain, but the core idea is the same: a third party (you, the developer) pays for the user's transaction costs.

On EVM chains, Openfort uses ERC-4337 Account Abstraction. The flow:

  1. Validation — The EntryPoint deducts the required prefund from the paymaster's deposit and calls validatePaymasterUserOp(). The paymaster verifies an off-chain signature to confirm the operation qualifies for sponsorship.
  2. Execution — The user's intended call is forwarded to their smart account and executed on-chain.
  3. Post-execution — The EntryPoint calls postOp() for accounting: refunding unused gas, collecting ERC-20 payments, or emitting events.

On Solana, fee sponsorship is native to the protocol. Solana transactions have an explicit fee payer field — any account can cover fees for another. Openfort validates the transaction against your policy and signs it as the fee payer before returning it for execution.

Prerequisites

  • An Openfort account with API keys
  • Node.js 18+
  • Basic familiarity with ERC-4337 (for EVM) or Solana transactions

_10
npm install @openfort/openfort-node

Part 1: Gasless Transactions on EVM Chains

Step 1: Initialize the SDK


_10
import Openfort from "@openfort/openfort-node";
_10
_10
const openfort = new Openfort("sk_test_YOUR_SECRET_KEY");

Use sk_test_ keys for testnet development. Switch to sk_live_ in production.

Step 2: Create a Gas Policy

A gas policy defines who pays and under what conditions. Three sponsor strategies are available:

StrategyDescriptionBest For
pay_for_userYou fully sponsor gasGaming, onboarding, free tiers
charge_custom_tokensUser pays in ERC-20 at live rateDeFi, stablecoin flows
fixed_rateUser pays a fixed token amountPredictable pricing, in-game currencies

Create a full-sponsorship policy:


_10
const policy = await openfort.policies.create({
_10
name: "Sponsor all user transactions",
_10
chainId: 80002, // Polygon Amoy testnet
_10
strategy: {
_10
sponsorSchema: "pay_for_user",
_10
},
_10
});

Step 3: Register Your Contract and Add Rules

Register the contract to sponsor, then scope the policy to specific functions:


_28
const contract = await openfort.contracts.create({
_28
name: "MyNFTContract",
_28
chainId: 80002,
_28
address: "0x38090d1636069c0ff1af6bc1737fb996b7f63ac0",
_28
});
_28
_28
// Sponsor the mint function
_28
await openfort.policyRules.create({
_28
policy: policy.id,
_28
type: "contract_functions",
_28
contract: contract.id,
_28
functionName: "mint",
_28
});
_28
_28
// Sponsor account deployment (required for first-time users)
_28
await openfort.policyRules.create({
_28
type: "account_functions",
_28
policy: policy.id,
_28
});
_28
_28
// Rate limit to prevent abuse
_28
await openfort.policyRules.create({
_28
policy: policy.id,
_28
type: "rate_limit",
_28
countPerInterval: 10,
_28
timeIntervalType: "day",
_28
timeIntervalValue: 1,
_28
});

Always include account_functions for new users — without it, their first transaction (account deployment) won't be sponsored, and they hit a gas wall immediately.

Step 4: Execute a Sponsored Transaction


_13
const transactionIntent = await openfort.transactionIntents.create({
_13
player: "pla_YOUR_PLAYER_ID",
_13
chainId: 80002,
_13
optimistic: false,
_13
policy: policy.id,
_13
interactions: [
_13
{
_13
contract: contract.id,
_13
functionName: "mint",
_13
functionArgs: ["0xRecipientAddress", "1"],
_13
},
_13
],
_13
});

Set optimistic: true for better UX in non-critical flows (returns immediately without waiting for confirmation).

Alternative: Pay Gas in ERC-20 Tokens

If you prefer users to pay gas in USDC rather than receiving free transactions:


_15
const usdcContract = await openfort.contracts.create({
_15
name: "USDC",
_15
chainId: 84532, // Base Sepolia
_15
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
_15
});
_15
_15
const erc20Policy = await openfort.policies.create({
_15
name: "Pay gas in USDC",
_15
chainId: 84532,
_15
strategy: {
_15
sponsorSchema: "charge_custom_tokens",
_15
tokenContract: usdcContract.id,
_15
tokenContractAmount: "0", // "0" = use live exchange rate
_15
},
_15
});

Viem Integration

If you're using viem directly, the Openfort paymaster works via the standard createPaymasterClient:


_20
import { createPaymasterClient, createBundlerClient } from 'viem/account-abstraction'
_20
_20
const paymasterClient = createPaymasterClient({
_20
transport: http('https://api.openfort.io/rpc/11155111', {
_20
fetchOptions: {
_20
headers: { 'Authorization': 'Bearer YOUR_OPENFORT_PUBLISHABLE_KEY' },
_20
},
_20
}),
_20
})
_20
_20
const bundlerClient = createBundlerClient({
_20
account,
_20
paymaster: paymasterClient,
_20
paymasterContext: { policyId: 'pol_YOUR_POLICY_ID' },
_20
transport: http('https://api.openfort.io/rpc/11155111', {
_20
fetchOptions: {
_20
headers: { 'Authorization': 'Bearer YOUR_OPENFORT_PUBLISHABLE_KEY' },
_20
},
_20
}),
_20
})

Part 2: Gasless Transactions on Solana

Solana's approach to fee sponsorship is architecturally different from EVM. There's no paymaster contract or ERC-4337 — Solana transactions natively support a feePayer field. Openfort validates your policy, then signs the transaction as the fee payer.

How It Works

  1. Your application builds a Solana transaction
  2. You send it to Openfort's Solana RPC endpoint
  3. Openfort checks the transaction against your sponsorship policy
  4. Openfort signs as the fee payer and returns the signed transaction
  5. The transaction is submitted to the network — the user pays zero SOL

Openfort's Solana paymaster endpoint:


_10
https://api.openfort.io/rpc/solana/{cluster}

Replace {cluster} with devnet or mainnet-beta. Include your publishable key in the Authorization header.

Create a Solana Sponsorship Policy

In the Openfort Dashboard, create a policy with the sponsorSolTransaction operation enabled. This policy type controls which Solana transactions receive fee sponsorship.

SDK Example

Using the Kora SDK with Openfort as the fee payer:


_44
import { KoraClient } from "@solana/kora";
_44
import {
_44
createKeyPairSignerFromBytes,
_44
createNoopSigner,
_44
address,
_44
partiallySignTransactionMessageWithSigners,
_44
createSolanaRpc,
_44
pipe,
_44
createTransactionMessage,
_44
setTransactionMessageFeePayerSigner,
_44
setTransactionMessageLifetimeUsingBlockhash,
_44
appendTransactionMessageInstructions,
_44
} from "@solana/web3.js";
_44
_44
// Initialize Kora client pointing to Openfort
_44
const koraClient = new KoraClient(
_44
"https://api.openfort.io/rpc/solana/mainnet-beta",
_44
{ headers: { Authorization: "Bearer YOUR_OPENFORT_PUBLISHABLE_KEY" } }
_44
);
_44
_44
// Get Openfort's fee payer address
_44
const { feePayer } = await koraClient.getPayerSigner();
_44
const feePayerSigner = createNoopSigner(address(feePayer));
_44
_44
// Build your transaction with Openfort as fee payer
_44
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
_44
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
_44
_44
const transactionMessage = pipe(
_44
createTransactionMessage({ version: 0 }),
_44
(tx) => setTransactionMessageFeePayerSigner(feePayerSigner, tx),
_44
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
_44
(tx) => appendTransactionMessageInstructions(yourInstructions, tx)
_44
);
_44
_44
// Sign with user's key first
_44
const userSigner = await createKeyPairSignerFromBytes(userKeypairBytes);
_44
const partiallySignedTx = await partiallySignTransactionMessageWithSigners(
_44
transactionMessage
_44
);
_44
_44
// Openfort co-signs as fee payer and broadcasts
_44
const result = await koraClient.signAndSendTransaction(partiallySignedTx);
_44
console.log("Transaction signature:", result.signature);

Available Solana Endpoints

MethodDescription
signAndSendTransactionSign as fee payer and broadcast
signTransactionSign without broadcasting (for manual submission)
transferTransactionCreate a sponsored token transfer
estimateTransactionFeeEstimate fee in lamports or tokens
getSupportedTokensList tokens accepted for fee payment

Part 3: Gas Sponsorship for Agent Wallets

Agent wallets — wallets controlled by AI agents, bots, or automated systems — work the same way as user wallets when it comes to gas policies. You apply the same pay_for_user, charge_custom_tokens, or fixed_rate strategies.

This matters for two reasons:

  1. Agents shouldn't hold gas — a sponsored wallet removes the operational overhead of keeping agent wallets funded with native tokens on every chain they operate on.
  2. Agents need predictable costscharge_custom_tokens with a fixed_rate lets you predict exactly what each agent transaction costs, which is easier to account for than fluctuating native gas prices.

The setup is identical to user wallets. Create a gas policy, add policy rules scoped to the contracts your agent interacts with, and reference the policy when creating transaction intents. The agent wallet ID replaces the player ID:


_13
const transactionIntent = await openfort.transactionIntents.create({
_13
player: "agt_YOUR_AGENT_WALLET_ID", // agent wallet, same API as user wallet
_13
chainId: 8453, // Base mainnet
_13
optimistic: true,
_13
policy: policy.id,
_13
interactions: [
_13
{
_13
contract: contract.id,
_13
functionName: "executeAction",
_13
functionArgs: [...],
_13
},
_13
],
_13
});

Rate limiting is especially useful here — cap agent wallets to prevent runaway transaction loops from draining your gas budget.

Best Practices

Fund your policy before going to production. In testnet, use Openfort's test balance. In production, fund via Balance Credit — gas costs auto-deduct as transactions execute.

Always scope policy rules. An unscoped policy sponsors everything on your account. Lock it down to specific contracts, functions, and rate limits before launch:


_10
// Don't do this (sponsors everything):
_10
const policy = await openfort.policies.create({ strategy: { sponsorSchema: "pay_for_user" } });
_10
_10
// Do this (scoped to your contract + rate limited):
_10
await openfort.policyRules.create({ policy: policy.id, type: "contract_functions", contract: contract.id, functionName: "mint" });
_10
await openfort.policyRules.create({ policy: policy.id, type: "rate_limit", countPerInterval: 10, timeIntervalType: "day", timeIntervalValue: 1 });

Include account_functions for onboarding flows. The first transaction deploys the smart account. Without this rule, new users hit a gas wall on their very first interaction.

Test on testnets before mainnet. Validate that policy rules correctly scope sponsorship, rate limits work as expected, and the full signing flow completes. Polygon Amoy, Base Sepolia, and Sepolia are good choices.

Monitor gas spending. Track costs via the Openfort dashboard. Set alerts for unusual spend patterns. If a policy burns through budget faster than expected, tighten rate limits or narrow the function scope.

Handle policy exhaustion gracefully. If your policy balance hits zero, sponsored transactions fail. Build a fallback in your app — either a UI prompt to inform the user, or an automated top-up triggered by a balance webhook — so policy exhaustion doesn't silently break your product in production.

Next Steps

Share this article

Keep Reading