@gelatocloud/gasless
v0.0.7
Published
Gelato Gasless SDK: All-in-one solution for gasless transactions
Readme
@gelatocloud/gasless
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
nonceandnonceKeyfor 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
Learn more in our docs
Installation
pnpm install viem @gelatocloud/gaslessPeer dependencies: viem
Quick Start
- Get your API key at https://app.gelato.cloud/
- 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, StatusCode } from '@gelatocloud/gasless';
import { baseSepolia } from 'viem/chains';
const relayer = createGelatoEvmRelayerClient({
apiKey: process.env.GELATO_API_KEY,
testnet: true
});
// Send transaction (returns immediately with task ID)
const taskId = await relayer.sendTransaction({
chainId: baseSepolia.id,
to: '0xTargetContract...',
data: '0xCalldata...'
});
// Poll for status separately
const { status, receipt } = await relayer.waitForStatus({ id: taskId });
if (status.status === StatusCode.Success) {
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,
StatusCode } from '@gelatocloud/gasless';
// ... same setup as above ...
// Send transaction (returns immediately with task ID)
const taskId = await client.sendTransaction({
calls: [{ to: '0xContract...', data: '0xCalldata...' }] });
// Poll for status separately
const status = await client.waitForStatus({ id: taskId });
if (status.status === StatusCode.Success) {
console.log(`Transaction hash: ${status.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}`);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?, ... } | Promise<TransactionReceipt> | Send and wait for receipt |
| getStatus | { id: string } | Promise<Status> | Get transaction status |
| waitForStatus | { id: string, timeout?, pollingInterval? } | Promise<TerminalStatus> | Wait for final status |
| waitForReceipt | { id: string, timeout?, pollingInterval? } | Promise<TransactionReceipt> | Wait for receipt, throws on failure |
| 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?, ... } | Promise<TransactionReceipt> | Send and wait for receipt |
| getStatus | { id: string } | Promise<Status> | Get transaction status |
| waitForStatus | { id: string, timeout?, pollingInterval? } | Promise<TerminalStatus> | Wait for final status |
| waitForReceipt | { id: string, timeout?, pollingInterval? } | Promise<TransactionReceipt> | Wait for receipt, throws on failure |
| getCapabilities | - | Promise<Capabilities> | Get supported chains |
Nonce Options:
nonce: Explicit nonce valuenonceKey: Key for 2D nonce (allows parallel transactions)
Polling Configuration:
All synchronous methods (sendTransactionSync, sendUserOperationSync, waitForStatus, waitForReceipt) support customizable polling behavior:
timeout(optional): Maximum wait time in milliseconds- Default:
120000(2 minutes) - Must not exceed
600000(10 minutes)
- Default:
pollingInterval(optional): Frequency to check status in milliseconds- Default:
1000(1 second)
- Default:
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 |
| 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
import { StatusCode } from '@gelatocloud/gasless';
const status = await client.waitForStatus({ 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, waitForStatus, 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);
}
}Automatic Fallback on Timeout
When sendTransactionSync times out, it 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:
- Wait for automatic fallback:
sendTransactionSyncautomatically polls after timeout - Check status manually: Use
getStatus({ id })to check if transaction is still processing - Retry with longer timeout: Increase
timeoutand callwaitForStatusagain - Use async methods: Switch to async pattern for more control
try {
// Try with default 10s timeout
const receipt = await relayer.sendTransactionSync({
chainId: baseSepolia.id,
to: '0xTargetContract...',
data: '0xCalldata...',
});
} catch (error) {
if (error instanceof TimeoutError) {
// Retry with 60s timeout
const taskId = await relayer.sendTransaction({
chainId: baseSepolia.id,
to: '0xTargetContract...',
data: '0xCalldata...',
});
const status = await relayer.waitForStatus({
id: taskId,
timeout: 60000
});
console.log('Transaction completed:', status);
}
}Configuration Limits
The SDK enforces the following limits to prevent denial of service:
import {
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:
examples/relayer/sponsored- Direct relayer usageexamples/account/sponsored- Gelato smart accountexamples/bundler/sponsored- ERC-4337 bundler
Run an example:
cd examples/account/sponsored
npm install
GELATO_API_KEY=your-key npm run devLinks
License
MIT
