Zero Gas Mass Payment with SBC
This guide demonstrates how to implement gasless mass payments using SBC's custom paymaster and bundler. With this functionality, you can send SBC tokens to multiple recipients in a single transaction without paying gas fees.
Prerequisites
- Access to SBC's bundler and paymaster services
- Sufficient SBC tokens in your wallet
Reach out to us on Telegram to access our Paymaster and Bundler, or to get some SBC on Base Sepolia to test things out.
Contract Addresses
Base Sepolia (Testnet)
- SBC Token:
0xf9FB20B8E097904f0aB7d12e9DbeE88f2dcd0F16
How It Works
The SBC MassPayment contract allows you to:
- Send tokens to multiple recipients in a single transaction
- Pay zero gas fees using SBC's paymaster
- Support up to 200 recipients in a single operation
We'll send 0.01 SBC to 2 random addresses.
Implementation Steps
1. Create a SimpleAccount smart account from your wallet (owner)
const owner = createWalletClient({
account: owner.address as Hex,
chain,
transport: custom((window as any).ethereum),
});
const simpleAccount = await toSimpleSmartAccount({
client: localPublicClient,
owner: owner,
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
});
2. Create a Paymaster Client from our Paymaster URL
// Reach out to us on Telegram https://t.me/stablecoin_xyz to get an API KEY
const PAYMASTER_API_URL = `https://api.aa.stablecoin.xyz/rpc/v1/baseSepolia/${SBC_PAYMASTER_API_KEY}`;
const pmClient = createPaymasterClient({
transport: http(PAYMASTER_API_URL),
});
3. Create the Smart Account Bundler Client with our Paymaster Client
const smartAccountClient = createSmartAccountClient({
account: simpleAccount,
chain: baseSepolia,
bundlerTransport: http(PAYMASTER_API_URL),
paymaster: pmClient,
userOperation: {
estimateFeesPerGas: async () => {
return {
maxFeePerGas: 10n,
maxPriorityFeePerGas: 10n,
};
},
},
});
4. Prepare the calldata
const txs = [
{to: "0x124b082e8DF36258198da4Caa3B39c7dFa64D9cE", value: 0.1},
{to: "0xB5f6fECd59dAd3d5bA4Dfe8FcCA6617CE71B99f9", value: 0.1}
];
const decimalPlaces = 6;
const txnBigInts: { to: string; value: bigint }[] = txs.map((tx) => {
return {
to: tx.to,
value: BigInt(fromReadableAmount(tx.value, decimalPlaces).toString()),
};
});
const SBC_BASE_SEPOLIA_CONTRACT_ADDRESS =
"0xf9FB20B8E097904f0aB7d12e9DbeE88f2dcd0F16";
// Build the calls array where you send from owner wallet to each `to` address.
// You can get erc20Abi from viem with: import { erc20Abi } from "viem"
const calls = txnBigInts.map((tx) => {
const transferData = encodeFunctionData({
abi: erc20Abi,
functionName: "transferFrom",
args: [owner.account.address as Hex, tx.to as Hex, tx.value],
});
return {
from: owner.account.address as Hex,
to: SBC_BASE_SEPOLIA_CONTRACT_ADDRESS as Hex,
data: transferData,
};
});
5. Compute the Permit Signature
First we'll need a helper function getPermitSignature
.
// Sum up the total value of the mass pay
const totalValue = BigInt(txnBigInts.reduce((acc, tx) => acc + tx.value, 0n));
// Set a 30 min deadline for the signature
const deadline = Math.floor(Date.now() / 1000) + 60 * 30;
// Get the sender (counterfactual) address of the SimpleAccount
const senderAddress = simpleAccount.address;
// prepend the permit data instruction
const signature = await getPermitSignature(
chain,
owner,
SBC_BASE_SEPOLIA_CONTRACT_ADDRESS,
owner.address as Hex,
senderAddress,
totalValue,
deadline,
);
const { r, s, v } = parseSignature(signature);
// Encode the permit transaction calldata
// Get `erc20PermitAbi`: import erc20PermitAbi from "@openzeppelin/contracts/build/contracts/IERC20Permit.json";
const permitData = encodeFunctionData({
abi: erc20PermitAbi,
functionName: "permit",
args: [
owner.address as Hex,
senderAddress,
totalValue,
deadline,
v,
r,
s,
],
});
// prepend to the calls array
calls.unshift({
from: owner.account.address as Hex,
to: SBC_BASE_SEPOLIA_CONTRACT_ADDRESS as Hex,
data: permitData,
});
- Send the batch call with our Smart Account Client
const userOpHash = await smartAccountClient.sendUserOperation({
calls,
});
const receipt = await smartAccountClient.waitForUserOperationReceipt({
hash: userOpHash,
})
console.log(`User Op Hash: ${receipt.userOpHash}`)
Performance Considerations
- Gas Limits: Mass payments are gas intensive as the information needed (addresses) take up room in the calldata. Each chain has its own maximum gas per block.
- Transaction Limits: The maximum number of recipients in a single batch is 200.
Next Steps
- Learn about NFT Minting with gasless transactions