Skip to main content

Serverless Subscriptions

Oyster Serverless Subscription allows Users to automate periodic serverless requests using smart contract methods. More details about the protocol are described here. Following are the instructions to start with an example.

Prepare a Contract

A smart contract has to be developed for real-world use cases, which is going to interact with Oyster Serverless contracts to execute serverless functions.

To setup solidity development environment for Serverless Smart Contract, follow this guide.

Here is the sample contract:

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract SubsUser is Ownable {
using SafeERC20 for IERC20;

address public relaySubscriptionsAddress;

/// @notice refers to USDC token
IERC20 public token;

event OwnerEthWithdrawal();
event OwnerUsdcWithdrawal();

error EthWithdrawalFailed();

constructor(
address _relaySubscriptionsAddress,
address _token,
address _owner
) Ownable(_owner) {
relaySubscriptionsAddress = _relaySubscriptionsAddress;
token = IERC20(_token);
}

struct JobSubscriptionParams {
uint8 env;
uint256 startTime;
uint256 maxGasPrice;
uint256 usdcDeposit;
uint256 callbackGasLimit;
address callbackContract;
bytes32 codehash;
bytes codeInputs;
uint256 periodicGap;
uint256 terminationTimestamp;
uint256 userTimeout;
address refundAccount;
}

event CalledBack(
uint256 indexed jobId,
address jobOwner,
bytes32 codehash,
bytes codeInputs,
bytes outputs,
uint8 errorCode
);

// bytes32 txhash = 0xc7d9122f583971d4801747ab24cf3e83984274b8d565349ed53a73e0a547d113;

function oysterResultCall(
uint256 _jobId,
address _jobOwner,
bytes32 _codehash,
bytes calldata _codeInputs,
bytes calldata _output,
uint8 _errorCode
) public {
emit CalledBack(_jobId, _jobOwner, _codehash, _codeInputs, _output, _errorCode);
}

function startJobSubscription(
uint256 _usdcDeposit,
bytes32 _codehash,
bytes memory _codeInputs,
uint256 _periodicGap,
uint256 _terminationTimestamp,
uint256 _userTimeout,
uint256 _callbackDeposit
) external returns (bool) {
// usdcDeposit = _userTimeout * EXECUTION_FEE_PER_MS + GATEWAY_FEE_PER_JOB;
token.safeIncreaseAllowance(relaySubscriptionsAddress, _usdcDeposit);
JobSubscriptionParams memory _jobSubsParams = JobSubscriptionParams(
{
env: 1, // execution environment ID
startTime: block.timestamp + 10, // start subscription after 10 seconds
maxGasPrice: 2 * tx.gasprice, // twice of current gas price
usdcDeposit: _usdcDeposit,
callbackGasLimit: 5000, // Based on gas usage by oysterResultCall
callbackContract: address(this), // this contract is to be called with response
codehash: _codehash,
codeInputs: _codeInputs,
periodicGap: _periodicGap,
terminationTimestamp: _terminationTimestamp,
userTimeout: _userTimeout,
refundAccount: _msgSender() // Subscription creator will be compensated from slashings on common chain
}
);
(bool success, ) = relaySubscriptionsAddress.call{value: _callbackDeposit}(
abi.encodeWithSignature(
"startJobSubscription((uint8,uint256,uint256,uint256,uint256,address,bytes32,bytes,uint256,uint256,uint256,address))",
_jobSubsParams
)
);
return success;
}

function withdrawEth() external onlyOwner {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
if (!success) revert EthWithdrawalFailed();

emit OwnerEthWithdrawal();
}

function withdrawUsdc() external onlyOwner {
uint256 balance = token.balanceOf(address(this));
token.transfer(owner(), balance);

emit OwnerUsdcWithdrawal();
}

receive() external payable {}
}

This sample contract calls startJobSubscription method on the Subscription Relay Contract to create a subscription.

Next, deploy sample contract which require three constructor arguments, Serverless contract address, USDC token address and owner, as instructed here.

Transfer ETH to deployed user contract address to be used as deposit for response callbacks. Follow this Metamask Guide to transfer ETH. User contract address is used as recipient and amount of 0.01 ETH is sufficient for this example.

Transfer USDC tokens to deployed user contract address to be used as deposit and payment for serverless job. USDC token contract used here is deployed at address 0xaf88d065e77c8cC2239327C5EDb3A432268e5831. Send 1000000 USDC for this example.

Create a Serverless Subscription

Send a transaction to call startJobSubscription method of the sample contract. Take a look at the sample transaction 0x2aab7dae60eca6cb8b9c3556487a30c38b44f03c98e3fde16bf3138627f6feb8.

Create Transaction

usdcDeposit

Subscription Relay Contract deposits 1000000 USDC from user contract.

codeHash

This job is using the serverless function stored at 0x6516be2032b475da2a96df1eefeb1679a8032faa434f8311a1441e92f2058fe5 which returns the factors of given integer.

codeInputs

The input argument is "0x00". This tool can be used to convert string to hexadecimal strings for input.

periodicGap

Serverless function will be executed periodically at interval of 30 seconds.

terminationTimestamp

Subscription ends at timestamp 1727980473 (i.e. ~200 seconds later than timestamp of transaction) and no serverless function is executed after this.

userTimeout

Serverless function should not take more than 2000 milliseconds to execute.

callbackDeposit

0.1 eth to be deposited for response callback

This method call will place a subscription request on Subscription Relay Contract. Make sure that user contract sends enough value (in gas currency) while calling the relay contract to execute the callback, otherwise job won’t be placed.

Request Complete Callbacks

Once a instance of subscription request has been completed, Subscription Relay Contract will call ‘oysterResultCall’ method of user contract. To validate the callback, check the event ‘CalledBack’ emitted by sample contract with output in event data. Note that response is getting submitted periodically. For example, check events here.