Subscription Utility
Warning: This is very very Alpha software. We do not recommend using this for large values or with accounts with large balances yet.
Canister: hl3xq-uiaaa-aaaar-qbxqa-cai
Controller: NNS Root
Goal: Provide subscription services to Dapps on the IC and fund public goods.
What:
- Your Dapp can now use this canister to set up subscriptions for your users. See ICRC-79 for a specification of the standard.
- This canister is an implementation of that standard that has been deployed by ICDevs.org and will be put under ICRC-137 governance with oversight from the NNS.
- This means you can approve a service provider a very large number of tokens to pay for your subscription forever without the service provider being able to take more tokens than the contract allows at the appropriate times.
- ICDevs.org will have upgrade rights to the canister, but upgrades can be vetoed by NNS if we try to deploy code that has not been open-sourced and announced beforehand. Both this service and the 137 veto canister are in alpha, so give us a bit of leeway on this, but we hope to have this configuration in place soon.
- The service applies 1.5% of the subscription fee at each interval to a donation to the contract’s public goods account, currently ICDevs.org. This funds public goods including software for the IC, and educational content about the IC. (Typical credit card fees are $0.30 + 3%).
Example: Charge a user 10 ICP a month to use your service.
Pending Features for future version:
- Canister Notifications via ICRC-72
- Exchange Rate canister integration (Charge $10 worth of ICP each month)
- Broker feature for incentivizing others to sign users up as subscribers for your service.
- ICRC-80 multi-token canister support
Current Centralized Aspects:
- ICDevs.org can add known tokens to the available tokens list
- ICDevs.org can add a principle for which donations will be blocked such that ICDevs.org can block donations from any account that uses the service for a purpose they find unacceptable. Currently, ICDevs.org cannot block anyone from creating a subscription or using the utility; it can only block being paid fees.
How do I use the utility:
- Have your user approve an icrc2_approve transaction with the subscription canister as the spender. Make sure to approve enough to cover the cost of the subscription for the full period of subscription.
- Create the subscription.
- Profit.
const handleSubscribe = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (!isConnected) {
alert("Please connect your wallet first.");
return;
}
if (!selectedToken) {
alert("Please select a token.");
return;
}
let symbol = getTokenSymbol(selectedToken);
const amountToMint = prompt("Enter the amount of " + symbol + " you would like to donate per month");
const amountInE8s = BigInt(Number(amountToMint) * (10 ** getTokenDecimals(selectedToken))) ;
const allowanceAmount = amountInE8s * BigInt(12) * BigInt(30);
alert("You will now approve 30 years worth of payments. The contract has NNS oversight and cannot take more than you have approved per month. You can cancel at any time.");
if(selectedToken.toString() != icpCanisterID.toString() && selectedToken.toString() != icdvCanisterID.toString()){
let pubkey = await window.ic.plug.requestConnect({
// whitelist, host, and onConnectionUpdate need to be defined or imported appropriately
whitelist: [icdvCanisterID, icpCanisterID, subsCanisterID, selectedToken.toString()],
host: "https://ic0.app",
});
};
setLoading(true);
try {
let ICRC2Actor = await window.ic.plug.createActor({
canisterId: selectedToken,
interfaceFactory: icdvFactory,
});
const approvalResult = await ICRC2Actor.icrc2_approve({
amount: allowanceAmount,
spender: {
owner: await Principal.fromText(subsCanisterID),
subaccount: [],
},
memo: [],
fee: getTokenFee(selectedToken),
created_at_time: [],
expires_at: [],
expected_allowance: [],
from_subaccount: [],
});
if ("Ok" in approvalResult) {
alert("Your ICP has been authorized for subscription. Please click ok and wait for the subscription to be completed complete. A message box should appear after a few seconds.");
let result = await subsActor?.icrc79_subscribe([
[
{interval : {Monthly: null}},
{amountPerInterval: amountInE8s},
{tokenCanister: selectedToken},
{productId: BigInt(stringToNumberHash(selectedToken.toString()))},
{serviceCanister: Principal.fromText("agtsn-xyaaa-aaaag-ak3kq-cai")}, //send to the ICDV canister
{targetAccount: {owner : Principal.fromText("agtsn-xyaaa-aaaag-ak3kq-cai"), subaccount :[[39,167,236,212,75,183,197,29,163,240,112,67,54,45,238,71,220,227,55,132,102,170,154,183,149,180,185,26,233,48,38,105]]}}
]
]);
if(result && result.length > 0 && result[0][0] && "Ok" in result[0][0]){
alert("Subscription successful! SubscriptionID: " + (result[0][0].Ok.subscriptionId as bigint).toString() + ".");
} else if (result && result.length > 0 && result[0][0] && "Err" in result[0][0]) {
console.log("error", result[0][0].Err);
alert("Subscription failed! " + JSON.stringify(result[0][0].Err));
} else {
alert("Subscription failed! Unknown error.");
};
} else {
console.log("error", approvalResult);
alert("Subscribe failed." + JSON.stringify(approvalResult));
}
} catch (error) {
console.error('Subscribe failed:', error);
alert("An error occurred.");
} finally {
setLoading(false);
}
};
What happens when someone subscribes
- The subscription is created and logged to an ICRC-3 blockchain.
- The first payment is scheduled
- When a user pays 100 ICP for a subscription, the service gets 98.5 ICP and ICDevs.org gets 1.5 ICP to fund public goods.
- The next payment is scheduled.
- The subscription ends, gets canceled, or runs out of funds in the approved account.
Where is the code:
https://github.com/icdevsorg/nns-sub-utility
Deep Dive: ICRC Fungible Part 7 - https://youtu.be/4GpLlcZ00Fw
How to help
- Review the code, provide issues, fork, submit pull requests.
- Donate to ICDevs.org by trying out the services and creating a subscription to send us a set amount of tokens a month or mint some ICDV tokens: https://icdevs.org/donations.html