Calling Agreements In Super Apps
Understanding CallAgreement vs CallAgreementWithContext

CallAgreement vs CallAgreementWithContext

TLDR: If you're making a call to a Superfluid agreement inside of a Super App callback, you should remember that you need to "receive a context, and return a context" by using the callAgreementWithContext function. If you're not making this call inside of a Super App callback, you should use callAgreement.
When calling the host contract to trigger actions related to the constant flow agreement (CFA) or instant distribution agreement (IDA), you may use either callAgreement or callAgreementWithContext. Both of these functions allow you to pass in an encoded call to the agreement you'd like to interact with, as well as an optional userData value. The callAgreementWithContext function will perform the same actions as the callAgreement function, but it also enables you to pass in a new context value (abbreviated ctx ) to your function call.
Keep in mind that callAgreementWithContext is designed for use within Super Apps - if this function is run outside of a Super App, then callAgreementWithContext will fail due to this statement:
1
require(
2
context.appAddress == msg.sender,
3
"SF: callAgreementWithContext from wrong address"
4
);
Copied!
callAgreementWithContext is primarily meant for use within Super App callbacks. Each Super App callback will be passed a context value (ctx) from the host contract (as the Superfluid host contract is the caller of each callback). This ctx value is what needs to be passed to any call you want to make to the Superfluid host contract inside of your callback (this goes for all operations which create, update, and delete flows in these callbacks). As a reminder, the logic within the 'host' contract can be found in Superfluid.sol.
The process looks like this:
1
//a function which we'll use to create a flow within a callback
2
function _createFlowInCallback(
3
bytes calldata ctx,
4
ISuperfluid _host,
5
IConstantFlowAgreementV1 _cfa,
6
ISuperfluidToken _acceptedToken,
7
address _receiver,
8
int96 _flowRate
9
)
10
private
11
returns (bytes memory newCtx)
12
{
13
newCtx = ctx;
14
​
15
(newCtx,) = _host.callAgreementwithContext(
16
_cfa,
17
abi.encodeWithSelector(
18
_cfa.deleteFlow.selector,
19
_acceptedToken,
20
address(this),
21
_receiver,
22
new bytes(0) // placeholder
23
),
24
"0x", //placeholder userdata value
25
newCtx //passing in the context from the super app callback
26
);
27
}
28
​
29
function afterAgreementCreated(
30
ISuperToken _superToken,
31
address _agreementClass,
32
bytes32, // _agreementId,
33
bytes calldata /*_agreementData*/,
34
bytes calldata ,// _cbdata,
35
bytes calldata _ctx
36
)
37
external override
38
onlyExpected(_superToken, _agreementClass)
39
onlyHost
40
returns (bytes memory newCtx)
41
{
42
//passing in the ctx which is sent to the callback here
43
return _createFlowInCallback(_ctx, _host, _cfa, _acceptedToken, _receiver, _flowrate);
44
}
Copied!
You can also do this in a much easier way by using our new CFA Library, which abstracts away the need to use host.callAgreement or host.callAgreementWithContext directly.
1
using CFALibraryV1 for CFALibraryV1.InitData;
2
​
3
//initialize cfaV1 variable
4
CFALibraryV1.InitData public cfaV1;
5
​
6
constructor(
7
ISuperfluid host
8
) {
9
​
10
//initialize InitData struct, and set equal to cfaV1
11
cfaV1 = CFALibraryV1.InitData(
12
host,
13
IConstantFlowAgreementV1(
14
address(host.getAgreementClass(
15
keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1")
16
))
17
)
18
);
19
}
20
​
21
function afterAgreementCreated(
22
ISuperToken _superToken,
23
address _agreementClass,
24
bytes32, // _agreementId,
25
bytes calldata /*_agreementData*/,
26
bytes calldata ,// _cbdata,
27
bytes calldata _ctx
28
)
29
external override
30
onlyExpected(_superToken, _agreementClass)
31
onlyHost
32
returns (bytes memory newCtx)
33
{
34
//passing in the ctx which is sent to the callback here
35
//createFlowWithCtx makes use of callAgreementWithContext
36
return cfaV1.createFlowWithCtx(_ctx, receiver, token, flowRate);
37
}
Copied!
You may have another scenario in which you want to make additional calls to the host contract after you first run callAgreementWithContext. If you do this, you can save the value returned by the first callAgreementWithContext function to a new variable, then pass this value to your next call to callAgreementWithContext . The takeaway here is that you need to pass the most recent iteration of ctx when creating, updating, or deleting flows inside Super App callbacks. You can see this done here with the CFA Library:
1
using CFALibraryV1 for CFALibraryV1.InitData;
2
​
3
//initialize cfaV1 variable
4
CFALibraryV1.InitData public cfaV1;
5
​
6
constructor(
7
ISuperfluid host
8
) {
9
​
10
//initialize InitData struct, and set equal to cfaV1
11
cfaV1 = CFALibraryV1.InitData(
12
host,
13
IConstantFlowAgreementV1(
14
address(host.getAgreementClass(
15
keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1")
16
))
17
)
18
);
19
}
20
​
21
function afterAgreementCreated(
22
ISuperToken _superToken,
23
address _agreementClass,
24
bytes32, // _agreementId,
25
bytes calldata /*_agreementData*/,
26
bytes calldata ,// _cbdata,
27
bytes calldata _ctx
28
)
29
external override
30
onlyExpected(_superToken, _agreementClass)
31
onlyHost
32
returns (bytes memory newCtx)
33
{
34
newCtx = cfaV1.createFlowWithCtx(_ctx, receiver, token, flowRate); //passing in the ctx which is sent to the callback here
35
newCtx = cfaV1.createFlowWithCtx(newCtx, receiver, token, flowRate); //passing in the ctx which is returned from the first call here
36
37
}
Copied!
One final item to note is to not manually change the value of ctx when it's used within a Super App. Ctx is formatted in a very specific way within a struct that is compiled to bytecode by the protocol, and it's not meant to be manipulated directly. If you wish to pass in userData to your function call, this can be done by simply adding the userData value in as a parameter:
1
//using the CFA Library:
2
function afterAgreementCreated(
3
ISuperToken _superToken,
4
address _agreementClass,
5
bytes32, // _agreementId,
6
bytes calldata /*_agreementData*/,
7
bytes calldata ,// _cbdata,
8
bytes calldata _ctx
9
)
10
external override
11
onlyExpected(_superToken, _agreementClass)
12
onlyHost
13
returns (bytes memory newCtx)
14
{
15
//passing in the ctx which is sent to the callback here
16
//createFlowWithCtx makes use of callAgreementWithContext
17
return cfaV1.createFlowWithCtx(_ctx, receiver, token, flowRate, userData);
18
}
19
​
20
//using a low level call
21
function afterAgreementCreated(
22
ISuperToken _superToken,
23
address _agreementClass,
24
bytes32, // _agreementId,
25
bytes calldata /*_agreementData*/,
26
bytes calldata ,// _cbdata,
27
bytes calldata _ctx
28
)
29
external override
30
onlyExpected(_superToken, _agreementClass)
31
onlyHost
32
returns (bytes memory newCtx)
33
{
34
newCtx = _ctx;
35
(newCtx,) = _host.callAgreementwithContext(
36
_cfa,
37
abi.encodeWithSelector(
38
_cfa.deleteFlow.selector,
39
_acceptedToken,
40
address(this),
41
_receiver,
42
new bytes(0) // placeholder
43
),
44
userData, //userData goes here
45
newCtx //passing in the context from the super app callback
46
);
47
}
48
​
49
​
Copied!
If you read through the Superfluid codebase, you'll see that nearly every state changing operation will return a context value. This ctx value helps to provide additional internal accounting for the protocol to enhance security, and it allows you to decode it and make use of values like userData inside Super Apps. When making calls within your Super Apps, keep in mind that you need to pass in updated context values if you want to make use of the callbacks properly. Remember: if you need to run callAgreement within a Super App callback, you'll need to use callAgreementWithContext and pass in ctx.
Last modified 19d ago
Copy link