import { createGelatoEvmRelayerClient, StatusCode, sponsored } from '@gelatocloud/gasless';
import {
createPublicClient,
createWalletClient,
http,
encodeFunctionData,
pad,
toHex,
type Hex
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { baseSepolia } from 'viem/chains';
const simpleCounterAddress = '0x5115B85246bb32dCEd920dc6a33E2Be6E37fFf6F';
const simpleCounterAbi = [
{ name: 'increment', type: 'function', inputs: [], outputs: [] },
{ name: 'counter', type: 'function', inputs: [], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
{ name: 'getNonce', type: 'function', inputs: [{ name: 'user', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
{ name: 'executeMetaTransaction', type: 'function', inputs: [
{ name: 'userAddress', type: 'address' },
{ name: 'functionSignature', type: 'bytes' },
{ name: 'sigR', type: 'bytes32' },
{ name: 'sigS', type: 'bytes32' },
{ name: 'sigV', type: 'uint8' }
], outputs: [{ type: 'bytes' }] }
] as const;
const metaTransactionTypes = {
MetaTransaction: [
{ name: 'nonce', type: 'uint256' },
{ name: 'from', type: 'address' },
{ name: 'functionSignature', type: 'bytes' },
],
} as const;
const executeMetaTransaction = async () => {
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const publicClient = createPublicClient({
chain: baseSepolia,
transport: http(),
});
const walletClient = createWalletClient({
account,
chain: baseSepolia,
transport: http(),
});
const relayer = createGelatoEvmRelayerClient({
apiKey: process.env.GELATO_API_KEY,
testnet: true,
});
// Get user nonce from contract
const nonce = await publicClient.readContract({
address: simpleCounterAddress,
abi: simpleCounterAbi,
functionName: 'getNonce',
args: [account.address],
});
// Encode the increment() function call
const functionSignature = encodeFunctionData({
abi: simpleCounterAbi,
functionName: 'increment',
});
// Setup EIP-712 domain (salt is chain ID padded to 32 bytes)
const domain = {
name: 'SimpleCounter',
version: '1',
verifyingContract: simpleCounterAddress as Hex,
salt: pad(toHex(baseSepolia.id), { size: 32 }),
};
// Sign the meta-transaction using EIP-712
const signature = await walletClient.signTypedData({
domain,
types: metaTransactionTypes,
primaryType: 'MetaTransaction',
message: {
nonce: nonce,
from: account.address,
functionSignature: functionSignature,
},
});
// Parse signature into r, s, v components
const r = `0x${signature.slice(2, 66)}` as Hex;
const s = `0x${signature.slice(66, 130)}` as Hex;
const v = parseInt(signature.slice(130, 132), 16);
// Encode executeMetaTransaction call
const metaPayload = encodeFunctionData({
abi: simpleCounterAbi,
functionName: 'executeMetaTransaction',
args: [account.address, functionSignature, r, s, v],
});
// Send via Gelato Relay
const id = await relayer.sendTransaction({
chainId: baseSepolia.id,
to: simpleCounterAddress,
data: metaPayload,
payment: sponsored(),
});
console.log(`Gelato task ID: ${id}`);
// Wait for status
const status = await relayer.waitForStatus({ id });
if (status.status === StatusCode.Included) {
console.log(`Transaction hash: ${status.receipt.transactionHash}`);
} else {
console.log(`Transaction failed: ${status.message}`);
}
};
executeMetaTransaction();