Subscription Utility

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:

  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 subscription.
  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 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
7 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

…so…I found a few dumb errors during an implementation. Misspelled product in the trx log and a few queries aren’t working. Also need to add a few continence fields to the queries.

Nothing show stoping but it makes it hard to find what you are looking for.

Since there are still less than 2000 trxs I can just rebuild the block chain.

…so it looks like we’ll have one of the first third party nns upgrade proposals coming soon.

Gird yourself.

1 Like

relevant to this convo: Regarding NNS controlling application canisters

…And a year later, we are back!

I’m sorry it took so long, but we had to develop Anti-DAO, Pre-DAO, DAOless — ICRC-137 Veto-Based Governance before we could redeploy this canister. I really don’t want to control a canister that has approvals for thousands and thousands of ICP and have someone show up at my door with a gun asking me to rug it. With 137, I won’t be able to without someone seeing what I’m trying to do and being able to stop it.

Now we can set up USDC, ICP, BTC, etc subscriptions. Does anyone know of a service that could benefit from on-chain subscriptions? And where the fees roll right back into building public goods for the IC?

Our first subscription dapp is up at ICDevs.org and you can set up a subscription donation in ICP, SNS, and ckXX currencies.

This time the canister is on the fiduciary subnet.

Next steps: Set up a dashboard so we can see what services are taking in the most revenue.

2 Likes