npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gelatocloud/gasless

v0.0.12

Published

Gelato Gasless SDK: All-in-one solution for gasless transactions

Readme

@gelatocloud/gasless

npm version

All-in-one solution for gasless transactions.

Features

  • Three abstraction levels - Choose the optimal integration model for your application::
    • Turbo Relayer: The fastest, most efficient way to submit transactions on-chain with zero gas overhead, ideal for latency-sensitive workflows.
    • Turbo Relayer with Smart Account - Leverage Smart Accounts for streamlined transaction encoding and signing while retaining Turbo-level performance.
    • ERC-4337 Bundler - A fully compliant ERC-4337 bundler for native Account Abstraction flows.
  • Sponsorship via Gas Tank - Support for sponsored transactions using your Gas Tank.
  • 2D nonce support - Advanced nonce management using both nonce and nonceKey for parallelized execution.
  • Type-safe - Implemented on top of viem, offering complete TypeScript type safety and developer ergonomics.
  • Synchronous methods: Send transaction and get the receipt in a single call
  • WebSockets: Live notifications and updates via WebSockets

Learn more in our docs

Installation

pnpm install viem @gelatocloud/gasless

Peer dependencies: viem

Quick Start

  1. Get your API key at https://app.gelato.cloud/
  2. Set your environment variable:
export GELATO_API_KEY="your-api-key"

Usage

Turbo Relayer

Direct gasless transaction relay without smart accounts. Best for simple sponsored transactions.

Synchronous:

import { createGelatoEvmRelayerClient } from '@gelatocloud/gasless';
import { baseSepolia } from 'viem/chains';

const relayer = createGelatoEvmRelayerClient({
  apiKey: process.env.GELATO_API_KEY,
  testnet: true
});

// Send and wait for inclusion in one call
const receipt = await relayer.sendTransactionSync({
  chainId: baseSepolia.id,
  to: '0xTargetContract...',
  data: '0xCalldata...'
});

console.log(`Transaction hash: ${receipt.transactionHash}`);

Asynchronous:

import { createGelatoEvmRelayerClient } from '@gelatocloud/gasless';
import { baseSepolia } from 'viem/chains';

const relayer = createGelatoEvmRelayerClient({
  apiKey: process.env.GELATO_API_KEY,
  testnet: true
});

// Send transaction (returns immediately with ID)
const id = await relayer.sendTransaction({
  chainId: baseSepolia.id,
  to: '0xTargetContract...',
  data: '0xCalldata...'
});

// Poll for status separately
const receipt = await relayer.waitForReceipt({ id });

console.log(`Transaction hash: ${receipt.transactionHash}`);

Account (Gelato Smart Account)

Gelato's smart account implementation with ERC-7821 delegation pattern.

Synchronous:

import {
  createGelatoSmartAccountClient,
  toGelatoSmartAccount } from '@gelatocloud/gasless';
import { createPublicClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { baseSepolia } from 'viem/chains';

const owner = privateKeyToAccount('0xYourPrivateKey...');

const publicClient = createPublicClient({
  chain: baseSepolia,
  transport: http()
});

// Create a Gelato smart account
const account = toGelatoSmartAccount({ client: publicClient, owner });

// Create the smart account client
const client = await createGelatoSmartAccountClient({
  account,
  apiKey: process.env.GELATO_API_KEY
});

// Send and wait for inclusion in one call
const receipt = await client.sendTransactionSync({
  calls: [
    { to: '0xContract1...', data: '0xCalldata1...' },
    { to: '0xContract2...', data: '0xCalldata2...' }
  ]// Optional: nonce or nonceKey for 2D nonce management
});

console.log(`Transaction hash: ${receipt.transactionHash}`);

Asynchronous:

import {
  createGelatoSmartAccountClient,
  toGelatoSmartAccount } from '@gelatocloud/gasless';

// ... same setup as above ...

// Send transaction (returns immediately with ID)
const id = await client.sendTransaction({
  calls: [{ to: '0xContract...', data: '0xCalldata...' }] });

// Poll for status separately
const receipt = await client.waitForReceipt({ id });

console.log(`Transaction hash: ${receipt.transactionHash}`);

Bundler (ERC-4337)

Compatible with any ERC-4337 smart account.

import { createGelatoBundlerClient } from '@gelatocloud/gasless';
import { to7702SimpleSmartAccount } from 'permissionless/accounts';
import { createPublicClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { baseSepolia } from 'viem/chains';

const owner = privateKeyToAccount('0xYourPrivateKey...');

const client = createPublicClient({
  chain: baseSepolia,
  transport: http()
});

// Create a smart account 
const account = await to7702SimpleSmartAccount({
  client,
  owner
});

// Create the Gelato bundler client
const bundler = await createGelatoBundlerClient({
  account,
  client,
  apiKey: process.env.GELATO_API_KEY,
  sponsored: true
});

// Send a user operation
const hash = await bundler.sendUserOperation({
  calls: [{ to: '0x...', data: '0x...' }]
});

// Wait for the receipt
const { receipt } = await bundler.waitForUserOperationReceipt({ hash });

console.log(`Transaction hash: ${receipt.transactionHash}`);

WebSocket Subscriptions

WebSockets are enabled by default. Methods automatically race WebSocket notifications against HTTP polling for the fastest result. To disable:

const relayer = createGelatoEvmRelayerClient({
  apiKey: process.env.GELATO_API_KEY,
  ws: { disable: true }
});

Relayer — single transaction:

const id = await relayer.sendTransaction({
  chainId: baseSepolia.id,
  to: '0xTargetContract...',
  data: '0xCalldata...'
});

const subscription = await relayer.ws.subscribe({ id });

subscription.on('success', (data) => {
  console.log(`Included in block ${data.receipt.blockNumber}`);
});

subscription.on('reverted', (data) => {
  console.log(`Reverted: ${data.receipt.blockNumber}`);
});

// Cleanup when done
await relayer.ws.unsubscribe(subscription.subscriptionId);
relayer.ws.disconnect();

Relayer — global (all transactions):

const subscription = await relayer.ws.subscribe();

subscription.on('submitted', (data) => console.log(`${data.id} submitted`));
subscription.on('success', (data) => console.log(`${data.id} success`));
subscription.on('rejected', (data) => console.log(`${data.id} rejected`));

Bundler — same patterns apply via bundler.ws:

const subscription = await bundler.ws.subscribe({ id: userOpHash });
subscription.on('success', (data) => console.log(data.receipt));

Events:

| Event | Description | |-------------|-------------------------------------| | pending | Transaction pending | | submitted | Submitted to network | | success | Successfully included on-chain | | rejected | Rejected by relayer | | reverted | Reverted on-chain |

Cleanup:

await relayer.ws.unsubscribe(subscription.subscriptionId);
relayer.ws.disconnect();

Get Balance

Check your Gas Tank balance. Available on all client types.

const { balance, decimals, unit } = await relayer.getBalance();
// Also: client.getBalance(), bundler.getBalance()

Sync vs Async

The SDK offers two ways to send transactions:

| | Sync (sendTransactionSync) | Async (sendTransaction + WS) | |---|---|---| | Returns | Final TransactionReceipt | ID (Hex) immediately | | Lifecycle events | None (handled internally) | All: pending, submitted, success, rejected, reverted | | Tx hash on (re)submission | Not exposed | hash field on every submitted event | | Control | Minimal — fire and forget | Full — react to each status change via WS subscription | | Reorg Protection | None | Yes, a new update event is published |

Sync — call sendTransactionSync to send the transaction and get the receipt in the same call. If the transaction is not included, the SDK will handle it internally and return a final TransactionReceipt by racing http polls and ws updates. Simplest approach when you just need the result:

const receipt = await relayer.sendTransactionSync({ chainId, to, data });

Async — call sendTransaction to get a ID immediately, then subscribe via WebSocket for granular status updates. WS subscriptions give you full control over the transaction lifecycle — you can react to resubmissions, display tx hashes to users in real time, handle rejections immediately, and more. The submitted event includes the transaction hash each time the relay (re)submits the transaction (e.g. with bumped gas):

const id = await relayer.sendTransaction({ chainId, to, data });
const subscription = await relayer.ws.subscribe({ id });

subscription.on('submitted', (data) => {
  console.log(`Tx hash: ${data.hash}`); // available on every (re)submission
});

subscription.on('success', (data) => {
  console.log(`Confirmed in block ${data.receipt.blockNumber}`);
});

API Reference

Relayer API

createGelatoEvmRelayerClient(config)

Creates a low-level relayer client for direct transaction submission.

const client = createGelatoEvmRelayerClient({
  apiKey: string,    // Your Gelato API key
  testnet: boolean   // true for testnets, false for mainnet
});

Methods:

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | sendTransaction | { chainId, to, data, authorizationList?, context? } | Promise<Hex> | Submit a transaction | | sendTransactionSync | { chainId, to, data, timeout?, pollingInterval?, throwOnReverted?, ... } | Promise<TransactionReceipt> | Send and wait for receipt | | getStatus | { id: string } | Promise<Status> | Get transaction status | | waitForReceipt | { id: string, timeout?, pollingInterval?, throwOnReverted? } | Promise<TransactionReceipt> | Wait for receipt, throws on failure | | getBalance | - | Promise<Balance> | Get Gas Tank balance | | getCapabilities | - | Promise<Capabilities> | Get supported chains | | getFeeData | { chainId, gas, l1Fee? } | Promise<FeeData> | Get network fee data |


Account API

toGelatoSmartAccount(params)

Creates a Gelato smart account using ERC-7821 delegation.

const account = toGelatoSmartAccount({
  client: Client,   // viem public client
  owner: Account    // EOA that owns the smart account
});

createGelatoSmartAccountClient(config)

Creates a client for interacting with a Gelato smart account.

const client = await createGelatoSmartAccountClient({
  apiKey: string,              // Your Gelato API key
  account: SmartAccount        // From toGelatoSmartAccount()
});

Methods:

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | sendTransaction | { calls, nonce?, nonceKey?} | Promise<Hex> | Send transaction(s) | | sendTransactionSync | { calls, nonce?, nonceKey?, timeout?, pollingInterval?, throwOnReverted?, ... } | Promise<TransactionReceipt> | Send and wait for receipt | | getStatus | { id: string } | Promise<Status> | Get transaction status | | waitForReceipt | { id: string, timeout?, pollingInterval?, throwOnReverted? } | Promise<TransactionReceipt> | Wait for receipt, throws on failure | | getBalance | - | Promise<Balance> | Get Gas Tank balance | | getCapabilities | - | Promise<Capabilities> | Get supported chains |

Nonce Options:

  • nonce: Explicit nonce value
  • nonceKey: Key for 2D nonce (allows parallel transactions)

Polling Configuration:

All synchronous methods (sendTransactionSync, sendUserOperationSync, waitForReceipt) support customizable polling behavior:

  • timeout (optional): Maximum wait time in milliseconds
    • Default: 120000 (2 minutes)
    • Must not exceed 600000 (10 minutes)
  • pollingInterval (optional): Frequency to check status in milliseconds
    • Default: 1000 (1 second)

Example:

// Wait up to 30 seconds, checking every 500ms
const receipt = await relayer.sendTransactionSync({
  chainId: baseSepolia.id,
  to: '0xTargetContract...',
  data: '0xCalldata...',
  timeout: 30000,
  pollingInterval: 500
});

Bundler API

createGelatoBundlerClient(config)

Creates an ERC-4337 bundler client. Extends viem's BundlerClient.

const bundler = await createGelatoBundlerClient({
  account: SmartAccount,       // Any ERC-4337 smart account
  client: Client,              // viem public client
  apiKey: string,              // Your Gelato API key
  sponsored: boolean,          // Whether to use sponsored payment via Gas Tank
  pollingInterval?: number     // Polling interval in ms
});

Methods:

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | sendUserOperation | { calls } | Promise<Hex> | Send a user operation | | sendUserOperationSync | { calls, timeout?, pollingInterval? } | Promise<UserOperationReceipt> | Send and wait for receipt | | waitForUserOperationReceipt | { hash } | Promise<{ receipt }> | Wait for receipt | | getBalance | - | Promise<Balance> | Get Gas Tank balance | | estimateUserOperationGas | UserOperationParams | Promise<GasEstimate> | Estimate gas | | prepareUserOperation | UserOperationParams | Promise<UserOperation> | Prepare operation | | getUserOperationGasPrice | - | Promise<GasPrice> | Get current gas prices | | getUserOperationQuote | UserOperationParams | Promise<Quote> | Get fee quote |


Types

StatusCode

enum StatusCode {
  Pending = 100,    // Transaction pending
  Submitted = 110,  // Submitted to network
  Success = 200,   // Successfully included
  Rejected = 400,   // Rejected by relayer
  Reverted = 500    // Reverted on-chain
}

ErrorCode

enum ErrorCode {
  // JSON-RPC
  ParseError = -32700,
  InvalidRequest = -32600,
  MethodNotFound = -32601,
  InvalidParams = -32602,
  InternalError = -32603,
  TimeoutError = -32070,

  // Relayer
  Unauthorized = 4100,
  UnsupportedPaymentToken = 4202,
  InsufficientPayment = 4200,
  InsufficientBalance = 4205,
  UnsupportedChain = 4206,
  UnknownTransactionId = 4208,
  InvalidAuthorizationList = 4210,
  SimulationFailed = 4211,

  // Bundler
  ValidationFailed = -32500,
  PaymasterValidationFailed = -32501,
  InvalidSignature = -32507,
  ExecutionFailed = -32521
}

Call

type Call = {
  to: Address;      // Target contract address
  data?: Hex;       // Calldata (optional)
  value?: bigint;   // ETH value (optional)
};

Status Handling

const status = await client.getStatus({ id: hash });

switch (status.status) {
  case StatusCode.Success:
    console.log('Success:', status.receipt.transactionHash);
    break;
  case StatusCode.Rejected:
    console.log('Rejected:', status.message);
    break;
  case StatusCode.Reverted:
    console.log('Reverted:', status.data);
    break;
}

Error Handling

Timeout Errors

Synchronous methods (sendTransactionSync, waitForReceipt) throw TimeoutError when operations don't complete within the configured timeout:

import { TimeoutError } from '@gelatocloud/gasless';

try {
  const receipt = await relayer.sendTransactionSync({
    chainId: baseSepolia.id,
    to: '0xTargetContract...',
    data: '0xCalldata...',
    timeout: 10000
  });
} catch (error) {
  if (error instanceof TimeoutError) {
    console.error('Transaction timed out:', error.message);
    // Transaction may still be pending - you can retry with longer timeout
    // or use async methods to check status manually
  } else {
    console.error('Other error:', error);
  }
}

Revert Handling

By default, sendTransactionSync and waitForReceipt return the receipt even when a transaction reverts on-chain. Set throwOnReverted: true to throw a TransactionRevertedError instead:

import { TransactionRevertedError } from '@gelatocloud/gasless';

try {
  const receipt = await relayer.sendTransactionSync({
    chainId: baseSepolia.id,
    to: '0xTargetContract...',
    data: '0xCalldata...',
    throwOnReverted: true
  });
} catch (error) {
  if (error instanceof TransactionRevertedError) {
    console.error('Transaction reverted:', error.receipt.transactionHash);
    console.error('Error message:', error.erroMessage);
    console.error('Error data:', error.errorData);
    // Also available: error.id, error.chainId, error.createdAt
  }
}

Available on both relayer and account clients via sendTransactionSync and waitForReceipt. Properties on TransactionRevertedError: receipt, id, chainId, createdAt, errorData, errorMessage.

Simulation Errors

When the relayer simulates a transaction before submission and the simulation reverts, a SimulationFailedRpcError is thrown with error code 4211. This happens before the transaction is sent on-chain:

import { SimulationFailedRpcError } from '@gelatocloud/gasless';

try {
  const receipt = await relayer.sendTransactionSync({
    chainId: baseSepolia.id,
    to: '0xTargetContract...',
    data: '0xCalldata...'
  });
} catch (error) {
  if (error instanceof SimulationFailedRpcError) {
    console.error('Simulation reverted:', error.message);
    console.error('Revert data:', error.revertData);
    // error.params contains the original { to, chainId, data } sent
  }
}

Properties on SimulationFailedRpcError: revertData, params, code (4211).

Automatic Retries

Relayer methods (sendTransaction, sendTransactionSync) support automatic retries when specific error codes are encountered. This is useful for transient failures like simulation errors (4211) that may succeed on a subsequent attempt.

const receipt = await relayer.sendTransactionSync(
  {
    chainId: baseSepolia.id,
    to: '0xTargetContract...',
    data: '0xCalldata...'
  },
  {
    retries: {
      max: 3,          // Retry up to 3 times (default: 0, max: 5)
      delay: 1000,     // Wait 1s between retries (default: 200ms)
      backoff: 'fixed', // 'exponential' (default) or 'fixed'
      errorCodes: [4211] // Error codes that trigger a retry (default: [4211])
    }
  }
);

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | max | number | 0 | Maximum number of retries (0–5). Clamped to 5. | | delay | number | 200 | Base delay in milliseconds before each retry. | | backoff | 'fixed' \| 'exponential' | 'exponential' | Backoff strategy. 'fixed' keeps constant delay; 'exponential' doubles each retry (delay × 2^attempt). | | maxDelay | number | 10000 | Maximum delay in ms. Caps exponential backoff so it never exceeds this value. | | errorCodes | number[] | [4211] | RPC error codes that trigger a retry. Default is SimulationFailedRpcError. |

Works with both async and sync relayer methods:

// Async
const id = await relayer.sendTransaction(
  { chainId, to, data },
  { retries: { max: 3 } }
);

// Sync
const receipt = await relayer.sendTransactionSync(
  { chainId, to, data },
  { retries: { max: 3 } }
);

Only errors with a matching code property are retried. All other errors are thrown immediately.

Automatic Fallback on Timeout

When sendTransactionSync rpc request times out, the SDK automatically falls back to polling for the transaction status. If you see a warning message like:

Transaction 0x... sync call timed out, falling back to polling for completion. DO NOT RETRY this transaction.

This means your transaction was successfully submitted but the sync method timed out. The SDK will continue polling for completion automatically. Do not retry the operation as this could result in duplicate transactions.

Recovery Strategies

If a timeout occurs:

  1. Wait for automatic fallback: sendTransactionSync automatically polls after timeout
  2. Check status manually: Use getStatus({ id }) to check if transaction is still processing if needed
  3. Retry with longer timeout: Increase timeout and call waitForReceipt again
  4. Use async methods: Switch to async pattern for more control

Configuration Limits

The SDK enforces the following limits to prevent denial of service:

import {
  TimeoutError,
  TransactionRevertedError,
  SimulationFailedRpcError,
  MIN_TIMEOUT,
  MAX_TIMEOUT,
  MIN_POLLING_INTERVAL,
  MAX_POLLING_INTERVAL
} from '@gelatocloud/gasless';

console.log(MIN_TIMEOUT); // 1000ms (1 second)
console.log(MAX_TIMEOUT); // 600000ms (10 minutes)
console.log(MIN_POLLING_INTERVAL); // 100ms
console.log(MAX_POLLING_INTERVAL); // 300000ms (5 minutes)

You can set default timeout and polling interval at the client level:

const relayer = createGelatoEvmRelayerClient({
  apiKey: process.env.GELATO_API_KEY,
  timeout: 30000, // Default 30 second timeout
  pollingInterval: 500 // Default 500ms polling interval
});

// Methods use client defaults unless overridden
const receipt = await relayer.sendTransactionSync({
  chainId: baseSepolia.id,
  to: '0xTargetContract...',
  data: '0xCalldata...',
  // timeout: 60000 // Optional: override client default
});

Requirements

  • Node.js >= 23
  • Peer dependencies: viem, zod
  • Optional: typescript

Examples

See the /examples directory for complete working examples:

Run an example:

cd examples/account/sponsored
npm install
GELATO_API_KEY=your-key npm run dev

Links

License

MIT