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 via
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 coordinator
contract (see: architecture: coordinator if unfamiliar) to the SubscriptonConsumer
constructor:
import {SubscriptionConsumer} from "infernet/core/consumers/Subscription.sol";
contract MyContract is SubscriptionConsumer {
constructor(address infernetCoordinator) SubscriptionConsumer(infernetCoordinator) {}
}
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 infernetCoordinator) SubscriptionConsumer(infernetCoordinator) {}
function createHourlySubscription() external {
_createComputeSubscription(
"test-model", // Container ID
100 gwei, // Max callback gas price
100_000 wei + COORDINATOR.DELIVERY_OVERHEAD_WEI(), // Max callback gas limit
7 * 24, // Running for a week
1 hours, // Hourly subscription
1, // Only 1 responding node
)
}
}
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 infernetCoordinator) SubscriptionConsumer(infernetCoordinator) {}
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
100 gwei, // Max callback gas price
100_000 wei + COORDINATOR.DELIVERY_OVERHEAD_WEI(), // Max callback gas limit
7 * 24, // Running for a week
1 hours, // Hourly subscription
1, // Only 1 responding node
)
}
}
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 infernetCoordinator) SubscriptionConsumer(infernetCoordinator) {}
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
) internal override {
// We simply track `output` for future consumption in our callback
outputs.push(output);
}
function createHourlySubscription() external {
_createComputeSubscription(
"test-model", // Container ID
100 gwei, // Max callback gas price
100_000 wei + COORDINATOR.DELIVERY_OVERHEAD_WEI(), // Max callback gas limit
7 * 24, // Running for a week
1 hours, // Hourly subscription
1, // Only 1 responding node
)
}
}
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
maxGasPrice
to be a fewgwei
higher than the rolling average for yourperiod
historically - Setting
maxGasLimit
to be higher than your benchmarked usage; there is no downside - 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