Tutorial
In this tutorial, we're going to be utilizing Acala's on-chain scheduler to create an automatic subscription service that awards users for the time they are subscribed. A user can subscribe to this service and every time a certain period passed, the contract will reward them with some tokens.
We will create a project from scratch, build and test it using Waffle. If you'd rather build your smart contracts using Remix, please check out this page.
To start, let's install
nodejs
and yarn if you haven't already:sudo apt install -y nodejs
npm install --global yarn
Next, run the following commands in the folder where you want your project to live:
And add the following script to your
package.json
:"build": "waffle"
This will create a folder for our project, initialize a new yarn project, and add waffle as a dev dependency. We'll be using waffle to compile our smart contracts.
Within your project folder, create a file called
waffle.json
and paste the following inside the file:{
"compilerType": "solcjs",
"compilerVersion": "0.6.2",
"sourceDirectory": "./contracts",
"outputDirectory": "./build"
}
This file is the configurations for
waffle
to use when building our smart contracts.Let's create our
contracts
folder.mkdir contracts
Inside the
contracts
folder create Scheduler.sol
paste the following code:pragma solidity ^0.6.0;
abstract contract Scheduler {
// Schedule a contract call.
function scheduleCall(
address contract_address, // The contract address to be called in future.
uint256 value, // How much native token to send alone with the call.
uint256 gas_limit, // The gas limit for the call. Corresponding fee will be reserved upfront and refunded after call.
uint256 storage_limit, // The storage limit for the call. Corresponding fee will be reserved upfront and refunded after call.
uint256 min_delay, // Minimum number of blocks before the scheduled call will be called.
bytes memory input_data // The input data to the call.
)
virtual
public
returns (uint256, uint256); // Returns a task id that can be used to cancel or reschedule call.
}
This file describes the scheduler smart contract and what arguments you must supply to it to use it. But how does the scheduler work?
The Acala EVM provides Solidity, Substrate, and Web3 developers a complete full-stack (Acala+EVM+Substrate+WASM) experience seamlessly with a single wallet. Many Substrate and WASM customization are made available inside EVM via pre-compiled contracts, such as on-chain scheduler, bring your own gas, Quality of Service oracle feeds, and more. It allows us to add new features that would be impossible on Ethereum while still letting developers use existing code and tooling to create DApps that leverage these features. These pre-compiled smart contracts have static, known addresses on the Acala EVM making it incredibly easy to use.
Let's create our smart contract file by creating a
Subscription.sol
file within the contracts
folder. In it add the following code:pragma solidity ^0.6.0;
import "./Scheduler.sol";
contract SubscriptionToken {
Scheduler scheduler;
constructor(address scheduler_address) public payable {
scheduler = Scheduler(scheduler_address);
}
}
Here you can see we're importing the abstract class that describes the
Scheduler
smart contract and pointing our scheduler
to its address in the constructor. Again, Scheduler is a pre-compiled smart contract with a static address. It will always live on-chain at this address, no need to deploy your own version of it. 
Next, let's set some state variables and modify a constructor. Within the contract add the following:
contract SubscriptionToken {
uint period;
address[] subscribers;
mapping(address => uint) public balanceOf;
Scheduler scheduler;
constructor(uint _period, address scheduler_address) public payable {
period = _period;
scheduler = Scheduler(scheduler_address);
scheduler.scheduleCall(address(this), 0, 50000, 100, period, abi.encodeWithSignature("paySubscribers()"));
}
}
Here we added 6 state variables. Let's go through them one by one:
- 1.
balanceOf
: defines/shows the number of native tokens in a user's balance. - 2.
period
: a period of how many blocks the Scheduler should be called
The constructor is self-explanatory, all it does is set the first state variables for the contract.
Next let's add the
subscribe
function:contract SubscriptionToken {
...
function subscribe() public {
subscribers.push(msg.sender);
}
function transfer(address _to, uint amount) public {
require(balanceOf[msg.sender] > amount -1, "Insuffcient funds");
balanceOf[msg.sender] -= amount;
balanceOf[_to] += amount;
}
}
Here the
subscribe
function simply adds a user to a list to which tokens will be distributed.Finally, let's have a look at the arguments of
scheduleCall
:scheduler.scheduleCall(address(this), 0, 50000, 100, period, abi.encodeWithSignature("paySubscribers()"));
Let's break this line down:
address(this)
is the address of the smart contract to call, in this case, we are calling the same smart contract that is calling the schedule contractvalue
- How much native token to send alone with the call.gas_limit
- The gas limit for the call. The corresponding fee will be reserved upfront and refunded after the call.storage_limit
- The storage limit for the call. The corresponding fee will be reserved upfront and refunded after the call.min_delay
is the number of blocks from now it should try and execute this call (though it could be more!)abi.encodeWithSignature("paySubscribers()")
is the function to call within the smart contract (we'll define it next).
For more information on the rest of the arguments being passed to the
scheduler
contract scroll up to where we created Scheduler.sol
.Now let's create the
paySubscribers
function that the scheduler is calling:contract SubscriptionToken {
...
function paySubscribers() public {
require(msg.sender == address(this));
if (subscribers.length > 0) {
for (uint256 i = 0; i < subscribers.length; i++) {
address subscriber = subscribers[i];
balanceOf[subscriber] += 1;
}
}
scheduler.scheduleCall(address(this), 0, 50000, 100, period, abi.encodeWithSignature("paySubscribers()"));
}
}
Notice the first line of the function:
require(msg.sender == address(this), "No Permission");
What this is saying is that only the contract itself can call this function. When we schedule a call using the
Scheduler
contract, it is not actually the scheduler calling the function but the function itself. The scheduler is just dispatching the call at a later time.And that's it! We now have a functioning smart contract with automated subscriptions!
To build the smart contract run the following in the project's root directory:
yarn build
You can set a
period
to 1 (which is ≈6 secs), and find the scheduler address in the list of predeployed contracts.
When everything is done press deploy.

the only way is to get this new token is to subscribe to the smart contract. Execute
subscribe
function and check how balance changes over time for the subscriber (balanceOf
function).Last modified 1yr ago