diff --git a/docs/bridge/docs/rfq/API/upsert-quotes.api.mdx b/docs/bridge/docs/rfq/API/upsert-quotes.api.mdx
new file mode 100644
index 0000000000..c001ef8ea1
--- /dev/null
+++ b/docs/bridge/docs/rfq/API/upsert-quotes.api.mdx
@@ -0,0 +1,244 @@
+---
+id: upsert-quotes
+title: "Upsert quotes"
+description: "upsert bulk quotes from relayer."
+sidebar_label: "Upsert quotes"
+hide_title: true
+hide_table_of_contents: true
+api: eJyNVMGOmzAQ/RXkc9pEPea4VU89tKo2p3SFBhjAG8DesdkNQvx7ZzDZ0IRFCxIy4+fx87w37lWGLiVtvTaN2qvWOiQfJW11il5a49FFOZk6IqygQ/qqNorwpUXnH0zWqX2vUtN4bLwMwdpKpyCpts9O8vXKpSXWICNLxnJujU7+QnIZaY+1uwcwLx9DbdqQ23cWmZ/zpJtCDZswn5agm1hnM4RmOgXSOyQH/iSkswJjyDJC5z7O580JmxG2iMn1GbM4R1ycreEcG9IFM1rhPSHWmU+gz3Kf4KvsGTZFTPKMqVfXABBBt4QYQl1m/mDpqYssELBmwQuakE/hqcVBAs6axgUJv+12k5KzDL9+hp2gYMzx4oMnLh/60nAmZVvPqS34kn+2YsV4Qm2U2OrP1YA/zlDbCueGOt5Y51KAO8fs1gxys2pe2evUzA7X4IILrpN34u/Wtb5buURkeOJ66iY3l3aEVKwnRdZeiqN4/SuSCwKI8NY4X8PYoA3UAjmExn8v9H+izbr8MzfE5CKPZ7+1FR9VtmypGpt8lPWo5rKy+CUTknDfJ+DwQNUwSHi0G8d5+AqkIZHjHOW8JULGvSJyn5Ah6ntg+OVRNhd41Y7evr2ThrnVfh8eGZtMV1ltMllC8CbW5u9e/eVXqj8WYjT1GO9VBU3RQiH4kFaefxaR3uo=
+sidebar_class_name: "put api-method"
+custom_edit_url: null
+---
+
+import ApiTabs from "@theme/ApiTabs";
+import DiscriminatorTabs from "@theme/DiscriminatorTabs";
+import MethodEndpoint from "@theme/ApiExplorer/MethodEndpoint";
+import SecuritySchemes from "@theme/ApiExplorer/SecuritySchemes";
+import MimeTabs from "@theme/MimeTabs";
+import ParamsItem from "@theme/ParamsItem";
+import ResponseSamples from "@theme/ResponseSamples";
+import SchemaItem from "@theme/SchemaItem";
+import SchemaTabs from "@theme/SchemaTabs";
+import Markdown from "@theme/Markdown";
+import Heading from "@theme/Heading";
+import OperationTabs from "@theme/OperationTabs";
+import TabItem from "@theme/TabItem";
+
+
+
+
+
+
+
+
+
+
+upsert bulk quotes from relayer.
+
+
+
+
+
+
+
+
+
+ Body
+
+ required
+
+
+
+
+
+ query params
+
+
+
+
+
+
+
+
+
+ quotes
+
+ object[]
+
+
+
+
-
+
+ Array [
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+ ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OK
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/services/rfq/api/client/client.go b/services/rfq/api/client/client.go
index 2e878afaf2..38a16ced13 100644
--- a/services/rfq/api/client/client.go
+++ b/services/rfq/api/client/client.go
@@ -26,6 +26,7 @@ import (
// It provides methods for creating, retrieving and updating quotes.
type AuthenticatedClient interface {
PutQuote(ctx context.Context, q *model.PutQuoteRequest) error
+ PutBulkQuotes(ctx context.Context, q *model.PutBulkQuotesRequest) error
PutRelayAck(ctx context.Context, req *model.PutAckRequest) (*model.PutRelayAckResponse, error)
UnauthenticatedClient
}
@@ -125,6 +126,19 @@ func (c *clientImpl) PutQuote(ctx context.Context, q *model.PutQuoteRequest) err
return err
}
+// PutBulkQuotes puts multiple new quotes in the RFQ quoting API.
+func (c *clientImpl) PutBulkQuotes(ctx context.Context, q *model.PutBulkQuotesRequest) error {
+ res, err := c.rClient.R().
+ SetContext(ctx).
+ SetBody(q).
+ Put(rest.BulkQuotesRoute)
+
+ // TODO: Figure out if there's anything to do with the response, right now it's result: Status Code 200 OK
+ _ = res
+
+ return err
+}
+
func (c *clientImpl) PutRelayAck(ctx context.Context, req *model.PutAckRequest) (*model.PutRelayAckResponse, error) {
var ack *model.PutRelayAckResponse
resp, err := c.rClient.R().
diff --git a/services/rfq/api/client/client_test.go b/services/rfq/api/client/client_test.go
index f5d5f12364..bfc8dc3483 100644
--- a/services/rfq/api/client/client_test.go
+++ b/services/rfq/api/client/client_test.go
@@ -38,6 +38,65 @@ func (c *ClientSuite) TestPutAndGetQuote() {
c.Equal(expectedResp, *quotes[0])
}
+func (c *ClientSuite) TestPutAndGetBulkQuotes() {
+ req := model.PutBulkQuotesRequest{
+ Quotes: []model.PutQuoteRequest{
+ {
+ OriginChainID: 1,
+ OriginTokenAddr: "0xOriginTokenAddr",
+ DestChainID: 42161,
+ DestTokenAddr: "0xDestTokenAddr",
+ DestAmount: "100",
+ MaxOriginAmount: "200",
+ FixedFee: "10",
+ },
+ {
+ OriginChainID: 42161,
+ OriginTokenAddr: "0xOriginTokenAddr",
+ DestChainID: 1,
+ DestTokenAddr: "0xDestTokenAddr",
+ DestAmount: "100",
+ MaxOriginAmount: "200",
+ FixedFee: "10",
+ },
+ },
+ }
+
+ err := c.client.PutBulkQuotes(c.GetTestContext(), &req)
+ c.Require().NoError(err)
+
+ quotes, err := c.client.GetAllQuotes(c.GetTestContext())
+ c.Require().NoError(err)
+
+ expectedResp := []model.GetQuoteResponse{
+ {
+ OriginChainID: 1,
+ OriginTokenAddr: "0xOriginTokenAddr",
+ DestChainID: 42161,
+ DestTokenAddr: "0xDestTokenAddr",
+ DestAmount: "100",
+ MaxOriginAmount: "200",
+ FixedFee: "10",
+ RelayerAddr: c.testWallet.Address().String(),
+ UpdatedAt: quotes[0].UpdatedAt,
+ },
+ {
+ OriginChainID: 42161,
+ OriginTokenAddr: "0xOriginTokenAddr",
+ DestChainID: 1,
+ DestTokenAddr: "0xDestTokenAddr",
+ DestAmount: "100",
+ MaxOriginAmount: "200",
+ FixedFee: "10",
+ RelayerAddr: c.testWallet.Address().String(),
+ UpdatedAt: quotes[0].UpdatedAt,
+ },
+ }
+ c.Len(quotes, 2)
+ c.Equal(expectedResp[0], *quotes[0])
+ c.Equal(expectedResp[1], *quotes[1])
+}
+
func (c *ClientSuite) TestGetSpecificQuote() {
req := model.PutQuoteRequest{
OriginChainID: 1,
diff --git a/services/rfq/api/client/suite_test.go b/services/rfq/api/client/suite_test.go
index faf9e6c35d..e87436fcca 100644
--- a/services/rfq/api/client/suite_test.go
+++ b/services/rfq/api/client/suite_test.go
@@ -58,6 +58,15 @@ func NewTestClientSuite(tb testing.TB) *ClientSuite {
func (c *ClientSuite) SetupTest() {
c.TestSuite.SetupTest()
+ metricsHandler := metrics.NewNullHandler()
+ c.handler = metricsHandler
+ dbType, err := dbcommon.DBTypeFromString("sqlite")
+ c.Require().NoError(err)
+ // TODO use temp file / in memory sqlite3 to not create in directory files
+ testDB, err := sql.Connect(c.GetSuiteContext(), dbType, filet.TmpDir(c.T(), ""), metricsHandler)
+ c.Require().NoError(err)
+ c.database = testDB
+
testOmnirpc := omnirpcHelper.NewOmnirpcServer(c.GetTestContext(), c.T(), c.omniRPCTestBackends...)
omniRPCClient := omniClient.NewOmnirpcClient(testOmnirpc, c.handler, omniClient.WithCaptureReqRes())
c.omniRPCClient = omniRPCClient
@@ -175,14 +184,6 @@ func (c *ClientSuite) SetupSuite() {
c.T().Fatal(err)
}
- dbType, err := dbcommon.DBTypeFromString("sqlite")
- c.Require().NoError(err)
- metricsHandler := metrics.NewNullHandler()
- c.handler = metricsHandler
- // TODO use temp file / in memory sqlite3 to not create in directory files
- testDB, err := sql.Connect(c.GetSuiteContext(), dbType, filet.TmpDir(c.T(), ""), metricsHandler)
- c.Require().NoError(err)
- c.database = testDB
// setup config
}
diff --git a/services/rfq/api/db/api_db.go b/services/rfq/api/db/api_db.go
index dc84aa7f65..48c7344484 100644
--- a/services/rfq/api/db/api_db.go
+++ b/services/rfq/api/db/api_db.go
@@ -50,6 +50,8 @@ type APIDBReader interface {
type APIDBWriter interface {
// UpsertQuote upserts a quote in the database.
UpsertQuote(ctx context.Context, quote *Quote) error
+ // UpsertQuotes upserts multiple quotes in the database.
+ UpsertQuotes(ctx context.Context, quotes []*Quote) error
}
// APIDB is the interface for the database service.
diff --git a/services/rfq/api/db/sql/base/store.go b/services/rfq/api/db/sql/base/store.go
index 0c44c59930..8ef37607e8 100644
--- a/services/rfq/api/db/sql/base/store.go
+++ b/services/rfq/api/db/sql/base/store.go
@@ -3,6 +3,7 @@ package base
import (
"context"
"fmt"
+
"gorm.io/gorm/clause"
"github.com/synapsecns/sanguine/services/rfq/api/db"
@@ -63,3 +64,16 @@ func (s *Store) UpsertQuote(ctx context.Context, quote *db.Quote) error {
}
return nil
}
+
+// UpsertQuotes inserts multiple quotes into the database or updates existing ones.
+func (s *Store) UpsertQuotes(ctx context.Context, quotes []*db.Quote) error {
+ dbTx := s.DB().WithContext(ctx).
+ Clauses(clause.OnConflict{
+ UpdateAll: true,
+ }).Create(quotes)
+
+ if dbTx.Error != nil {
+ return fmt.Errorf("could not update quotes: %w", dbTx.Error)
+ }
+ return nil
+}
diff --git a/services/rfq/api/docs/docs.go b/services/rfq/api/docs/docs.go
index fdd79b976b..1edb775079 100644
--- a/services/rfq/api/docs/docs.go
+++ b/services/rfq/api/docs/docs.go
@@ -46,6 +46,37 @@ const docTemplate = `{
}
}
},
+ "/bulk_quotes": {
+ "put": {
+ "description": "upsert bulk quotes from relayer.",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "quotes"
+ ],
+ "summary": "Upsert quotes",
+ "parameters": [
+ {
+ "description": "query params",
+ "name": "request",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/model.PutBulkQuotesRequest"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK"
+ }
+ }
+ }
+ },
"/quotes": {
"get": {
"description": "get quotes from all relayers.",
@@ -184,6 +215,17 @@ const docTemplate = `{
}
}
},
+ "model.PutBulkQuotesRequest": {
+ "type": "object",
+ "properties": {
+ "quotes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/model.PutQuoteRequest"
+ }
+ }
+ }
+ },
"model.PutQuoteRequest": {
"type": "object",
"properties": {
diff --git a/services/rfq/api/docs/swagger.json b/services/rfq/api/docs/swagger.json
index 0f4707ab3f..45ee72b695 100644
--- a/services/rfq/api/docs/swagger.json
+++ b/services/rfq/api/docs/swagger.json
@@ -35,6 +35,37 @@
}
}
},
+ "/bulk_quotes": {
+ "put": {
+ "description": "upsert bulk quotes from relayer.",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "quotes"
+ ],
+ "summary": "Upsert quotes",
+ "parameters": [
+ {
+ "description": "query params",
+ "name": "request",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/model.PutBulkQuotesRequest"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK"
+ }
+ }
+ }
+ },
"/quotes": {
"get": {
"description": "get quotes from all relayers.",
@@ -173,6 +204,17 @@
}
}
},
+ "model.PutBulkQuotesRequest": {
+ "type": "object",
+ "properties": {
+ "quotes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/model.PutQuoteRequest"
+ }
+ }
+ }
+ },
"model.PutQuoteRequest": {
"type": "object",
"properties": {
diff --git a/services/rfq/api/docs/swagger.yaml b/services/rfq/api/docs/swagger.yaml
index a95f1dcddc..e2b9376218 100644
--- a/services/rfq/api/docs/swagger.yaml
+++ b/services/rfq/api/docs/swagger.yaml
@@ -43,6 +43,13 @@ definitions:
description: UpdatedAt is the time that the quote was last upserted
type: string
type: object
+ model.PutBulkQuotesRequest:
+ properties:
+ quotes:
+ items:
+ $ref: '#/definitions/model.PutQuoteRequest'
+ type: array
+ type: object
model.PutQuoteRequest:
properties:
dest_amount:
@@ -87,6 +94,26 @@ paths:
summary: Relay ack
tags:
- ack
+ /bulk_quotes:
+ put:
+ consumes:
+ - application/json
+ description: upsert bulk quotes from relayer.
+ parameters:
+ - description: query params
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/model.PutBulkQuotesRequest'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ summary: Upsert quotes
+ tags:
+ - quotes
/quotes:
get:
consumes:
diff --git a/services/rfq/api/model/request.go b/services/rfq/api/model/request.go
index f3ce466ce1..cff2db2161 100644
--- a/services/rfq/api/model/request.go
+++ b/services/rfq/api/model/request.go
@@ -13,6 +13,11 @@ type PutQuoteRequest struct {
DestFastBridgeAddress string `json:"dest_fast_bridge_address"`
}
+// PutBulkQuotesRequest contains the schema for a PUT /quote request.
+type PutBulkQuotesRequest struct {
+ Quotes []PutQuoteRequest `json:"quotes"`
+}
+
// PutAckRequest contains the schema for a PUT /ack request.
type PutAckRequest struct {
TxID string `json:"tx_id"`
diff --git a/services/rfq/api/rest/handler.go b/services/rfq/api/rest/handler.go
index 7722294b6b..7f982a5949 100644
--- a/services/rfq/api/rest/handler.go
+++ b/services/rfq/api/rest/handler.go
@@ -1,6 +1,7 @@
package rest
import (
+ "fmt"
"net/http"
"strconv"
@@ -54,23 +55,84 @@ func (h *Handler) ModifyQuote(c *gin.Context) {
return
}
- destAmount, err := decimal.NewFromString(putRequest.DestAmount)
+ dbQuote, err := parseDBQuote(*putRequest, relayerAddr)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid DestAmount"})
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
- maxOriginAmount, err := decimal.NewFromString(putRequest.MaxOriginAmount)
+ err = h.db.UpsertQuote(c, dbQuote)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid DestAmount"})
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- fixedFee, err := decimal.NewFromString(putRequest.FixedFee)
+ c.Status(http.StatusOK)
+}
+
+// ModifyBulkQuotes upserts multiple quotes
+//
+// PUT /bulk_quotes
+// @dev Protected Method: Authentication is handled through middleware in server.go.
+// nolint: cyclop
+// @Summary Upsert quotes
+// @Schemes
+// @Description upsert bulk quotes from relayer.
+// @Param request body model.PutBulkQuotesRequest true "query params"
+// @Tags quotes
+// @Accept json
+// @Produce json
+// @Success 200
+// @Router /bulk_quotes [put].
+func (h *Handler) ModifyBulkQuotes(c *gin.Context) {
+ // Retrieve the request from context
+ req, exists := c.Get("putRequest")
+ if !exists {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Request not found"})
+ return
+ }
+ relayerAddr, exists := c.Get("relayerAddr")
+ if !exists {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "No relayer address recovered from signature"})
+ return
+ }
+ putRequest, ok := req.(*model.PutBulkQuotesRequest)
+ if !ok {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request type"})
+ return
+ }
+
+ dbQuotes := []*db.Quote{}
+ for _, quoteReq := range putRequest.Quotes {
+ dbQuote, err := parseDBQuote(quoteReq, relayerAddr)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid quote request"})
+ return
+ }
+ dbQuotes = append(dbQuotes, dbQuote)
+ }
+
+ err := h.db.UpsertQuotes(c, dbQuotes)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid FixedFee"})
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
+ c.Status(http.StatusOK)
+}
+
+func parseDBQuote(putRequest model.PutQuoteRequest, relayerAddr interface{}) (*db.Quote, error) {
+ destAmount, err := decimal.NewFromString(putRequest.DestAmount)
+ if err != nil {
+ return nil, fmt.Errorf("invalid DestAmount")
+ }
+ maxOriginAmount, err := decimal.NewFromString(putRequest.MaxOriginAmount)
+ if err != nil {
+ return nil, fmt.Errorf("invalid MaxOriginAmount")
+ }
+ fixedFee, err := decimal.NewFromString(putRequest.FixedFee)
+ if err != nil {
+ return nil, fmt.Errorf("invalid FixedFee")
+ }
// nolint: forcetypeassert
- quote := &db.Quote{
+ return &db.Quote{
OriginChainID: uint64(putRequest.OriginChainID),
OriginTokenAddr: putRequest.OriginTokenAddr,
DestChainID: uint64(putRequest.DestChainID),
@@ -82,13 +144,7 @@ func (h *Handler) ModifyQuote(c *gin.Context) {
RelayerAddr: relayerAddr.(string),
OriginFastBridgeAddress: putRequest.OriginFastBridgeAddress,
DestFastBridgeAddress: putRequest.DestFastBridgeAddress,
- }
- err = h.db.UpsertQuote(c, quote)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
- return
- }
- c.Status(http.StatusOK)
+ }, nil
}
// GetQuotes retrieves all quotes from the database.
diff --git a/services/rfq/api/rest/server.go b/services/rfq/api/rest/server.go
index 886c7d075c..03cfcdeed5 100644
--- a/services/rfq/api/rest/server.go
+++ b/services/rfq/api/rest/server.go
@@ -142,6 +142,8 @@ func NewAPI(
const (
// QuoteRoute is the API endpoint for handling quote related requests.
QuoteRoute = "/quotes"
+ // BulkQuotesRoute is the API endpoint for handling bulk quote related requests.
+ BulkQuotesRoute = "/bulk_quotes"
// AckRoute is the API endpoint for handling relay ack related requests.
AckRoute = "/ack"
cacheInterval = time.Minute
@@ -160,6 +162,9 @@ func (r *QuoterAPIServer) Run(ctx context.Context) error {
quotesPut := engine.Group(QuoteRoute)
quotesPut.Use(r.AuthMiddleware())
quotesPut.PUT("", h.ModifyQuote)
+ bulkQuotesPut := engine.Group(BulkQuotesRoute)
+ bulkQuotesPut.Use(r.AuthMiddleware())
+ bulkQuotesPut.PUT("", h.ModifyBulkQuotes)
ackPut := engine.Group(AckRoute)
ackPut.Use(r.AuthMiddleware())
ackPut.PUT("", r.PutRelayAck)
@@ -187,8 +192,8 @@ func (r *QuoterAPIServer) Run(ctx context.Context) error {
func (r *QuoterAPIServer) AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
var loggedRequest interface{}
- var destChainID uint32
var err error
+ destChainIDs := []uint32{}
// Parse the dest chain id from the request
switch c.Request.URL.Path {
@@ -196,14 +201,23 @@ func (r *QuoterAPIServer) AuthMiddleware() gin.HandlerFunc {
var req model.PutQuoteRequest
err = c.BindJSON(&req)
if err == nil {
- destChainID = uint32(req.DestChainID)
+ destChainIDs = append(destChainIDs, uint32(req.DestChainID))
+ loggedRequest = &req
+ }
+ case BulkQuotesRoute:
+ var req model.PutBulkQuotesRequest
+ err = c.BindJSON(&req)
+ if err == nil {
+ for _, quote := range req.Quotes {
+ destChainIDs = append(destChainIDs, uint32(quote.DestChainID))
+ }
loggedRequest = &req
}
case AckRoute:
var req model.PutAckRequest
err = c.BindJSON(&req)
if err == nil {
- destChainID = uint32(req.DestChainID)
+ destChainIDs = append(destChainIDs, uint32(req.DestChainID))
loggedRequest = &req
}
default:
@@ -216,11 +230,21 @@ func (r *QuoterAPIServer) AuthMiddleware() gin.HandlerFunc {
}
// Authenticate and fetch the address from the request
- addressRecovered, err := r.checkRole(c, destChainID)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()})
- c.Abort()
- return
+ var addressRecovered *common.Address
+ for _, destChainID := range destChainIDs {
+ addr, err := r.checkRole(c, destChainID)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()})
+ c.Abort()
+ return
+ }
+ if addressRecovered == nil {
+ addressRecovered = &addr
+ } else if *addressRecovered != addr {
+ c.JSON(http.StatusBadRequest, gin.H{"msg": "relayer address mismatch"})
+ c.Abort()
+ return
+ }
}
// Log and pass to the next middleware if authentication succeeds
diff --git a/services/rfq/relayer/quoter/quoter.go b/services/rfq/relayer/quoter/quoter.go
index 6e182426dd..b607e2c2c6 100644
--- a/services/rfq/relayer/quoter/quoter.go
+++ b/services/rfq/relayer/quoter/quoter.go
@@ -270,21 +270,31 @@ func (m *Manager) prepareAndSubmitQuotes(ctx context.Context, inv map[int]map[co
span.SetAttributes(attribute.Int("num_quotes", len(allQuotes)))
// Now, submit all the generated quotes
- for _, quote := range allQuotes {
- if err := m.submitQuote(ctx, quote); err != nil {
- span.AddEvent("error submitting quote; setting relayPaused to true", trace.WithAttributes(
- attribute.String("error", err.Error()),
- attribute.Int(metrics.Origin, quote.OriginChainID),
- attribute.Int(metrics.Destination, quote.DestChainID),
- attribute.String("origin_token_addr", quote.OriginTokenAddr),
- attribute.String("dest_token_addr", quote.DestTokenAddr),
- attribute.String("max_origin_amount", quote.MaxOriginAmount),
- attribute.String("dest_amount", quote.DestAmount),
- ))
+ if m.config.SubmitSingleQuotes {
+ for _, quote := range allQuotes {
+ if err := m.submitQuote(ctx, quote); err != nil {
+ span.AddEvent("error submitting quote; setting relayPaused to true", trace.WithAttributes(
+ attribute.String("error", err.Error()),
+ attribute.Int(metrics.Origin, quote.OriginChainID),
+ attribute.Int(metrics.Destination, quote.DestChainID),
+ attribute.String("origin_token_addr", quote.OriginTokenAddr),
+ attribute.String("dest_token_addr", quote.DestTokenAddr),
+ attribute.String("max_origin_amount", quote.MaxOriginAmount),
+ attribute.String("dest_amount", quote.DestAmount),
+ ))
+ m.relayPaused.Store(true)
+
+ // Suppress error so that we can continue submitting quotes
+ return nil
+ }
+ }
+ } else {
+ err = m.submitBulkQuotes(ctx, allQuotes)
+ if err != nil {
+ span.AddEvent("error submitting bulk quotes; setting relayPaused to true", trace.WithAttributes(
+ attribute.String("error", err.Error())))
m.relayPaused.Store(true)
-
- // Suppress error so that we can continue submitting quotes
- return nil
+ return fmt.Errorf("error submitting bulk quotes: %w", err)
}
}
@@ -600,3 +610,18 @@ func (m *Manager) submitQuote(ctx context.Context, quote model.PutQuoteRequest)
}
return nil
}
+
+// Submits multiple quotes.
+func (m *Manager) submitBulkQuotes(ctx context.Context, quotes []model.PutQuoteRequest) error {
+ quoteCtx, quoteCancel := context.WithTimeout(ctx, m.config.GetQuoteSubmissionTimeout())
+ defer quoteCancel()
+
+ req := model.PutBulkQuotesRequest{
+ Quotes: quotes,
+ }
+ err := m.rfqClient.PutBulkQuotes(quoteCtx, &req)
+ if err != nil {
+ return fmt.Errorf("error submitting bulk quotes: %w", err)
+ }
+ return nil
+}
diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go
index d0cdce49c6..344f2c7b07 100644
--- a/services/rfq/relayer/relconfig/config.go
+++ b/services/rfq/relayer/relconfig/config.go
@@ -55,6 +55,8 @@ type Config struct {
EnableAPIWithdrawals bool `yaml:"enable_api_withdrawals"`
// WithdrawalWhitelist is a list of addresses that are allowed to withdraw.
WithdrawalWhitelist []string `yaml:"withdrawal_whitelist"`
+ // SubmitSingleQuotes enables submitting single quotes.
+ SubmitSingleQuotes bool `yaml:"submit_single_quotes"`
}
// ChainConfig represents the configuration for a chain.