@sfpro/sdk

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

  1. Order matters: Operations execute sequentially
  2. Gas estimation: Test batch calls to estimate gas costs
  3. Error handling: Handle failures gracefully
  4. Atomic execution: All succeed or all fail

Next Steps