diff --git a/contrib/opbot/botmd/botmd.go b/contrib/opbot/botmd/botmd.go index 32ce34bfae..0d8c22f930 100644 --- a/contrib/opbot/botmd/botmd.go +++ b/contrib/opbot/botmd/botmd.go @@ -6,6 +6,7 @@ import ( "github.com/slack-io/slacker" "github.com/synapsecns/sanguine/contrib/opbot/config" + "github.com/synapsecns/sanguine/contrib/opbot/internal" "github.com/synapsecns/sanguine/contrib/opbot/signoz" screenerClient "github.com/synapsecns/sanguine/contrib/screener-api/client" "github.com/synapsecns/sanguine/core/dbcommon" @@ -29,6 +30,7 @@ type Bot struct { signozClient *signoz.Client signozEnabled bool rpcClient omnirpcClient.RPCClient + rfqClient internal.RFQClient signer signer.Signer submitter submitter.TransactionSubmitter screener screenerClient.ScreenerClient @@ -42,10 +44,11 @@ func NewBot(handler metrics.Handler, cfg config.Config) *Bot { sugaredLogger := otelzap.New(experimentalLogger.MakeZapLogger()).Sugar() bot := Bot{ - handler: handler, - cfg: cfg, - server: server, - logger: experimentalLogger.MakeWrappedSugaredLogger(sugaredLogger), + handler: handler, + cfg: cfg, + server: server, + logger: experimentalLogger.MakeWrappedSugaredLogger(sugaredLogger), + rfqClient: internal.NewRFQClient(handler, cfg.RFQIndexerAPIURL, cfg.RelayerURLS), } // you should be able to run opbot even without signoz. diff --git a/contrib/opbot/botmd/commands.go b/contrib/opbot/botmd/commands.go index b72f4ff7c2..69823809bc 100644 --- a/contrib/opbot/botmd/commands.go +++ b/contrib/opbot/botmd/commands.go @@ -11,7 +11,6 @@ import ( "regexp" "sort" "strings" - "sync" "time" "github.com/dustin/go-humanize" @@ -21,14 +20,13 @@ import ( "github.com/hako/durafmt" "github.com/slack-go/slack" "github.com/slack-io/slacker" + "github.com/synapsecns/sanguine/contrib/opbot/internal" "github.com/synapsecns/sanguine/contrib/opbot/signoz" "github.com/synapsecns/sanguine/core/retry" "github.com/synapsecns/sanguine/ethergo/chaindata" - "github.com/synapsecns/sanguine/ethergo/client" "github.com/synapsecns/sanguine/ethergo/submitter" rfqClient "github.com/synapsecns/sanguine/services/rfq/api/client" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" - "github.com/synapsecns/sanguine/services/rfq/relayer/relapi" ) func (b *Bot) requiresSignoz(definition *slacker.CommandDefinition) *slacker.CommandDefinition { @@ -159,62 +157,17 @@ func (b *Bot) traceCommand() *slacker.CommandDefinition { func (b *Bot) rfqLookupCommand() *slacker.CommandDefinition { return &slacker.CommandDefinition{ Command: "rfq ", - Description: "find a rfq transaction by either tx hash or txid on all configured relayers", + Description: "find a rfq transaction by either tx hash or txid from the rfq-indexer api", Examples: []string{ "rfq 0x30f96b45ba689c809f7e936c140609eb31c99b182bef54fccf49778716a7e1ca", }, Handler: func(ctx *slacker.CommandContext) { - type Status struct { - relayer string - *relapi.GetQuoteRequestResponse - } - - var statuses []Status - var sliceMux sync.Mutex - - if len(b.cfg.RelayerURLS) == 0 { - _, err := ctx.Response().Reply("no relayer urls configured") - if err != nil { - log.Println(err) - } - return - } - tx := stripLinks(ctx.Request().Param("tx")) - var wg sync.WaitGroup - // 2 routines per relayer, one for tx hashh one for tx id - wg.Add(len(b.cfg.RelayerURLS) * 2) - for _, relayer := range b.cfg.RelayerURLS { - client := relapi.NewRelayerClient(b.handler, relayer) - go func() { - defer wg.Done() - res, err := client.GetQuoteRequestByTxHash(ctx.Context(), tx) - if err != nil { - log.Printf("error fetching quote request status by tx hash: %v\n", err) - return - } - sliceMux.Lock() - defer sliceMux.Unlock() - statuses = append(statuses, Status{relayer: relayer, GetQuoteRequestResponse: res}) - }() - - go func() { - defer wg.Done() - res, err := client.GetQuoteRequestByTXID(ctx.Context(), tx) - if err != nil { - log.Printf("error fetching quote request status by tx id: %v\n", err) - return - } - sliceMux.Lock() - defer sliceMux.Unlock() - statuses = append(statuses, Status{relayer: relayer, GetQuoteRequestResponse: res}) - }() - } - wg.Wait() - - if len(statuses) == 0 { - _, err := ctx.Response().Reply("no quote request found") + res, status, err := b.rfqClient.GetRFQ(ctx.Context(), tx) + if err != nil { + b.logger.Errorf(ctx.Context(), "error fetching quote request: %v", err) + _, err := ctx.Response().Reply(fmt.Sprintf("error fetching quote request %s", err.Error())) if err != nil { log.Println(err) } @@ -223,51 +176,46 @@ func (b *Bot) rfqLookupCommand() *slacker.CommandDefinition { var slackBlocks []slack.Block - for _, status := range statuses { - client, err := b.rpcClient.GetChainClient(ctx.Context(), int(status.OriginChainID)) - if err != nil { - log.Printf("error getting chain client: %v\n", err) - } - - objects := []*slack.TextBlockObject{ - { - Type: slack.MarkdownType, - Text: fmt.Sprintf("*Relayer*: %s", status.relayer), - }, - { - Type: slack.MarkdownType, - Text: fmt.Sprintf("*Status*: %s", status.Status), - }, - { - Type: slack.MarkdownType, - Text: fmt.Sprintf("*TxID*: %s", toExplorerSlackLink(status.TxID)), - }, - { - Type: slack.MarkdownType, - Text: fmt.Sprintf("*OriginTxHash*: %s", toTXSlackLink(status.OriginTxHash, status.OriginChainID)), - }, - { - Type: slack.MarkdownType, - Text: fmt.Sprintf("*Estimated Tx Age*: %s", getTxAge(ctx.Context(), client, status.GetQuoteRequestResponse)), - }, - } - - if status.DestTxHash == (common.Hash{}).String() { - objects = append(objects, &slack.TextBlockObject{ - Type: slack.MarkdownType, - Text: "*DestTxHash*: not available", - }) - } else { - objects = append(objects, &slack.TextBlockObject{ - Type: slack.MarkdownType, - Text: fmt.Sprintf("*DestTxHash*: %s", toTXSlackLink(status.DestTxHash, status.DestChainID)), - }) - } + objects := []*slack.TextBlockObject{ + { + Type: slack.MarkdownType, + Text: fmt.Sprintf("*Relayer*: %s", res.BridgeRelay.Relayer), + }, + { + Type: slack.MarkdownType, + Text: fmt.Sprintf("*Status*: %s", status), + }, + { + Type: slack.MarkdownType, + Text: fmt.Sprintf("*TxID*: %s", toExplorerSlackLink(res.Bridge.TransactionID)), + }, + { + Type: slack.MarkdownType, + //nolint: gosec + Text: fmt.Sprintf("*OriginTxHash*: %s", toTXSlackLink(res.BridgeRequest.TransactionHash, uint32(res.Bridge.OriginChainID))), + }, + { + Type: slack.MarkdownType, + Text: fmt.Sprintf("*Estimated Tx Age*: %s", humanize.Time(time.Unix(res.BridgeRelay.BlockTimestamp, 0))), + }, + } - slackBlocks = append(slackBlocks, slack.NewSectionBlock(nil, objects, nil)) + if status == "Requested" { + objects = append(objects, &slack.TextBlockObject{ + Type: slack.MarkdownType, + Text: "*DestTxHash*: not available", + }) + } else { + //nolint: gosec + objects = append(objects, &slack.TextBlockObject{ + Type: slack.MarkdownType, + Text: fmt.Sprintf("*DestTxHash*: %s", toTXSlackLink(res.BridgeRelay.TransactionHash, uint32(res.Bridge.DestChainID))), + }) } - _, err := ctx.Response().ReplyBlocks(slackBlocks, slacker.WithUnfurlLinks(false)) + slackBlocks = append(slackBlocks, slack.NewSectionBlock(nil, objects, nil)) + + _, err = ctx.Response().ReplyBlocks(slackBlocks, slacker.WithUnfurlLinks(false)) if err != nil { log.Println(err) } @@ -275,7 +223,7 @@ func (b *Bot) rfqLookupCommand() *slacker.CommandDefinition { } } -// nolint: gocognit, cyclop. +// nolint: gocognit, cyclop, gosec. func (b *Bot) rfqRefund() *slacker.CommandDefinition { return &slacker.CommandDefinition{ Command: "refund ", @@ -292,16 +240,7 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition { return } - var rawRequest *relapi.GetQuoteRequestResponse - var err error - var relClient relapi.RelayerClient - for _, relayer := range b.cfg.RelayerURLS { - relClient = relapi.NewRelayerClient(b.handler, relayer) - rawRequest, err = getQuoteRequest(ctx.Context(), relClient, tx) - if err == nil { - break - } - } + rawRequest, _, err := b.rfqClient.GetRFQ(ctx.Context(), tx) if err != nil { b.logger.Errorf(ctx.Context(), "error fetching quote request: %v", err) _, err := ctx.Response().Reply("error fetching quote request") @@ -320,7 +259,7 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition { return } - isScreened, err := b.screener.ScreenAddress(ctx.Context(), rawRequest.Sender) + isScreened, err := b.screener.ScreenAddress(ctx.Context(), rawRequest.Bridge.Sender) if err != nil { _, err := ctx.Response().Reply("error screening address") if err != nil { @@ -336,13 +275,16 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition { return } - nonce, err := b.submitter.SubmitTransaction(ctx.Context(), big.NewInt(int64(rawRequest.OriginChainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { - tx, err = fastBridgeContract.Refund(transactor, common.Hex2Bytes(rawRequest.QuoteRequestRaw)) - if err != nil { - return nil, fmt.Errorf("error submitting refund: %w", err) - } - return tx, nil - }) + nonce, err := b.submitter.SubmitTransaction( + ctx.Context(), + big.NewInt(int64(rawRequest.Bridge.OriginChainID)), + func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { + tx, err = fastBridgeContract.Refund(transactor, common.Hex2Bytes(rawRequest.Bridge.Request)) + if err != nil { + return nil, fmt.Errorf("error submitting refund: %w", err) + } + return tx, nil + }) if err != nil { log.Printf("error submitting refund: %v\n", err) return @@ -352,7 +294,7 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition { err = retry.WithBackoff( ctx.Context(), func(ctx context.Context) error { - status, err = b.submitter.GetSubmissionStatus(ctx, big.NewInt(int64(rawRequest.OriginChainID)), nonce) + status, err = b.submitter.GetSubmissionStatus(ctx, big.NewInt(int64(rawRequest.Bridge.OriginChainID)), nonce) if err != nil || !status.HasTx() { b.logger.Errorf(ctx, "error fetching quote request: %v", err) return fmt.Errorf("error fetching quote request: %w", err) @@ -364,12 +306,14 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition { ) if err != nil { b.logger.Errorf(ctx.Context(), "error fetching quote request: %v", err) - _, err := ctx.Response().Reply(fmt.Sprintf("error fetching explorer link to refund, but nonce is %d", nonce)) - log.Printf("error fetching quote request: %v\n", err) + _, err := ctx.Response().Reply(fmt.Sprintf("refund submitted with nonce %d", nonce)) + if err != nil { + log.Println(err) + } return } - _, err = ctx.Response().Reply(fmt.Sprintf("refund submitted: %s", toExplorerSlackLink(status.TxHash().String()))) + _, err = ctx.Response().Reply(fmt.Sprintf("refund submitted: %s", toTXSlackLink(status.TxHash().String(), uint32(rawRequest.Bridge.OriginChainID)))) if err != nil { log.Println(err) } @@ -377,7 +321,7 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition { } } -func (b *Bot) makeFastBridge(ctx context.Context, req *relapi.GetQuoteRequestResponse) (*fastbridge.FastBridge, error) { +func (b *Bot) makeFastBridge(ctx context.Context, req *internal.GetRFQByTxIDResponse) (*fastbridge.FastBridge, error) { client, err := rfqClient.NewUnauthenticatedClient(b.handler, b.cfg.RFQApiURL) if err != nil { return nil, fmt.Errorf("error creating rfq client: %w", err) @@ -388,12 +332,12 @@ func (b *Bot) makeFastBridge(ctx context.Context, req *relapi.GetQuoteRequestRes return nil, fmt.Errorf("error fetching rfq contracts: %w", err) } - chainClient, err := b.rpcClient.GetChainClient(ctx, int(req.OriginChainID)) + chainClient, err := b.rpcClient.GetChainClient(ctx, int(req.Bridge.OriginChainID)) if err != nil { return nil, fmt.Errorf("error getting chain client: %w", err) } - contractAddress, ok := contracts.Contracts[req.OriginChainID] + contractAddress, ok := contracts.Contracts[uint32(req.Bridge.OriginChainID)] if !ok { return nil, errors.New("contract address not found") } @@ -405,24 +349,10 @@ func (b *Bot) makeFastBridge(ctx context.Context, req *relapi.GetQuoteRequestRes return fastBridgeHandle, nil } -func getTxAge(ctx context.Context, client client.EVM, res *relapi.GetQuoteRequestResponse) string { - // TODO: add CreatedAt field to GetQuoteRequestStatusResponse so we don't need to make network calls? - receipt, err := client.TransactionReceipt(ctx, common.HexToHash(res.OriginTxHash)) - if err != nil { - return "unknown time ago" - } - txBlock, err := client.HeaderByHash(ctx, receipt.BlockHash) - if err != nil { - return "unknown time ago" - } - - return humanize.Time(time.Unix(int64(txBlock.Time), 0)) -} - func toExplorerSlackLink(ogHash string) string { rfqHash := strings.ToUpper(ogHash) // cut off 0x - if strings.HasPrefix(rfqHash, "0x") { + if strings.HasPrefix(rfqHash, "0X") { rfqHash = strings.ToLower(rfqHash[2:]) } @@ -444,16 +374,3 @@ func stripLinks(input string) string { linkRegex := regexp.MustCompile(`]+\|([^>]+)>`) return linkRegex.ReplaceAllString(input, "$1") } - -func getQuoteRequest(ctx context.Context, client relapi.RelayerClient, tx string) (qr *relapi.GetQuoteRequestResponse, err error) { - if qr, err = client.GetQuoteRequestByTxHash(ctx, tx); err == nil { - return qr, nil - } - - // look up quote request - if qr, err = client.GetQuoteRequestByTXID(ctx, tx); err == nil { - return qr, nil - } - - return nil, fmt.Errorf("error fetching quote request: %w", err) -} diff --git a/contrib/opbot/botmd/commands_test.go b/contrib/opbot/botmd/commands_test.go index b9ab0a39ee..6b5456094e 100644 --- a/contrib/opbot/botmd/commands_test.go +++ b/contrib/opbot/botmd/commands_test.go @@ -1,13 +1,9 @@ package botmd_test import ( - "context" "testing" "github.com/synapsecns/sanguine/contrib/opbot/botmd" - "github.com/synapsecns/sanguine/core/metrics" - omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client" - "github.com/synapsecns/sanguine/services/rfq/relayer/relapi" ) func TestStripLinks(t *testing.T) { @@ -18,24 +14,3 @@ func TestStripLinks(t *testing.T) { t.Errorf("StripLinks(%s) = %s; want %s", testLink, got, expected) } } - -func TestTxAge(t *testing.T) { - notExpected := "unknown time ago" // should be a definite time - - status := &relapi.GetQuoteRequestResponse{ - OriginTxHash: "0x954264d120f5f3cf50edc39ebaf88ea9dc647d9d6843b7a120ed3677e23d7890", - OriginChainID: 421611, - } - - ctx := context.Background() - - client := omnirpcClient.NewOmnirpcClient("https://arb1.arbitrum.io/rpc", metrics.Get()) - cc, err := client.GetChainClient(ctx, int(status.OriginChainID)) - if err != nil { - t.Fatalf("GetChainClient() failed: %v", err) - } - - if got := botmd.GetTxAge(context.Background(), cc, status); got == notExpected { - t.Errorf("TxAge(%s) = %s; want not %s", status.OriginTxHash, got, notExpected) - } -} diff --git a/contrib/opbot/botmd/export_test.go b/contrib/opbot/botmd/export_test.go index 79b1e7a0f2..36a3ba400c 100644 --- a/contrib/opbot/botmd/export_test.go +++ b/contrib/opbot/botmd/export_test.go @@ -1,16 +1,5 @@ package botmd -import ( - "context" - - "github.com/synapsecns/sanguine/ethergo/client" - "github.com/synapsecns/sanguine/services/rfq/relayer/relapi" -) - func StripLinks(input string) string { return stripLinks(input) } - -func GetTxAge(ctx context.Context, client client.EVM, res *relapi.GetQuoteRequestResponse) string { - return getTxAge(ctx, client, res) -} diff --git a/contrib/opbot/config/config.go b/contrib/opbot/config/config.go index e9dde19453..0e52bb3e3c 100644 --- a/contrib/opbot/config/config.go +++ b/contrib/opbot/config/config.go @@ -38,6 +38,8 @@ type Config struct { ScreenerURL string `yaml:"screener_url"` // Database is the database config. Database DatabaseConfig `yaml:"database"` + // RFQIndexerAPIURL is the URL of the RFQ indexer API. + RFQIndexerAPIURL string `yaml:"rfq_indexer_api_url"` } // DatabaseConfig represents the configuration for the database. diff --git a/contrib/opbot/internal/client.go b/contrib/opbot/internal/client.go new file mode 100644 index 0000000000..b1b4c86ab8 --- /dev/null +++ b/contrib/opbot/internal/client.go @@ -0,0 +1,79 @@ +// Package internal provides the RFQ client implementation. +package internal + +import ( + "context" + "fmt" + "net/http" + + "github.com/dubonzi/otelresty" + "github.com/go-http-utils/headers" + "github.com/go-resty/resty/v2" + "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/services/rfq/relayer/relapi" +) + +const ( + getRFQRoute = "/transaction-id/%s" +) + +// RFQClient is the interface for the RFQ client. +type RFQClient interface { + // GetRFQ gets a quote request by transaction ID. + GetRFQ(ctx context.Context, txIdentifier string) (resp *GetRFQByTxIDResponse, status string, err error) +} + +type rfqClientImpl struct { + client *resty.Client + relayerClients []relapi.RelayerClient +} + +// NewRFQClient creates a new RFQClient. +func NewRFQClient(handler metrics.Handler, indexerURL string, relayerURLs []string) RFQClient { + client := resty.New() + client.SetBaseURL(indexerURL) + client.SetHeader(headers.UserAgent, "rfq-client") + + otelresty.TraceClient(client, otelresty.WithTracerProvider(handler.GetTracerProvider())) + + var relayerClients []relapi.RelayerClient + for _, url := range relayerURLs { + relayerClients = append(relayerClients, relapi.NewRelayerClient(handler, url)) + } + + return &rfqClientImpl{ + client: client, + relayerClients: relayerClients, + } +} + +// GetRFQByTxID gets a quote request by transaction ID or transaction hash. +func (r *rfqClientImpl) GetRFQ(ctx context.Context, txIdentifier string) (*GetRFQByTxIDResponse, string, error) { + var res GetRFQByTxIDResponse + resp, err := r.client.R().SetContext(ctx). + SetResult(&res). + Get(fmt.Sprintf(getRFQRoute, txIdentifier)) + if err != nil { + return nil, "", fmt.Errorf("failed to get quote request by tx ID: %w", err) + } + + if resp.StatusCode() != http.StatusOK { + return nil, "", fmt.Errorf("unexpected status code: %d", resp.StatusCode()) + } + + var status string + switch { + case res.BridgeClaim != (BridgeClaim{}): + status = "Claimed" + case res.BridgeProof != (BridgeProof{}): + status = "Proven" + case res.BridgeRelay != (BridgeRelay{}): + status = "Relayed" + case res.BridgeRequest != (BridgeRequest{}): + status = "Requested" + default: + status = "Unknown" + } + + return &res, status, nil +} diff --git a/contrib/opbot/internal/model.go b/contrib/opbot/internal/model.go new file mode 100644 index 0000000000..7b44f1ab91 --- /dev/null +++ b/contrib/opbot/internal/model.go @@ -0,0 +1,60 @@ +package internal + +// GetRFQByTxIDResponse is the response for GetRFQByTxID. +type GetRFQByTxIDResponse struct { + Bridge Bridge `json:"Bridge"` + BridgeRequest BridgeRequest `json:"BridgeRequest"` + BridgeRelay BridgeRelay `json:"BridgeRelay"` + BridgeProof BridgeProof `json:"BridgeProof"` + BridgeClaim BridgeClaim `json:"BridgeClaim"` +} + +// Bridge contains the bridge information. +type Bridge struct { + TransactionID string `json:"transactionId"` + OriginChain string `json:"originChain"` + DestChain string `json:"destChain"` + OriginChainID int `json:"originChainId"` + DestChainID int `json:"destChainId"` + OriginToken string `json:"originToken"` + DestToken string `json:"destToken"` + OriginAmountFormatted string `json:"originAmountFormatted"` + DestAmountFormatted string `json:"destAmountFormatted"` + Sender string `json:"sender"` + SendChainGas int `json:"sendChainGas"` + Request string `json:"request"` +} + +// BridgeRequest contains the bridge request information. +type BridgeRequest struct { + BlockNumber string `json:"blockNumber"` + BlockTimestamp int64 `json:"blockTimestamp"` + TransactionHash string `json:"transactionHash"` +} + +// BridgeRelay contains the bridge relay information. +type BridgeRelay struct { + BlockNumber string `json:"blockNumber"` + BlockTimestamp int64 `json:"blockTimestamp"` + TransactionHash string `json:"transactionHash"` + Relayer string `json:"relayer"` + To string `json:"to"` +} + +// BridgeProof contains the bridge proof information. +type BridgeProof struct { + BlockNumber string `json:"blockNumber"` + BlockTimestamp int64 `json:"blockTimestamp"` + TransactionHash string `json:"transactionHash"` + Relayer string `json:"relayer"` +} + +// BridgeClaim contains the bridge claim information. +type BridgeClaim struct { + BlockNumber string `json:"blockNumber"` + BlockTimestamp int64 `json:"blockTimestamp"` + TransactionHash string `json:"transactionHash"` + To string `json:"to"` + Relayer string `json:"relayer"` + AmountFormatted string `json:"amountFormatted"` +} diff --git a/ethergo/chaindata/chaindata.go b/ethergo/chaindata/chaindata.go index 0fab04a166..9f20b536b3 100644 --- a/ethergo/chaindata/chaindata.go +++ b/ethergo/chaindata/chaindata.go @@ -162,6 +162,16 @@ var ChainMetadataList = []ChainMetadata{ ChainName: "scroll", Explorer: "https://scrollscan.com", }, + { + ChainID: 59144, + ChainName: "linea", + Explorer: "https://lineascan.build", + }, + { + ChainID: 480, + ChainName: "world chain", + Explorer: "https://worldscan.org", + }, } // ChainNameToChainID converts the chain name to the chain id. diff --git a/packages/explorer-ui/components/ChainChart/index.tsx b/packages/explorer-ui/components/ChainChart/index.tsx index a99f02dbb7..35ae42acb6 100644 --- a/packages/explorer-ui/components/ChainChart/index.tsx +++ b/packages/explorer-ui/components/ChainChart/index.tsx @@ -320,6 +320,12 @@ export const OverviewChart: React.FC = ({ stackId="a" fill={loading ? 'rgba(255, 255, 255, 0.1)' : '#000000'} /> + )} diff --git a/packages/rfq-indexer/api/src/constants/networkConfig.ts b/packages/rfq-indexer/api/src/constants/networkConfig.ts index 8d2dc78e3f..579f8f7273 100644 --- a/packages/rfq-indexer/api/src/constants/networkConfig.ts +++ b/packages/rfq-indexer/api/src/constants/networkConfig.ts @@ -8,6 +8,7 @@ import { linea, bsc, blast, + worldchain, } from 'viem/chains' import { FastBridgeV2Abi } from './abis/FastBridgeV2' @@ -114,4 +115,15 @@ export const networkConfig: NetworkConfig = { transport: http(), }), }, + 480: { + name: 'Worldchain', + FastBridgeV2: { + address: '0x5523D3c98809DdDB82C686E152F5C58B1B0fB59E', + abi: FastBridgeV2Abi, + }, + client: createPublicClient({ + chain: worldchain, + transport: http(), + }), + }, } as const diff --git a/packages/rfq-indexer/api/src/controllers/conflictingProofsController.ts b/packages/rfq-indexer/api/src/controllers/conflictingProofsController.ts index 7f1de3137a..879473b32e 100644 --- a/packages/rfq-indexer/api/src/controllers/conflictingProofsController.ts +++ b/packages/rfq-indexer/api/src/controllers/conflictingProofsController.ts @@ -13,7 +13,7 @@ export const conflictingProofsController = async ( const query = db .with('deposits', () => qDeposits()) .with('relays', () => qRelays()) - .with('proofs', () => qProofs({activeOnly: true})) + .with('proofs', () => qProofs({ activeOnly: true })) .with('combined', (qb) => qb .selectFrom('deposits') diff --git a/packages/rfq-indexer/api/src/controllers/disputesController.ts b/packages/rfq-indexer/api/src/controllers/disputesController.ts index 2e65632c77..840d133db8 100644 --- a/packages/rfq-indexer/api/src/controllers/disputesController.ts +++ b/packages/rfq-indexer/api/src/controllers/disputesController.ts @@ -7,7 +7,7 @@ import { nest_results } from '../utils/nestResults' export const disputesController = async (req: Request, res: Response) => { try { const query = db - .with('disputes', () => qDisputes({activeOnly: true})) + .with('disputes', () => qDisputes({ activeOnly: true })) .selectFrom('disputes') .selectAll() .orderBy('blockTimestamp_dispute', 'desc') diff --git a/packages/rfq-indexer/api/src/controllers/pendingTransactionsController.ts b/packages/rfq-indexer/api/src/controllers/pendingTransactionsController.ts index a2012c53c4..5d235e92cc 100644 --- a/packages/rfq-indexer/api/src/controllers/pendingTransactionsController.ts +++ b/packages/rfq-indexer/api/src/controllers/pendingTransactionsController.ts @@ -1,14 +1,7 @@ import { Request, Response } from 'express' import { db } from '../db' -import { - qDeposits, - qRelays, - qProofs, - qClaims, - qRefunds, - qDisputes, -} from '../queries' +import { qDeposits, qRelays, qProofs, qClaims, qRefunds } from '../queries' import { nest_results } from '../utils/nestResults' const sevenDaysAgo = Math.floor(Date.now() / 1000) - 7 * 24 * 60 * 60 @@ -21,7 +14,7 @@ export const pendingTransactionsMissingClaimController = async ( const query = db .with('deposits', () => qDeposits()) .with('relays', () => qRelays()) - .with('proofs', () => qProofs({activeOnly: true})) + .with('proofs', () => qProofs({ activeOnly: true })) .with('claims', () => qClaims()) .with('combined', (qb) => qb @@ -45,7 +38,7 @@ export const pendingTransactionsMissingClaimController = async ( res.json(nestedResults) } else { res - .status(404) + .status(200) .json({ message: 'No pending transactions missing claim found' }) } } catch (error) { @@ -62,7 +55,7 @@ export const pendingTransactionsMissingProofController = async ( const query = db .with('deposits', () => qDeposits()) .with('relays', () => qRelays()) - .with('proofs', () => qProofs({activeOnly: true})) + .with('proofs', () => qProofs({ activeOnly: true })) .with('combined', (qb) => qb .selectFrom('deposits') @@ -83,7 +76,7 @@ export const pendingTransactionsMissingProofController = async ( res.json(nestedResults) } else { res - .status(404) + .status(200) .json({ message: 'No pending transactions missing proof found' }) } } catch (error) { @@ -128,7 +121,7 @@ export const pendingTransactionsMissingRelayController = async ( res.json(nestedResults) } else { res - .status(404) + .status(200) .json({ message: 'No pending transactions missing relay found' }) } } catch (error) { @@ -173,7 +166,7 @@ export const pendingTransactionsMissingRelayExceedDeadlineController = async ( res.json(nestedResults) } else { res - .status(404) + .status(200) .json({ message: 'No pending transactions missing relay found' }) } } catch (error) { diff --git a/packages/rfq-indexer/api/src/controllers/transactionIdController.ts b/packages/rfq-indexer/api/src/controllers/transactionIdController.ts index 23f1496814..d5cafa23fa 100644 --- a/packages/rfq-indexer/api/src/controllers/transactionIdController.ts +++ b/packages/rfq-indexer/api/src/controllers/transactionIdController.ts @@ -1,7 +1,14 @@ import { Request, Response } from 'express' import { db } from '../db' -import { qDeposits, qRelays, qProofs, qClaims, qRefunds, qDisputes } from '../queries' +import { + qDeposits, + qRelays, + qProofs, + qClaims, + qRefunds, + qDisputes, +} from '../queries' import { nest_results } from '../utils/nestResults' export const getTransactionById = async (req: Request, res: Response) => { @@ -18,8 +25,8 @@ export const getTransactionById = async (req: Request, res: Response) => { ) ) .with('relays', () => qRelays()) - .with('proofs', () => qProofs({activeOnly: false})) // display proofs even if they have been invalidated/replaced by a dispute - .with('disputes', () => qDisputes({activeOnly: true})) // do not show disputes that have been invalidated/replaced by a proof + .with('proofs', () => qProofs({ activeOnly: false })) // display proofs even if they have been invalidated/replaced by a dispute + .with('disputes', () => qDisputes({ activeOnly: true })) // do not show disputes that have been invalidated/replaced by a proof .with('claims', () => qClaims()) .with('refunds', () => qRefunds()) .with('combined', (qb) => @@ -27,7 +34,11 @@ export const getTransactionById = async (req: Request, res: Response) => { .selectFrom('deposits') .leftJoin('relays', 'transactionId_deposit', 'transactionId_relay') .leftJoin('proofs', 'transactionId_deposit', 'transactionId_proof') - .leftJoin('disputes', 'transactionId_deposit', 'transactionId_dispute') + .leftJoin( + 'disputes', + 'transactionId_deposit', + 'transactionId_dispute' + ) .leftJoin('claims', 'transactionId_deposit', 'transactionId_claim') .leftJoin('refunds', 'transactionId_deposit', 'transactionId_refund') .selectAll('deposits') diff --git a/packages/rfq-indexer/api/src/graphql/resolvers.ts b/packages/rfq-indexer/api/src/graphql/resolvers.ts index 1deb7950d9..b346371ec7 100644 --- a/packages/rfq-indexer/api/src/graphql/resolvers.ts +++ b/packages/rfq-indexer/api/src/graphql/resolvers.ts @@ -20,6 +20,7 @@ const qDeposits = () => { 'BridgeRequestEvents.originAmountFormatted', 'BridgeRequestEvents.destAmountFormatted', 'BridgeRequestEvents.sender', + 'BridgeRequestEvents.request', 'BridgeRequestEvents.sendChainGas', ]) .where('BridgeRequestEvents.blockTimestamp', '>', 1722729600) diff --git a/packages/rfq-indexer/api/src/graphql/types/events.graphql b/packages/rfq-indexer/api/src/graphql/types/events.graphql index 2e3716de0e..45fcf930db 100644 --- a/packages/rfq-indexer/api/src/graphql/types/events.graphql +++ b/packages/rfq-indexer/api/src/graphql/types/events.graphql @@ -17,6 +17,7 @@ scalar BigInt destAmountFormatted: String! destChainId: Int! destChain: String! + request: String! sendChainGas: Boolean! } diff --git a/packages/rfq-indexer/api/src/queries/depositsQueries.ts b/packages/rfq-indexer/api/src/queries/depositsQueries.ts index 61e33aa3d1..e765e032d5 100644 --- a/packages/rfq-indexer/api/src/queries/depositsQueries.ts +++ b/packages/rfq-indexer/api/src/queries/depositsQueries.ts @@ -17,6 +17,7 @@ export const qDeposits = () => { 'BridgeRequestEvents.originAmountFormatted', 'BridgeRequestEvents.destAmountFormatted', 'BridgeRequestEvents.sender', + 'BridgeRequestEvents.request', 'BridgeRequestEvents.sendChainGas', ]) .where('BridgeRequestEvents.blockTimestamp', '>', 1722729600) diff --git a/packages/rfq-indexer/api/src/routes/conflictingProofsRoute.ts b/packages/rfq-indexer/api/src/routes/conflictingProofsRoute.ts index aa48c89e07..0a03dc5a1f 100644 --- a/packages/rfq-indexer/api/src/routes/conflictingProofsRoute.ts +++ b/packages/rfq-indexer/api/src/routes/conflictingProofsRoute.ts @@ -41,7 +41,7 @@ const router = express.Router() * BridgeDispute: * type: object * description: Dispute information (if available) - * 404: + * 200: * description: No conflicting proofs found * content: * application/json: diff --git a/packages/rfq-indexer/api/src/routes/disputesRoute.ts b/packages/rfq-indexer/api/src/routes/disputesRoute.ts index de964220b0..ee1b2f9bf4 100644 --- a/packages/rfq-indexer/api/src/routes/disputesRoute.ts +++ b/packages/rfq-indexer/api/src/routes/disputesRoute.ts @@ -41,7 +41,7 @@ const router = express.Router() * BridgeDispute: * type: object * description: Dispute information (if available) - * 404: + * 200: * description: No disputes found * content: * application/json: diff --git a/packages/rfq-indexer/api/src/routes/invalidRelaysRoute.ts b/packages/rfq-indexer/api/src/routes/invalidRelaysRoute.ts index 31356156f2..df20c8f60e 100644 --- a/packages/rfq-indexer/api/src/routes/invalidRelaysRoute.ts +++ b/packages/rfq-indexer/api/src/routes/invalidRelaysRoute.ts @@ -41,7 +41,7 @@ const router = express.Router() * BridgeDispute: * type: object * description: Dispute information (if available) - * 404: + * 200: * description: No recent invalid relays found * content: * application/json: diff --git a/packages/rfq-indexer/api/src/routes/pendingTransactionsRoute.ts b/packages/rfq-indexer/api/src/routes/pendingTransactionsRoute.ts index e80dbd7fac..5bd3f0ba1a 100644 --- a/packages/rfq-indexer/api/src/routes/pendingTransactionsRoute.ts +++ b/packages/rfq-indexer/api/src/routes/pendingTransactionsRoute.ts @@ -46,7 +46,7 @@ const router = express.Router() * BridgeDispute: * type: object * description: Dispute information (if available) - * 404: + * 200: * description: No pending transactions missing claim found * content: * application/json: @@ -87,7 +87,7 @@ router.get('/missing-claim', pendingTransactionsMissingClaimController) * type: object * relay: * type: object - * 404: + * 200: * description: No pending transactions missing proof found * content: * application/json: @@ -126,7 +126,7 @@ router.get('/missing-proof', pendingTransactionsMissingProofController) * properties: * deposit: * type: object - * 404: + * 200: * description: No pending transactions missing relay found * content: * application/json: @@ -165,7 +165,7 @@ router.get('/missing-relay', pendingTransactionsMissingRelayController) * properties: * deposit: * type: object - * 404: + * 200: * description: No pending transactionst that exceed the deadline found * content: * application/json: diff --git a/packages/rfq-indexer/api/src/routes/refundedAndRelayedRoute.ts b/packages/rfq-indexer/api/src/routes/refundedAndRelayedRoute.ts index cd38b3b33e..ab54203ed7 100644 --- a/packages/rfq-indexer/api/src/routes/refundedAndRelayedRoute.ts +++ b/packages/rfq-indexer/api/src/routes/refundedAndRelayedRoute.ts @@ -41,7 +41,7 @@ const router = express.Router() * BridgeDispute: * type: object * description: Dispute information (if available) - * 404: + * 200: * description: No refunded and relayed transactions found * content: * application/json: diff --git a/packages/rfq-indexer/api/src/routes/transactionIdRoute.ts b/packages/rfq-indexer/api/src/routes/transactionIdRoute.ts index 253060dcdf..0a654e8d30 100644 --- a/packages/rfq-indexer/api/src/routes/transactionIdRoute.ts +++ b/packages/rfq-indexer/api/src/routes/transactionIdRoute.ts @@ -46,7 +46,7 @@ const router = express.Router() * BridgeDispute: * type: object * description: Dispute information (if available) - * 404: + * 200: * description: Transaction not found * content: * application/json: diff --git a/packages/rfq-indexer/api/src/types/index.ts b/packages/rfq-indexer/api/src/types/index.ts index 738e9469fd..503563e741 100644 --- a/packages/rfq-indexer/api/src/types/index.ts +++ b/packages/rfq-indexer/api/src/types/index.ts @@ -10,6 +10,7 @@ export interface BridgeRequestEvents { originChainId: ColumnType originChain: ColumnType sender: ColumnType + request: ColumnType originToken: ColumnType destToken: ColumnType originAmount: ColumnType diff --git a/packages/rfq-indexer/indexer/.env.example b/packages/rfq-indexer/indexer/.env.example index 33ca569c2c..b1361bc84e 100644 --- a/packages/rfq-indexer/indexer/.env.example +++ b/packages/rfq-indexer/indexer/.env.example @@ -8,7 +8,7 @@ BLAST_MAINNET_RPC= SCROLL_MAINNET_RPC= LINEA_MAINNET_RPC= BNB_MAINNET_RPC= +WORLDCHAIN_MAINNET_RPC= # (Optional) Postgres database URL. If not provided, SQLite will be used. DATABASE_URL= - \ No newline at end of file diff --git a/packages/rfq-indexer/indexer/ponder.config.ts b/packages/rfq-indexer/indexer/ponder.config.ts index df73b39d5e..2329d0e89f 100644 --- a/packages/rfq-indexer/indexer/ponder.config.ts +++ b/packages/rfq-indexer/indexer/ponder.config.ts @@ -13,6 +13,7 @@ const blastChainId = 81457 const scrollChainId = 534352 const lineaChainId = 59144 const bnbChainId = 56 +const worldchainChainId = 480 const configByChainId = { [1]: { @@ -69,6 +70,12 @@ const configByChainId = { FastBridgeV2Address: '0x5523D3c98809DdDB82C686E152F5C58B1B0fB59E', FastBridgeV2StartBlock: 40497843, // first block and new block }, + [480]: { + transport: http(process.env.WORLDCHAIN_MAINNET_RPC), + chainName: 'worldchain', + FastBridgeV2Address: '0x05C62156C7C47E76223A560210EA648De5e6B53B', + FastBridgeV2StartBlock: 4598830, // first block and new block + }, disableCache: true, } @@ -137,6 +144,14 @@ export const networkDetails = { startBlock: configByChainId[bnbChainId].FastBridgeV2StartBlock, }, }, + [worldchainChainId]: { + name: configByChainId[worldchainChainId].chainName, + FastBridgeV2: { + address: configByChainId[worldchainChainId].FastBridgeV2Address, + abi: FastBridgeV2Abi, + startBlock: configByChainId[worldchainChainId].FastBridgeV2StartBlock, + }, + }, } as Record const config = createConfig({ @@ -181,6 +196,11 @@ const config = createConfig({ transport: configByChainId[bnbChainId].transport, // disableCache: configByChainId.disableCache, }, + [configByChainId[worldchainChainId].chainName]: { + chainId: worldchainChainId, + transport: configByChainId[worldchainChainId].transport, + // disableCache: configByChainId.disableCache, + }, }, contracts: { FastBridgeV2: { @@ -217,6 +237,11 @@ const config = createConfig({ address: networkDetails[bnbChainId]?.FastBridgeV2.address, startBlock: networkDetails[bnbChainId]?.FastBridgeV2.startBlock, }, + [configByChainId[worldchainChainId].chainName]: { + address: networkDetails[worldchainChainId]?.FastBridgeV2.address, + startBlock: + networkDetails[worldchainChainId]?.FastBridgeV2.startBlock, + }, }, abi: FastBridgeV2Abi, }, diff --git a/packages/rfq-indexer/indexer/ponder.schema.ts b/packages/rfq-indexer/indexer/ponder.schema.ts index 8d32ea595c..24f80bdf00 100644 --- a/packages/rfq-indexer/indexer/ponder.schema.ts +++ b/packages/rfq-indexer/indexer/ponder.schema.ts @@ -5,6 +5,7 @@ export default createSchema((p) => ({ id: p.string(), transactionId: p.string(), sender: p.string(), + request: p.string(), originToken: p.string(), destToken: p.string(), originAmount: p.bigint().optional(), diff --git a/packages/rfq-indexer/indexer/src/index.ts b/packages/rfq-indexer/indexer/src/index.ts index 3a6a71380f..f61146c7d6 100644 --- a/packages/rfq-indexer/indexer/src/index.ts +++ b/packages/rfq-indexer/indexer/src/index.ts @@ -16,6 +16,7 @@ ponder.on('FastBridgeV2:BridgeRequested', async ({ event, context }) => { args: { transactionId, sender, + request, destChainId, originToken, destToken, @@ -33,6 +34,7 @@ ponder.on('FastBridgeV2:BridgeRequested', async ({ event, context }) => { data: { transactionId, sender: trim(sender), + request: request, originChainId: Number(chainId), originChain: getChainName(Number(chainId)), destChainId: Number(destChainId), diff --git a/packages/rfq-indexer/indexer/src/utils/chains.ts b/packages/rfq-indexer/indexer/src/utils/chains.ts index e521f65def..121f48c546 100644 --- a/packages/rfq-indexer/indexer/src/utils/chains.ts +++ b/packages/rfq-indexer/indexer/src/utils/chains.ts @@ -7,6 +7,7 @@ export const chainIdToName: { [key: number]: string } = { 534352: 'scroll', 59144: 'linea', 56: 'bnb', + 480: 'worldchain', } export const getChainName = (chainId: number): string => { diff --git a/packages/rfq-indexer/indexer/src/utils/formatAmount.ts b/packages/rfq-indexer/indexer/src/utils/formatAmount.ts index 9cbc1b2768..2ea80247e9 100644 --- a/packages/rfq-indexer/indexer/src/utils/formatAmount.ts +++ b/packages/rfq-indexer/indexer/src/utils/formatAmount.ts @@ -1,8 +1,13 @@ import { formatUnits } from 'viem' -const ETH_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' +const ADDRESSES_WITH_18_DECIMALS = [ + '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // ETH + '0x2cFc85d8E48F8EAB294be644d9E25C3030863003', // WLD + '0x163f8c2467924be0ae7b5347228cabf260318753', // WLD + '0xdC6fF44d5d932Cbd77B52E5612Ba0529DC6226F1', // WLD +].map(address => address.toLowerCase()) export function formatAmount(amount: bigint, tokenAddress: string): string { - const decimals = tokenAddress.toLowerCase() === ETH_ADDRESS.toLowerCase() ? 18 : 6 + const decimals = ADDRESSES_WITH_18_DECIMALS.includes(tokenAddress.toLowerCase()) ? 18 : 6 return formatUnits(amount, decimals) -} \ No newline at end of file +} diff --git a/services/explorer/api/server_test.go b/services/explorer/api/server_test.go index 6a5a0b27df..1125854ecb 100644 --- a/services/explorer/api/server_test.go +++ b/services/explorer/api/server_test.go @@ -35,30 +35,31 @@ func TestHandleJSONDailyStat(t *testing.T) { // nolint valueStruct := gqlClient.GetDailyStatisticsByChain{ Response: []*struct { - Date *string "json:\"date\" graphql:\"date\"" - Ethereum *float64 "json:\"ethereum\" graphql:\"ethereum\"" - Optimism *float64 "json:\"optimism\" graphql:\"optimism\"" - Cronos *float64 "json:\"cronos\" graphql:\"cronos\"" - Bsc *float64 "json:\"bsc\" graphql:\"bsc\"" - Polygon *float64 "json:\"polygon\" graphql:\"polygon\"" - Fantom *float64 "json:\"fantom\" graphql:\"fantom\"" - Boba *float64 "json:\"boba\" graphql:\"boba\"" - Metis *float64 "json:\"metis\" graphql:\"metis\"" - Moonbeam *float64 "json:\"moonbeam\" graphql:\"moonbeam\"" - Moonriver *float64 "json:\"moonriver\" graphql:\"moonriver\"" - Klaytn *float64 "json:\"klaytn\" graphql:\"klaytn\"" - Arbitrum *float64 "json:\"arbitrum\" graphql:\"arbitrum\"" - Avalanche *float64 "json:\"avalanche\" graphql:\"avalanche\"" - Dfk *float64 "json:\"dfk\" graphql:\"dfk\"" - Aurora *float64 "json:\"aurora\" graphql:\"aurora\"" - Harmony *float64 "json:\"harmony\" graphql:\"harmony\"" - Canto *float64 "json:\"canto\" graphql:\"canto\"" - Dogechain *float64 "json:\"dogechain\" graphql:\"dogechain\"" - Base *float64 "json:\"base\" graphql:\"base\"" - Blast *float64 "json:\"blast\" graphql:\"blast\"" - Scroll *float64 "json:\"scroll\" graphql:\"scroll\"" - Linea *float64 "json:\"linea\" graphql:\"linea\"" - Total *float64 "json:\"total\" graphql:\"total\"" + Date *string "json:\"date\" graphql:\"date\"" + Ethereum *float64 "json:\"ethereum\" graphql:\"ethereum\"" + Optimism *float64 "json:\"optimism\" graphql:\"optimism\"" + Cronos *float64 "json:\"cronos\" graphql:\"cronos\"" + Bsc *float64 "json:\"bsc\" graphql:\"bsc\"" + Polygon *float64 "json:\"polygon\" graphql:\"polygon\"" + Fantom *float64 "json:\"fantom\" graphql:\"fantom\"" + Boba *float64 "json:\"boba\" graphql:\"boba\"" + Metis *float64 "json:\"metis\" graphql:\"metis\"" + Moonbeam *float64 "json:\"moonbeam\" graphql:\"moonbeam\"" + Moonriver *float64 "json:\"moonriver\" graphql:\"moonriver\"" + Klaytn *float64 "json:\"klaytn\" graphql:\"klaytn\"" + Arbitrum *float64 "json:\"arbitrum\" graphql:\"arbitrum\"" + Avalanche *float64 "json:\"avalanche\" graphql:\"avalanche\"" + Dfk *float64 "json:\"dfk\" graphql:\"dfk\"" + Aurora *float64 "json:\"aurora\" graphql:\"aurora\"" + Harmony *float64 "json:\"harmony\" graphql:\"harmony\"" + Canto *float64 "json:\"canto\" graphql:\"canto\"" + Dogechain *float64 "json:\"dogechain\" graphql:\"dogechain\"" + Base *float64 "json:\"base\" graphql:\"base\"" + Blast *float64 "json:\"blast\" graphql:\"blast\"" + Scroll *float64 "json:\"scroll\" graphql:\"scroll\"" + Linea *float64 "json:\"linea\" graphql:\"linea\"" + Worldchain *float64 "json:\"worldchain\" graphql:\"worldchain\"" + Total *float64 "json:\"total\" graphql:\"total\"" }{ { Total: &valueFloat, diff --git a/services/explorer/consumer/parser/rfqparser.go b/services/explorer/consumer/parser/rfqparser.go index 65da0f5bd7..8342b7efb1 100644 --- a/services/explorer/consumer/parser/rfqparser.go +++ b/services/explorer/consumer/parser/rfqparser.go @@ -120,6 +120,11 @@ func (p *RFQParser) MatureLogs(ctx context.Context, rfqEvent *model.RFQEvent, iF rfqEvent.TokenDecimal = new(uint8) *rfqEvent.TokenDecimal = 18 curCoinGeckoID = ethCoinGeckoID + } else if strings.EqualFold(tokenAddressStr, "0x2cFc85d8E48F8EAB294be644d9E25C3030863003") || strings.EqualFold(tokenAddressStr, "0xdC6fF44d5d932Cbd77B52E5612Ba0529DC6226F1") { + rfqEvent.TokenSymbol = "WLD" + rfqEvent.TokenDecimal = new(uint8) + *rfqEvent.TokenDecimal = 18 + curCoinGeckoID = "worldchain" } else { // Assuming any other token is USDC rfqEvent.TokenSymbol = "USDC" diff --git a/services/explorer/consumer/parser/tokendata/cache.go b/services/explorer/consumer/parser/tokendata/cache.go index ab52591c94..aaf76877e2 100644 --- a/services/explorer/consumer/parser/tokendata/cache.go +++ b/services/explorer/consumer/parser/tokendata/cache.go @@ -287,5 +287,10 @@ var tokenDataMap = map[string]TokenData{ "8453_0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb": {"DAI", 18, "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb"}, "1_0xAdF7C35560035944e805D98fF17d58CDe2449389": {"SPEC", 18, "0xAdF7C35560035944e805D98fF17d58CDe2449389"}, "8453_0x96419929d7949D6A801A6909c145C8EEf6A40431": {"SPEC", 18, "0x96419929d7949D6A801A6909c145C8EEf6A40431"}, - // Add additional tokens that are not part of the cache yet (and not by nature in bridge config) here + "480_0x2cFc85d8E48F8EAB294be644d9E25C3030863003": {"WLD", 18, "0x2cFc85d8E48F8EAB294be644d9E25C3030863003"}, + "10_0xdC6fF44d5d932Cbd77B52E5612Ba0529DC6226F1": {"WLD", 18, "0xdC6fF44d5d932Cbd77B52E5612Ba0529DC6226F1"}, + "480_0x79A02482A880bCE3F13e09Da970dC34db4CD24d1": {"USDC.e", 6, "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1"}, + "480_0x4200000000000000000000000000000000000006": {"WETH", 18, "0x4200000000000000000000000000000000000006"}, + "1_0x163f8c2467924be0ae7b5347228cabf260318753": {"WLD", 18, "0x163f8c2467924be0ae7b5347228cabf260318753"}, + // Add additional tokens that are not part of the cache yet (and is not by nature in bridge config) here } diff --git a/services/explorer/graphql/client/client.go b/services/explorer/graphql/client/client.go index 04f5498f6d..704c82de6b 100644 --- a/services/explorer/graphql/client/client.go +++ b/services/explorer/graphql/client/client.go @@ -106,30 +106,31 @@ type GetAmountStatistic struct { } type GetDailyStatisticsByChain struct { Response []*struct { - Date *string "json:\"date\" graphql:\"date\"" - Ethereum *float64 "json:\"ethereum\" graphql:\"ethereum\"" - Optimism *float64 "json:\"optimism\" graphql:\"optimism\"" - Cronos *float64 "json:\"cronos\" graphql:\"cronos\"" - Bsc *float64 "json:\"bsc\" graphql:\"bsc\"" - Polygon *float64 "json:\"polygon\" graphql:\"polygon\"" - Fantom *float64 "json:\"fantom\" graphql:\"fantom\"" - Boba *float64 "json:\"boba\" graphql:\"boba\"" - Metis *float64 "json:\"metis\" graphql:\"metis\"" - Moonbeam *float64 "json:\"moonbeam\" graphql:\"moonbeam\"" - Moonriver *float64 "json:\"moonriver\" graphql:\"moonriver\"" - Klaytn *float64 "json:\"klaytn\" graphql:\"klaytn\"" - Arbitrum *float64 "json:\"arbitrum\" graphql:\"arbitrum\"" - Avalanche *float64 "json:\"avalanche\" graphql:\"avalanche\"" - Dfk *float64 "json:\"dfk\" graphql:\"dfk\"" - Aurora *float64 "json:\"aurora\" graphql:\"aurora\"" - Harmony *float64 "json:\"harmony\" graphql:\"harmony\"" - Canto *float64 "json:\"canto\" graphql:\"canto\"" - Dogechain *float64 "json:\"dogechain\" graphql:\"dogechain\"" - Base *float64 "json:\"base\" graphql:\"base\"" - Blast *float64 "json:\"blast\" graphql:\"blast\"" - Scroll *float64 "json:\"scroll\" graphql:\"scroll\"" - Linea *float64 "json:\"linea\" graphql:\"linea\"" - Total *float64 "json:\"total\" graphql:\"total\"" + Date *string "json:\"date\" graphql:\"date\"" + Ethereum *float64 "json:\"ethereum\" graphql:\"ethereum\"" + Optimism *float64 "json:\"optimism\" graphql:\"optimism\"" + Cronos *float64 "json:\"cronos\" graphql:\"cronos\"" + Bsc *float64 "json:\"bsc\" graphql:\"bsc\"" + Polygon *float64 "json:\"polygon\" graphql:\"polygon\"" + Fantom *float64 "json:\"fantom\" graphql:\"fantom\"" + Boba *float64 "json:\"boba\" graphql:\"boba\"" + Metis *float64 "json:\"metis\" graphql:\"metis\"" + Moonbeam *float64 "json:\"moonbeam\" graphql:\"moonbeam\"" + Moonriver *float64 "json:\"moonriver\" graphql:\"moonriver\"" + Klaytn *float64 "json:\"klaytn\" graphql:\"klaytn\"" + Arbitrum *float64 "json:\"arbitrum\" graphql:\"arbitrum\"" + Avalanche *float64 "json:\"avalanche\" graphql:\"avalanche\"" + Dfk *float64 "json:\"dfk\" graphql:\"dfk\"" + Aurora *float64 "json:\"aurora\" graphql:\"aurora\"" + Harmony *float64 "json:\"harmony\" graphql:\"harmony\"" + Canto *float64 "json:\"canto\" graphql:\"canto\"" + Dogechain *float64 "json:\"dogechain\" graphql:\"dogechain\"" + Base *float64 "json:\"base\" graphql:\"base\"" + Blast *float64 "json:\"blast\" graphql:\"blast\"" + Scroll *float64 "json:\"scroll\" graphql:\"scroll\"" + Linea *float64 "json:\"linea\" graphql:\"linea\"" + Worldchain *float64 "json:\"worldchain\" graphql:\"worldchain\"" + Total *float64 "json:\"total\" graphql:\"total\"" } "json:\"response\" graphql:\"response\"" } type GetMessageBusTransactions struct { @@ -502,6 +503,7 @@ const GetDailyStatisticsByChainDocument = `query GetDailyStatisticsByChain ($cha blast scroll linea + worldchain total } } diff --git a/services/explorer/graphql/client/queries/queries.graphql b/services/explorer/graphql/client/queries/queries.graphql index 60eb3d63eb..3d351f3cc1 100644 --- a/services/explorer/graphql/client/queries/queries.graphql +++ b/services/explorer/graphql/client/queries/queries.graphql @@ -155,6 +155,7 @@ query GetDailyStatisticsByChain($chainID: Int, $type: DailyStatisticType, $durat blast scroll linea + worldchain total } } diff --git a/services/explorer/graphql/server/graph/model/models_gen.go b/services/explorer/graphql/server/graph/model/models_gen.go index d3eeb60af4..c090c167e4 100644 --- a/services/explorer/graphql/server/graph/model/models_gen.go +++ b/services/explorer/graphql/server/graph/model/models_gen.go @@ -81,30 +81,31 @@ type DateResult struct { // DateResult is a given statistic for a given date. type DateResultByChain struct { - Date *string `json:"date,omitempty"` - Ethereum *float64 `json:"ethereum,omitempty"` - Optimism *float64 `json:"optimism,omitempty"` - Cronos *float64 `json:"cronos,omitempty"` - Bsc *float64 `json:"bsc,omitempty"` - Polygon *float64 `json:"polygon,omitempty"` - Fantom *float64 `json:"fantom,omitempty"` - Boba *float64 `json:"boba,omitempty"` - Metis *float64 `json:"metis,omitempty"` - Moonbeam *float64 `json:"moonbeam,omitempty"` - Moonriver *float64 `json:"moonriver,omitempty"` - Klaytn *float64 `json:"klaytn,omitempty"` - Arbitrum *float64 `json:"arbitrum,omitempty"` - Avalanche *float64 `json:"avalanche,omitempty"` - Dfk *float64 `json:"dfk,omitempty"` - Aurora *float64 `json:"aurora,omitempty"` - Harmony *float64 `json:"harmony,omitempty"` - Canto *float64 `json:"canto,omitempty"` - Dogechain *float64 `json:"dogechain,omitempty"` - Base *float64 `json:"base,omitempty"` - Blast *float64 `json:"blast,omitempty"` - Scroll *float64 `json:"scroll,omitempty"` - Linea *float64 `json:"linea,omitempty"` - Total *float64 `json:"total,omitempty"` + Date *string `json:"date,omitempty"` + Ethereum *float64 `json:"ethereum,omitempty"` + Optimism *float64 `json:"optimism,omitempty"` + Cronos *float64 `json:"cronos,omitempty"` + Bsc *float64 `json:"bsc,omitempty"` + Polygon *float64 `json:"polygon,omitempty"` + Fantom *float64 `json:"fantom,omitempty"` + Boba *float64 `json:"boba,omitempty"` + Metis *float64 `json:"metis,omitempty"` + Moonbeam *float64 `json:"moonbeam,omitempty"` + Moonriver *float64 `json:"moonriver,omitempty"` + Klaytn *float64 `json:"klaytn,omitempty"` + Arbitrum *float64 `json:"arbitrum,omitempty"` + Avalanche *float64 `json:"avalanche,omitempty"` + Dfk *float64 `json:"dfk,omitempty"` + Aurora *float64 `json:"aurora,omitempty"` + Harmony *float64 `json:"harmony,omitempty"` + Canto *float64 `json:"canto,omitempty"` + Dogechain *float64 `json:"dogechain,omitempty"` + Base *float64 `json:"base,omitempty"` + Blast *float64 `json:"blast,omitempty"` + Scroll *float64 `json:"scroll,omitempty"` + Linea *float64 `json:"linea,omitempty"` + Worldchain *float64 `json:"worldchain,omitempty"` + Total *float64 `json:"total,omitempty"` } type HeroType struct { diff --git a/services/explorer/graphql/server/graph/partials.go b/services/explorer/graphql/server/graph/partials.go index 85519e442b..da436a4096 100644 --- a/services/explorer/graphql/server/graph/partials.go +++ b/services/explorer/graphql/server/graph/partials.go @@ -401,6 +401,7 @@ const dailyVolumeBridgeMvPt1 = ` results[81457] AS blast, results[534352] AS scroll, results[59144] AS linea, + results[480] AS worldchain, arraySum(mapValues(results)) AS total FROM (SELECT date, maxMap(map(chain_id, total)) AS results FROM (SELECT coalesce(toString(b.date), toString(s.date)) AS date, @@ -445,6 +446,7 @@ const dailyVolumeBridge = ` results[81457] AS blast, results[534352] AS scroll, results[59144] AS linea, + results[480] AS worldchain, arraySum(mapValues(results)) AS total FROM (SELECT date, maxMap(map(chain_id, total)) AS results FROM (SELECT coalesce(toString(b.date), toString(s.date)) AS date, @@ -541,6 +543,7 @@ SELECT date, results[81457] AS blast, results[534352] AS scroll, results[59144] AS linea, + results[480] AS worldchain, arraySum(mapValues(results)) AS total FROM (SELECT date, maxMap(map(chain_id, total)) AS results FROM (SELECT coalesce(toString(b.date), toString(s.date), toString(m.date)) AS date, @@ -643,6 +646,7 @@ SELECT date, results[81457] AS blast, results[534352] AS scroll, results[59144] AS linea, + results[480] AS worldchain, arraySum(mapValues(results)) AS total FROM ( SELECT date, @@ -674,6 +678,7 @@ SELECT date, results[81457] AS blast, results[534352] AS scroll, results[59144] AS linea, + results[480] AS worldchain, arraySum(mapValues(results)) AS total FROM ( SELECT date, @@ -706,6 +711,7 @@ SELECT date, results[81457] AS blast, results[534352] AS scroll, results[59144] AS linea, + results[480] AS worldchain, arraySum(mapValues(results)) AS total FROM ( SELECT date, diff --git a/services/explorer/graphql/server/graph/resolver/server.go b/services/explorer/graphql/server/graph/resolver/server.go index 8abe58001c..29689da5c4 100644 --- a/services/explorer/graphql/server/graph/resolver/server.go +++ b/services/explorer/graphql/server/graph/resolver/server.go @@ -100,30 +100,31 @@ type ComplexityRoot struct { } DateResultByChain struct { - Arbitrum func(childComplexity int) int - Aurora func(childComplexity int) int - Avalanche func(childComplexity int) int - Base func(childComplexity int) int - Blast func(childComplexity int) int - Boba func(childComplexity int) int - Bsc func(childComplexity int) int - Canto func(childComplexity int) int - Cronos func(childComplexity int) int - Date func(childComplexity int) int - Dfk func(childComplexity int) int - Dogechain func(childComplexity int) int - Ethereum func(childComplexity int) int - Fantom func(childComplexity int) int - Harmony func(childComplexity int) int - Klaytn func(childComplexity int) int - Linea func(childComplexity int) int - Metis func(childComplexity int) int - Moonbeam func(childComplexity int) int - Moonriver func(childComplexity int) int - Optimism func(childComplexity int) int - Polygon func(childComplexity int) int - Scroll func(childComplexity int) int - Total func(childComplexity int) int + Arbitrum func(childComplexity int) int + Aurora func(childComplexity int) int + Avalanche func(childComplexity int) int + Base func(childComplexity int) int + Blast func(childComplexity int) int + Boba func(childComplexity int) int + Bsc func(childComplexity int) int + Canto func(childComplexity int) int + Cronos func(childComplexity int) int + Date func(childComplexity int) int + Dfk func(childComplexity int) int + Dogechain func(childComplexity int) int + Ethereum func(childComplexity int) int + Fantom func(childComplexity int) int + Harmony func(childComplexity int) int + Klaytn func(childComplexity int) int + Linea func(childComplexity int) int + Metis func(childComplexity int) int + Moonbeam func(childComplexity int) int + Moonriver func(childComplexity int) int + Optimism func(childComplexity int) int + Polygon func(childComplexity int) int + Scroll func(childComplexity int) int + Total func(childComplexity int) int + Worldchain func(childComplexity int) int } HeroType struct { @@ -660,6 +661,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.DateResultByChain.Total(childComplexity), true + case "DateResultByChain.worldchain": + if e.complexity.DateResultByChain.Worldchain == nil { + break + } + + return e.complexity.DateResultByChain.Worldchain(childComplexity), true + case "HeroType.heroID": if e.complexity.HeroType.HeroID == nil { break @@ -1664,6 +1672,7 @@ type DateResultByChain { blast: Float scroll: Float linea: Float + worldchain: Float total: Float } @@ -4832,6 +4841,47 @@ func (ec *executionContext) fieldContext_DateResultByChain_linea(ctx context.Con return fc, nil } +func (ec *executionContext) _DateResultByChain_worldchain(ctx context.Context, field graphql.CollectedField, obj *model.DateResultByChain) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DateResultByChain_worldchain(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Worldchain, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*float64) + fc.Result = res + return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DateResultByChain_worldchain(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DateResultByChain", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Float does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _DateResultByChain_total(ctx context.Context, field graphql.CollectedField, obj *model.DateResultByChain) (ret graphql.Marshaler) { fc, err := ec.fieldContext_DateResultByChain_total(ctx, field) if err != nil { @@ -7190,6 +7240,8 @@ func (ec *executionContext) fieldContext_Query_dailyStatisticsByChain(ctx contex return ec.fieldContext_DateResultByChain_scroll(ctx, field) case "linea": return ec.fieldContext_DateResultByChain_linea(ctx, field) + case "worldchain": + return ec.fieldContext_DateResultByChain_worldchain(ctx, field) case "total": return ec.fieldContext_DateResultByChain_total(ctx, field) } @@ -10434,6 +10486,8 @@ func (ec *executionContext) _DateResultByChain(ctx context.Context, sel ast.Sele out.Values[i] = ec._DateResultByChain_scroll(ctx, field, obj) case "linea": out.Values[i] = ec._DateResultByChain_linea(ctx, field, obj) + case "worldchain": + out.Values[i] = ec._DateResultByChain_worldchain(ctx, field, obj) case "total": out.Values[i] = ec._DateResultByChain_total(ctx, field, obj) default: diff --git a/services/explorer/graphql/server/graph/schema/types.graphql b/services/explorer/graphql/server/graph/schema/types.graphql index 9b8bebdbd0..084c2da036 100644 --- a/services/explorer/graphql/server/graph/schema/types.graphql +++ b/services/explorer/graphql/server/graph/schema/types.graphql @@ -180,6 +180,7 @@ type DateResultByChain { blast: Float scroll: Float linea: Float + worldchain: Float total: Float } diff --git a/services/explorer/static/chainIDs.yaml b/services/explorer/static/chainIDs.yaml index 84add22b21..250c5db0a6 100644 --- a/services/explorer/static/chainIDs.yaml +++ b/services/explorer/static/chainIDs.yaml @@ -19,3 +19,4 @@ 81457: 'Blast' 534352: 'Scroll' 59144: 'Linea' +480: 'Worldchain' diff --git a/services/explorer/static/tokenIDToCoinGeckoID.yaml b/services/explorer/static/tokenIDToCoinGeckoID.yaml index 6c618da717..574cc7edc8 100644 --- a/services/explorer/static/tokenIDToCoinGeckoID.yaml +++ b/services/explorer/static/tokenIDToCoinGeckoID.yaml @@ -40,3 +40,4 @@ USDbC: 'usd-coin' crvUSD: 'usd-coin' USDB: 'usdb' SPEC: 'spectral' +WLD: 'worldcoin' diff --git a/services/explorer/static/tokenSymbolToCoinGeckoID.yaml b/services/explorer/static/tokenSymbolToCoinGeckoID.yaml index 231aa8ef74..6288259e8d 100644 --- a/services/explorer/static/tokenSymbolToCoinGeckoID.yaml +++ b/services/explorer/static/tokenSymbolToCoinGeckoID.yaml @@ -40,3 +40,4 @@ usdbc: 'usd-coin' crvusd: 'usd-coin' usdb: 'usdb' spec: 'spectral' +wld: 'worldcoin' diff --git a/services/explorer/static/tokenSymbolToTokenID.yaml b/services/explorer/static/tokenSymbolToTokenID.yaml index 83bac16402..eaf7f01c7d 100644 --- a/services/explorer/static/tokenSymbolToTokenID.yaml +++ b/services/explorer/static/tokenSymbolToTokenID.yaml @@ -42,3 +42,4 @@ usdbc: 'usd-coin' crvusd: 'usd-coin' usdb: 'usdb' spec: 'spectral' +wld: 'worldcoin' diff --git a/services/rfq/relayer/quoter/quoter_test.go b/services/rfq/relayer/quoter/quoter_test.go index d9a4822f8b..a2eb400499 100644 --- a/services/rfq/relayer/quoter/quoter_test.go +++ b/services/rfq/relayer/quoter/quoter_test.go @@ -258,6 +258,30 @@ func (s *QuoterSuite) TestGetOriginAmount() { 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(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(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(quoteParams{ quotePct: 25,