Skip to main content

Gas Relayer API

The Gas Relayer enables gasless transactions for your users - they can interact with blockchain features (claiming perks, redeeming rewards) without needing ETH for gas fees. Your brand wallet pays all gas costs.

🚀 Try It Out: Interactive API Explorer → - Test gasless transactions directly in your browser!

Overview

Pattern: Meta-transaction relayer

  1. User performs action in your app
  2. Frontend creates transaction details
  3. Gas Relayer signs and executes with dev wallet
  4. User receives transaction hash
  5. Your brand pays the gas fee

Key Benefit: Zero blockchain complexity for end users - no wallet setup, no ETH needed, no gas fees.

Base URL

https://relayer.loyalteez.app

Production URL: https://relayer.loyalteez.app
Development: Same URL (uses Privy tokens for authentication)

Authentication

All requests require Privy authentication:

Authorization: Bearer YOUR_PRIVY_ACCESS_TOKEN

Get the access token from Privy SDK after user login.

Endpoints

POST /relay

Execute a gasless transaction on behalf of a user.

Headers:

Authorization: Bearer PRIVY_ACCESS_TOKEN
Content-Type: application/json
Origin: https://yoursite.com

Request Body:

{
"to": "0x1234567890123456789012345678901234567890",
"data": "0xabcdef...",
"value": "0",
"gasLimit": 500000,
"userAddress": "0x9876543210987654321098765432109876543210",
"metadata": {
"action": "claim_perk",
"perkId": "123",
"permit": {
"owner": "0x...",
"spender": "0x...",
"value": "1000000",
"deadline": 1699564800,
"v": 27,
"r": "0x...",
"s": "0x..."
}
}
}

Parameters:

FieldTypeRequiredDescription
toaddressYesContract address to call
datahexYesEncoded function call data
valuestringNoETH value to send (default: "0")
gasLimitnumberNoMax gas limit (default: auto-estimate)
userAddressaddressYesUser's wallet address
metadataobjectNoAdditional context
metadata.permitobjectNoEIP-2612 permit for gasless approval

Response:

{
"success": true,
"transactionHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"gasUsed": "250000",
"effectiveGasPrice": "25000000000",
"blockNumber": 12345678,
"from": "0x_DEV_WALLET_ADDRESS_",
"to": "0x_CONTRACT_ADDRESS_",
"status": "confirmed"
}

Error Response:

{
"error": "Transaction validation failed: Contract not whitelisted",
"code": "VALIDATION_FAILED",
"details": {
"contract": "0x...",
"allowed": ["0x...", "0x..."]
}
}

EIP-2612 Permit Integration

Enable gasless approvals using EIP-2612 permit signatures:

// 1. User signs permit (no gas needed)
const permit = await getPermitSignature({
token: tokenAddress,
owner: userAddress,
spender: contractAddress,
value: amount,
deadline: Math.floor(Date.now() / 1000) + 3600 // 1 hour
});

// 2. Send transaction with permit
const response = await fetch('https://relayer.loyalteez.app/relay', {
method: 'POST',
headers: {
'Authorization': `Bearer ${privyAccessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: contractAddress,
data: encodedFunctionCall,
userAddress: userAddress,
metadata: {
permit: permit // Gas relayer executes permit before main transaction
}
})
});

Permit Object Schema:

interface Permit {
owner: string; // User's wallet address
spender: string; // Contract to approve
value: string; // Amount to approve
deadline: number; // Unix timestamp
v: number; // Signature v
r: string; // Signature r
s: string; // Signature s
}

Security

Whitelisted Contracts

Only transactions to approved contracts are allowed:

  • PerkNFT: Claiming perks
  • Loyalteez: LTZ token transfers
  • PointsSale: Purchasing with LTZ
  • BusinessEscrow: Business transactions

Attempts to call non-whitelisted contracts will be rejected.

Rate Limiting

  • 35 transactions per hour per user
  • Prevents abuse and DoS attacks
  • Protects your gas budget

Gas Limits

  • Maximum: 1,000,000 gas per transaction
  • Cap: 100 Gwei gas price maximum
  • Prevents expensive transactions from draining budget

Authentication

  • Privy tokens verified on every request
  • Token must be valid and not expired
  • Origin header validated for CORS

Rate Limits

Limit TypeValueScope
Transactions per hour35Per user address
Max gas per transaction1,000,000Per transaction
Max gas price100 GweiPer transaction
API requests100/minPer IP

Error Codes

StatusCodeDescription
400MISSING_FIELDSRequired fields missing
401UNAUTHORIZEDInvalid or missing Privy token
403VALIDATION_FAILEDTransaction validation failed
403CONTRACT_NOT_WHITELISTEDContract not approved
429RATE_LIMIT_EXCEEDEDToo many transactions
500TRANSACTION_FAILEDOn-chain execution failed

Integration Example

React + Privy + Gas Relayer

import { usePrivy } from '@privy-io/react-auth';
import { ethers } from 'ethers';

function ClaimPerkButton({ perkId }) {
const { getAccessToken, user } = usePrivy();

const claimPerk = async () => {
// 1. Get Privy access token
const token = await getAccessToken();

// 2. Encode contract function call
const iface = new ethers.Interface(PERK_NFT_ABI);
const data = iface.encodeFunctionData('claimPerk', [perkId]);

// 3. Call gas relayer
const response = await fetch('https://relayer.loyalteez.app/relay', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: process.env.REACT_APP_PERK_NFT_ADDRESS,
data: data,
userAddress: user.wallet.address,
metadata: {
action: 'claim_perk',
perkId: perkId
}
})
});

const result = await response.json();

if (result.success) {
console.log('Perk claimed! TxHash:', result.transactionHash);
// Show success message to user
} else {
console.error('Claim failed:', result.error);
// Show error message
}
};

return (
<button onClick={claimPerk}>
Claim Perk (No Gas Fees!)
</button>
);
}

With EIP-2612 Permit

async function claimPerkWithPermit(perkId, ltzCost) {
const token = await getAccessToken();

// 1. Generate permit signature (gasless approval)
const permit = await signPermit({
token: LTZ_TOKEN_ADDRESS,
spender: PERK_NFT_ADDRESS,
value: ltzCost,
deadline: Math.floor(Date.now() / 1000) + 3600
});

// 2. Encode claim function
const data = ethers.utils.defaultAbiCoder.encode(
['uint256'],
[perkId]
);

// 3. Execute via gas relayer (handles permit + claim)
const response = await fetch('https://relayer.loyalteez.app/relay', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: PERK_NFT_ADDRESS,
data: data,
userAddress: user.wallet.address,
metadata: {
action: 'claim_perk',
perkId: perkId,
permit: permit // ← Gasless approval!
}
})
});

return await response.json();
}

Testing

Test Authentication

# Get Privy token from your app
TOKEN="your_privy_access_token"

curl -X POST https://relayer.loyalteez.app/relay \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "0x1234...",
"data": "0xabcd...",
"userAddress": "0x5678...",
"gasLimit": 100000
}'

Test Rate Limiting

Send 40 requests rapidly - requests 36+ should fail with 429 RATE_LIMIT_EXCEEDED.

Gas Cost Monitoring

Track your gas spending in the Partner Portal:

  • Dashboard → Gas Usage
    • Total ETH spent on gas
    • Transactions per day
    • Average gas per transaction
    • Estimated monthly cost

Set up alerts when gas spending exceeds budget.

Best Practices

1. Estimate Gas First

// Estimate before relaying
const estimated = await contract.estimateGas.claimPerk(perkId);
const withBuffer = estimated * 120n / 100n; // +20% buffer

// Include in relay request
gasLimit: Number(withBuffer)

2. Handle Errors Gracefully

try {
const result = await relayTransaction(txData);
// Success
} catch (error) {
if (error.code === 'RATE_LIMIT_EXCEEDED') {
// Show "Please wait before trying again"
} else if (error.code === 'VALIDATION_FAILED') {
// Show "Transaction not allowed"
} else {
// Generic error message
}
}

3. Show Transaction Status

// While waiting
showLoading('Processing transaction...');

// On success
showSuccess(`Transaction confirmed! View on explorer: ${explorerUrl}/${txHash}`);

// On failure
showError('Transaction failed. Please try again.');

4. Cache Permit Signatures

// Permit valid for 1 hour - reuse it!
const cachedPermit = localStorage.getItem(`permit_${userAddress}_${spender}`);

if (cachedPermit && cachedPermit.deadline > Date.now() / 1000) {
// Reuse existing permit
} else {
// Generate new permit
const newPermit = await signPermit(...)
localStorage.setItem(`permit_${userAddress}_${spender}`, newPermit);
}

Monitoring & Alerts

Set up monitoring for:

  • Gas spending: Alert when > $X per day
  • Failed transactions: Alert when failure rate > 5%
  • Rate limit hits: Alert when users hitting limits
  • Response time: Alert when > 2 seconds

Available in Partner Portal → Settings → Monitoring

Support