To send your first commitment, you will need to connect to the mev-commit Testnet. This will allow you to consume bids incoming to the network and submit commitments. To do this, you’ll want to run an emulator to simulate accepting and rejecting bids.
You will want to connect to a decisioning system that will accept and reject bids based on custom criteria. You can read more about the API to open a gRPC stream here.
As an example, a dummy decisioning system is implemented in the repository. This dummy system accepts all the bids it gets. To try it, you can open a new terminal and run the following command:
You require Go Version 1.21.1 to run the example emulator
git clone --recurse-submodules https://github.com/primev/mev-commit.git && cd mev-commit &&
git checkout v1.1.0 && cd p2p/examples/provideremulator && go run . 

Configuring custom logic to process bids and send Commitments

We can take a deeper look at how you can add custom logic to decision on the sending of commitments.

Integrating Bid Processing in Your System

To integrate bid processing in your system using the Provider API, follow these steps to receive bid information and send back processed bids:
1

Implement the gRPC Service

Implement the Provider service as outlined in the providerapi.proto. This requires setting up a server that can manage RPC methods, with a particular emphasis on the reception of bids and response of commitment authorizations.
2

Receive Bids

Utilize the ReceiveBids RPC method to listen for incoming bids from clients. This method streams bid messages to your server. The structure of the bid is as follows:
message Bid {
  option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
    json_schema: {
      title: "Bid message"
      description: "Signed bid message from bidders to the provider."
      required: ["txHashes", "bidAmount", "blockNumber", "bidDigest"]
    }
    example: "{\"txHashes\": [\"fe4cb47db3630551beedfbd02a71ecc69fd59758e2ba699606e2d5c74284ffa7\", \"71c1348f2d7ff7e814f9c3617983703435ea7446de420aeac488bf1de35737e8\"], \"amount\": \"1000000000000000000\", \"blockNumber\": 123456, \"bidDigest\": \"9dJinwL+FZ6B1xsIQQo8t8B0ZXJubJwY86l/Yu7yAH159QrPHU0qj2P+YFj+llbuI1ZygdxGsX8+P3byMEA5ig==\", \"decayStartTimestamp\":1725365301000, \"decayEndTimestamp\":1725365302000, \"revertingTxHashes\":[\"fe4cb47db3630551beedfbd02a71ecc69fd59758e2ba699606e2d5c74284ffa7\"]}"
  };
  repeated string tx_hashes = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Hex string encoding of the hashes of the transactions that the bidder wants to include in the block."
    pattern: "[a-fA-F0-9]{64}"
  }, (buf.validate.field).cel = {
      id: "tx_hashes",
      message: "tx_hashes must be a valid array of transaction hashes.",
      expression: "this.all(r, r.matches('^[a-fA-F0-9]{64}$'))"
  }];
  string bid_amount = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Amount of ETH that the bidder is willing to pay to the provider for including the transaction in the block."
    pattern: "[0-9]+"
  }, (buf.validate.field).cel = {
      id: "bid_amount",
      message: "bid_amount must be a valid integer.",
      expression: "this.matches('^[1-9][0-9]*$')"
  }];
  int64 block_number = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Max block number that the bidder wants to include the transaction in."
  }, (buf.validate.field).int64.gt = 0];
  bytes bid_digest = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Digest of the bid message signed by the bidder."
  }, (buf.validate.field).bytes = {
      min_len: 1,
      max_len: 64
  }];
  int64 decay_start_timestamp = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Timestamp at which the bid starts decaying."
  }, (buf.validate.field).cel = {
      id: "decay_start_timestamp",
      message: "decay_start_timestamp must be a valid integer.",
      expression: "uint(this) > 0"
  }];
  int64 decay_end_timestamp = 6 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Timestamp at which the bid ends decaying."
  }, (buf.validate.field).cel = {
      id: "decay_end_timestamp",
      message: "decay_end_timestamp must be a valid integer.",
      expression: "uint(this) > 0"
  }];
  repeated string reverting_tx_hashes = 7 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Optional array of tx hashes that are allowed to revert or be discarded."
  }, (buf.validate.field).cel = {
      id: "reverting_tx_hashes",
      message: "reverting_tx_hashes must be an array of valid transaction hashes.",
      expression: "this.all(r, r.matches('^[a-fA-F0-9]{64}$'))"
  }];
  repeated string raw_transactions = 8 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Optional array of RLP encoded raw signed transaction payloads that the bidder wants to include in the block."
  }, (buf.validate.field).cel = {
      id: "raw_transactions",
      message: "raw_transactions must be an array of valid raw transactions.",
      expression: "this.all(r, r.matches('^[a-fA-F0-9]+$'))"
  }];
  string slash_amount = 9 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Amount of ETH that will be slashed from the provider if they fail to include the transaction. If zero, the decayed bid amount is used for slashing."
    pattern: "[0-9]+"
  }, (buf.validate.field).cel = {
      id: "slash_amount",
      message: "slash_amount must be a valid integer.",
      expression: "this == '' || (this.matches('^[0-9]+$') && uint(this) >= 0)"
  }];
  BidOptions bid_options = 10 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Optional bid options for the transaction."
  }];
};
The raw transactions are optionally provided by the bidder. If provided the order of the transaction hashes in txHashes will be the same as the order or the corresponding rawTransactions.Below is an image of the flow through which bids would be received through the gRPC APIgRPC Flow for Getting BidsThe bid options are optionally used by bidders if they want additional constraints on the transaction. Currently, mev-commit supports positional constraints on the transactions w.r.t the block. The decision logic should take these into consideration before returning.
message PositionConstraint {
  option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
    json_schema: {
      title: "Position Constraint"
      description: "Constraint on the position of the transaction in the block."
    }
  };
  enum Anchor {
    ANCHOR_UNSPECIFIED = 0;
    ANCHOR_TOP = 1; // Position is at the top of the block
    ANCHOR_BOTTOM = 2; // Position is at the bottom of the block
  }
  enum Basis {
    BASIS_UNSPECIFIED = 0;
    BASIS_PERCENTILE = 1; // Position is a percentile of the block size
    BASIS_ABSOLUTE = 2; // Position is an absolute position in the block
    BASIS_GAS_PERCENTILE = 3; // Position is a percentile of the gas used in the block
  }
  Anchor anchor = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Anchor position of the transaction in the block."
  }];
  Basis basis = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Basis for the position constraint."
  }];
  int32 value = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Value of the position constraint. If anchor is TOP, this is the position from the top of the block. If anchor is BOTTOM, this is the position from the bottom of the block."
  }];
};
3

Process Bids

For each received bid, implement your custom logic to validate and process these bids. This could involve checking the bid’s validity and feasibility based on orderflow, computing the effective gas price, and deciding whether to accept or reject the bid.
4

Send Processed Bid Responses (Accept/Reject)

After processing each bid, construct a BidResponse message indicating the outcome. This message should include the bid’s digest and a status indicating acceptance or rejection. At a high level, to commit to a bid, your code must send a STATUS_ACCEPTED with the bid digest specified, and correspondingly STATUS_REJECTED to reject the bid.
The BidResponse structure is as follows:
message BidResponse {
  option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
    json_schema: {
      title: "Bid response"
      description: "Response sent by the provider with the decision on the bid received."
      required: ["bidDigest", "status"]
    }
    example: "{\"bidDigest\": \"9dJinwL+FZ6B1xsIQQo8t8B0ZXJubJwY86l/Yu7yAH159QrPHU0qj2P+YFj+llbuI1ZygdxGsX8+P3byMEA5ig==\", \"status\": \"STATUS_ACCEPTED\"}"
  };
  bytes bid_digest = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Digest of the bid message signed by the bidder."
  }];
  enum Status {
    STATUS_UNSPECIFIED = 0;
    STATUS_ACCEPTED = 1;
    STATUS_REJECTED = 2;
  }
  Status status = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
    description: "Status of the bid."
  }, (buf.validate.field).enum = {
      defined_only: true,
      in: [
        1,
        2
      ]
  }];
  int64 dispatch_timestamp = 3;
};
It contains the following fields:
  1. bid_digest: A byte array (bytes type) that represents the digest of the bid message signed by the bidder. This serves as a unique identifier for the bid in question.
  2. status: An enumeration (enum Status) that indicates the status of the bid. The status can be one of three values: STATUS_UNSPECIFIED: The default value, indicating that the status has not been set or is unknown. STATUS_ACCEPTED: Indicates that the bid has been accepted by the provider. STATUS_REJECTED: Indicates that the bid has been rejected by the provider.
  3. dispatch_timestamp: Timestamp (int64 type) at which the commitment is accepted by provider and is used to compute the expected revenue from the preconfirmation.
5

Server Setup

Deploy your gRPC server on suitable infrastructure, ensuring it’s accessible to clients. Configure necessary network settings for security and connectivity. Example Server Implementation in Go:

import (
	...

	"github.com/primev/mev-commit/p2p/examples/provideremulator/client"
	providerapiv1 "github.com/primev/mev-commit/p2p/gen/go/providerapi/v1"
)

func ...() {
	...

	providerClient, _ := client.NewProviderClient(*serverAddr, logger)

	bidS, _ := providerClient.ReceiveBids()

	for {
		select {
		case bid, more := <-bidS:
			if !more {
				return
			}
			logger.Info("received new bid", "bid", bid)
			_ := providerClient.SendBidResponse(context.Background(), &providerapiv1.BidResponse{
				BidDigest: bid.BidDigest,
				Status:    providerapiv1.BidResponse_STATUS_ACCEPTED, // decide here if you want to accept or reject
        DispatchTimestamp: time.Now().Unix(),
			})
		}
	}
}
This should help you integrate bid processing into your system, allowing you to receive bid information and send back processed bids as commitments.