NNS Controlled Subscription Utility

NNS Controlled 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: fe5iu-uiaaa-aaaal-ajxea-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 “DAO Horizioned” to 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.
  • Only the NNS can vote to upgrade the canister.(This is a similar pattern to the ckBTC/ckETH/ckERC20 canisters.)
  • 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.

Note: DFINITY did recommend that we NOT start submitting canisters to NNS control. Given the general usefulness of this canister, we decided to do it anyway to see how it goes. Our worst-case downside here is that the NNS decides to not shepherd this utility canister and a new one will need to be deployed when more functionality is complete. See thread

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 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:

  1. 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 subscribing.
  2. Create the subscription.
  3. 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 is governed by the NNS 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
6 Likes

I was informed that the repo was still private. It is open now at GitHub - icdevsorg/nns-sub-utility: NNS Subscription Utility

1 Like