Manage Access Control and User Data
This guide explains how to manage access control and user data in the Superfluid protocol from your client applications:
- Access control allows you to delegate flow management to another account
- User data lets you attach additional information to transactions.
Interacting with the Superfluid Protocol
To interact with the Superfluid protocol from client applications, you'll use the CFAv1Forwarder
contract.
In the case of a JavaScript/TypeScript based application, here's how to set it up:
Contract ABI and Address
The CFAv1Forwarder
contract address is the same on all networks:
0xcfA132E353cB4E398080B9700609bb008eceB125
You can find the full ABI of the CFAv1Forwarder
contract in the CFAv1Forwarder technical reference.
Setting up with ethers.js
Here's how to initiate interaction with the CFAv1Forwarder
contract using ethers.js:
import { ethers } from 'ethers';
// Assuming you have a provider set up (e.g., using MetaMask)
const provider = new ethers.providers.Web3Provider(window.ethereum);
// The address of the CFAv1Forwarder contract
const forwarderAddress = '0xcfA132E353cB4E398080B9700609bb008eceB125';
// The ABI of the CFAv1Forwarder contract (import this from the technical reference)
const forwarderABI = [...]; // Insert the ABI here
// Create a contract instance
const forwarderContract = new ethers.Contract(forwarderAddress, forwarderABI, provider.getSigner());
Access Control in Superfluid
Access control in Superfluid allows one account (the flow operator) to manage flows on behalf of another account. This is particularly useful for automated systems, multi-sig wallets, or any scenario where you want to delegate flow management.
Key concepts:
- Flow Operator: An account that has been granted permissions to manage flows on behalf of another account.
- Permissions: The specific actions a flow operator is allowed to perform (create, update, delete flows).
- Flow Rate Allowance: The maximum net flow rate a flow operator can create on behalf of the account.
User Data in Superfluid
User data in Superfluid allows you to attach additional information to transactions. This can be used for various purposes:
- Including metadata with transactions
- Triggering specific logic in receiver contracts
- Implementing off-chain systems that react to on-chain events
User data is typically passed as a bytes
parameter in Superfluid functions, allowing you to encode any type of data you need.
Key Functions for Access Control (with User Data)
grantPermissions
Grants permissions to a flow operator to manage flows on behalf of the caller.
function grantPermissions(
ISuperToken token,
address flowOperator
) external returns (bool)
Parameters
token
: The Super Token addressflowOperator
: The account to which permissions are granted
Usage Example
const grantPermissions = async (tokenAddress, flowOperatorAddress) => {
try {
const tx = await forwarderContract.grantPermissions(tokenAddress, flowOperatorAddress);
await tx.wait();
console.log("Permissions granted successfully!");
} catch (error) {
console.error("Error granting permissions:", error);
}
};
revokePermissions
Revokes all permissions previously granted to a flow operator.
function revokePermissions(
ISuperToken token,
address flowOperator
) external returns (bool)
Parameters
token
: The Super Token addressflowOperator
: The account from which permissions are revoked
Usage Example
const revokePermissions = async (tokenAddress, flowOperatorAddress) => {
try {
const tx = await forwarderContract.revokePermissions(tokenAddress, flowOperatorAddress);
await tx.wait();
console.log("Permissions revoked successfully!");
} catch (error) {
console.error("Error revoking permissions:", error);
}
};
setFlowrateFrom
Allows a flow operator to set the flow rate from one account to another.
function setFlowrateFrom(
ISuperToken token,
address sender,
address receiver,
int96 flowrate
) external returns (bool)
Parameters
token
: The Super Token addresssender
: The sender of the flowreceiver
: The receiver of the flowflowrate
: The desired flow rate in tokens per second (using 18 decimal places)
Usage Example
const setFlowrateFrom = async (tokenAddress, senderAddress, receiverAddress, flowRate) => {
try {
const tx = await forwarderContract.setFlowrateFrom(tokenAddress, senderAddress, receiverAddress, flowRate);
await tx.wait();
console.log("Flow rate set successfully!");
} catch (error) {
console.error("Error setting flow rate:", error);
}
};
Live UI Example for Access Control List Management
Here's an example of a UI component for managing access control lists:
function AccessControlManager() { const [tokenAddress, setTokenAddress] = useState(''); const [flowOperator, setFlowOperator] = useState(''); const [sender, setSender] = useState(''); const [receiver, setReceiver] = useState(''); const [flowRate, setFlowRate] = useState(''); const [walletConnected, setWalletConnected] = useState(false); const [account, setAccount] = useState(''); const toast = useToast(); const forwarderAddress = '0xcfA132E353cB4E398080B9700609bb008eceB125'; const forwarderABI = CFAv1ForwarderABI; // You will need to import the ABI in your code. You can do that from: https://docs.superfluid.finance/docs/technical-reference/CFAv1Forwarder const connectWallet = async () => { if (typeof window.ethereum !== 'undefined') { try { await window.ethereum.request({ method: 'eth_requestAccounts' }); const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); const address = await signer.getAddress(); setAccount(address); setWalletConnected(true); toast({ title: 'Wallet connected', description: `Connected to ${address}`, status: 'success', duration: 3000, isClosable: true, }); } catch (error) { console.error('Failed to connect wallet:', error); toast({ title: 'Connection failed', description: 'Failed to connect wallet. Please try again.', status: 'error', duration: 3000, isClosable: true, }); } } else { toast({ title: 'Metamask not detected', description: 'Please install Metamask to use this feature.', status: 'warning', duration: 3000, isClosable: true, }); } }; const handleAction = async (action) => { if (!walletConnected) { toast({ title: 'Wallet not connected', description: 'Please connect your wallet first.', status: 'warning', duration: 3000, isClosable: true, }); return; } const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); const contract = new ethers.Contract(forwarderAddress, forwarderABI, signer); try { let tx; switch (action) { case 'grant': tx = await contract.grantPermissions(tokenAddress, flowOperator); break; case 'revoke': tx = await contract.revokePermissions(tokenAddress, flowOperator); break; case 'setFlowrate': tx = await contract.setFlowrateFrom(tokenAddress, sender, receiver, flowRate); break; } await tx.wait(); toast({ title: 'Transaction successful', description: `${action} action completed successfully!`, status: 'success', duration: 3000, isClosable: true, }); } catch (error) { console.error(`Error performing ${action} action:`, error); toast({ title: 'Transaction failed', description: `Failed to ${action}. Please try again.`, status: 'error', duration: 3000, isClosable: true, }); } }; return ( <Box maxWidth="500px" margin="auto" padding="20px"> <VStack spacing={4} align="stretch"> <Text fontSize="2xl" fontWeight="bold" textAlign="center">Access Control Manager</Text> {!walletConnected ? ( <Button colorScheme="blue" onClick={connectWallet}>Connect Wallet</Button> ) : ( <Text>Connected: {account}</Text> )} <Input placeholder="Token Address" value={tokenAddress} onChange={(e) => setTokenAddress(e.target.value)} /> <Input placeholder="Flow Operator Address" value={flowOperator} onChange={(e) => setFlowOperator(e.target.value)} /> <Input placeholder="Sender Address (for setFlowrateFrom)" value={sender} onChange={(e) => setSender(e.target.value)} /> <Input placeholder="Receiver Address (for setFlowrateFrom)" value={receiver} onChange={(e) => setReceiver(e.target.value)} /> <Input placeholder="Flow Rate (for setFlowrateFrom)" value={flowRate} onChange={(e) => setFlowRate(e.target.value)} /> <HStack spacing={4}> <Button colorScheme="green" onClick={() => handleAction('grant')} flex={1}>Grant Permissions</Button> <Button colorScheme="red" onClick={() => handleAction('revoke')} flex={1}>Revoke Permissions</Button> </HStack> <Button colorScheme="blue" onClick={() => handleAction('setFlowrate')}>Set Flowrate From</Button> </VStack> </Box> ); }
This UI provides the following features:
- Wallet Connection: Users can connect their Ethereum wallet to interact with the Superfluid protocol.
- Input Fields: Users can enter the token address, flow operator address, sender address, receiver address, and flow rate.
- Action Buttons: Separate buttons for granting permissions, revoking permissions, and setting flow rate.
- Feedback: Toast notifications to inform users about the success or failure of their actions.
To use this component:
- Click "Connect Wallet" to connect your Ethereum wallet.
- Enter the required information in the input fields.
- Click the appropriate button to perform the desired action (grant permissions, revoke permissions, or set flow rate).
This example provides a starting point for building a user interface to manage access control in Superfluid. In a production environment, you would want to add more robust error checking, input validation, and possibly additional features like displaying current permissions or flow rates.
The example above is a live example and requires the ABI to be imported. In the case of this example, the ABI has already been imported through the live coder.
In order to make it work on your developer environment, head to the CFAv1Forwarder technical reference and copy the ABI.
Then, replace the forwarderABI
variable in the example with the ABI you copied.