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.
- Ethers.js
- Wagmi
npm install ethers
npm install wagmi ethers
Connecting to the Contract
After installing the necessary packages, connect to the Ethereum blockchain using a provider and sign transactions with a signer.
- Ethers.js
- Wagmi
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);
import { useContract } from "wagmi";
import { ethers } from "ethers";
// Define the contract ABI and address
const abi = "YOUR_CONTRACT_ABI";
const contractAddress = "YOUR_CONTRACT_ADDRESS";
// Use the useContract hook from wagmi
const cfaForwarder = useContract({
addressOrName: contractAddress,
contractInterface: abi,
signerOrProvider: "YOUR_RPC_PROVIDER", // replace with your provider
});
You can find the addresses of the deployed GDAv1Forwarder contracts on the Console with all the addresses of all the public Superfluid contracts.
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;
}
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.
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