πŸ”’
User Data
Including additional metadata in your Super Apps
Another powerful component of the Superfluid protocol is the ability to pass in additional user data along with your calls to super agreements. Think of it like metadata that can accompany your streams or IDAs 😎
Before we look at user data, let's take a quick dive into a new element: Context.
Context is used for several key items within the Superfluid protocol such as gas optimization, governance, security, and SuperApp callbacks. One parameter that's also available for use within the context field is userData.
This is from the host interface (ISuperfluid.sol) file inside of our interfaces folder in the Superfluid repo. On line 21, we see userData.
1
struct Context {
2
//
3
// Call context
4
//
5
// callback level
6
uint8 appLevel;
7
// type of call
8
uint8 callType;
9
// the system timestsamp
10
uint256 timestamp;
11
// The intended message sender for the call
12
address msgSender;
13
​
14
//
15
// Callback context
16
//
17
// For callbacks it is used to know which agreement function selector is called
18
bytes4 agreementSelector;
19
// User provided data for app callbacks
20
bytes userData;
21
​
22
//
23
// App context
24
//
25
// app allowance granted
26
uint256 appAllowanceGranted;
27
// app allowance wanted by the app callback
28
uint256 appAllowanceWanted;
29
// app allowance used, allowing negative values over a callback session
30
int256 appAllowanceUsed;
31
// app address
32
address appAddress;
33
// app allowance in super token
34
ISuperfluidToken appAllowanceToken;
35
}
Copied!
Whenever you see ctx being moved around within the protocol, this struct is what's under the hood (it's just compiled down to bytes each time it's passed between functions).
As you can see, userData is one of the elements that makes up Context. For the sake of this tutorial, we're going to focus exclusively on userData for the time being.

Quick Review: How Are Super Agreements Called Again?

To call a function in a Super Agreement, you first need to use abi.encode to compile a function call to the super agreement you're looking to trigger. Then, you need to pass the agreement type, the bytecode of the previously compiled function call, and userData to callAgreement (we'll get to userData next). The whole process looks like this:
1
//solidity
2
//Matic Addresses for host and cfa
3
​
4
ISuperfluid host = "0x3E14dC1b13c488a8d5D310918780c983bD5982E7";
5
IConstantFlowAgreementV1 cfa = "0x6EeE6060f715257b970700bc2656De21dEdF074C";
6
//DAIx
7
ISuperToken acceptedToken = "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063";
8
//empty user data
9
bytes userData = "0x";
10
​
11
//$1000 DAI per month
12
int96 flowRate = "385802469135802";
13
​
14
//receiver is arbitrary
15
address receiver = "0x...";
16
​
17
host.callAgreement(
18
cfa,
19
abi.encodeWithSelector(
20
cfa.createFlow.selector,
21
acceptedToken,
22
receiver
23
flowRate,
24
new bytes(0) // placeholder
25
),
26
userData,
27
);
Copied!
Note: userData is always passed into callAgreement as type bytes .
1
//solidity
2
//call agreement interface
3
function callAgreement(
4
ISuperAgreement agreementClass,
5
bytes calldata callData,
6
bytes calldata userData
7
)
8
external
9
//cleanCtx
10
returns(bytes memory returnedData);
Copied!
Behind the scenes, your userData variable is appended onto Context, which is then available to you as a developer in the SuperApp callbacks.
When you execute an operation in the CFA contract for example (and create, update, or delete a flow), you'll have access to the Context that's available after the initial call to the protocol was made. For example, if I pass in userData when a create a flow into a Super App, I can decode the context & this user data inside any of the super app callbacks, and re-use or manipulate this data as I please. For example, if I send a transaction where receiver is a SuperApp, and pass along an encoded string 'hello sir' as userData:
1
//solidity
2
string unformattedUserData = 'hello sir';
3
bytes userData = abi.encode(unformattedUserData);
4
​
5
​
6
host.callAgreement(
7
cfa,
8
abi.encodeWithSelector(
9
cfa.createFlow.selector,
10
acceptedToken,
11
//receiver is a super app...
12
receiver
13
flowRate,
14
new bytes(0) // placeholder
15
),
16
userData,
17
);
Copied!
I can decode the context that's passed into the callback, which will give me the Context struct displayed above. Then, since userData is one of the fields on the struct, we can abi.decode userData get back my value of 'hello sir' on the other side:
1
//inside of the afterAgreementCreated Super App Callback
2
​
3
function afterAgreementCreated(
4
ISuperToken _superToken,
5
address _agreementClass,
6
bytes32, // _agreementId,
7
bytes calldata /*_agreementData*/,
8
bytes calldata ,// _cbdata,
9
bytes calldata _ctx
10
)
11
external override
12
onlyExpected(_superToken, _agreementClass)
13
onlyHost
14
returns (bytes memory newCtx)
15
{
16
17
// decode Contex - this will return the entire Context struct
18
ISuperfluid.Context memory decompiledContext = _host.decodeCtx(_ctx);
19
​
20
//userData is a one of the fields on the Context struct
21
//set decodedUserData variable to decoded value
22
//this will return 'hello sir'
23
decodedUserData = abi.decode(decompiledContext.userData, (string));
24
25
//do some stuff with your decodedUserData
26
return _doSomeStuff(decodedUserData);
27
}
Copied!
UserData can be any arbitrary piece of data. Think of it as metadata that's associated with anything you do in a Super Agreement.
This metadata could be used for a wide variety of use cases:
  • You could pass in data to accompany a salary or payment stream - perhaps employee info or product info
  • You can send a message along with your distribution in an instant distribution agreement
  • You could even pass in the byte code for another entire smart contract.
We invite you to be creative with this!
Next up: a tutorial on how to leverage UserData within your applications.
Copy link
Contents