Operator Service
MM-UI and MM-Service Repositories are currently closed source
To complete the bridging process and enable data transmission between smart contracts, the Operator service is essential. Previously known as "eventWatch," the Operator's primary role is to listen for on-chain events.
While the core functionality is handled by the smart contracts, the Operator service is crucial for a fully operational bridge. Although it can be run locally, in a production environment, it's currently deployed on a server as an API service that the front-end can interact with. The Operator service also integrates a database to store all the orders, their statuses, and other relevant information. Inside the repository under src/listeners/
, there are several listeners that wait for events from the smart contracts, and upon receiving an event, they initiate the corresponding workflow.
Workflows
The first workflow is found in the escrowOrderReceived
function. When a user places an order, the listener captures the order details and logs them in the database, which the front-end queries to display the information for the MM to fulfill. The function checks if the order already exists in the database, and if not, it adds it. Next, it verifies if the MM has enabled auto-transfers. If enabled, it calls executeSendTransfer
, which automatically fulfills the order on behalf of the MM. After fulfillment, the database is updated to reflect the order's completion.
The second workflow is in registryTransferReceived
. When a transfer is made on the Payment Registry contract, the listener detects the event and updates the database to indicate that the order has been fulfilled. This is also where the automatic withdrawal system, could be activated in order to start the proving and withdrawing system as soon as the transfer is made. However, in the current SMM setup, the MM has the flexibility to withdraw their funds at a more convenient time, either manually or through an automated system. This system can monitor the MM's liquidity on the destination chain, the amount owed on the source chain, and the current gas prices to optimize the withdrawal process.
To start the proof process, whether it's done manually or automatically, the first step takes place in the src/functions/
folder. The process begins with calculating which storage slots are needed to prove that an order was fulfilled. The first function we look at is calculateSlots
, which takes in the following parameters: orderId
, usrDstAddress
, and the amount
that was transferred.
If you're curious about why we need to calculate storage slots, you can learn more about how storage works in smart contracts here.
With our parameters ready, the first step is to calculate the transfersMappingKey
, which identifies where the order is stored in the transfers
mapping. This key is derived from the orderId
, usrDstAddress
, and amount
, the same way we calculated the index in the smart Payment Registry contract. Since we’ve previously determined that the transfers
mapping starts at slot 2 in the smart contract, we can hardcode the transferMappingSlot
as 2. To find out the store layout of your smart contracts you can run the following command in forge: forge inspect <contract name> storage-layout --pretty
.
Next, using the transfersMappingKey
and the transferMappingSlot
, we calculate the baseStorageSlot
, which is the exact location in storage where the order data begins. This slot will hold the orderId
, and the subsequent slots will store the usrDstAddress
, expirationTimestamp
, and amount
, in that order. By incrementing the baseStorageSlot
, we can retrieve the storage slots for each of these values. In summary, orderId
is stored in the base slot, usrDstAddress
in the next, expirationTimestamp
two slots after the base, and amount
three slots from the base.
Next, head over to proveWithStorageProofs
function. Here, we’ll use the storage slots we just calculated and send them to generate a proof. The query to Herodotus requires several key inputs: the address of the smart contract whose slots we’re proving, the block number at which we want to prove it (important because the slots before the transfer will be empty), and, the destinationChainId
, which determines which Facts Registry smart contract Herodotus will send the proof to. We need to ensure this is the same chain where we’ll later access the proof. We also include the storage slots to be proven and a webhook address that will receive the notification once the proof is ready. Once the query is sent, we simply wait for the proof to be completed.
Once the proof is completed, we receive a status: DONE
from the storage proofs api, and we handle this webhook response with our market maker handling endpoint. This server also acts as the API that our front end interacts with. Upon receiving the response from Herodotus, we extract the block number where the slots were proven, along with the orders that were successfully proven.
Next we extract the orderId and the blockNumber at which the order was proven from the headers of the response, which we relay back to the Escrow smart contract via the calculateSlotsForFulfilledOrder
function, which takes the orderId
and blockNumber
as parameters. This function will recalculate the same slots that we have calculated ourselves, and if all goes well we will have sent the same slots to the Storage Proofs API, and the slots have been proven. In the event that a proof fails, for whatever reason, the order is flagged as failed, and can be re-proved. However, if the wrong amount
, orderId
or usrDstAddress
were provided while making the transfer through the Payment Registry contract, the order will have to be re-fulfilled with the correct data, before being proven and marked as able to withdraw.
This completes the core function of the Operator service. There are some differences depending on how the bridge is used, such as MMM versus SMM scenarios, as well as the option for auto-filling orders. Additionally, the Market Maker situation evaluator and executor monitors your outstanding debts on the destination chain. Depending on your liquidity and current gas fees, it will decide whether to start withdrawing smaller orders when gas prices are low or to withdraw one large order when gas prices are high but you are low on liquidity on the destination chain.
Last updated