SubscriptionConsumer
The SubscriptionConsumer
is a simple, easy-to-inherit interface that developers can use in their smart contracts to create recurring requests for off-chain output, delivered via callback.
It is best used when your contract needs:
- Recurring, time-based subscriptions fulfilled by Infernet nodes at a fixed interval
- To expose dynamic inputs by overriding
getContainerInputs()
Using this consumer
Install dependencies
Before getting started, ensure you have installed the Infernet SDK in your Solidity smart contract project. You can follow the installation instructions found in the introduction.
Inherit SubscriptionConsumer.sol
In your smart contract, you must inherit the SubscriptionConsumer.sol
abstract contract found in infernet/core/consumers/Subscription.sol
:
import {SubscriptionConsumer} from "infernet/core/consumers/Subscription.sol";
contract MyContract is SubscriptionConsumer {}
Initialize the consumer
Once inherited, you must provide the address to the Infernet registry
contract (see: architecture: coordinator if unfamiliar) to the SubscriptonConsumer
constructor:
import {SubscriptionConsumer} from "infernet/core/consumers/Subscription.sol";
contract MyContract is SubscriptionConsumer {
constructor(address registry) SubscriptionConsumer(registry) {}
}
Create a new subscription
By default, the SubscriptionConsumer
exposes two functions used to create and cancel subscriptions:
_createComputeSubscription()
(technical reference)_cancelComputeSubscription()
(technical refernece)
We can use the _createComputeSubscription()
function to run our off-chain test-model
ML container workflow once every hour, for a week, with responses from a single node.
import {SubscriptionConsumer} from "infernet/core/consumers/Subscription.sol";
contract MyContract is SubscriptionConsumer {
constructor(address registry) SubscriptionConsumer(registry) {}
function createHourlySubscription() external {
_createComputeSubscription(
"test-model", // Container ID
7 * 24, // Running for a week
1 hours, // Hourly subscription
1, // Only 1 responding node
false, // Eager response
// No payment
address(0),
0,
address(0),
// No proof verification
address(0)
);
}
}
Notice, that unlike in the CallbackConsumer, we do not explicitly specify inputs
when creating a subscription. This is because the SubscriptionConsumer
allows us to expose dynamic inputs via a view function.
Expose dynamic inputs
By default, the SubscriptionConsumer
exposes a getContainerInputs()
(technical reference) function that we can override to broadcast our inputs
dynamically to off-chain nodes.
For more details about the architecture behind how this works, reference Coordinator and Infernet nodes.
For our constrained example, we will simply broadcast the abi.encode
'd timestamp multiplied by two:
import {SubscriptionConsumer} from "infernet/core/consumers/Subscription.sol";
contract MyContract is SubscriptionConsumer {
constructor(address registry) SubscriptionConsumer(registry) {}
function getContainerInputs(
uint32 subscriptionId,
uint32 interval,
uint32 timestamp,
address caller
) external view override returns (bytes memory) {
return abi.encode(timestamp * 2);
}
function createHourlySubscription() external {
_createComputeSubscription(
"test-model", // Container ID
7 * 24, // Running for a week
1 hours, // Hourly subscription
1, // Only 1 responding node
false, // Eager response
// No payment
address(0),
0,
address(0),
// No proof verification
address(0)
);
}
}
Receive container output via callback
By default, the SubscriptionConsumer
exposes a _receiveCompute()
function (technical reference) which is called every time your smart contract receives a callback response from an Infernet node. This is where you should consume your response outputs, do proof verification, or store response data.
In our example, we will simply push our received output
from our test workflow above to an outputs
array for future consumption.
import {SubscriptionConsumer} from "infernet/core/consumers/Subscription.sol";
contract MyContract is SubscriptionConsumer {
bytes[] public outputs;
constructor(address registry) SubscriptionConsumer(registry) {}
function getContainerInputs(
uint32 subscriptionId,
uint32 interval,
uint32 timestamp,
address caller
) external view override returns (bytes memory) {
return abi.encode(timestamp * 2);
}
function _receiveCompute(
uint32 subscriptionId,
uint32 interval,
uint16 redundancy,
address node,
bytes calldata input,
bytes calldata output,
bytes calldata proof,
bytes32 containerId,
uint256 index
) internal override {
// We simply track `output` for future consumption in our callback
outputs.push(output);
}
function createHourlySubscription() external {
_createComputeSubscription(
"test-model", // Container ID
7 * 24, // Running for a week
1 hours, // Hourly subscription
1, // Only 1 responding node
false, // Eager response
// No payment
address(0),
0,
address(0),
// No proof verification
address(0)
);
}
}
Test your implementation
That's all it takes to get started with the SubscriptionConsumer
! You're now ready to test your implementation with mock data (see: our testing best-practices) and deploy your contracts.
Sane defaults
With great power comes great responsibility — Uncle Ben to Spider-Man
While the SubcriptionConsumer
provides unlimited configuration flexibility, using it to its limits requires understanding the SDK's architecture and considering the limits of both your own application and the smart contract execution environment where your contracts will live.
As sane defaults, we recommend:
- Setting
period
to be at least long enough for an Infernet node to run your container + reasonably respond on-chain, waiting the appropriate confirmation or finality window