Create Solidity contract instance as a result of long running process

Viewed 76

How can I write to the blockchain after an undetermined, time-consuming execution result?

Situation:

  1. Web Client (NextJS): User requests a long running process that can take up to 1 hour to complete. As a result of this transaction, a new contract instance should be written to the blockchain. We can't expect the client to stick around waiting => use a NodeJS or cloud function to delegate the long running task. Metamask on the client will define the contract owner.
  2. Planning to use NextJS to route to a Moralis Cloud Fx to kick off the long running task. The result of this long running task will be digital content to store to the blockchain.

Constraints/Problem:

  1. Can’t have the user wait for the process to complete
  2. Metamask runs on the client
  3. Need Moralis Cloud Fx or NodeJS to have access to the owner to instantiate the contract at an undetermined future point in time.

From: sequencediagram.org

title Long Running Task to Blockchain
actor User

User->UI:User Request
UI->NodeServer: Delegate Slow Tasks
activate NodeServer
NodeServer->RestService: Start slow process
activate RestService

RestService-->NodeServer: Confirmation ID
NodeServer-->UI: Confirmation ID
UI->Blockchain: Create empty contract

RestService->NodeServer: Result = Data for blockchain
deactivate RestService

NodeServer->Blockchain: **Public?** Contract.setRestResult(Result)

deactivate NodeServer

Attempt_v1

Here is how I was planning to approach setting up the contract that would straddle the client and server.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;


import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";


contract EncryptionNFT is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private tokenCounter;

    uint256 public fee_wei;
    bool public isLocked;               // has a user paid to access the resource?
    string private confirmationId;      // long temporary password
    string private sensitiveData;       // data produced by long running process
    address public encryptionService;   // service responsible for producing the sensitive data
    address public licensor;            // licensed process
    uint256 public tokenID;             // distinct contract id

    bool private isMutexed = false;             // Reentrancy guard
    bool private isSensitiveDataSet = false;    // Asserts can only be set 1 time

    event unlockPaid(address receiver, uint256 amount);
    event withdrawlMade(address receiver, uint256 amount);

    constructor(string memory name, uint256 _fee_gwei, string memory _confirmationId, address _encryption_service, address _licensor)
        ERC721(name, "DataX")
    {
        fee_wei = _fee_gwei * 1000000000;           // assert numbers are in WEI
        isLocked = true;                            // lock contract by default since data not available => owner shouldn't have to pay unless successful
        confirmationId = _confirmationId;           // assign temporary password
        encryptionService = _encryption_service;    // assign addresses for payments
        licensor = _licensor;                       // assign addresses for payments

        tokenID = tokenCounter.current();           // Set/Mint with distinct token id
        _safeMint(msg.sender, tokenID);
        tokenCounter.increment();
    }

    function unlock() public payable {
        require(isLocked == true, "Contract already unlocked.");
        require(msg.value >= fee_wei, "Not enough WEI to unlock NFT.");
        require(isMutexed == false, "Reentrant call detected!");
        isMutexed = true;

        // DO PAYMENTS
        uint256 rcv_payment = msg.value;
        uint256 licensor_cut = rcv_payment / 10;
        uint256 encryptor_cut = rcv_payment - licensor_cut;

        (bool is_encryptor_success, ) = payable(encryptionService).call{value: encryptor_cut}("");
        require(is_encryptor_success, "Encryptor transfer failed.");
        emit unlockPaid(encryptionService, encryptor_cut);

        (bool is_licensor_success, ) = payable(licensor).call{value: licensor_cut}("");
        require(is_licensor_success, "Licensor transfer failed.");
        emit unlockPaid(licensor, licensor_cut);

        isLocked = false;
        isMutexed = false;
    }

    function withdraw() public payable {
        require(msg.sender == ownerOf(tokenID), "Only owner may withdraw.");
        require(isLocked == false, "Contract must be unlocked.");
        require(isMutexed == false, "Reentrant call detected!");
        isMutexed = true;

        // DO PAYMENTS
        uint256 rcv_payment = msg.value;
        uint256 nft_service_cut = rcv_payment / 10;
        uint256 licensor_cut = nft_service_cut / 10;
        uint256 encryptor_cut = rcv_payment - licensor_cut;
        uint256 nft_owner_cut = rcv_payment - nft_service_cut;

        (bool is_encryptor_success, ) = payable(encryptionService).call{value: encryptor_cut}("");
        require(is_encryptor_success, "Encryptor transfer failed.");
        emit withdrawlMade(encryptionService, encryptor_cut);

        (bool is_licensor_success, ) = payable(licensor).call{value: licensor_cut}("");
        require(is_licensor_success, "Licensor transfer failed.");
        emit withdrawlMade(licensor, licensor_cut);

        (bool is_nft_owner_success, ) = payable(ownerOf(tokenID)).call{value: nft_owner_cut}("");
        require(is_nft_owner_success, "NFT Owner transfer failed.");
        emit withdrawlMade(ownerOf(tokenID), nft_owner_cut);

        isMutexed = false;
  }

    function setTokenURI(string memory _tokenURI) public {
        require(_isApprovedOrOwner(_msgSender(), tokenID), "ERC721: transfer caller is not owner nor approved.");
        _setTokenURI(tokenID, _tokenURI);
    }

    // Here's how I was planning to allow a server-side process to set the data on REST service completion (slow task)
    function setSensitiveData(string memory _confirmationId, string memory _sensitiveData) public {
        require(keccak256(bytes(_confirmationId)) == keccak256(bytes(confirmationId)), "Confirmation ID does not match.");
        require(isSensitiveDataSet == false, "Sensitive Data has already been set");
        sensitiveData = _sensitiveData;
        isSensitiveDataSet = true;
    }

    function getSensitiveData() public view returns (string memory){
        require(isSensitiveDataSet == true, "Sensitive Data must be set first.");
        require(msg.sender == encryptionService, "Only encryption service accessible.");
        return sensitiveData;
    }
}

Edit I just found Moralis has a session that appears to allow server side access as a corresponding user. Has anyone played with this before? Would it work for this use case?

Moralis Docs enter image description here

0 Answers
Related