{
+ return new Promise((resolve) => setTimeout(resolve, ms))
+}
+
+export function tStamp(startTimeStamp = 0) {
+ const timeCur = new Date().toISOString().replace('T', ' ').replace('Z', '')
+
+ const timeDiff =
+ startTimeStamp > 0
+ ? ` +${(Date.now() - startTimeStamp).toString().padStart(5)}ms`
+ : ''
+
+ return `${timeCur}${timeDiff} - `
+}
+
+export function print(...outputs: any[]) {
+ outputs = outputs.map((output: any) => {
+ if (typeof output == 'string') {
+ // Replace %ts with formatted timestamp
+ output = output.replaceAll('%ts', tStamp())
+ }
+ return output
+ })
+
+ console.log(...outputs)
+}
+
+export function getRandomInt(min: number, max: number) {
+ if (min > max) {
+ // fix mistake inputs
+ ;[min, max] = [max, min]
+ }
+ return Math.floor(Math.random() * (max - min + 1)) + min
+}
diff --git a/packages/rfq-loadtest/tsconfig.json b/packages/rfq-loadtest/tsconfig.json
new file mode 100644
index 0000000000..bf8c0320ea
--- /dev/null
+++ b/packages/rfq-loadtest/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "outDir": "./dist",
+ "rootDir": "./src"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
\ No newline at end of file
diff --git a/packages/sdk-router/CHANGELOG.md b/packages/sdk-router/CHANGELOG.md
index d8b4f3c109..a75b014286 100644
--- a/packages/sdk-router/CHANGELOG.md
+++ b/packages/sdk-router/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.11.4](https://github.com/synapsecns/sanguine/compare/@synapsecns/sdk-router@0.11.3...@synapsecns/sdk-router@0.11.4) (2024-10-11)
+
+**Note:** Version bump only for package @synapsecns/sdk-router
+
+
+
+
+
## [0.11.3](https://github.com/synapsecns/sanguine/compare/@synapsecns/sdk-router@0.11.2...@synapsecns/sdk-router@0.11.3) (2024-10-03)
diff --git a/packages/sdk-router/package.json b/packages/sdk-router/package.json
index a5e8afaf28..520390b750 100644
--- a/packages/sdk-router/package.json
+++ b/packages/sdk-router/package.json
@@ -1,7 +1,7 @@
{
"name": "@synapsecns/sdk-router",
"description": "An SDK for interacting with the Synapse Protocol",
- "version": "0.11.3",
+ "version": "0.11.4",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
diff --git a/packages/synapse-constants/CHANGELOG.md b/packages/synapse-constants/CHANGELOG.md
index 468bb18b63..67ce8de75f 100644
--- a/packages/synapse-constants/CHANGELOG.md
+++ b/packages/synapse-constants/CHANGELOG.md
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.7.0](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-constants@1.6.1...@synapsecns/synapse-constants@1.7.0) (2024-10-10)
+
+
+### Features
+
+* **synapse-constants:** adds preinstall step ([#3269](https://github.com/synapsecns/sanguine/issues/3269)) ([acd61de](https://github.com/synapsecns/sanguine/commit/acd61de4846d9b23d7aa834b8f2eefcaae486c7d))
+
+
+
+
+
+## [1.6.1](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-constants@1.6.0...@synapsecns/synapse-constants@1.6.1) (2024-10-10)
+
+**Note:** Version bump only for package @synapsecns/synapse-constants
+
+
+
+
+
# 1.6.0 (2024-10-05)
diff --git a/packages/synapse-constants/README.md b/packages/synapse-constants/README.md
index 94f44f48c7..f0e21d4947 100644
--- a/packages/synapse-constants/README.md
+++ b/packages/synapse-constants/README.md
@@ -1,53 +1,41 @@
# Synapse Constants
+
[![npm](https://img.shields.io/npm/v/synapse-constants?style=flat-square)](https://www.npmjs.com/package/synapse-constants)
This package contains the Synapse Protocol Token and Chain Constants
-
-#
-
-
-
## Installation
-```bash
-npm install synapse-constants
-```
-
With Yarn:
```bash
-yarn add synapse-constants
+yarn add @synapsecns/synapse-constants
```
-## Usage
-
-
-To restrict the assets and chains that are imported, you can create a "custom bridge list". From the set of all tokens imported from "bridgeable.ts" you can import specific tokens and use that as the custom list you use in your application. The same can be done for chains
+## Build
-## Usage
-For maintenance, when new tokens are added to the bridge the following steps should be taken.
+The following command will build the package locally
-1. Regenerate bridgeMaps.ts
-
-```bash
-yarn maps:generate
+```
+yarn build
```
-2. Update Bridgeable.ts with the new token addresses (check all other variables like decimals/ symbols etc. )
+## Usage
-3. Repackage and webpack all of the data
+Importing supported tokens and chains:
-```bash
-yarn compile
+```js
+import { BRIDGABLE_TOKENS, CHAINS } from '@synapsecns/synapse-constants'
```
-4. Republish the npm package (make sure to update the version)
+Importing a specific token:
-```bash
-npm publish
+```js
+import { USDC } from '@synapsecns/synapse-constants'
```
+## TODO
-TODO:
-- add the basic structure of the token type and the chain type to show accessibility for token logos, chain logos, and any additional information.
+- [ ] Instructions on adding new chains
+- [ ] Instructions on adding new tokens
+- [ ] Instructions on generating new token route map
diff --git a/packages/synapse-constants/package.json b/packages/synapse-constants/package.json
index 37f3244327..42c99d8383 100644
--- a/packages/synapse-constants/package.json
+++ b/packages/synapse-constants/package.json
@@ -1,6 +1,6 @@
{
"name": "@synapsecns/synapse-constants",
- "version": "1.6.0",
+ "version": "1.7.0",
"description": "This is an npm package that maintains all synapse constants",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
@@ -27,6 +27,7 @@
"lint:check": "eslint . --max-warnings=0 --config .eslintrc.cjs",
"prepare": "rollup -c --bundleConfigAsCjs",
"build": "rollup -c --bundleConfigAsCjs",
+ "preinstall": "command -v rollup >/dev/null 2>&1 && rollup -c --buildConfigAsCjs || echo 'rollup not found'",
"prepublish": "yarn build",
"maps:generate": "node ./src/scripts/generateMaps.cjs && node ./src/scripts/findMissing.cjs && yarn build"
},
diff --git a/packages/synapse-interface/CHANGELOG.md b/packages/synapse-interface/CHANGELOG.md
index bee3252c9c..735878108a 100644
--- a/packages/synapse-interface/CHANGELOG.md
+++ b/packages/synapse-interface/CHANGELOG.md
@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.40.10](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.40.9...@synapsecns/synapse-interface@0.40.10) (2024-10-15)
+
+
+### Bug Fixes
+
+* **synapse-interface:** translation period ([#3297](https://github.com/synapsecns/sanguine/issues/3297)) ([fd75dbb](https://github.com/synapsecns/sanguine/commit/fd75dbbbb6fec8ad2c35d5a16efb3a6a4812231a))
+
+
+
+
+
+## [0.40.9](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.40.8...@synapsecns/synapse-interface@0.40.9) (2024-10-15)
+
+**Note:** Version bump only for package @synapsecns/synapse-interface
+
+
+
+
+
+## [0.40.8](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.40.7...@synapsecns/synapse-interface@0.40.8) (2024-10-11)
+
+**Note:** Version bump only for package @synapsecns/synapse-interface
+
+
+
+
+
+## [0.40.7](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.40.6...@synapsecns/synapse-interface@0.40.7) (2024-10-09)
+
+**Note:** Version bump only for package @synapsecns/synapse-interface
+
+
+
+
+
## [0.40.6](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.40.5...@synapsecns/synapse-interface@0.40.6) (2024-10-05)
**Note:** Version bump only for package @synapsecns/synapse-interface
diff --git a/packages/synapse-interface/components/Activity/Activity.tsx b/packages/synapse-interface/components/Activity/Activity.tsx
index 5fe5012493..96a262e1d9 100644
--- a/packages/synapse-interface/components/Activity/Activity.tsx
+++ b/packages/synapse-interface/components/Activity/Activity.tsx
@@ -71,7 +71,7 @@ export const Activity = ({ visibility }: { visibility: boolean }) => {
{viewingAddress && !isLoading && !hasHistoricalTransactions && (
- {t('No transactions in last 30 days.')}
+ {t('No transactions in last 30 days')}
)}
diff --git a/packages/synapse-interface/components/layouts/StandardPageContainer.tsx b/packages/synapse-interface/components/layouts/StandardPageContainer.tsx
index 8c408194f5..e1c5f16868 100644
--- a/packages/synapse-interface/components/layouts/StandardPageContainer.tsx
+++ b/packages/synapse-interface/components/layouts/StandardPageContainer.tsx
@@ -31,7 +31,7 @@ const StandardPageContainer = ({
if (unsupported) {
unsupportedToaster = toast.error(
- t('Connected to an unsupported network; Please switch networks.'),
+ t('Connected to an unsupported network; Please switch networks'),
{ id: 'unsupported-popup', duration: 5000 }
)
} else {
diff --git a/packages/synapse-interface/package.json b/packages/synapse-interface/package.json
index bda524336a..152e129c94 100644
--- a/packages/synapse-interface/package.json
+++ b/packages/synapse-interface/package.json
@@ -1,6 +1,6 @@
{
"name": "@synapsecns/synapse-interface",
- "version": "0.40.6",
+ "version": "0.40.10",
"private": true,
"engines": {
"node": ">=18.18.0"
@@ -38,7 +38,7 @@
"@reduxjs/toolkit": "^1.9.5",
"@rtk-query/graphql-request-base-query": "^2.2.0",
"@segment/analytics-next": "^1.53.0",
- "@synapsecns/sdk-router": "^0.11.3",
+ "@synapsecns/sdk-router": "^0.11.4",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
diff --git a/packages/synapse-interface/public/blacklist.json b/packages/synapse-interface/public/blacklist.json
index d4efb6cf64..21ddc573ba 100644
--- a/packages/synapse-interface/public/blacklist.json
+++ b/packages/synapse-interface/public/blacklist.json
@@ -552,5 +552,12 @@
"0x408d8e12c7ed8e5a7291fbD5E6164f41ecdA6B46",
"0x278dF4492d16321b247660799FAD1A12dE152Dd1",
"0x551BE68Cdf9Ce453ead61097649C34196d0bDb27",
- "0x12AE4569d0e2B01857eD96D98cd4C9b09f21CA8b"
+ "0x12AE4569d0e2B01857eD96D98cd4C9b09f21CA8b",
+ "0x8bFE38d7c70F8953e701149E448c03E29ECcd4b0",
+ "0x6fdb264a876c811c7e101ee7a4f4fe7704ecbb72",
+ "0xb584050909a300fa0306b29f72e63dc4615b6f53",
+ "0xA963df55B326609a0cd205e85ca92d2a3c94DaB5",
+ "0x0605eDeE6a8b8b553caE09Abe83b2ebeb75516eC",
+ "0x4c968f6beecf1906710b08e8b472b8ba6e75f957",
+ "0xff3a8d02109393726a90c04d7afd76e2d571890e"
]
diff --git a/packages/widget/CHANGELOG.md b/packages/widget/CHANGELOG.md
index 9cab7694db..f50c33cd7e 100644
--- a/packages/widget/CHANGELOG.md
+++ b/packages/widget/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.7.4](https://github.com/synapsecns/sanguine/compare/@synapsecns/widget@0.7.3...@synapsecns/widget@0.7.4) (2024-10-11)
+
+**Note:** Version bump only for package @synapsecns/widget
+
+
+
+
+
## [0.7.3](https://github.com/synapsecns/sanguine/compare/@synapsecns/widget@0.7.2...@synapsecns/widget@0.7.3) (2024-10-03)
**Note:** Version bump only for package @synapsecns/widget
diff --git a/packages/widget/package.json b/packages/widget/package.json
index a9af018a9b..8d5e6de077 100644
--- a/packages/widget/package.json
+++ b/packages/widget/package.json
@@ -1,7 +1,7 @@
{
"name": "@synapsecns/widget",
"description": "Widget library for interacting with the Synapse Protocol",
- "version": "0.7.3",
+ "version": "0.7.4",
"license": "MIT",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
@@ -68,7 +68,7 @@
"@ethersproject/providers": "^5.7.2",
"@ethersproject/units": "^5.7.0",
"@reduxjs/toolkit": "^2.0.1",
- "@synapsecns/sdk-router": "^0.11.3",
+ "@synapsecns/sdk-router": "^0.11.4",
"ethers": "^6.9.1",
"lodash": "^4.17.21",
"react-redux": "^9.0.2"
diff --git a/services/omnirpc/http/client.go b/services/omnirpc/http/client.go
index a5e1647014..c07ffaeb77 100644
--- a/services/omnirpc/http/client.go
+++ b/services/omnirpc/http/client.go
@@ -12,6 +12,7 @@ type Client interface {
}
// Request is a request builder.
+// TODO: this needs to support tracing.
type Request interface {
// SetBody sets the request body
SetBody(body []byte) Request
diff --git a/services/omnirpc/modules/README.md b/services/omnirpc/modules/README.md
index 000b17df9b..1af2170760 100644
--- a/services/omnirpc/modules/README.md
+++ b/services/omnirpc/modules/README.md
@@ -1,3 +1,6 @@
# Modules
Modules are implementations that can modify the inputs to or outputs of an rpc call. They are meant to deal w/ specific application level limitations or requirements. For example, a module could be used to add a custom header to all requests, or to modify the response of a call to a specific service. These do not neccesarily emulate the original functionality of omnirpc and are run through seperate commands.
+
+
+Mixins are meant to add metadata to make debugging easier.
diff --git a/services/omnirpc/modules/confirmedtofinalized/finalizedproxy.go b/services/omnirpc/modules/confirmedtofinalized/finalizedproxy.go
index aca1e6faaa..ce974daa3f 100644
--- a/services/omnirpc/modules/confirmedtofinalized/finalizedproxy.go
+++ b/services/omnirpc/modules/confirmedtofinalized/finalizedproxy.go
@@ -18,6 +18,7 @@ import (
"github.com/synapsecns/sanguine/ethergo/parser/rpc"
"github.com/synapsecns/sanguine/services/omnirpc/collection"
omniHTTP "github.com/synapsecns/sanguine/services/omnirpc/http"
+ "github.com/synapsecns/sanguine/services/omnirpc/modules/mixins"
"github.com/synapsecns/sanguine/services/omnirpc/swagger"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -189,10 +190,7 @@ func (r *finalizedProxyImpl) checkShouldRequest(parentCtx context.Context, req r
metrics.EndSpanWithErr(span, err)
}()
- tx := new(types.Transaction)
-
- hex := common.FromHex(string(bytes.ReplaceAll(req.Params[0], []byte{'"'}, []byte{})))
- err = tx.UnmarshalBinary(hex)
+ tx, err := mixins.ReqToTX(req)
if err != nil {
return false
}
diff --git a/services/omnirpc/modules/mixins/doc.go b/services/omnirpc/modules/mixins/doc.go
new file mode 100644
index 0000000000..57b49f7e73
--- /dev/null
+++ b/services/omnirpc/modules/mixins/doc.go
@@ -0,0 +1,2 @@
+// Package mixins provides a set of mixins for the omnirpc module.
+package mixins
diff --git a/services/omnirpc/modules/mixins/helpers.go b/services/omnirpc/modules/mixins/helpers.go
new file mode 100644
index 0000000000..1e7d3b51b7
--- /dev/null
+++ b/services/omnirpc/modules/mixins/helpers.go
@@ -0,0 +1,22 @@
+package mixins
+
+import (
+ "bytes"
+ "fmt"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/synapsecns/sanguine/ethergo/parser/rpc"
+)
+
+// ReqToTX converts a request to a transaction.
+func ReqToTX(req rpc.Request) (tx *types.Transaction, err error) {
+ tx = new(types.Transaction)
+
+ hex := common.FromHex(string(bytes.ReplaceAll(req.Params[0], []byte{'"'}, []byte{})))
+ err = tx.UnmarshalBinary(hex)
+ if err != nil {
+ return nil, fmt.Errorf("could not unmarshal transaction: %w", err)
+ }
+
+ return tx, nil
+}
diff --git a/services/omnirpc/modules/mixins/txsubmit.go b/services/omnirpc/modules/mixins/txsubmit.go
new file mode 100644
index 0000000000..0d5d84fa26
--- /dev/null
+++ b/services/omnirpc/modules/mixins/txsubmit.go
@@ -0,0 +1,34 @@
+package mixins
+
+import (
+ "context"
+ "github.com/synapsecns/sanguine/core/metrics"
+ "github.com/synapsecns/sanguine/ethergo/client"
+ "github.com/synapsecns/sanguine/ethergo/parser/rpc"
+ "github.com/synapsecns/sanguine/ethergo/util"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/trace"
+)
+
+// TxSubmitMixin is a mixin for tracking submitted transactions.
+// it can be used to index additional data in otel regarding tx submission status.
+func TxSubmitMixin(parentCtx context.Context, handler metrics.Handler, r rpc.Request) {
+ if client.RPCMethod(r.Method) != client.SendRawTransactionMethod {
+ return
+ }
+
+ ctx, span := handler.Tracer().Start(parentCtx, "txsubmit", trace.WithAttributes(attribute.Int("txsubmit", r.ID)))
+
+ var err error
+ defer func() {
+ metrics.EndSpanWithErr(span, err)
+ }()
+
+ tx, err := ReqToTX(r)
+ if err != nil {
+ handler.ExperimentalLogger().Warnf(ctx, "could not convert request to transaction: %v", err)
+ return
+ }
+
+ span.SetAttributes(util.TxToAttributes(tx)...)
+}
diff --git a/services/omnirpc/modules/receiptsbackup/receiptsbackup.go b/services/omnirpc/modules/receiptsbackup/receiptsbackup.go
index 5cd02c1cac..f472d40105 100644
--- a/services/omnirpc/modules/receiptsbackup/receiptsbackup.go
+++ b/services/omnirpc/modules/receiptsbackup/receiptsbackup.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "github.com/synapsecns/sanguine/services/omnirpc/modules/mixins"
"io"
"net/http"
"time"
@@ -136,9 +137,18 @@ func (r *receiptsProxyImpl) ProxyRequest(c *gin.Context) (err error) {
}
func (r *receiptsProxyImpl) processRequest(ctx context.Context, rpcRequest rpc.Request, requestID []byte) (resp omniHTTP.Response, err error) {
+ ctx, span := r.handler.Tracer().Start(ctx, "proxyrequest")
+ defer func() {
+ metrics.EndSpanWithErr(span, err)
+ }()
+
+ mixins.TxSubmitMixin(ctx, r.handler, rpcRequest)
+
req := r.client.NewRequest()
body, err := json.Marshal(rpcRequest)
+ span.AddEvent("request marshaled", trace.WithAttributes(attribute.String("body", string(body))))
+
//nolint: exhaustive
switch client.RPCMethod(rpcRequest.Method) {
case client.TransactionReceiptByHashMethod:
@@ -189,6 +199,8 @@ func (r *receiptsProxyImpl) processRequest(ctx context.Context, rpcRequest rpc.R
return nil, fmt.Errorf("could not get response from RPC %s: %w", r.proxyURL, err)
}
+ span.AddEvent("response returned", trace.WithAttributes(attribute.String("body", string(resp.Body()))))
+
return resp, nil
}
}
diff --git a/services/rfq/relayer/quoter/quoter.go b/services/rfq/relayer/quoter/quoter.go
index 3b5ecc8352..2cd3dfcf94 100644
--- a/services/rfq/relayer/quoter/quoter.go
+++ b/services/rfq/relayer/quoter/quoter.go
@@ -207,6 +207,15 @@ func (m *Manager) ShouldProcess(parentCtx context.Context, quote reldb.QuoteRequ
return false, nil
}
+ // check relay amount
+ maxRelayAmount := m.config.GetMaxRelayAmount(int(quote.Transaction.OriginChainId), quote.Transaction.OriginToken)
+ if maxRelayAmount != nil {
+ if quote.Transaction.OriginAmount.Cmp(maxRelayAmount) > 0 {
+ span.AddEvent("origin amount is greater than max relay amount")
+ return false, nil
+ }
+ }
+
// all checks have passed
return true, nil
}
@@ -713,7 +722,7 @@ func (m *Manager) getOriginAmount(parentCtx context.Context, input QuoteInput) (
}
}
- // Finally, clip the quoteAmount by the dest balance
+ // Clip the quoteAmount by the dest balance
if quoteAmount.Cmp(input.DestBalance) > 0 {
span.AddEvent("quote amount greater than destination balance", trace.WithAttributes(
attribute.String("quote_amount", quoteAmount.String()),
@@ -722,6 +731,16 @@ func (m *Manager) getOriginAmount(parentCtx context.Context, input QuoteInput) (
quoteAmount = input.DestBalance
}
+ // Clip the quoteAmount by the maxQuoteAmount
+ maxQuoteAmount := m.config.GetMaxRelayAmount(input.DestChainID, input.DestTokenAddr)
+ if maxQuoteAmount != nil && quoteAmount.Cmp(maxQuoteAmount) > 0 {
+ span.AddEvent("quote amount greater than max quote amount", trace.WithAttributes(
+ attribute.String("quote_amount", quoteAmount.String()),
+ attribute.String("max_quote_amount", maxQuoteAmount.String()),
+ ))
+ quoteAmount = maxQuoteAmount
+ }
+
// Deduct gas cost from the quote amount, if necessary
quoteAmount, err = m.deductGasCost(ctx, quoteAmount, input.DestTokenAddr, input.DestChainID)
if err != nil {
diff --git a/services/rfq/relayer/quoter/quoter_test.go b/services/rfq/relayer/quoter/quoter_test.go
index 1d6a52c7de..321d55b189 100644
--- a/services/rfq/relayer/quoter/quoter_test.go
+++ b/services/rfq/relayer/quoter/quoter_test.go
@@ -136,6 +136,13 @@ func (s *QuoterSuite) TestShouldProcess() {
s.False(s.manager.ShouldProcess(s.GetTestContext(), quote))
s.manager.SetRelayPaused(false)
s.True(s.manager.ShouldProcess(s.GetTestContext(), quote))
+
+ // Set max relay amount
+ originTokenCfg := s.config.Chains[int(s.origin)].Tokens["USDC"]
+ originTokenCfg.MaxRelayAmount = "900" // less than balance
+ s.config.Chains[int(s.origin)].Tokens["USDC"] = originTokenCfg
+ s.manager.SetConfig(s.config)
+ s.False(s.manager.ShouldProcess(s.GetTestContext(), quote))
}
func (s *QuoterSuite) TestIsProfitable() {
@@ -173,13 +180,23 @@ func (s *QuoterSuite) TestGetOriginAmount() {
originAddr := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
balance := big.NewInt(1000_000_000) // 1000 USDC
- setQuoteParams := func(quotePct, quoteOffset float64, minQuoteAmount, maxBalance string) {
- s.config.BaseChainConfig.QuotePct = "ePct
+ type quoteParams struct {
+ quotePct float64
+ quoteOffset float64
+ minQuoteAmount string
+ maxBalance string
+ maxQuoteAmount string
+ }
+
+ setQuoteParams := func(params quoteParams) {
+ s.config.BaseChainConfig.QuotePct = ¶ms.quotePct
destTokenCfg := s.config.Chains[dest].Tokens["USDC"]
- destTokenCfg.MinQuoteAmount = minQuoteAmount
+ destTokenCfg.MinQuoteAmount = params.minQuoteAmount
+ destTokenCfg.MaxRelayAmount = params.maxQuoteAmount
originTokenCfg := s.config.Chains[origin].Tokens["USDC"]
- originTokenCfg.QuoteOffsetBps = quoteOffset
- originTokenCfg.MaxBalance = &maxBalance
+ originTokenCfg.QuoteOffsetBps = params.quoteOffset
+ originTokenCfg.MaxBalance = ¶ms.maxBalance
+ originTokenCfg.MaxRelayAmount = params.maxQuoteAmount
s.config.Chains[dest].Tokens["USDC"] = destTokenCfg
s.config.Chains[origin].Tokens["USDC"] = originTokenCfg
s.manager.SetConfig(s.config)
@@ -201,42 +218,85 @@ func (s *QuoterSuite) TestGetOriginAmount() {
s.Equal(expectedAmount, quoteAmount)
// Set QuotePct to 50 with MinQuoteAmount of 0; should be 50% of balance.
- setQuoteParams(50, 0, "0", "0")
+ setQuoteParams(quoteParams{
+ quotePct: 50,
+ quoteOffset: 0,
+ minQuoteAmount: "0",
+ maxBalance: "0",
+ })
quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input)
s.NoError(err)
expectedAmount = big.NewInt(500_000_000)
s.Equal(expectedAmount, quoteAmount)
// Set QuotePct to 50 with QuoteOffset of -1%. Should be 1% less than 50% of balance.
- setQuoteParams(50, -100, "0", "0")
+ setQuoteParams(quoteParams{
+ quotePct: 50,
+ quoteOffset: -100,
+ minQuoteAmount: "0",
+ maxBalance: "0",
+ })
quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input)
s.NoError(err)
expectedAmount = big.NewInt(495_000_000)
s.Equal(expectedAmount, quoteAmount)
// Set QuotePct to 25 with MinQuoteAmount of 500; should be 50% of balance.
- setQuoteParams(25, 0, "500", "0")
+ setQuoteParams(quoteParams{
+ quotePct: 25,
+ quoteOffset: 0,
+ minQuoteAmount: "500",
+ maxBalance: "0",
+ })
quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input)
s.NoError(err)
expectedAmount = big.NewInt(500_000_000)
s.Equal(expectedAmount, quoteAmount)
// Set QuotePct to 25 with MinQuoteAmount of 500; should be 50% of balance.
- setQuoteParams(25, 0, "500", "0")
+ setQuoteParams(quoteParams{
+ quotePct: 25,
+ quoteOffset: 0,
+ minQuoteAmount: "500",
+ maxBalance: "0",
+ })
quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input)
s.NoError(err)
expectedAmount = big.NewInt(500_000_000)
s.Equal(expectedAmount, quoteAmount)
// Set QuotePct to 25 with MinQuoteAmount of 1500; should be total balance.
- setQuoteParams(25, 0, "1500", "0")
+ setQuoteParams(quoteParams{
+ quotePct: 25,
+ quoteOffset: 0,
+ minQuoteAmount: "1500",
+ maxBalance: "0",
+ })
quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input)
s.NoError(err)
expectedAmount = big.NewInt(1000_000_000)
s.Equal(expectedAmount, quoteAmount)
+ // Set QuotePct to 100 with MinQuoteAmount of 0 and MaxRelayAmount of 500; should be 500.
+ setQuoteParams(quoteParams{
+ quotePct: 100,
+ quoteOffset: 0,
+ minQuoteAmount: "0",
+ maxBalance: "0",
+ maxQuoteAmount: "500",
+ })
+ quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input)
+ s.NoError(err)
+ expectedAmount = big.NewInt(500_000_000)
+ s.Equal(expectedAmount, quoteAmount)
+
// Set QuotePct to 25 with MinQuoteAmount of 1500 and MaxBalance of 1200; should be 200.
- setQuoteParams(25, 0, "1500", "1200")
+ setQuoteParams(quoteParams{
+ quotePct: 25,
+ quoteOffset: 0,
+ minQuoteAmount: "1500",
+ maxBalance: "1200",
+ })
quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input)
s.NoError(err)
expectedAmount = big.NewInt(200_000_000)
diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go
index a4449bf8db..220627e13a 100644
--- a/services/rfq/relayer/relconfig/config.go
+++ b/services/rfq/relayer/relconfig/config.go
@@ -124,6 +124,8 @@ type TokenConfig struct {
PriceUSD float64 `yaml:"price_usd"`
// MinQuoteAmount is the minimum amount to quote for this token in human-readable units.
MinQuoteAmount string `yaml:"min_quote_amount"`
+ // MaxRelayAmount is the maximum amount to quote and relay for this token in human-readable units.
+ MaxRelayAmount string `yaml:"max_relay_amount"`
// RebalanceMethods are the supported methods for rebalancing.
RebalanceMethods []string `yaml:"rebalance_methods"`
// MaintenanceBalancePct is the percentage of the total balance under which a rebalance will be triggered.
diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go
index 2cb4880712..a3fbb25cc7 100644
--- a/services/rfq/relayer/relconfig/getters.go
+++ b/services/rfq/relayer/relconfig/getters.go
@@ -746,6 +746,41 @@ func (c Config) GetMinQuoteAmount(chainID int, addr common.Address) *big.Int {
return quoteAmountScaled
}
+var defaultMaxRelayAmount *big.Int // nil
+
+// GetMaxRelayAmount returns the quote amount for the given chain and address.
+// Note that this getter returns the value in native token decimals.
+func (c Config) GetMaxRelayAmount(chainID int, addr common.Address) *big.Int {
+ chainCfg, ok := c.Chains[chainID]
+ if !ok {
+ return defaultMaxRelayAmount
+ }
+
+ var tokenCfg *TokenConfig
+ for _, cfg := range chainCfg.Tokens {
+ if common.HexToAddress(cfg.Address).Hex() == addr.Hex() {
+ cfgCopy := cfg
+ tokenCfg = &cfgCopy
+ break
+ }
+ }
+ if tokenCfg == nil {
+ return defaultMaxRelayAmount
+ }
+ quoteAmountFlt, ok := new(big.Float).SetString(tokenCfg.MaxRelayAmount)
+ if !ok {
+ return defaultMaxRelayAmount
+ }
+ if quoteAmountFlt.Cmp(big.NewFloat(0)) <= 0 {
+ return defaultMaxRelayAmount
+ }
+
+ // Scale the minQuoteAmount by the token decimals.
+ denomDecimalsFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenCfg.Decimals)), nil)
+ quoteAmountScaled, _ := new(big.Float).Mul(quoteAmountFlt, new(big.Float).SetInt(denomDecimalsFactor)).Int(nil)
+ return quoteAmountScaled
+}
+
var defaultMinRebalanceAmount = big.NewInt(1000)
// GetMinRebalanceAmount returns the min rebalance amount for the given chain and address.
diff --git a/services/rfq/relayer/service/handlers.go b/services/rfq/relayer/service/handlers.go
index fc1bc1abe8..dbba3dea6e 100644
--- a/services/rfq/relayer/service/handlers.go
+++ b/services/rfq/relayer/service/handlers.go
@@ -47,7 +47,12 @@ func (r *Relayer) handleBridgeRequestedLog(parentCtx context.Context, req *fastb
return nil
}
- defer unlocker.Unlock()
+ shouldUnlock := true
+ defer func() {
+ if shouldUnlock {
+ unlocker.Unlock()
+ }
+ }()
_, err = r.db.GetQuoteRequestByID(ctx, req.TransactionId)
// expect no results
@@ -120,12 +125,17 @@ func (r *Relayer) handleBridgeRequestedLog(parentCtx context.Context, req *fastb
if err != nil {
return fmt.Errorf("could not get quote request handler: %w", err)
}
- // Forward instead of lock since we called lock above.
- fwdErr := qr.Forward(ctx, dbReq)
- if fwdErr != nil {
- logger.Errorf("could not forward to handle seen: %w", fwdErr)
- span.AddEvent("could not forward to handle seen")
- }
+
+ // Forward in new goroutine and retain the lock.
+ shouldUnlock = false
+ go func() {
+ defer unlocker.Unlock()
+ fwdErr := qr.Forward(ctx, dbReq)
+ if fwdErr != nil {
+ logger.Errorf("could not forward to handle seen: %w", fwdErr)
+ span.AddEvent(fmt.Sprintf("could not forward to handle seen: %s", fwdErr.Error()))
+ }
+ }()
return nil
}
diff --git a/yarn.lock b/yarn.lock
index 61c7578a54..bbf0529519 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -55,7 +55,7 @@
resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069"
integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==
-"@adraffy/ens-normalize@^1.8.8":
+"@adraffy/ens-normalize@1.11.0", "@adraffy/ens-normalize@^1.8.8":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33"
integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==
@@ -6007,7 +6007,7 @@
dependencies:
"@noble/hashes" "1.4.0"
-"@noble/curves@^1.4.0":
+"@noble/curves@1.6.0", "@noble/curves@^1.4.0", "@noble/curves@~1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b"
integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==
@@ -7122,7 +7122,7 @@
resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.22.2.tgz#d4ff9972e58f9344fc95f8d41b2ec6517baa8e79"
integrity sha512-Y0yAxRaB98LFp2Dm+ACZqBSdAmI3FlpH/LjxOZ94g/ouuDJecSq0iR26XZ5QDuEL8Rf+L4jBJaoDC08CD0KkJw==
-"@scure/base@^1.1.3", "@scure/base@~1.1.0", "@scure/base@~1.1.2", "@scure/base@~1.1.6", "@scure/base@~1.1.8":
+"@scure/base@^1.1.3", "@scure/base@~1.1.0", "@scure/base@~1.1.2", "@scure/base@~1.1.6", "@scure/base@~1.1.7", "@scure/base@~1.1.8":
version "1.1.9"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1"
integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==
@@ -7154,6 +7154,15 @@
"@noble/hashes" "~1.4.0"
"@scure/base" "~1.1.6"
+"@scure/bip32@1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.5.0.tgz#dd4a2e1b8a9da60e012e776d954c4186db6328e6"
+ integrity sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==
+ dependencies:
+ "@noble/curves" "~1.6.0"
+ "@noble/hashes" "~1.5.0"
+ "@scure/base" "~1.1.7"
+
"@scure/bip39@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5"
@@ -9324,7 +9333,7 @@
jest-diff "^25.2.1"
pretty-format "^25.2.1"
-"@types/js-yaml@^4.0.0":
+"@types/js-yaml@^4.0.0", "@types/js-yaml@^4.0.9":
version "4.0.9"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2"
integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==
@@ -9469,6 +9478,13 @@
dependencies:
undici-types "~6.19.2"
+"@types/node@^22.7.5":
+ version "22.7.5"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b"
+ integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==
+ dependencies:
+ undici-types "~6.19.2"
+
"@types/normalize-package-data@^2.4.0":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
@@ -9859,7 +9875,7 @@
dependencies:
"@types/yargs-parser" "*"
-"@types/yargs@^17.0.8":
+"@types/yargs@^17.0.33", "@types/yargs@^17.0.8":
version "17.0.33"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d"
integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==
@@ -11090,6 +11106,11 @@ abitype@1.0.5:
resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6"
integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==
+abitype@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.6.tgz#76410903e1d88e34f1362746e2d407513c38565b"
+ integrity sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==
+
abitype@^0.10.2:
version "0.10.3"
resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.10.3.tgz#27ce7a7cdb9a80ccd732a3f3cf1ce6ff05266fce"
@@ -21583,6 +21604,11 @@ isows@1.0.4:
resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061"
integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==
+isows@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7"
+ integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==
+
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -33985,6 +34011,11 @@ typescript@^5.0.4, typescript@^5.2.2, typescript@^5.3.2, typescript@^5.3.3, type
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0"
integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==
+typescript@^5.6.3:
+ version "5.6.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b"
+ integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==
+
typescript@~5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
@@ -35024,6 +35055,21 @@ viem@^2.1.1, viem@^2.13.6, viem@^2.21.6:
webauthn-p256 "0.0.5"
ws "8.17.1"
+viem@^2.21.19:
+ version "2.21.25"
+ resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.25.tgz#5e4a7c6a8543396f67ef221ea5d2226321f000b8"
+ integrity sha512-fQbFLVW5RjC1MwjelmzzDygmc2qMfY17NruAIIdYeiB8diQfhqsczU5zdGw/jTbmNXbKoYnSdgqMb8MFZcbZ1w==
+ dependencies:
+ "@adraffy/ens-normalize" "1.11.0"
+ "@noble/curves" "1.6.0"
+ "@noble/hashes" "1.5.0"
+ "@scure/bip32" "1.5.0"
+ "@scure/bip39" "1.4.0"
+ abitype "1.0.6"
+ isows "1.0.6"
+ webauthn-p256 "0.0.10"
+ ws "8.18.0"
+
vite-node@^1.0.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f"
@@ -35891,6 +35937,14 @@ web3@1.7.4:
web3-shh "1.7.4"
web3-utils "1.7.4"
+webauthn-p256@0.0.10:
+ version "0.0.10"
+ resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.10.tgz#877e75abe8348d3e14485932968edf3325fd2fdd"
+ integrity sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA==
+ dependencies:
+ "@noble/curves" "^1.4.0"
+ "@noble/hashes" "^1.4.0"
+
webauthn-p256@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.5.tgz#0baebd2ba8a414b21cc09c0d40f9dd0be96a06bd"
@@ -36544,6 +36598,11 @@ ws@8.17.1, ws@~8.17.1:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==
+ws@8.18.0, ws@^8.12.0, ws@^8.13.0, ws@^8.17.1, ws@^8.2.3:
+ version "8.18.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
+ integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
+
ws@^3.0.0:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
@@ -36558,11 +36617,6 @@ ws@^7.0.0, ws@^7.3.1, ws@^7.4.6, ws@^7.5.1:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
-ws@^8.12.0, ws@^8.13.0, ws@^8.17.1, ws@^8.2.3:
- version "8.18.0"
- resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
- integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
-
x-default-browser@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/x-default-browser/-/x-default-browser-0.4.0.tgz#70cf0da85da7c0ab5cb0f15a897f2322a6bdd481"