Skip to main content

Interact Off-Chain

Introduction

The CFAv1Forwarder contract in the Superfluid protocol provides a user-friendly interface to interact with the Money Streaming functionality of Super Tokens. It's designed to be used as a trusted forwarder, allowing off-chain interactions through frameworks like Ethers.js or Wagmi.

Click here to show CFAv1Forwarder contract

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

import {
ISuperfluid,
ISuperToken,
FlowOperatorDefinitions,
IConstantFlowAgreementV1
} from "../interfaces/superfluid/ISuperfluid.sol";
import { ForwarderBase } from "./ForwarderBase.sol";


/**
* @title CFAv1Forwarder
* @author Superfluid
* The CFAv1Forwarder contract provides an easy to use interface to
* ConstantFlowAgreementV1 specific functionality of Super Tokens.
* Instances of this contract can operate on the protocol only if configured as "trusted forwarder"
* by protocol governance.
*/
contract CFAv1Forwarder is ForwarderBase {
error CFA_FWD_INVALID_FLOW_RATE();

IConstantFlowAgreementV1 internal immutable _cfa;

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

/**
* @notice Sets the given flowrate between msg.sender and a given receiver.
* If there's no pre-existing flow and `flowrate` non-zero, a new flow is created.
* If there's an existing flow and `flowrate` non-zero, the flowrate of that flow is updated.
* If there's an existing flow and `flowrate` zero, the flow is deleted.
* If the existing and given flowrate are equal, no action is taken.
* On creation of a flow, a "buffer" amount is automatically detracted from the sender account's available balance.
* If the sender account is solvent when the flow is deleted, this buffer is redeemed to it.
* @param token Super token address
* @param receiver The receiver of the flow
* @param flowrate The wanted flowrate in wad/second. Only positive values are valid here.
* @return bool
*/
function setFlowrate(ISuperToken token, address receiver, int96 flowrate) external returns (bool) {
return _setFlowrateFrom(token, msg.sender, receiver, flowrate);
}

/**
* @notice Like `setFlowrate`, but can be invoked by an account with flowOperator permissions
* on behalf of the sender account.
* @param token Super token address
* @param sender The sender of the flow
* @param receiver The receiver of the flow
* @param flowrate The wanted flowrate in wad/second. Only positive values are valid here.
* @return bool
*/
function setFlowrateFrom(
ISuperToken token,
address sender,
address receiver,
int96 flowrate
) external returns (bool) {
return _setFlowrateFrom(token, sender, receiver, flowrate);
}

/**
* @notice Get the flowrate of the flow between 2 accounts if exists.
* @dev Currently, only 0 or 1 flows can exist between 2 accounts. This may change in the future.
* @param token Super token address
* @param sender The sender of the flow
* @param receiver The receiver of the flow
* @return flowrate The flowrate from the sender to the receiver account. Returns 0 if no flow exists.
*/
function getFlowrate(ISuperToken token, address sender, address receiver) external view
returns(int96 flowrate)
{
(, flowrate, , ) = _cfa.getFlow(token, sender, receiver);
}

/**
* @notice Get all available information about a flow (if exists).
* If only the flowrate is needed, consider using `getFlowrate` instead.
* @param token Super token address
* @param sender The sender of the flow
* @param receiver The receiver of the flow
* @return lastUpdated Timestamp of last update (flowrate change) or zero if no flow exists
* @return flowrate Current flowrate of the flow or zero if no flow exists
* @return deposit Deposit amount locked as security buffer during the lifetime of the flow
* @return owedDeposit Extra deposit amount borrowed to a SuperApp receiver by the flow sender
*/
function getFlowInfo(ISuperToken token, address sender, address receiver) external view
returns(uint256 lastUpdated, int96 flowrate, uint256 deposit, uint256 owedDeposit)
{
(lastUpdated, flowrate, deposit, owedDeposit) = _cfa.getFlow(token, sender, receiver);
}

/**
* @notice Get the buffer amount required for the given token and flowrate.
* This amount can vary based on the combination of token, flowrate and chain being queried.
* The result for a given set of parameters can change over time,
* because it depends on governance configurable protocol parameters.
* Changes of the required buffer amount affect only flows created or updated after the change.
* @param token Super token address
* @param flowrate The flowrate for which the buffer amount is calculated
* @return bufferAmount The buffer amount required for the given configuration.
*/
function getBufferAmountByFlowrate(ISuperToken token, int96 flowrate) external view
returns (uint256 bufferAmount)
{
return _cfa.getDepositRequiredForFlowRate(token, flowrate);
}

/**
* @notice Get the net flowrate of an account.
* @param token Super token address
* @param account Account to query
* @return flowrate The net flowrate (aggregate incoming minus aggregate outgoing flowrate), can be negative.
*/
function getAccountFlowrate(ISuperToken token, address account) external view
returns (int96 flowrate)
{
return _cfa.getNetFlow(token, account);
}

/**
* @notice Get aggregated flow information (if any exist) of an account.
* If only the net flowrate is needed, consider using `getAccountFlowrate` instead.
* @param token Super token address
* @param account Account to query
* @return lastUpdated Timestamp of last update of a flow to or from the account (flowrate change)
* @return flowrate Current net aggregate flowrate
* @return deposit Aggregate deposit amount currently locked as security buffer for outgoing flows
* @return owedDeposit Aggregate extra deposit amount currently borrowed to SuperApps receiving from this account
*/
function getAccountFlowInfo(ISuperToken token, address account) external view
returns (uint256 lastUpdated, int96 flowrate, uint256 deposit, uint256 owedDeposit)
{
return _cfa.getAccountFlowInfo(token, account);
}

/**
* @notice Low-level wrapper of createFlow/createFlowByOperator.
* If the address of msg.sender is not the same as the address of the `sender` argument,
* createFlowByOperator is used internally. In this case msg.sender needs to have permission to create flows
* on behalf of the given sender account with sufficient flowRateAllowance.
* Currently, only 1 flow can exist between 2 accounts, thus `createFlow` will fail if one already exists.
* @param token Super token address
* @param sender Sender address of the flow
* @param receiver Receiver address of the flow
* @param flowrate The flowrate in wad/second to be set initially
* @param userData (optional) User data to be set. Should be set to zero if not needed.
* @return bool
*/
function createFlow(ISuperToken token, address sender, address receiver, int96 flowrate, bytes memory userData)
external returns (bool)
{
return _createFlow(token, sender, receiver, flowrate, userData);
}

/**
* @notice Low-level wrapper if updateFlow/updateFlowByOperator.
* If the address of msg.sender doesn't match the address of the `sender` argument,
* updateFlowByOperator is invoked. In this case msg.sender needs to have permission to update flows
* on behalf of the given sender account with sufficient flowRateAllowance.
* @param token Super token address
* @param sender Sender address of the flow
* @param receiver Receiver address of the flow
* @param flowrate The flowrate in wad/second the flow should be updated to
* @param userData (optional) User data to be set. Should be set to zero if not needed.
* @return bool
*/
function updateFlow(ISuperToken token, address sender, address receiver, int96 flowrate, bytes memory userData)
external returns (bool)
{
return _updateFlow(token, sender, receiver, flowrate, userData);
}

/**
* @notice Low-level wrapper of deleteFlow/deleteFlowByOperator.
* If msg.sender isn't the same as sender address, msg.sender needs to have permission
* to delete flows on behalf of the given sender account.
* @param token Super token address
* @param sender Sender address of the flow
* @param receiver Receiver address of the flow
* @param userData (optional) User data to be set. Should be set to zero if not needed.
* @return bool
*/
function deleteFlow(
ISuperToken token,
address sender,
address receiver,
bytes memory userData
)
external returns (bool)
{
return _deleteFlow(token, sender, receiver, userData);
}

/**
* @notice Grants a flowOperator permission to create/update/delete flows on behalf of msg.sender.
* In order to restrict what a flowOperator can or can't do, the flowOperator account
* should be a contract implementing the desired restrictions.
* @param token Super token address
* @param flowOperator Account to which permissions are granted
* @return bool
*/
function grantPermissions(ISuperToken token, address flowOperator) external returns (bool)
{
return _updateFlowOperatorPermissions(
token,
flowOperator,
FlowOperatorDefinitions.AUTHORIZE_FULL_CONTROL,
type(int96).max
);
}

/**
* @notice Revokes all permissions previously granted to a flowOperator by msg.sender.
* Revocation doesn't undo or reset flows previously created/updated by the flowOperator.
* In order to be sure about the state of flows at the time of revocation, you need to check that state
* either in the same transaction or after this transaction.
* @param token Super token address
* @param flowOperator Account from which permissions are revoked
* @return bool
*/
function revokePermissions(ISuperToken token, address flowOperator) external returns (bool)
{
return _updateFlowOperatorPermissions(
token,
flowOperator,
0,
0
);
}

/**
* @notice Low-level wrapper of `IConstantFlowAgreementV1.updateFlowOperatorPermissions`
* @param token Super token address
* @param flowOperator Account for which permissions are set on behalf of msg.sender
* @param permissions Bitmask for create/update/delete permission flags. See library `FlowOperatorDefinitions`
* @param flowrateAllowance Max. flowrate in wad/second the operator can set for individual flows.
* @return bool
* @notice flowrateAllowance does NOT restrict the net flowrate a flowOperator is able to set.
* In order to restrict that, flowOperator needs to be a contract implementing the wanted limitations.
*/
function updateFlowOperatorPermissions(
ISuperToken token,
address flowOperator,
uint8 permissions,
int96 flowrateAllowance
)
external returns (bool)
{
return _updateFlowOperatorPermissions(token, flowOperator, permissions, flowrateAllowance);
}

/**
* @notice Get the currently set permissions granted to the given flowOperator by the given sender account.
* @param token Super token address
* @param sender The account which (possibly) granted permissions
* @param flowOperator Account to which (possibly) permissions were granted
* @return permissions A bitmask of the permissions currently granted (or not) by `sender` to `flowOperator`
* @return flowrateAllowance Max. flowrate in wad/second the flowOperator can set for individual flows.
*/
function getFlowOperatorPermissions(ISuperToken token, address sender, address flowOperator) external view
returns (uint8 permissions, int96 flowrateAllowance)
{
(, permissions, flowrateAllowance) = _cfa.getFlowOperatorData(token, sender, flowOperator);
}

/**************************************************************************
* Internal functions
*************************************************************************/

function _setFlowrateFrom(
ISuperToken token,
address sender,
address receiver,
int96 flowrate
) internal returns (bool) {
(, int96 prevFlowRate,,) = _cfa.getFlow(token, sender, receiver);

if (flowrate > 0) {
if (prevFlowRate == 0) {
return _createFlow(token, sender, receiver, flowrate, new bytes(0));
} else if (prevFlowRate != flowrate) {
return _updateFlow(token, sender, receiver, flowrate, new bytes(0));
} // else no change, do nothing
return true;
} else if (flowrate == 0) {
if (prevFlowRate > 0) {
return _deleteFlow(token, sender, receiver, new bytes(0));
} // else no change, do nothing
return true;
} else {
revert CFA_FWD_INVALID_FLOW_RATE();
}
}

function _createFlow(
ISuperToken token,
address sender,
address receiver,
int96 flowrate,
bytes memory userData
)
internal returns (bool)
{
bytes memory cfaCallData = sender == msg.sender ?
abi.encodeCall(
_cfa.createFlow,
(
token,
receiver,
flowrate,
new bytes(0) // placeholder
)
) :
abi.encodeCall(
_cfa.createFlowByOperator,
(
token,
sender,
receiver,
flowrate,
new bytes(0) // placeholder
)
);

return _forwardBatchCall(address(_cfa), cfaCallData, userData);
}

function _updateFlow(
ISuperToken token,
address sender,
address receiver,
int96 flowrate,
bytes memory userData
)
internal returns (bool)
{
bytes memory cfaCallData = sender == msg.sender ?
abi.encodeCall(
_cfa.updateFlow,
(
token,
receiver,
flowrate,
new bytes(0) // placeholder
)
) :
abi.encodeCall(
_cfa.updateFlowByOperator,
(
token,
sender,
receiver,
flowrate,
new bytes(0) // placeholder
)
);

return _forwardBatchCall(address(_cfa), cfaCallData, userData);
}

function _deleteFlow(
ISuperToken token,
address sender,
address receiver,
bytes memory userData
)
internal returns (bool)
{
bytes memory cfaCallData = sender == msg.sender || receiver == msg.sender ?
abi.encodeCall(
_cfa.deleteFlow,
(
token,
sender,
receiver,
new bytes(0) // placeholder
)
) :
abi.encodeCall(
_cfa.deleteFlowByOperator,
(
token,
sender,
receiver,
new bytes(0) // placeholder
)
);

return _forwardBatchCall(address(_cfa), cfaCallData, userData);
}

function _updateFlowOperatorPermissions(
ISuperToken token,
address flowOperator,
uint8 permissions,
int96 flowrateAllowance
)
internal returns (bool)
{
bytes memory cfaCallData =
abi.encodeCall(
_cfa.updateFlowOperatorPermissions,
(
token,
flowOperator,
permissions,
flowrateAllowance,
new bytes(0) // placeholder
)
);

return _forwardBatchCall(address(_cfa), cfaCallData, new bytes(0));
}
}

Prerequisites

Before you start, make sure you have the following:

  • A basic understanding of Ethereum and smart contracts.
  • Node.js and npm installed.
  • Ethers.js or Wagmi installed in your project.

Setup and Connection

To interact with the CFAv1Forwarder 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);

Key Functions

The CFAv1Forwarder contract facilitates interactions with the Superfluid protocol, offering a range of functionalities. Here, we'll explore six essential external/public functions and illustrate their usage with ethers.js.

1. setFlowrate

This function sets the flow rate of a token stream between the sender and a receiver.

  • Parameters:
    • token: The address of the Super Token.
    • receiver: The address of the flow receiver.
    • flowrate: The desired flow rate in wei per second (int96).
async function setFlowrate(contract, token, receiver, flowrate) {
const tx = await contract.setFlowrate(token, receiver, flowrate);
await tx.wait();
}

2. setFlowrateFrom

Similar to setFlowrate, but allows an account with flowOperator permissions to set the flow rate on behalf of another account.

  • Parameters:
    • token: The address of the Super Token.
    • sender: The address of the flow sender.
    • receiver: The address of the flow receiver.
    • flowrate: The desired flow rate in wei per second (int96).
async function setFlowrateFrom(contract, token, sender, receiver, flowrate) {
const tx = await contract.setFlowrateFrom(token, sender, receiver, flowrate);
await tx.wait();
}

3. getFlowrate

Retrieves the current flow rate between two accounts.

  • Parameters:
    • token: The address of the Super Token.
    • sender: The address of the flow sender.
    • receiver: The address of the flow receiver.
async function getFlowrate(contract, token, sender, receiver) {
const flowrate = await contract.getFlowrate(token, sender, receiver);
return flowrate;
}

4. getFlowInfo

Provides comprehensive information about a specific flow.

  • Parameters:
    • token: The address of the Super Token.
    • sender: The address of the flow sender.
    • receiver: The address of the flow receiver.
async function getFlowInfo(contract, token, sender, receiver) {
const flowInfo = await contract.getFlowInfo(token, sender, receiver);
return flowInfo;
}

5. getBufferAmountByFlowrate

Calculates the buffer amount required for a given flow rate of a specific token.

  • Parameters:
    • token: The address of the Super Token.
    • flowrate: The flow rate for which the buffer amount is calculated.
async function getBufferAmountByFlowrate(contract, token, flowrate) {
const bufferAmount = await contract.getBufferAmountByFlowrate(token, flowrate);
return bufferAmount;
}

6. getAccountFlowrate

Fetches the net flow rate (incoming minus outgoing) for a specific account.

  • Parameters:
    • token: The address of the Super Token.
    • account: The account address for which the net flow rate is queried.
async function getAccountFlowrate(contract, token, account) {
const netFlowrate = await contract.getAccountFlowrate(token, account);
return netFlowrate;
}

Conclusion

This guide covers the basics of interacting with the CFAv1Forwarder contract using Ethers.js.

For more detailed information and advanced use cases, refer to the CFAv1Forwarder Technical Reference.