Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rfq): active quoting API #3128

Draft
wants to merge 43 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
bb18412
WIP: initial websocket wiring
dwasse Sep 13, 2024
31e52d8
WIP: add ws client and handling
dwasse Sep 13, 2024
e8ab231
Fix: receive respsects context
dwasse Sep 13, 2024
782cffd
Cleanup: split into rfq.go
dwasse Sep 13, 2024
6344a37
Fix: build
dwasse Sep 13, 2024
8aa16cb
Feat: add mocked ws client
dwasse Sep 13, 2024
4764926
Fix: build
dwasse Sep 16, 2024
afb2f19
Feat: add SubscribeActiveQuotes() to client
dwasse Sep 16, 2024
e30cd63
Feat: add PutUserQuoteRequest() to api client
dwasse Sep 16, 2024
3c10c02
Fix: build
dwasse Sep 16, 2024
bdae4ca
WIP: rfq tests with ws auth
dwasse Sep 16, 2024
138297d
WIP: test fixes
dwasse Sep 17, 2024
fc1ea97
Feat: working TestHandleActiveRFQ
dwasse Sep 17, 2024
6ae7a71
Feat: add expired request case
dwasse Sep 17, 2024
7cdcade
WIP: functionalize test relayer resps
dwasse Sep 17, 2024
01d83dc
Feat: add runMockRelayer with multiple relayers
dwasse Sep 17, 2024
ee408a9
Feat: add MultipleRelayers case
dwasse Sep 17, 2024
94a8f4d
Feat: add FallbackToPassive case
dwasse Sep 17, 2024
c39d62c
Fix: bigint ptr issue
dwasse Sep 17, 2024
6beb23a
Cleanup: bump expiration window
dwasse Sep 17, 2024
fdf9d12
WIP: logs
dwasse Sep 17, 2024
e23175f
Feat: split into separate tests
dwasse Sep 17, 2024
4b99340
Cleanup: logs
dwasse Sep 17, 2024
c557a28
Feat: add PassiveBestQuote case
dwasse Sep 17, 2024
888ce50
WIP: update db interface with new models
dwasse Sep 17, 2024
3293166
Feat: impl new db funcs
dwasse Sep 17, 2024
63f1a1e
Feat: insert models within api server
dwasse Sep 17, 2024
594d6ea
Feat: update quote request / response statuses
dwasse Sep 17, 2024
7e7c5a1
Fix: db error handling
dwasse Sep 17, 2024
7dcdf59
Fix: api tests
dwasse Sep 17, 2024
8cae8e4
Feat: add initial response validation
dwasse Sep 17, 2024
94ee250
Feat: impl pingpong
dwasse Sep 18, 2024
46d04e2
Fix: register models
dwasse Sep 18, 2024
36701ba
Feat: verify quote request in SingleRelayer case
dwasse Sep 18, 2024
2616b54
Feat: verify more db requests
dwasse Sep 18, 2024
fe7a774
Cleanup: common vars
dwasse Sep 18, 2024
60db841
Cleanup: break down handleActiveRFQ into sub funcs
dwasse Sep 18, 2024
83b7f6d
Cleanup: comments
dwasse Sep 18, 2024
32065ee
Cleanup: remove unused mock
dwasse Sep 18, 2024
c5e9a00
Fix: builds
dwasse Sep 18, 2024
e7d08e7
Feat: make relayer response data optional to signify null resp
dwasse Sep 19, 2024
8e405e5
Fix: response primary key on quote id
dwasse Sep 19, 2024
c6db31f
Fix: build
dwasse Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 142 additions & 20 deletions services/rfq/api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ package client

import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"

"github.com/ipfs/go-log"

"github.com/google/uuid"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

Expand All @@ -17,17 +20,21 @@ import (

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/go-resty/resty/v2"
"github.com/gorilla/websocket"
"github.com/synapsecns/sanguine/ethergo/signer/signer"
"github.com/synapsecns/sanguine/services/rfq/api/model"
"github.com/synapsecns/sanguine/services/rfq/api/rest"
)

var logger = log.Logger("rfq-client")

// AuthenticatedClient is an interface for the RFQ API.
// It provides methods for creating, retrieving and updating quotes.
type AuthenticatedClient interface {
PutQuote(ctx context.Context, q *model.PutQuoteRequest) error
PutQuote(ctx context.Context, q *model.PutRelayerQuoteRequest) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

Update PutQuote usage in quoter.go and verify client_test.go

Based on the verification process, we've identified the following issues that need attention:

  • In services/rfq/relayer/quoter/quoter.go, the submitQuote function is still using the old model.PutQuoteRequest type. This needs to be updated to model.PutRelayerQuoteRequest.

  • In services/rfq/api/client/client_test.go, while the correct model package is imported, it's unclear if the tests are using the new PutRelayerQuoteRequest type. The structure of the req variable in the tests suggests it might still be using the old type.

To resolve these issues:

  1. Update quoter.go to use model.PutRelayerQuoteRequest instead of model.PutQuoteRequest.
  2. Review and update the test cases in client_test.go to ensure they're using the new PutRelayerQuoteRequest type.

Please make these changes and thoroughly test to ensure compatibility with the updated PutQuote method signature.

Analysis chain

Ensure all implementations and usages of PutQuote are updated

The PutQuote method in the AuthenticatedClient interface has changed its parameter from *model.PutQuoteRequest to *model.PutRelayerQuoteRequest. Please verify that all implementations of this interface and all calls to this method throughout the codebase have been updated accordingly to prevent any potential runtime errors or incompatibilities.

Run the following script to identify usages of PutQuote that may need to be updated:


To further verify the correct usage of PutQuote, we need to examine the content of the files where it's being called. Let's generate scripts to check the relevant parts of client_test.go and quoter.go.


To complete our verification, we need to check the import statements and type declarations in client_test.go. Let's run one more script to gather this information.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all Go files that call `PutQuote` to check for compatibility with the new parameter type.

rg --type go "PutQuote\("

Length of output: 615


Script:

#!/bin/bash
# Check the content of client_test.go around PutQuote calls
echo "Content of client_test.go:"
rg -C 5 "PutQuote\(" services/rfq/api/client/client_test.go

echo "\nContent of quoter.go:"
rg -C 5 "PutQuote\(" services/rfq/relayer/quoter/quoter.go

Length of output: 1485


Script:

#!/bin/bash
# Check import statements and type declarations in client_test.go
echo "Imports and type declarations in client_test.go:"
rg -C 5 "import|type.*struct" services/rfq/api/client/client_test.go

Length of output: 342

PutBulkQuotes(ctx context.Context, q *model.PutBulkQuotesRequest) error
PutRelayAck(ctx context.Context, req *model.PutAckRequest) (*model.PutRelayAckResponse, error)
SubscribeActiveQuotes(ctx context.Context, req *model.SubscribeActiveRFQRequest, reqChan chan *model.ActiveRFQMessage) (respChan chan *model.ActiveRFQMessage, err error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implement SubscribeActiveQuotes in all AuthenticatedClient implementations

A new method SubscribeActiveQuotes has been added to the AuthenticatedClient interface. Ensure that all types implementing this interface also implement the new method to satisfy interface requirements and prevent compile-time errors.

UnauthenticatedClient
}

Expand All @@ -37,6 +44,7 @@ type UnauthenticatedClient interface {
GetSpecificQuote(ctx context.Context, q *model.GetQuoteSpecificRequest) ([]*model.GetQuoteResponse, error)
GetQuoteByRelayerAddress(ctx context.Context, relayerAddr string) ([]*model.GetQuoteResponse, error)
GetRFQContracts(ctx context.Context) (*model.GetContractsResponse, error)
PutUserQuoteRequest(ctx context.Context, q *model.PutUserQuoteRequest) (*model.PutUserQuoteResponse, error)
resty() *resty.Client
}

Expand All @@ -50,12 +58,14 @@ func (c unauthenticatedClient) resty() *resty.Client {

type clientImpl struct {
UnauthenticatedClient
rClient *resty.Client
rClient *resty.Client
wsURL *string
reqSigner signer.Signer
}

// NewAuthenticatedClient creates a new client for the RFQ quoting API.
// TODO: @aurelius, you don't actually need to be authed for GET Requests.
func NewAuthenticatedClient(metrics metrics.Handler, rfqURL string, reqSigner signer.Signer) (AuthenticatedClient, error) {
func NewAuthenticatedClient(metrics metrics.Handler, rfqURL string, wsURL *string, reqSigner signer.Signer) (AuthenticatedClient, error) {
unauthedClient, err := NewUnauthenticatedClient(metrics, rfqURL)
if err != nil {
return nil, fmt.Errorf("could not create unauthenticated client: %w", err)
Expand All @@ -65,33 +75,41 @@ func NewAuthenticatedClient(metrics metrics.Handler, rfqURL string, reqSigner si
// to a new variable for clarity.
authedClient := unauthedClient.resty().
OnBeforeRequest(func(client *resty.Client, request *resty.Request) error {
// if request.Method == "PUT" && request.URL == rfqURL+rest.QUOTE_ROUTE {
// i.e. signature (hex encoded) = keccak(bytes.concat("\x19Ethereum Signed Message:\n", len(strconv.Itoa(time.Now().Unix()), strconv.Itoa(time.Now().Unix())))
// so that full auth header string: auth = strconv.Itoa(time.Now().Unix()) + ":" + signature
// Get the current Unix timestamp as a string.
now := strconv.Itoa(int(time.Now().Unix()))

// Prepare the data to be signed.
data := "\x19Ethereum Signed Message:\n" + strconv.Itoa(len(now)) + now

sig, err := reqSigner.SignMessage(request.Context(), []byte(data), true)

authHeader, err := getAuthHeader(request.Context(), reqSigner)
if err != nil {
return fmt.Errorf("failed to sign request: %w", err)
return fmt.Errorf("failed to get auth header: %w", err)
}

res := fmt.Sprintf("%s:%s", now, hexutil.Encode(signer.Encode(sig)))
request.SetHeader("Authorization", res)

request.SetHeader(rest.AuthorizationHeader, authHeader)
return nil
})

return &clientImpl{
UnauthenticatedClient: unauthedClient,
rClient: authedClient,
wsURL: wsURL,
reqSigner: reqSigner,
}, nil
}

func getAuthHeader(ctx context.Context, reqSigner signer.Signer) (string, error) {
// if request.Method == "PUT" && request.URL == rfqURL+rest.QUOTE_ROUTE {
// i.e. signature (hex encoded) = keccak(bytes.concat("\x19Ethereum Signed Message:\n", len(strconv.Itoa(time.Now().Unix()), strconv.Itoa(time.Now().Unix())))
// so that full auth header string: auth = strconv.Itoa(time.Now().Unix()) + ":" + signature
// Get the current Unix timestamp as a string.
now := strconv.Itoa(int(time.Now().Unix()))

// Prepare the data to be signed.
data := "\x19Ethereum Signed Message:\n" + strconv.Itoa(len(now)) + now

sig, err := reqSigner.SignMessage(ctx, []byte(data), true)

if err != nil {
return "", fmt.Errorf("failed to sign request: %w", err)
}

return fmt.Sprintf("%s:%s", now, hexutil.Encode(signer.Encode(sig))), nil
}

// NewUnauthenticatedClient creates a new client for the RFQ quoting API.
func NewUnauthenticatedClient(metricHandler metrics.Handler, rfqURL string) (UnauthenticatedClient, error) {
client := resty.New().
Expand All @@ -115,7 +133,7 @@ func NewUnauthenticatedClient(metricHandler metrics.Handler, rfqURL string) (Una
}

// PutQuote puts a new quote in the RFQ quoting API.
func (c *clientImpl) PutQuote(ctx context.Context, q *model.PutQuoteRequest) error {
func (c *clientImpl) PutQuote(ctx context.Context, q *model.PutRelayerQuoteRequest) error {
res, err := c.rClient.R().
SetContext(ctx).
SetBody(q).
Expand Down Expand Up @@ -159,6 +177,91 @@ func (c *clientImpl) PutRelayAck(ctx context.Context, req *model.PutAckRequest)
return ack, nil
}

func (c *clientImpl) SubscribeActiveQuotes(ctx context.Context, req *model.SubscribeActiveRFQRequest, reqChan chan *model.ActiveRFQMessage) (respChan chan *model.ActiveRFQMessage, err error) {
if c.wsURL == nil {
return nil, fmt.Errorf("websocket URL is not set")
}
if len(req.ChainIDs) == 0 {
return nil, fmt.Errorf("chain IDs are required")
}

reqURL := *c.wsURL + rest.QuoteRequestsRoute

header := http.Header{}
chainIDsJSON, err := json.Marshal(req.ChainIDs)
if err != nil {
return nil, fmt.Errorf("failed to marshal chain IDs: %w", err)
}
header.Set(rest.ChainsHeader, string(chainIDsJSON))
authHeader, err := getAuthHeader(ctx, c.reqSigner)
if err != nil {
return nil, fmt.Errorf("failed to get auth header: %w", err)
}
header.Set(rest.AuthorizationHeader, authHeader)

// Use the header when dialing
conn, _, err := websocket.DefaultDialer.Dial(reqURL, header)
if err != nil {
return nil, fmt.Errorf("failed to connect to websocket: %w", err)
}

respChan = make(chan *model.ActiveRFQMessage, 1000)

go func() {
defer close(respChan)
defer conn.Close()

readChan := make(chan []byte, 1000)
go func() {
defer close(readChan)
for {
_, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
logger.Warnf("websocket connection closed unexpectedly: %v", err)
}
return
}
readChan <- message
}
}()

for {
select {
case <-ctx.Done():
return
case msg, ok := <-reqChan:
if !ok {
return
}
err := conn.WriteJSON(msg)
if err != nil {
logger.Warnf("error sending message to websocket: %v", err)
return
}
case msg, ok := <-readChan:
if !ok {
return
}
var rfqMsg model.ActiveRFQMessage
err = json.Unmarshal(msg, &rfqMsg)
if err != nil {
logger.Warn("error unmarshalling message: %v", err)
continue
}

select {
case respChan <- &rfqMsg:
case <-ctx.Done():
return
}
}
}
}()

return respChan, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prevent potential goroutine leaks in SubscribeActiveQuotes

In the SubscribeActiveQuotes function, there are goroutines that may not properly exit if an error occurs or if the context is canceled, leading to resource leaks.

Consider the following improvements:

  • Use defer statements to ensure that all resources, such as the WebSocket connection and channels, are properly closed.
  • Monitor the context within goroutines and exit when the context is done.
  • Handle errors in a way that guarantees goroutines will terminate correctly.

Apply this diff to enhance resource cleanup:

 func (c *clientImpl) SubscribeActiveQuotes(ctx context.Context, req *model.SubscribeActiveRFQRequest, reqChan chan *model.ActiveRFQMessage) (respChan chan *model.ActiveRFQMessage, err error) {
     // Existing code...
 
     go func() {
+        defer conn.Close()
+        defer close(respChan)
         // Existing code...
 
         go func() {
+            defer close(readChan)
             // Existing code...
             for {
                 // Existing code...
                 if err != nil {
                     // Handle error...
+                    return
                 }
                 // Existing code...
             }
         }()
 
         for {
             select {
             case <-ctx.Done():
+                return
             // Existing code...
             }
         }
     }()
 
     return respChan, nil
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (c *clientImpl) SubscribeActiveQuotes(ctx context.Context, req *model.SubscribeActiveRFQRequest, reqChan chan *model.ActiveRFQMessage) (respChan chan *model.ActiveRFQMessage, err error) {
if c.wsURL == nil {
return nil, fmt.Errorf("websocket URL is not set")
}
if len(req.ChainIDs) == 0 {
return nil, fmt.Errorf("chain IDs are required")
}
reqURL := *c.wsURL + rest.QuoteRequestsRoute
header := http.Header{}
chainIDsJSON, err := json.Marshal(req.ChainIDs)
if err != nil {
return nil, fmt.Errorf("failed to marshal chain IDs: %w", err)
}
header.Set(rest.ChainsHeader, string(chainIDsJSON))
authHeader, err := getAuthHeader(ctx, c.reqSigner)
if err != nil {
return nil, fmt.Errorf("failed to get auth header: %w", err)
}
header.Set(rest.AuthorizationHeader, authHeader)
// Use the header when dialing
conn, _, err := websocket.DefaultDialer.Dial(reqURL, header)
if err != nil {
return nil, fmt.Errorf("failed to connect to websocket: %w", err)
}
respChan = make(chan *model.ActiveRFQMessage, 1000)
go func() {
defer close(respChan)
defer conn.Close()
readChan := make(chan []byte, 1000)
go func() {
defer close(readChan)
for {
_, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
logger.Warnf("websocket connection closed unexpectedly: %v", err)
}
return
}
readChan <- message
}
}()
for {
select {
case <-ctx.Done():
return
case msg, ok := <-reqChan:
if !ok {
return
}
err := conn.WriteJSON(msg)
if err != nil {
logger.Warnf("error sending message to websocket: %v", err)
return
}
case msg, ok := <-readChan:
if !ok {
return
}
var rfqMsg model.ActiveRFQMessage
err = json.Unmarshal(msg, &rfqMsg)
if err != nil {
logger.Warn("error unmarshalling message: %v", err)
continue
}
select {
case respChan <- &rfqMsg:
case <-ctx.Done():
return
}
}
}
}()
return respChan, nil
}
func (c *clientImpl) SubscribeActiveQuotes(ctx context.Context, req *model.SubscribeActiveRFQRequest, reqChan chan *model.ActiveRFQMessage) (respChan chan *model.ActiveRFQMessage, err error) {
if c.wsURL == nil {
return nil, fmt.Errorf("websocket URL is not set")
}
if len(req.ChainIDs) == 0 {
return nil, fmt.Errorf("chain IDs are required")
}
reqURL := *c.wsURL + rest.QuoteRequestsRoute
header := http.Header{}
chainIDsJSON, err := json.Marshal(req.ChainIDs)
if err != nil {
return nil, fmt.Errorf("failed to marshal chain IDs: %w", err)
}
header.Set(rest.ChainsHeader, string(chainIDsJSON))
authHeader, err := getAuthHeader(ctx, c.reqSigner)
if err != nil {
return nil, fmt.Errorf("failed to get auth header: %w", err)
}
header.Set(rest.AuthorizationHeader, authHeader)
// Use the header when dialing
conn, _, err := websocket.DefaultDialer.Dial(reqURL, header)
if err != nil {
return nil, fmt.Errorf("failed to connect to websocket: %w", err)
}
respChan = make(chan *model.ActiveRFQMessage, 1000)
go func() {
defer conn.Close()
defer close(respChan)
readChan := make(chan []byte, 1000)
go func() {
defer close(readChan)
for {
_, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
logger.Warnf("websocket connection closed unexpectedly: %v", err)
}
return
}
readChan <- message
}
}()
for {
select {
case <-ctx.Done():
return
case msg, ok := <-reqChan:
if !ok {
return
}
err := conn.WriteJSON(msg)
if err != nil {
logger.Warnf("error sending message to websocket: %v", err)
return
}
case msg, ok := <-readChan:
if !ok {
return
}
var rfqMsg model.ActiveRFQMessage
err = json.Unmarshal(msg, &rfqMsg)
if err != nil {
logger.Warn("error unmarshalling message: %v", err)
continue
}
select {
case respChan <- &rfqMsg:
case <-ctx.Done():
return
}
}
}
}()
return respChan, nil
}


// GetAllQuotes retrieves all quotes from the RFQ quoting API.
func (c *unauthenticatedClient) GetAllQuotes(ctx context.Context) ([]*model.GetQuoteResponse, error) {
var quotes []*model.GetQuoteResponse
Expand Down Expand Up @@ -242,6 +345,25 @@ func (c unauthenticatedClient) GetRFQContracts(ctx context.Context) (*model.GetC
return contracts, nil
}

func (c unauthenticatedClient) PutUserQuoteRequest(ctx context.Context, q *model.PutUserQuoteRequest) (*model.PutUserQuoteResponse, error) {
var response model.PutUserQuoteResponse
resp, err := c.rClient.R().
SetContext(ctx).
SetBody(q).
SetResult(&response).
Put(rest.PutQuoteRequestRoute)

if err != nil {
return nil, fmt.Errorf("error from server: %s: %w", getStatus(resp), err)
}

if resp.IsError() {
return nil, fmt.Errorf("error from server: %s", getStatus(resp))
}

return &response, nil
}

func getStatus(resp *resty.Response) string {
if resp == nil {
return "http status unavailable"
Expand Down
8 changes: 4 additions & 4 deletions services/rfq/api/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
// TODO: @aurelius tese tests make a lot less sesnes w/ a composite index

func (c *ClientSuite) TestPutAndGetQuote() {
req := model.PutQuoteRequest{
req := model.PutRelayerQuoteRequest{
OriginChainID: 1,
OriginTokenAddr: "0xOriginTokenAddr",
DestChainID: 42161,
Expand Down Expand Up @@ -40,7 +40,7 @@ func (c *ClientSuite) TestPutAndGetQuote() {

func (c *ClientSuite) TestPutAndGetBulkQuotes() {
req := model.PutBulkQuotesRequest{
Quotes: []model.PutQuoteRequest{
Quotes: []model.PutRelayerQuoteRequest{
{
OriginChainID: 1,
OriginTokenAddr: "0xOriginTokenAddr",
Expand Down Expand Up @@ -98,7 +98,7 @@ func (c *ClientSuite) TestPutAndGetBulkQuotes() {
}

func (c *ClientSuite) TestGetSpecificQuote() {
req := model.PutQuoteRequest{
req := model.PutRelayerQuoteRequest{
OriginChainID: 1,
OriginTokenAddr: "0xOriginTokenAddr",
DestChainID: 42161,
Expand Down Expand Up @@ -135,7 +135,7 @@ func (c *ClientSuite) TestGetSpecificQuote() {
}

func (c *ClientSuite) TestGetQuoteByRelayerAddress() {
req := model.PutQuoteRequest{
req := model.PutRelayerQuoteRequest{
OriginChainID: 1,
OriginTokenAddr: "0xOriginTokenAddr",
DestChainID: 42161,
Expand Down
2 changes: 1 addition & 1 deletion services/rfq/api/client/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (c *ClientSuite) SetupTest() {
}()
time.Sleep(2 * time.Second) // Wait for the server to start.

c.client, err = client.NewAuthenticatedClient(metrics.Get(), fmt.Sprintf("http://127.0.0.1:%d", port), localsigner.NewSigner(c.testWallet.PrivateKey()))
c.client, err = client.NewAuthenticatedClient(metrics.Get(), fmt.Sprintf("http://127.0.0.1:%d", port), nil, localsigner.NewSigner(c.testWallet.PrivateKey()))
c.Require().NoError(err)
}

Expand Down
1 change: 1 addition & 0 deletions services/rfq/api/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Config struct {
Port string `yaml:"port"`
RelayAckTimeout time.Duration `yaml:"relay_ack_timeout"`
MaxQuoteAge time.Duration `yaml:"max_quote_age"`
WebsocketPort *string `yaml:"websocket_port"`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using string instead of *string for WebSocketPort

Unless there's a specific need to distinguish between an unset value (nil) and an empty string, it might be simpler to declare WebSocketPort as a string instead of a *string. This would be consistent with the Port field and simplify field access.

Apply this diff:

-WebSocketPort   *string           `yaml:"websocket_port"`
+WebSocketPort   string            `yaml:"websocket_port"`

Committable suggestion was skipped due to low confidence.


Comment on lines 30 to 31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a GetWebSocketPort method with a default value

To provide a default WebSocket port when none is specified in the configuration, consider adding a GetWebSocketPort method, similar to GetRelayAckTimeout and GetMaxQuoteAge.

Add the following code:

const defaultWebSocketPort = ":8080"

// GetWebSocketPort returns the WebSocket port, defaulting to ":8080" if not set
func (c Config) GetWebSocketPort() string {
    if c.WebSocketPort == "" {
        return defaultWebSocketPort
    }
    return c.WebSocketPort
}

This ensures your application has a default port for WebSocket connections, improving robustness.

const defaultRelayAckTimeout = 30 * time.Second
Expand Down
27 changes: 27 additions & 0 deletions services/rfq/api/db/activequoterequeststatus_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions services/rfq/api/db/activequoteresponsestatus_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading