1

I'm working on a smart contract that allows users to pay for monthly subscriptions like Netflix, Amazon, etc...

Assuming that each subscription costs 20$ per month which is equivalent to 22 Matic at the time of writing this post.

During the time of the subscription, the user should pay 20$ per month with Matic, and its value will vary.

How can I update the monthly payment inside the smart contract so the subscriber will be able to pay each with the current value of MATIC not more not less? It's possible in solidity?

Or should I let the user pay his subscription manually each month based on the price of each subscription in dollars but with MATIC?

I tried to implement a solution with Tellor in my smart contract but I got stuck.

// smart contract subscription(part of it)
struct Subscription {
        address payable subscriber;
        uint start;
        uint nextPayment;
        bool activated;
    }

    
    /* nested mapping from address to id to Subscription */ 
    mapping(address => mapping(uint => Subscription)) private AllSubscriptions;


 function pay(address subscriber, uint planId) external {
    Subscription storage subscription = subscriptions[subscriber][planId];
    Plan storage plan = plans[planId];
    IERC20 token = IERC20(plan.token);
    require(
      subscription.subscriber != address(0), 
      'this subscription does not exist'
    );
    require(
      block.timestamp > subscription.nextPayment,
      'not due yet'
    );

    token.transferFrom(subscriber, plan.merchant, plan.amount);  
    emit PaymentSent(
      subscriber,
      plan.merchant, 
      plan.amount, 
      planId, 
      block.timestamp
    );
    subscription.nextPayment = subscription.nextPayment + plan.frequency;
  }

// UsingTellor functionnalities
// SPDX-License-Identifier: GPL-3.0
  pragma solidity ^0.8.0;

  import "usingtellor/contracts/UsingTellor.sol";

  contract PriceContract is UsingTellor {

  uint256 public btcPrice;

  //This Contract now has access to all functions in UsingTellor

  constructor(address payable _tellorAddress) UsingTellor(_tellorAddress) public {}

  function setBtcPrice() public {

    bytes memory _b = abi.encode("SpotPrice",abi.encode("MATIC","USD"));
    bytes32 _queryID = keccak256(_b);

    bool _didGet;
    uint256 _timestamp;
    bytes _value;

    (_didGet, _value, _timestamp) = getDataBefore(_queryID);

    uint256 maticPrice = abi.decode(_value,(uint256));

    require(token.transferFrom(subscriber,address(this),cost/maticPrice));
  }
}

2 Answers 2

2

following up on @thefett's code here's one way to do it in your project.

    // UsingTellor functionnalities
// SPDX-License-Identifier: GPL-3.0
  pragma solidity ^0.8.0;

  import "usingtellor/contracts/UsingTellor.sol";

  contract PriceContract is UsingTellor {

  uint256 public maticPrice;
  uint256 public cost = 20 ether;

  uint256 public monthlyPayment;

  //This Contract now has access to all functions in UsingTellor
  constructor(address payable _tellorAddress) UsingTellor(_tellorAddress) public {}

// smart contract subscription(part of it)
struct Subscription {
        address payable subscriber;
        uint start;
        uint nextPayment;
        bool activated;
    }

    
    /* nested mapping from address to id to Subscription */ 
    mapping(address => mapping(uint => Subscription)) private AllSubscriptions;

/* Pay subcription every Month*/
    function pay(uint256 planId) 
        external payable
        onlyUsers()
        {
        Subscription storage submitSubscription = AllSubscriptions[msg.sender][planId];

        require(block.timestamp > submitSubscription.nextPayment, " Payment not due yet");
        Plan storage plan = idToPlan[planId];

        // require(msg.value >= plan.monthlyPayment, " Monthly payment not correct");
        require(msg.value >= monthlyPayment, "Monthly payment not correct");

        emit PaymentSent(
            payable(msg.sender),
            payable(address(this)),
            plan.monthlyPayment,
            planId,
            block.timestamp);
        totalPaymentsPerWallet[msg.sender] += 1; 
        submitSubscription.nextPayment = submitSubscription.nextPayment + 4 weeks;

    }


 function setMaticPrice() public {

    bytes memory _b = abi.encode("SpotPrice",abi.encode("matic","usd"));
    bytes32 _queryID = keccak256(_b);

    bool _didGet;
    uint256 _timestamp;
    bytes memory _value;

    (_didGet, _value, _timestamp) = getDataBefore(_queryID, block.timestamp - 5 minutes);

    maticPrice = abi.decode(_value,(uint256));

    uint256 price = (cost/maticPrice);

    monthlyPayment = price * 10 **18;

  }


}
11
  • Thank you so much for your solution. I deployed the smart contract using "0x3477EB82263dabb59AC0CAcE47a61292f28A2eA7" for MATIC/USD pair. the smart contract was deployed successfully, but when I click on setMaticPrice(). I got an error from remix "Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending? Internal JSON-RPC error. { "code": -32000, "message": "execution reverted" }". I tried to edit the gas fee to high. but the transaction failed. what do you think?
    – David Jay
    May 11, 2022 at 12:31
  • 1
    the correct oracle address for the mumbai network can be found here: docs.tellor.io/tellor/integration/reference, thus you should use "0x41b66dd93b03e89D29114a7613A6f9f0d4F40178" as the tellor address when deploying your contract
    – Quap5
    May 11, 2022 at 13:17
  • it works, I got the maticprice. Thank you so much for your help, I just got another two questions please when I click on monthlyPayment to get the value it gave me 0? what does block.timestamp - 5 minutes mean?
    – David Jay
    May 11, 2022 at 14:19
  • 1
    hm not sure, I deployed my working example as a verified contract here: mumbai.polygonscan.com/address/… if that helps
    – Quap5
    May 12, 2022 at 1:41
  • 1
    The user pays with Matic, so you’ll receive Matic
    – Quap5
    May 13, 2022 at 14:50
1

You need the MATIC/USD price on-chain. You can either use an AMM (e.g. uniswap) or an oracle like LINK or Tellor.

Since you don't need the price updated that frequently (if it's 1 or 2% off for instance, it won't be the end of the world), I'd just pull a daily price from an oracle, and more often if the price changes a lot.

bytes memory _b = abi.encode("SpotPrice",abi.encode("MATIC","USD"));
bytes32 _queryID = keccak256(_b);

bool _didGet;
uint256 _timestamp;
bytes _value

(_, _value, _timestamp) = getDataBefore(_queryId);

uint256 maticPrice = abi.decode(_value,(uint256));

require(token.transferFrom(subscriber,address(this),cost/maticPrice));
4
  • These tools will allow me to update the monthly payment of each subscription even though was submitted within the smart contract?
    – David Jay
    May 9, 2022 at 13:13
  • 1
    Yup, so basically the transfer would be set in USD and you would calculate out how much needs to be transferred based on the price
    – thefett
    May 9, 2022 at 13:40
  • Would you please demonstrate that in code?
    – David Jay
    May 9, 2022 at 15:49
  • gave you the tellor example...where the subscriber is the person paying for the service and cost is the USD value
    – thefett
    May 9, 2022 at 15:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.