Skip to main content

Off-Chain

Introduction

The GDAv1Forwarder contract is a crucial component of the Superfluid protocol, enabling users to easily interact with Instant and Streaming Distributions (formerly known as General Distribution Agreement - GDA) through Super Tokens. This guide will walk you through the most important functions of the GDAv1Forwarder.sol contract and show you how to use them with ethers.js.

Click here to show GDAv1Forwarder contract

// SPDX-License-Identifier: AGPLv3
pragma solidity 0.8.19;

import {
ISuperfluid,
ISuperfluidToken,
} from "../interfaces/superfluid/ISuperfluid.sol";
import { ISuperfluidPool } from "../agreements/gdav1/SuperfluidPool.sol";
import {
IGeneralDistributionAgreementV1,
PoolConfig,
} from "../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
import { ForwarderBase } from "./ForwarderBase.sol";

/\*\*

- @title GDAv1Forwarder
- @author Superfluid
- The GDAv1Forwarder contract provides an easy to use interface to
- GeneralDistributionAgreementV1 specific functionality of Super Tokens.
- Instances of this contract can operate on the protocol only if configured as "trusted forwarder"
- by protocol governance.
\*/
contract GDAv1Forwarder is ForwarderBase {
IGeneralDistributionAgreementV1 internal immutable \_gda;

// is tied to a specific instance of host and agreement contracts at deploy time
constructor(ISuperfluid host) ForwarderBase(host) {
_gda = IGeneralDistributionAgreementV1(
address(
_host.getAgreementClass(keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1"))
)
);
}

/**
* @dev Creates a new Superfluid Pool.
* @param token The Super Token address.
* @param admin The pool admin address.
* @param config The pool configuration (see PoolConfig in IGeneralDistributionAgreementV1.sol)
* @return success A boolean value indicating whether the pool was created successfully.
* @return pool The address of the deployed Superfluid Pool
*/
function createPool(ISuperfluidToken token, address admin, PoolConfig memory config)
external
returns (bool success, ISuperfluidPool pool)
{
pool = _gda.createPool(token, admin, config);
success = true;
}

/**
* @dev Updates the units of a pool member.
* @param pool The Superfluid Pool to update.
* @param memberAddress The address of the member to update.
* @param newUnits The new units of the member.
* @param userData User-specific data.
*/
function updateMemberUnits(ISuperfluidPool pool, address memberAddress, uint128 newUnits, bytes memory userData)
external
returns (bool success)
{
bytes memory callData = abi.encodeCall(_gda.updateMemberUnits, (pool, memberAddress, newUnits, new bytes(0)));

return _forwardBatchCall(address(_gda), callData, userData);
}

/**
* @dev Claims all tokens from the pool.
* @param pool The Superfluid Pool to claim from.
* @param memberAddress The address of the member to claim for.
* @param userData User-specific data.
*/
function claimAll(ISuperfluidPool pool, address memberAddress, bytes memory userData)
external
returns (bool success)
{
bytes memory callData = abi.encodeCall(_gda.claimAll, (pool, memberAddress, new bytes(0)));

return _forwardBatchCall(address(_gda), callData, userData);
}

/**
* @dev Connects a pool member to `pool`.
* @param pool The Superfluid Pool to connect.
* @param userData User-specific data.
* @return A boolean value indicating whether the connection was successful.
*/
function connectPool(ISuperfluidPool pool, bytes memory userData) external returns (bool) {
bytes memory callData = abi.encodeCall(_gda.connectPool, (pool, new bytes(0)));

return _forwardBatchCall(address(_gda), callData, userData);
}

/**
* @dev Disconnects a pool member from `pool`.
* @param pool The Superfluid Pool to disconnect.
* @param userData User-specific data.
* @return A boolean value indicating whether the disconnection was successful.
*/
function disconnectPool(ISuperfluidPool pool, bytes memory userData) external returns (bool) {
bytes memory callData = abi.encodeCall(_gda.disconnectPool, (pool, new bytes(0)));

return _forwardBatchCall(address(_gda), callData, userData);
}

/**
* @dev Tries to distribute `requestedAmount` amount of `token` from `from` to `pool`.
* @param token The Super Token address.
* @param from The address from which to distribute tokens.
* @param pool The Superfluid Pool address.
* @param requestedAmount The amount of tokens to distribute.
* @param userData User-specific data.
* @return A boolean value indicating whether the distribution was successful.
*/
function distribute(
ISuperfluidToken token,
address from,
ISuperfluidPool pool,
uint256 requestedAmount,
bytes memory userData
) external returns (bool) {
bytes memory callData = abi.encodeCall(_gda.distribute, (token, from, pool, requestedAmount, new bytes(0)));

return _forwardBatchCall(address(_gda), callData, userData);
}

/**
* @dev Tries to distribute flow at `requestedFlowRate` of `token` from `from` to `pool`.
* @param token The Super Token address.
* @param from The address from which to distribute tokens.
* @param pool The Superfluid Pool address.
* @param requestedFlowRate The flow rate of tokens to distribute.
* @param userData User-specific data.
* @return A boolean value indicating whether the distribution was successful.
*/
function distributeFlow(
ISuperfluidToken token,
address from,
ISuperfluidPool pool,
int96 requestedFlowRate,
bytes memory userData
) external returns (bool) {
bytes memory callData =
abi.encodeCall(_gda.distributeFlow, (token, from, pool, requestedFlowRate, new bytes(0)));

return _forwardBatchCall(address(_gda), callData, userData);
}

/**
* @dev Checks if the specified account is a pool.
* @param token The Super Token address.
* @param account The account address to check.
* @return A boolean value indicating whether the account is a pool.
*/
function isPool(ISuperfluidToken token, address account) external view virtual returns (bool) {
return _gda.isPool(token, account);
}

/**
* @dev Gets the GDA net flow rate for the specified account.
* @param token The Super Token address.
* @param account The account address.
* @return The gda net flow rate for the account.
*/
function getNetFlow(ISuperfluidToken token, address account) external view returns (int96) {
return _gda.getNetFlow(token, account);
}

/**
* @dev Gets the flow rate of tokens between the specified accounts.
* @param token The Super Token address.
* @param from The sender address.
* @param to The receiver address (the pool address).
* @return The flow distribution flow rate
*/
function getFlowDistributionFlowRate(ISuperfluidToken token, address from, ISuperfluidPool to)
external
view
returns (int96)
{
return _gda.getFlowRate(token, from, to);
}

/**
* @dev Gets the pool adjustment flow rate for the specified pool.
* @param pool The pool address.
* @return The pool adjustment flow rate.
*/
function getPoolAdjustmentFlowRate(address pool) external view virtual returns (int96) {
return _gda.getPoolAdjustmentFlowRate(pool);
}

/**
* @dev Estimates the actual flow rate for flow distribution to the specified pool.
* @param token The Super Token address.
* @param from The sender address.
* @param to The pool address.
* @param requestedFlowRate The requested flow rate.
* @return actualFlowRate
* @return totalDistributionFlowRate
*/
function estimateFlowDistributionActualFlowRate(
ISuperfluidToken token,
address from,
ISuperfluidPool to,
int96 requestedFlowRate
) external view returns (int96 actualFlowRate, int96 totalDistributionFlowRate) {
return _gda.estimateFlowDistributionActualFlowRate(token, from, to, requestedFlowRate);
}

/**
* @dev Estimates the actual amount for distribution to the specified pool.
* @param token The Super Token address.
* @param from The sender address.
* @param to The pool address.
* @param requestedAmount The requested amount.
* @return actualAmount The actual amount for distribution.
*/
function estimateDistributionActualAmount(
ISuperfluidToken token,
address from,
ISuperfluidPool to,
uint256 requestedAmount
) external view returns (uint256 actualAmount) {
return _gda.estimateDistributionActualAmount(token, from, to, requestedAmount);
}

/**
* @dev Checks if the specified member is connected to the pool.
* @param pool The Superfluid Pool address.
* @param member The member address.
* @return A boolean value indicating whether the member is connected to the pool.
*/
function isMemberConnected(ISuperfluidPool pool, address member) external view returns (bool) {
return _gda.isMemberConnected(pool, member);
}

/**
* @dev Gets the pool adjustment flow information for the specified pool.
* @param pool The pool address.
* @return The pool admin, pool ID, and pool adjustment flow rate.
*/
function getPoolAdjustmentFlowInfo(ISuperfluidPool pool) external view virtual returns (address, bytes32, int96) {
return _gda.getPoolAdjustmentFlowInfo(pool);
}

}

Setup and Connection

To interact with the GDAv1Forwarder contract, you'll first need to set up your project environment.

Installation

Install the necessary packages for either ethers.js or wagmi, depending on your preference.

npm install ethers

Connecting to the Contract

After installing the necessary packages, connect to the Ethereum blockchain using a provider and sign transactions with a signer.

import { ethers } from "ethers";

// Connect to an Ethereum node
const provider = new ethers.providers.JsonRpcProvider("YOUR_RPC_URL");

// Connect to the CFAv1Forwarder contract
const contractAddress = "YOUR_CONTRACT_ADDRESS";
const abi = "YOUR_CONTRACT_ABI";
const cfaForwarder = new ethers.Contract(contractAddress, abi, provider);
Where to find contract addresses?

You can find the addresses of the deployed GDAv1Forwarder contracts on the Console with all the addresses of all the public Superfluid contracts.

Where to find the contract ABI?

You can find the ABI of the GDAv1Forwarder contracts below:

Click here to show GDAv1Forwarder ABI

[
{
"inputs": [
{
"internalType": "contract ISuperfluid",
"name": "host",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidPool",
"name": "pool",
"type": "address"
},
{ "internalType": "address", "name": "memberAddress", "type": "address" },
{ "internalType": "bytes", "name": "userData", "type": "bytes" }
],
"name": "claimAll",
"outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidPool",
"name": "pool",
"type": "address"
},
{ "internalType": "bytes", "name": "userData", "type": "bytes" }
],
"name": "connectPool",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidToken",
"name": "token",
"type": "address"
},
{ "internalType": "address", "name": "admin", "type": "address" },
{
"components": [
{
"internalType": "bool",
"name": "transferabilityForUnitsOwner",
"type": "bool"
},
{
"internalType": "bool",
"name": "distributionFromAnyAddress",
"type": "bool"
}
],
"internalType": "struct PoolConfig",
"name": "config",
"type": "tuple"
}
],
"name": "createPool",
"outputs": [
{ "internalType": "bool", "name": "success", "type": "bool" },
{
"internalType": "contract ISuperfluidPool",
"name": "pool",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidPool",
"name": "pool",
"type": "address"
},
{ "internalType": "bytes", "name": "userData", "type": "bytes" }
],
"name": "disconnectPool",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidToken",
"name": "token",
"type": "address"
},
{ "internalType": "address", "name": "from", "type": "address" },
{
"internalType": "contract ISuperfluidPool",
"name": "pool",
"type": "address"
},
{
"internalType": "uint256",
"name": "requestedAmount",
"type": "uint256"
},
{ "internalType": "bytes", "name": "userData", "type": "bytes" }
],
"name": "distribute",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidToken",
"name": "token",
"type": "address"
},
{ "internalType": "address", "name": "from", "type": "address" },
{
"internalType": "contract ISuperfluidPool",
"name": "pool",
"type": "address"
},
{ "internalType": "int96", "name": "requestedFlowRate", "type": "int96" },
{ "internalType": "bytes", "name": "userData", "type": "bytes" }
],
"name": "distributeFlow",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidToken",
"name": "token",
"type": "address"
},
{ "internalType": "address", "name": "from", "type": "address" },
{
"internalType": "contract ISuperfluidPool",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "requestedAmount",
"type": "uint256"
}
],
"name": "estimateDistributionActualAmount",
"outputs": [
{ "internalType": "uint256", "name": "actualAmount", "type": "uint256" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidToken",
"name": "token",
"type": "address"
},
{ "internalType": "address", "name": "from", "type": "address" },
{
"internalType": "contract ISuperfluidPool",
"name": "to",
"type": "address"
},
{ "internalType": "int96", "name": "requestedFlowRate", "type": "int96" }
],
"name": "estimateFlowDistributionActualFlowRate",
"outputs": [
{ "internalType": "int96", "name": "actualFlowRate", "type": "int96" },
{
"internalType": "int96",
"name": "totalDistributionFlowRate",
"type": "int96"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidToken",
"name": "token",
"type": "address"
},
{ "internalType": "address", "name": "from", "type": "address" },
{
"internalType": "contract ISuperfluidPool",
"name": "to",
"type": "address"
}
],
"name": "getFlowDistributionFlowRate",
"outputs": [{ "internalType": "int96", "name": "", "type": "int96" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidToken",
"name": "token",
"type": "address"
},
{ "internalType": "address", "name": "account", "type": "address" }
],
"name": "getNetFlow",
"outputs": [{ "internalType": "int96", "name": "", "type": "int96" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidPool",
"name": "pool",
"type": "address"
}
],
"name": "getPoolAdjustmentFlowInfo",
"outputs": [
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "bytes32", "name": "", "type": "bytes32" },
{ "internalType": "int96", "name": "", "type": "int96" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "pool", "type": "address" }
],
"name": "getPoolAdjustmentFlowRate",
"outputs": [{ "internalType": "int96", "name": "", "type": "int96" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidPool",
"name": "pool",
"type": "address"
},
{ "internalType": "address", "name": "member", "type": "address" }
],
"name": "isMemberConnected",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidToken",
"name": "token",
"type": "address"
},
{ "internalType": "address", "name": "account", "type": "address" }
],
"name": "isPool",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISuperfluidPool",
"name": "pool",
"type": "address"
},
{ "internalType": "address", "name": "memberAddress", "type": "address" },
{ "internalType": "uint128", "name": "newUnits", "type": "uint128" },
{ "internalType": "bytes", "name": "userData", "type": "bytes" }
],
"name": "updateMemberUnits",
"outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
}
]

Key steps to build your app

1. Creating the pool

For most of the applications build using Distribution Pools, pool creation happens on-chain using the SuperTokenV1Library. However, in case your pool is not created through your smart contract, you can always use the external function createPool on the GDAv1Forwarder in order to create a Pool.

The createPool function is used to create a new Superfluid Pool and assign an admin and a config to the pool.

  • Parameters:
    • token: Super Token address (ISuperfluidToken).
    • admin: Pool admin address.
    • config: Pool configuration (PoolConfig).
async function createPool(token, admin, config) {
const tx = await gdaForwarder.createPool(token, admin, config);
const receipt = await tx.wait();
return receipt.events.find((event) => event.event === "PoolCreated").args
.pool;
}
What is a pool config?

The PoolConfig struct is defined in the IGeneralDistributionAgreementV1.sol interface as such:

    struct PoolConfig {
/// @dev if true, the pool members can transfer their owned units
/// else, only the pool admin can manipulate the units for pool members
bool transferabilityForUnitsOwner;
/// @dev if true, anyone can execute distributions via the pool
/// else, only the pool admin can execute distributions via the pool
bool distributionFromAnyAddress;
}

It is used to configure the pool's transferability and distribution permissions.

2. Setting the units of a pool member

Once your pool is created, the admin can also interact with it off-chain using the updateMemberUnits function from the GDAv1Forwarder contract.

The function updateMemberUnits updates the units of a specific pool member.

  • Parameters:
    • pool: Superfluid Pool address (ISuperfluidPool).
    • memberAddress: Address of the member.
    • newUnits: New units of the member.
    • userData: User-specific data.
async function updateMemberUnits(pool, memberAddress, newUnits, userData) {
const tx = await gdaForwarder.updateMemberUnits(pool, memberAddress, newUnits, userData);
await tx.wait();
}

3. Distributing the tokens

Once you have set up your pool and updated the units of the pool members, you can distribute tokens to the pool members using the distribute function in the case of Instant Distributions and the distributeFlow function in the case of Streaming Distributions.

The function distribute distributes a specified amount of tokens from one address to a pool.

  • Parameters:
    • token: Super Token address (ISuperfluidToken).
    • from: Address from which to distribute tokens.
    • pool: Superfluid Pool address (ISuperfluidPool).
    • requestedAmount: Amount of tokens to distribute.
    • userData: User-specific data.
async function distribute(token, from, pool, requestedAmount, userData) {
const tx = await gdaForwarder.distribute(token, from, pool, requestedAmount, userData);
await tx.wait();
}

The function distributeFlow distributes a specified flow rate of tokens from one address to a pool.

  • Parameters:
    • token: Super Token address (ISuperfluidToken).
    • from: Address from which to distribute tokens.
    • pool: Superfluid Pool address (ISuperfluidPool).
    • requestedFlowRate: Flow rate of tokens to distribute.
    • userData: User-specific data.
async function distributeFlow(token, from, pool, requestedFlowRate, userData) {
const tx = await gdaForwarder.distributeFlow(token, from, pool, requestedFlowRate, userData);
await tx.wait();
}

4. Claiming the tokens

Pool members can claim their tokens from the pool using the claimAll function at any time. Tokens accumulate in the pool until they are claimed by the members. The claimAll function can be called by any address to claim the tokens from the pool to any member.

What happens if a member is already connected to the pool?

If a member is already connected to the pool, the claimAll function is probably redundant. As we will see in the next section, the connectPool function is used to connect a member to a pool and automatically claim the previously accumulated tokens.

The claimAll function claims all the tokens from the pool.

  • Parameters:
    • pool: Superfluid Pool address (ISuperfluidPool).
    • memberAddress: Address of the member.
    • userData: User-specific data.
async function claimAll(pool, memberAddress, userData) {
const tx = await gdaForwarder.claimAll(pool, memberAddress, userData);
await tx.wait();
}

5. Connecting to the pool

In order to avoid periodic claims, a pool member can connect to a pool using the connectPool function. Connecting to a pool makes it so the member's balance gets updated automatically with distributions. It also automatically claims the accumulated tokens from the pool. As opposed to claiming, only the pool member themselves can connect to the pool.

The connectPool function connects a pool member to a pool.

  • Parameters:
    • pool: Superfluid Pool address (ISuperfluidPool).
    • userData: User-specific data.
async function connectPool(pool, userData) {
const tx = await gdaForwarder.connectPool(pool, userData);
await tx.wait();
}

6. Disonnecting from the pool

If a pool member wants to stop receiving distributions automatically from a pool, they can disconnect from the pool using the disconnectPool function.

The function disconnectPool disconnects a pool member from a pool.

  • Parameters:
    • pool: Superfluid Pool address (ISuperfluidPool).
    • userData: User-specific data.
async function disconnectPool(pool, userData) {
const tx = await gdaForwarder.disconnectPool(pool, userData);
await tx.wait();
}

This guide provides an overview of how to interact with the GDAv1Forwarder contract using ethers.js or Wagmi. For more information, check out the GDAv1Forwarder Technical Reference