How can I write to the blockchain after an undetermined, time-consuming execution result?
Situation:
- 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.
- 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:
- Can’t have the user wait for the process to complete
- Metamask runs on the client
- 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
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?

