Setting up native gas sponsorship allows your app to pay for all transaction fees, creating a
frictionless experience across all networks.
Getting started
Enable gas sponsorship in the dashboard
Go to the gas sponsorship tab in the Privy
Dashboard, and enable gas sponsorship for
your application.
Configure chains
Select which chains you want to enable sponsorship for. Sponsored requests
may only come from the chains that you have configured. Want support for more networks? Reach out to us! Send transaction requests
Apps must use TEE execution in order to use our native gas sponsorship feature. Learn how to migrate here! Ethereum (React)
Solana (React)
Ethereum (REST API)
Ethereum (Node SDK)
Ethereum (Rust SDK)
Solana (REST API)
Solana (Node SDK)
Solana (Rust SDK)
With the React SDK, use the useSendTransaction hook with sponsor: true:import {useSendTransaction, useWallets} from '@privy-io/react-auth';
const {sendTransaction} = useSendTransaction();
const {wallets} = useWallets();
sendTransaction(
{
to: '0xE3070d3e4309afA3bC9a6b057685743CF42da77C',
value: 100000
},
{
sponsor: true // Enable gas sponsorship
}
);
With the React SDK, use the useSignAndSendTransaction hook with sponsor: true:import {useSignAndSendTransaction, useWallets} from '@privy-io/react-auth/solana';
const {signAndSendTransaction} = useSignAndSendTransaction();
const {wallets} = useWallets();
const selectedWallet = wallets[0];
// Create your transaction (example using @solana/kit)
const transaction = new Uint8Array([/* your encoded transaction */]);
const result = await signAndSendTransaction({
transaction: transaction,
wallet: selectedWallet,
options: {
sponsor: true // Enable gas sponsorship
}
});
console.log('Transaction sent with signature:', result.signature);
Allow transactions from the clientTo sponsor transactions from your client-side application, enable this setting in your gas sponsorship dashboard configuration. When disabled, transactions can only be sponsored from your server.
Gas sponsored transactions share the same path and interfaces as our other RPC requests. Learn
more about sending transactions here.
You must also include the sponsor: true parameter for transactions to be sponsored.$ curl --request POST https://api.privy.io/v1/wallets/<wallet_id>/rpc \
-u "<your-privy-app-id>:<your-privy-app-secret>" \
-H "privy-app-id: <your-privy-app-id>" \
-H "privy-authorization-signature: <authorization-signature-for-request>" \
-H 'Content-Type: application/json' \
-d '{
"method": "eth_sendTransaction",
"caip2": "eip155:1",
"sponsor": true
"params": {
"transaction": {
"to": "0xE3070d3e4309afA3bC9a6b057685743CF42da77C",
"value": "0x2386F26FC10000",
},
},
}'
To sponsor transactions that are initiated from a client SDK, you may relay the transaction from your server. With the Node SDK, use the wallets().ethereum().sendTransaction method with sponsor: true:import { PrivyClient } from '@privy-io/node';
const privy = new PrivyClient({
appId: 'your-app-id',
appSecret: 'your-app-secret'
});
async function sendSponsoredTransaction(walletId: string) {
try {
// Send a sponsored transaction on Ethereum
const response = await privy
.wallets()
.ethereum()
.sendTransaction(walletId, {
caip2: 'eip155:1', // Ethereum mainnet
params: {
transaction: {
to: '0x{address}',
value: '0x2386F26FC10000', // Amount in wei (hex format)
data: '0x', // optional contract call data
},
},
sponsor: true, // Enable gas sponsorship
});
// Response contains transaction hash and ID
console.log('Transaction hash:', response.hash);
console.log('Transaction ID:', response.transaction_id);
return response;
} catch (error) {
console.error('Transaction failed:', error);
throw error;
}
}
To sponsor transactions that are initiated from a client SDK, you may relay the transaction from your server. With the Rust SDK, use the ethereum_send_transaction method with sponsor: true:use privy_rs::{PrivyClient, generated::types::*};
let client = PrivyClient::new(app_id, app_secret)?;
// Create a sponsored ETH transfer
let request = EthereumSendTransactionRpcInput {
method: "eth_sendTransaction".to_string(),
caip2: "eip155:1".to_string(), // Ethereum mainnet
sponsor: Some(true), // Enable gas sponsorship
params: EthereumSendTransactionRpcInputParams {
transaction: EthereumSendTransactionRpcInputParamsTransaction {
to: Some("0xE3070d3e4309afA3bC9a6b057685743CF42da77C".to_string()),
value: Some("0x2386F26FC10000".to_string()), // 0.01 ETH
gas: None, // Let Privy estimate
gas_price: None, // Let Privy set optimal price
data: None,
nonce: None,
}
}
};
let response = client
.wallets()
.ethereum_send_transaction("wallet-id", request, &authorization_context)
.await?;
println!("Sponsored transaction hash: {}", response.data.transaction_hash);
To sponsor transactions that are initiated from a client SDK, you may relay the transaction from your server. Parameters and Returns
See the Rust SDK documentation for detailed parameter and return types, including embedded examples:For REST API details, see the API reference. Gas sponsored transactions share the same path and interfaces as our other RPC requests. Learn
more about sending transactions here.
You must also include the sponsor: true parameter for transactions to be sponsored.$ curl --request POST https://api.privy.io/v1/wallets/<wallet_id>/rpc \
-u "<your-privy-app-id>:<your-privy-app-secret>" \
-H "privy-app-id: <your-privy-app-id>" \
-H "privy-authorization-signature: <authorization-signature-for-request>" \
-H 'Content-Type: application/json' \
-d '{
"method": "signAndSendTransaction",
"caip2": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
"sponsor": true
"params": {
"transaction": "insert-base-64-encoded-serialized-transaction",
"encoding": "base64"
}
}'
To sponsor transactions that are initiated from a client SDK, you may relay the transaction from your server. With the Node SDK, use the wallets().solana().signAndSendTransaction method with sponsor: true:import { PrivyClient } from '@privy-io/node';
const privy = new PrivyClient({
appId: 'your-app-id',
appSecret: 'your-app-secret'
});
// A base64 encoded serialized transaction to sign
const transaction = "insert-base-64-encoded-serialized-transaction";
async function sendSponsoredSolanaTransaction(
walletId: string,
serializedTransaction: string
) {
try {
// Send a sponsored transaction on Solana
const response = await privy
.wallets()
.solana()
.signAndSendTransaction(walletId, {
caip2: 'solana:mainnet',
transaction, // Base64-encoded or Uint8Array
sponsor: true, // Enable gas sponsorship
});
// Response contains transaction hash and ID
console.log('Transaction hash:', response.hash);
console.log('Transaction ID:', response.transaction_id);
return response;
} catch (error) {
console.error('Transaction failed:', error);
throw error;
}
}
To sponsor transactions that are initiated from a client SDK, you may relay the transaction from your server. With the Rust SDK, use the solana_sign_and_send_transaction method with sponsor: true:use privy_rs::{PrivyClient, generated::types::*};
let client = PrivyClient::new(app_id, app_secret)?;
// Prepare your transaction (this example assumes you have a serialized transaction)
let serialized_transaction = "base64-encoded-transaction-here";
let request = SolanaSignAndSendTransactionRpcInput {
method: "signAndSendTransaction".to_string(),
caip2: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1".to_string(), // Solana mainnet
sponsor: Some(true), // Enable gas sponsorship
params: SolanaSignAndSendTransactionRpcInputParams {
transaction: serialized_transaction.to_string(),
encoding: Some("base64".to_string()),
send_options: None,
}
};
let response = client
.wallets()
.solana_sign_and_send_transaction("wallet-id", request, &authorization_context)
.await?;
println!("Sponsored transaction signature: {}", response.signature);
To sponsor transactions that are initiated from a client SDK, you may relay the transaction from your server. Parameters and Returns
See the Rust SDK documentation for detailed parameter and return types, including embedded examples:For REST API details, see the API reference.
Certain flows that require on-chain ECDSA signature verification such as Permit2 are not supported
by EIP-7702 upgraded wallets. We recommend using an approval based flow where possible.
Security recommendations
When implementing gas sponsorship, it’s critical to protect your application from abuse to prevent drainage of your gas sponsorship balance.
Privy natively offers controls to limit total spend and set logic for when to sponsor conditionally.
Rate limiting
To protect against abuse, we recommend:
- Implement strict rate limits: Set per-user and per-wallet transaction limits appropriate for your use case
- Monitor spending patterns: Track unusual activity and implement automatic circuit breakers for suspicious behavior
- Set maximum sponsorship amounts: Configure spending caps to limit potential losses from exploitation
- Send transactions from your backend: Privy aggressively rate limits transactions sent from the client. In order to get more granular control
over limiting, you can relay the transaction from your server.
Example threat models
Solana Rent Refund Vulnerability: On Solana, when Associated Token Accounts (ATAs) are created
during sponsored transactions, the rent deposit refund upon account closure goes to the account
owner, not the fee payer. This creates a potential exploitation vector where users can profit from
the rent refunds while your application pays the creation fees. For example, a user swapping USDC
to SOL could gain ~$0.40 per transaction from the rent refund, effectively draining your gas
sponsorship funds.
Some APIs (like Jupiter) will automatically include the CloseAccount instruction for transfers that will close
the ATA and refund the rent to the user. If your application plans to reuse a limited set of tokens, you should
strip the CloseAccount instruction from transactions to prevent users from profiting off the rent refunds.
Here’s an example of how to remove the CloseAccount instruction from a Solana transaction:
import {Transaction, TransactionInstruction} from '@solana/web3.js';
function stripCloseAccountInstruction(transaction) {
// Filter out any CloseAccount instructions from the transaction
const filteredInstructions = transaction.instructions.filter((instruction) => {
// CloseAccount instruction has discriminator 0x0a (10 in decimal)
const discriminator = instruction.data[0];
return discriminator !== 0x0a;
});
// Create a new transaction with the filtered instructions
const newTransaction = new Transaction();
for (const instruction of filteredInstructions) {
newTransaction.add(instruction);
}
// Copy signers from original transaction
newTransaction.feePayer = transaction.feePayer;
newTransaction.recentBlockhash = transaction.recentBlockhash;
return newTransaction;
}
This approach keeps the ATAs open for future transactions, reducing creation costs and eliminating the rent refund exploit.