How to do batch calls
Execute multiple Superfluid operations in a single transaction
Superfluid Protocol supports batch calls, allowing you to execute multiple operations in a single transaction. This is useful for complex workflows and gas optimization.
Understanding Batch Calls
Batch calls let you combine multiple Superfluid operations like:
- Wrapping tokens and creating flows
- Updating multiple flows at once
- Creating pools and distributing tokens
- Any combination of Superfluid operations
Operation Types
Each operation in a batch call requires an operation type identifier:
import { OPERATION_TYPE } from "@sfpro/sdk/util"
// Available operation types:
// OPERATION_TYPE.ERC20_APPROVE
// OPERATION_TYPE.SUPERTOKEN_UPGRADE
// OPERATION_TYPE.SUPERTOKEN_DOWNGRADE
// OPERATION_TYPE.SUPERFLUID_CALL_AGREEMENT // For all agreement calls (createFlow, updateFlow, deleteFlow, createPool, distribute, etc.)
// OPERATION_TYPE.CALL_APP_ACTION
// OPERATION_TYPE.SIMPLE_FORWARD_CALL
Example Without Helper
Here's how to create a batch call manually:
import { useWriteHost } from "@sfpro/sdk/hook/core"
import { OPERATION_TYPE } from "@sfpro/sdk/util"
import { superTokenAbi, cfaForwarderAbi } from "@sfpro/sdk/abi/core"
import { encodeFunctionData } from "viem"
import { useAccount } from "wagmi"
function BatchOperations() {
const { address } = useAccount()
const operations = [
{
operationType: OPERATION_TYPE.SUPERTOKEN_UPGRADE,
target: "0x...", // Super Token address
data: encodeFunctionData({
abi: superTokenAbi,
functionName: "upgrade",
args: [1000000n]
})
},
{
operationType: OPERATION_TYPE.SUPERFLUID_CALL_AGREEMENT,
target: "0x...", // CFA Forwarder address
data: encodeFunctionData({
abi: cfaForwarderAbi,
functionName: "createFlow",
args: ["0x...", address!, "0x...", 1000n, "0x"]
})
}
]
const { writeContract: batchCall } = useWriteHost({
functionName: "batchCall",
args: [operations]
})
return (
<button onClick={() => batchCall?.()}>
Execute Batch Operations
</button>
)
}
import { hostAbi, hostAddress, superTokenAbi, cfaForwarderAbi } from "@sfpro/sdk/abi/core"
import { OPERATION_TYPE } from "@sfpro/sdk/util"
import { createWalletClient, http, encodeAbiParameters, encodeFunctionData } from "viem"
import { mainnet } from "viem/chains"
import { privateKeyToAccount } from "viem/accounts"
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http()
})
async function batchCall() {
const operations = [
// Operation 1: Upgrade tokens
{
operationType: OPERATION_TYPE.SUPERTOKEN_UPGRADE,
target: "0x...", // Super Token address
data: encodeFunctionData({
abi: superTokenAbi,
functionName: "upgrade",
args: [1000000n] // Amount to upgrade
})
},
// Operation 2: Create flow
{
operationType: OPERATION_TYPE.SUPERFLUID_CALL_AGREEMENT,
target: "0x...", // CFA Forwarder address
data: encodeFunctionData({
abi: cfaForwarderAbi,
functionName: "createFlow",
args: ["0x...", account.address, "0x...", 1000n, "0x"]
})
}
]
const hash = await walletClient.writeContract({
address: hostAddress[mainnet.id],
abi: hostAbi,
functionName: "batchCall",
args: [operations]
})
return hash
}
import { writeHost } from "@sfpro/sdk/action/core"
import { OPERATION_TYPE } from "@sfpro/sdk/util"
import { superTokenAbi, cfaForwarderAbi } from "@sfpro/sdk/abi/core"
import { createConfig } from "@wagmi/core"
import { createWalletClient, http, encodeFunctionData } from "viem"
import { mainnet } from "viem/chains"
import { privateKeyToAccount } from "viem/accounts"
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const wagmiConfig = createConfig({
chains: [mainnet],
client({ chain }) {
return createWalletClient({
account,
chain,
transport: http(),
})
},
})
async function batchCall() {
const operations = [
{
operationType: OPERATION_TYPE.SUPERTOKEN_UPGRADE,
target: "0x...", // Super Token address
data: encodeFunctionData({
abi: superTokenAbi,
functionName: "upgrade",
args: [1000000n]
})
},
{
operationType: OPERATION_TYPE.SUPERFLUID_CALL_AGREEMENT,
target: "0x...", // CFA Forwarder address
data: encodeFunctionData({
abi: cfaForwarderAbi,
functionName: "createFlow",
args: ["0x...", account.address, "0x...", 1000n, "0x"]
})
}
]
const hash = await writeHost(wagmiConfig, {
chainId: mainnet.id,
functionName: "batchCall",
args: [operations]
})
return hash
}
Example With Helper
The SDK provides a prepareOperation
helper to simplify batch call creation:
import {
hostAbi,
hostAddress,
superTokenAbi,
cfaForwarderAbi,
cfaForwarderAddress
} from "@sfpro/sdk/abi"
import {
prepareOperation,
OPERATION_TYPE,
calculateFlowrate
} from "@sfpro/sdk/util"
import { createWalletClient, http, parseEther } from "viem"
import { mainnet } from "viem/chains"
import { privateKeyToAccount } from "viem/accounts"
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http()
})
const superToken = "0x..." // USDCx address
const receiver = "0x..." // Recipient address
async function batchWithHelper() {
// Prepare operations using the helper
const operations = [
// Upgrade 100 tokens
prepareOperation({
operationType: OPERATION_TYPE.SUPERTOKEN_UPGRADE,
target: superToken,
abi: superTokenAbi,
functionName: "upgrade",
args: [parseEther("100")]
}),
// Create a flow of 10 tokens per month
prepareOperation({
operationType: OPERATION_TYPE.SUPERFLUID_CALL_AGREEMENT,
target: cfaForwarderAddress[mainnet.id],
abi: cfaForwarderAbi,
functionName: "createFlow",
args: [
superToken,
account.address,
receiver,
calculateFlowrate({ amountWei: parseEther("10"), timeUnit: "month" }),
"0x"
]
})
]
// Execute batch call
const hash = await walletClient.writeContract({
address: hostAddress[mainnet.id],
abi: hostAbi,
functionName: "batchCall",
args: [operations]
})
return hash
}
Common Batch Patterns
Wrap and Stream
Wrap tokens and immediately start streaming:
Note: The underlying token approval cannot be included in the batch call because msg.sender
changes when operations go through the Superfluid Host. Approve the underlying token separately before executing the batch.
import { superTokenAbi, cfaForwarderAbi } from "@sfpro/sdk/abi"
import { prepareOperation, OPERATION_TYPE, calculateFlowrate } from "@sfpro/sdk/util"
import { parseEther } from "viem"
const wrapAndStream = [
// 1. Upgrade to super token
prepareOperation({
operationType: OPERATION_TYPE.SUPERTOKEN_UPGRADE,
target: "0x...", // Super token
abi: superTokenAbi,
functionName: "upgrade",
args: [parseEther("100")]
}),
// 2. Create flow
prepareOperation({
operationType: OPERATION_TYPE.CFA_CREATE_FLOW,
target: "0x...", // CFA Forwarder
abi: cfaForwarderAbi,
functionName: "setFlowrate",
args: [
"0x...", // Super token
"0x...", // Receiver
calculateFlowrate({ amountWei: parseEther("10"), timeUnit: "month" })
]
})
]
Multiple Flow Updates
Update multiple flows in one transaction:
import { cfaForwarderAbi } from "@sfpro/sdk/abi"
import { prepareOperation, OPERATION_TYPE, calculateFlowrate } from "@sfpro/sdk/util"
import { parseEther } from "viem"
const receivers = [
{ address: "0x...", flowrate: "100" },
{ address: "0x...", flowrate: "200" },
{ address: "0x...", flowrate: "150" }
]
const multipleFlowUpdates = receivers.map(receiver =>
prepareOperation({
operationType: OPERATION_TYPE.SUPERFLUID_CALL_AGREEMENT,
target: "0x...", // CFA Forwarder
abi: cfaForwarderAbi,
functionName: "setFlowrate",
args: [
"0x...", // Super token
receiver.address,
calculateFlowrate({ amountWei: parseEther(receiver.flowrate), timeUnit: "month" })
]
})
)
Error Handling
Batch calls are atomic - if any operation fails, the entire batch reverts:
import { hostAddress, hostAbi } from "@sfpro/sdk/abi/core"
import { mainnet } from "viem/chains"
// @noErrors
const walletClient = {} as any
const operations = [] as any[]
// ---cut---
try {
// Execute batch call
const hash = await walletClient.writeContract({
address: hostAddress[mainnet.id],
abi: hostAbi,
functionName: "batchCall",
args: [operations]
})
// All operations succeeded
console.log("Batch successful:", hash)
} catch (error) {
// One or more operations failed
console.error("Batch failed:", error)
}
Best Practices
- Order matters: Operations execute sequentially
- Gas estimation: Test batch calls to estimate gas costs
- Error handling: Handle failures gracefully
- Atomic execution: All succeed or all fail
Next Steps
- Explore the API reference
- Learn about advanced protocol features
- Check out automation contracts