From 74708134c8a3bff0beda7c198907dd9b4baaf74c Mon Sep 17 00:00:00 2001 From: mpetrun5 Date: Wed, 13 May 2026 16:23:58 +0200 Subject: [PATCH 1/5] Use processor or repayer depending on unlock params --- Makefile | 1 + api/handlers/unlock.go | 33 ++++++----- api/handlers/unlock_test.go | 9 ++- app/app.go | 44 ++++++++------ chains/evm/config.go | 8 +++ chains/evm/config_test.go | 11 ++++ chains/evm/message/message.go | 8 ++- chains/evm/message/mock/unlock.go | 97 +++++++++++++++++++++++++++++++ chains/evm/message/unlock.go | 89 +++++++++++++++++++++++----- chains/evm/message/unlock_test.go | 97 ++++++++++++++++++++++++++++--- go.mod | 4 +- go.sum | 8 +-- 12 files changed, 346 insertions(+), 63 deletions(-) create mode 100644 chains/evm/message/mock/unlock.go diff --git a/Makefile b/Makefile index 8e2cc586..3b60345a 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ genmocks: mockgen -destination=./comm/p2p/mock/stream/stream.go github.com/libp2p/go-libp2p/core/network Stream,Conn mockgen -source=./chains/evm/message/across.go -destination=./chains/evm/message/mock/across.go mockgen -source=./chains/evm/message/lifiEscrow.go -destination=./chains/evm/message/mock/lifiEscrow.go + mockgen -source=./chains/evm/message/unlock.go -destination=./chains/evm/message/mock/unlock.go mockgen -source=./chains/evm/message/confirmations.go -destination=./chains/evm/message/mock/confirmations.go mockgen -source=./api/handlers/signing.go -destination=./api/handlers/mock/signing.go mockgen -package mock_message -destination=./chains/evm/message/mock/pricing.go github.com/sprintertech/lifi-solver/pkg/pricing OrderPricer diff --git a/api/handlers/unlock.go b/api/handlers/unlock.go index 29bb64ec..113ff0ac 100644 --- a/api/handlers/unlock.go +++ b/api/handlers/unlock.go @@ -18,15 +18,17 @@ import ( const SIGNATURE_TIMEOUT = time.Second * 15 type UnlockResponse struct { - Signature string `json:"signature"` - ID string `json:"id"` + Signature string `json:"signature"` + RepaymentAddress string `json:"repaymentAddress"` + ID string `json:"id"` } type UnlockBody struct { - ChainId uint64 - Protocol ProtocolType `json:"protocol"` - OrderID string `json:"orderId"` - Settler string `json:"settler"` + ChainId uint64 + Protocol ProtocolType `json:"protocol"` + OrderID string `json:"orderId"` + Settler string `json:"settler"` + BorrowToken string `json:"borrowToken"` } type UnlockHandler struct { @@ -60,16 +62,19 @@ func (h *UnlockHandler) HandleUnlock(w http.ResponseWriter, r *http.Request) { } sigChn := make(chan interface{}, 1) + repaymentAddressChn := make(chan common.Hash, 1) var m *message.Message switch b.Protocol { case LifiEscrowProtocol: { m = evmMessage.NewLifiUnlockMessage(0, b.ChainId, &evmMessage.LifiUnlockData{ - Source: 0, - Destination: b.ChainId, - SigChn: sigChn, - OrderID: b.OrderID, - Settler: common.HexToAddress(b.Settler), + Source: 0, + Destination: b.ChainId, + SigChn: sigChn, + OrderID: b.OrderID, + Settler: common.HexToAddress(b.Settler), + RepaymentAddressChn: repaymentAddressChn, + BorrowToken: b.BorrowToken, }) } default: @@ -85,6 +90,7 @@ func (h *UnlockHandler) HandleUnlock(w http.ResponseWriter, r *http.Request) { return case sig := <-sigChn: { + repaymentAddress := <-repaymentAddressChn sig, ok := sig.(signing.EcdsaSignature) if !ok { JSONError(w, fmt.Errorf("invalid signature"), http.StatusInternalServerError) @@ -92,8 +98,9 @@ func (h *UnlockHandler) HandleUnlock(w http.ResponseWriter, r *http.Request) { } data, _ := json.Marshal(UnlockResponse{ - Signature: hex.EncodeToString(sig.Signature), - ID: sig.ID, + Signature: hex.EncodeToString(sig.Signature), + ID: sig.ID, + RepaymentAddress: repaymentAddress.Hex(), }) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) diff --git a/api/handlers/unlock_test.go b/api/handlers/unlock_test.go index 837a563e..a6d86989 100644 --- a/api/handlers/unlock_test.go +++ b/api/handlers/unlock_test.go @@ -9,6 +9,7 @@ import ( "net/http/httptest" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/gorilla/mux" "github.com/sprintertech/sprinter-signing/api/handlers" across "github.com/sprintertech/sprinter-signing/chains/evm/message" @@ -117,6 +118,7 @@ func (s *UnlockHandlerTestSuite) Test_HandleUnlock_ValidRequest() { Signature: sigBytes, ID: "id", } + ad.RepaymentAddressChn <- common.HexToHash("0x1") }() handler.HandleUnlock(recorder, req) @@ -125,5 +127,10 @@ func (s *UnlockHandlerTestSuite) Test_HandleUnlock_ValidRequest() { data, err := io.ReadAll(recorder.Body) s.Nil(err) - s.Equal(string(data), "{\"signature\":\"abcd\",\"id\":\"id\"}") + s.Equal( + string(data), + "{\"signature\":\"abcd\","+ + "\"repaymentAddress\":\"0x0000000000000000000000000000000000000000000000000000000000000001\","+ + "\"id\":\"id\"}", + ) } diff --git a/app/app.go b/app/app.go index b77b1a69..f7e38150 100644 --- a/app/app.go +++ b/app/app.go @@ -167,6 +167,15 @@ func Run() error { mpcAddress = common.HexToAddress(solverConfig.ProtocolsMetadata.Sprinter.MpcAddress) } + usdPricer := pyth.NewClient(ctx) + err = usdPricer.Start(ctx) + panicOnError(err) + + lifiConfig, err := lifiConfig.GetSolverConfig(solverConfig, protocols.LifiEscrow, lifiConfig.PulsarSolver) + panicOnError(err) + + resolver := token.NewTokenResolver(solverConfig, usdPricer) + var hubPoolContract across.TokenMatcher acrossPools := make(map[uint64]common.Address) lifiOutputSettlers := make(map[uint64]common.Address) @@ -271,14 +280,6 @@ func Run() error { } if c.LifiOutputSettler != "" { - usdPricer := pyth.NewClient(ctx) - err = usdPricer.Start(ctx) - panicOnError(err) - - lifiConfig, err := lifiConfig.GetSolverConfig(solverConfig, protocols.LifiEscrow, lifiConfig.PulsarSolver) - panicOnError(err) - - resolver := token.NewTokenResolver(solverConfig, usdPricer) orderPricer := pricing.NewStandardPricer(resolver) lifiApi := lifi.NewLifiEventFetcher( client, @@ -325,16 +326,23 @@ func Run() error { ) } - lifiUnlockMh := evmMessage.NewLifiUnlockHandler( - *c.GeneralChainConfig.Id, - repayerAddresses, - coordinator, - host, - communication, - keyshareStore, - ) - go lifiUnlockMh.Listen(ctx) - mh.RegisterMessageHandler(message.MessageType(comm.LifiUnlockMsg.String()), lifiUnlockMh) + repayer, ok := repayerAddresses[*c.GeneralChainConfig.Id] + if ok { + lifiAPI := lifi.NewLifiAPI() + lifiUnlockMh := evmMessage.NewLifiUnlockHandler( + *c.GeneralChainConfig.Id, + repayer, + c.Processors, + lifiAPI, + resolver, + coordinator, + host, + communication, + keyshareStore, + ) + go lifiUnlockMh.Listen(ctx) + mh.RegisterMessageHandler(message.MessageType(comm.LifiUnlockMsg.String()), lifiUnlockMh) + } var startBlock *big.Int var listener *coreListener.EVMListener diff --git a/chains/evm/config.go b/chains/evm/config.go index 439b462a..68787bd3 100644 --- a/chains/evm/config.go +++ b/chains/evm/config.go @@ -6,6 +6,7 @@ package evm import ( "fmt" "math/big" + "strings" "time" "github.com/creasty/defaults" @@ -28,6 +29,7 @@ type EVMConfig struct { LifiOutputSettler string LifiInputSettlerEscrow string Repayer string + Processors map[string]common.Address // Liquidator contract per token address Liquidators map[common.Address]common.Address @@ -97,6 +99,11 @@ func NewEVMConfig(chainConfig map[string]interface{}, solverConfig solverConfig. } } + processors := make(map[string]common.Address) + for symbol, processor := range solverConfig.ProtocolsMetadata.Lifi.RepaymentProcessors[id] { + processors[strings.ToLower(symbol)] = common.HexToAddress(processor.Address) + } + confirmations := make(map[uint64]uint64) for _, confirmation := range sc.Confirmations { // nolint:gosec @@ -115,6 +122,7 @@ func NewEVMConfig(chainConfig map[string]interface{}, solverConfig solverConfig. GeneralChainConfig: c.GeneralChainConfig, Admin: c.Admin, Repayer: solverConfig.ProtocolsMetadata.Sprinter.Repayer[id], + Processors: processors, AcrossPool: solverConfig.ProtocolsMetadata.Across.SpokePools[id], AcrossHubPool: solverConfig.ProtocolsMetadata.Across.HubPools[id], LifiOutputSettler: solverConfig.ProtocolsMetadata.Lifi.OutputSettler, diff --git a/chains/evm/config_test.go b/chains/evm/config_test.go index 7e15f933..681f3763 100644 --- a/chains/evm/config_test.go +++ b/chains/evm/config_test.go @@ -143,6 +143,7 @@ func (s *NewEVMConfigTestSuite) Test_ValidConfig() { Liquidators: map[common.Address]common.Address{ common.HexToAddress("0x12"): common.HexToAddress(liquidator), }, + Processors: make(map[string]common.Address), }) } @@ -231,10 +232,19 @@ func (s *NewEVMConfigTestSuite) Test_ValidConfigWithCustomTxParams() { }, Lifi: &solverConfig.LifiMetadata{ OutputSettler: "settler", + RepaymentProcessors: map[string]map[string]solverConfig.RepaymentProcessor{ + "eip155:1": map[string]solverConfig.RepaymentProcessor{ + "uSdC": solverConfig.RepaymentProcessor{ + Address: "0xdBBE3D8c2d2b22A2611c5A94A9a12C2fCD49Eb28", + }, + }, + }, }, }, }) + processors := make(map[string]common.Address) + processors["usdc"] = common.HexToAddress("0xdBBE3D8c2d2b22A2611c5A94A9a12C2fCD49Eb28") id := new(uint64) *id = 1 s.Nil(err) @@ -255,6 +265,7 @@ func (s *NewEVMConfigTestSuite) Test_ValidConfigWithCustomTxParams() { Repayer: "repayer", ConfirmationsByValue: expectedBlockConfirmations, Tokens: expectedTokens, + Processors: processors, Liquidators: make(map[common.Address]common.Address), }) } diff --git a/chains/evm/message/message.go b/chains/evm/message/message.go index 8b037f8a..0abea52e 100644 --- a/chains/evm/message/message.go +++ b/chains/evm/message/message.go @@ -96,10 +96,12 @@ func NewSprinterCreditMessage( } type LifiUnlockData struct { - SigChn chan interface{} `json:"-"` + SigChn chan interface{} `json:"-"` + RepaymentAddressChn chan common.Hash `json:"-"` - OrderID string - Settler common.Address + OrderID string + Settler common.Address + BorrowToken string Coordinator peer.ID Source uint64 diff --git a/chains/evm/message/mock/unlock.go b/chains/evm/message/mock/unlock.go new file mode 100644 index 00000000..ef1be219 --- /dev/null +++ b/chains/evm/message/mock/unlock.go @@ -0,0 +1,97 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./chains/evm/message/unlock.go +// +// Generated by this command: +// +// mockgen -source=./chains/evm/message/unlock.go -destination=./chains/evm/message/mock/unlock.go +// + +// Package mock_message is a generated GoMock package. +package mock_message + +import ( + reflect "reflect" + + order "github.com/sprintertech/lifi-solver/pkg/order" + lifi "github.com/sprintertech/lifi-solver/pkg/protocols/lifi" + token "github.com/sprintertech/lifi-solver/pkg/token" + gomock "go.uber.org/mock/gomock" +) + +// MockLifiAPI is a mock of LifiAPI interface. +type MockLifiAPI struct { + ctrl *gomock.Controller + recorder *MockLifiAPIMockRecorder + isgomock struct{} +} + +// MockLifiAPIMockRecorder is the mock recorder for MockLifiAPI. +type MockLifiAPIMockRecorder struct { + mock *MockLifiAPI +} + +// NewMockLifiAPI creates a new mock instance. +func NewMockLifiAPI(ctrl *gomock.Controller) *MockLifiAPI { + mock := &MockLifiAPI{ctrl: ctrl} + mock.recorder = &MockLifiAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLifiAPI) EXPECT() *MockLifiAPIMockRecorder { + return m.recorder +} + +// GetOrder mocks base method. +func (m *MockLifiAPI) GetOrder(orderID string) (*lifi.LifiOrder, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOrder", orderID) + ret0, _ := ret[0].(*lifi.LifiOrder) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOrder indicates an expected call of GetOrder. +func (mr *MockLifiAPIMockRecorder) GetOrder(orderID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrder", reflect.TypeOf((*MockLifiAPI)(nil).GetOrder), orderID) +} + +// MockTokenResolver is a mock of TokenResolver interface. +type MockTokenResolver struct { + ctrl *gomock.Controller + recorder *MockTokenResolverMockRecorder + isgomock struct{} +} + +// MockTokenResolverMockRecorder is the mock recorder for MockTokenResolver. +type MockTokenResolverMockRecorder struct { + mock *MockTokenResolver +} + +// NewMockTokenResolver creates a new mock instance. +func NewMockTokenResolver(ctrl *gomock.Controller) *MockTokenResolver { + mock := &MockTokenResolver{ctrl: ctrl} + mock.recorder = &MockTokenResolverMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTokenResolver) EXPECT() *MockTokenResolverMockRecorder { + return m.recorder +} + +// Token mocks base method. +func (m *MockTokenResolver) Token(caipID order.ChainID, address [32]byte) (token.Token, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Token", caipID, address) + ret0, _ := ret[0].(token.Token) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Token indicates an expected call of Token. +func (mr *MockTokenResolverMockRecorder) Token(caipID, address any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Token", reflect.TypeOf((*MockTokenResolver)(nil).Token), caipID, address) +} diff --git a/chains/evm/message/unlock.go b/chains/evm/message/unlock.go index 239e0804..52807530 100644 --- a/chains/evm/message/unlock.go +++ b/chains/evm/message/unlock.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math/big" + "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -13,6 +14,9 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/rs/zerolog/log" + "github.com/sprintertech/lifi-solver/pkg/order" + "github.com/sprintertech/lifi-solver/pkg/protocols/lifi" + "github.com/sprintertech/lifi-solver/pkg/token" "github.com/sprintertech/sprinter-signing/comm" "github.com/sprintertech/sprinter-signing/tss" "github.com/sprintertech/sprinter-signing/tss/ecdsa/signing" @@ -25,10 +29,21 @@ const ( VERSION = "1" ) +type LifiAPI interface { + GetOrder(orderID string) (*lifi.LifiOrder, error) +} + +type TokenResolver interface { + Token(caipID order.ChainID, address [32]byte) (token.Token, error) +} + type LifiUnlockHandler struct { chainID uint64 - repayers map[uint64]common.Address + repayer common.Address + processors map[string]common.Address + api LifiAPI + tokenResolver TokenResolver coordinator Coordinator host host.Host @@ -38,19 +53,25 @@ type LifiUnlockHandler struct { func NewLifiUnlockHandler( chainID uint64, - repayers map[uint64]common.Address, + repayer common.Address, + processors map[string]common.Address, + api LifiAPI, + tokenResolver TokenResolver, coordinator Coordinator, host host.Host, comm comm.Communication, fetcher signing.SaveDataFetcher, ) *LifiUnlockHandler { return &LifiUnlockHandler{ - chainID: chainID, - repayers: repayers, - coordinator: coordinator, - host: host, - comm: comm, - fetcher: fetcher, + chainID: chainID, + repayer: repayer, + processors: processors, + coordinator: coordinator, + host: host, + comm: comm, + fetcher: fetcher, + api: api, + tokenResolver: tokenResolver, } } @@ -62,7 +83,20 @@ func (h *LifiUnlockHandler) HandleMessage(m *message.Message) (*proposal.Proposa log.Warn().Msgf("Failed to notify relayers because of %s", err) } - unlockHash, err := h.lifiUnlockHash(data) + order, err := h.api.GetOrder(data.OrderID) + if err != nil { + return nil, err + } + + repaymentAddress, err := h.repaymentAddress( + data, + order, + ) + if err != nil { + return nil, err + } + + unlockHash, err := h.lifiUnlockHash(data, repaymentAddress) if err != nil { return nil, err } @@ -79,6 +113,7 @@ func (h *LifiUnlockHandler) HandleMessage(m *message.Message) (*proposal.Proposa return nil, err } + data.RepaymentAddressChn <- common.BytesToHash(repaymentAddress.Bytes()) err = h.coordinator.Execute(context.Background(), []tss.TssProcess{signing}, data.SigChn, data.Coordinator) if err != nil { return nil, err @@ -86,6 +121,32 @@ func (h *LifiUnlockHandler) HandleMessage(m *message.Message) (*proposal.Proposa return nil, nil } +func (h *LifiUnlockHandler) repaymentAddress( + data *LifiUnlockData, + order *lifi.LifiOrder, +) (common.Address, error) { + tokenIn, err := h.tokenResolver.Token( + order.GenericInputs[0].ChainID, + *order.GenericInputs[0].TokenAddress) + if err != nil { + return common.Address{}, err + } + + if strings.EqualFold(data.BorrowToken, tokenIn.Symbol) { + return h.repayer, nil + } + + processor, ok := h.processors[strings.ToLower(data.BorrowToken)] + if !ok { + return common.Address{}, fmt.Errorf( + "no processor specified for token %s", + data.BorrowToken, + ) + } + + return processor, nil +} + func (h *LifiUnlockHandler) Listen(ctx context.Context) { msgChn := make(chan *comm.WrappedMessage) subID := h.comm.Subscribe(fmt.Sprintf("%d-%s", h.chainID, comm.LifiUnlockSessionID), comm.LifiUnlockMsg, msgChn) @@ -136,12 +197,10 @@ func (h *LifiUnlockHandler) notify(data *LifiUnlockData) error { fmt.Sprintf("%d-%s", h.chainID, comm.LifiUnlockSessionID)) } -func (h *LifiUnlockHandler) lifiUnlockHash(data *LifiUnlockData) ([]byte, error) { - repaymentAddress, ok := h.repayers[h.chainID] - if !ok { - return nil, fmt.Errorf("invalid repayment chain %d", h.chainID) - } - +func (h *LifiUnlockHandler) lifiUnlockHash( + data *LifiUnlockData, + repaymentAddress common.Address, +) ([]byte, error) { msg := apitypes.TypedDataMessage{ "orderId": common.HexToHash(data.OrderID), "destination": common.HexToHash(repaymentAddress.Hex()), diff --git a/chains/evm/message/unlock_test.go b/chains/evm/message/unlock_test.go index b88f9763..c8ff4db5 100644 --- a/chains/evm/message/unlock_test.go +++ b/chains/evm/message/unlock_test.go @@ -7,6 +7,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" + "github.com/sprintertech/lifi-solver/pkg/order" + "github.com/sprintertech/lifi-solver/pkg/protocols/lifi" + "github.com/sprintertech/lifi-solver/pkg/token" "github.com/sprintertech/sprinter-signing/chains/evm/message" mock_message "github.com/sprintertech/sprinter-signing/chains/evm/message/mock" "github.com/sprintertech/sprinter-signing/comm" @@ -25,7 +28,9 @@ type LifiUnlockHandlerTestSuite struct { mockCommunication *mock_communication.MockCommunication mockCoordinator *mock_message.MockCoordinator mockHost *mock_host.MockHost + mockApi *mock_message.MockLifiAPI mockFetcher *mock_tss.MockSaveDataFetcher + mockTokenResolver *mock_message.MockTokenResolver handler *message.LifiUnlockHandler } @@ -37,8 +42,10 @@ func TestRunLifiUnlockHandlerTestSuite(t *testing.T) { func (s *LifiUnlockHandlerTestSuite) SetupTest() { ctrl := gomock.NewController(s.T()) - repayers := make(map[uint64]common.Address) - repayers[10] = common.HexToAddress("0x5c7BCd6E7De5423a257D81B442095A1a6ced35C6") + repayer := common.HexToAddress("0x5c7BCd6E7De5423a257D81B442095A1a6ced35C6") + + processors := make(map[string]common.Address) + processors["usdc"] = common.HexToAddress("0x5c7BCd6E7De5423a257D81B442095A1a6ced35C7") s.mockCommunication = mock_communication.NewMockCommunication(ctrl) s.mockCoordinator = mock_message.NewMockCoordinator(ctrl) @@ -56,10 +63,15 @@ func (s *LifiUnlockHandlerTestSuite) SetupTest() { comm.LifiUnlockMsg, fmt.Sprintf("%d-%s", 10, comm.LifiUnlockSessionID), ).Return(nil) + s.mockApi = mock_message.NewMockLifiAPI(ctrl) + s.mockTokenResolver = mock_message.NewMockTokenResolver(ctrl) s.handler = message.NewLifiUnlockHandler( 10, - repayers, + repayer, + processors, + s.mockApi, + s.mockTokenResolver, s.mockCoordinator, s.mockHost, s.mockCommunication, @@ -67,14 +79,37 @@ func (s *LifiUnlockHandlerTestSuite) SetupTest() { ) } -func (s *LifiUnlockHandlerTestSuite) Test_HandleMessage_ValidMessage() { +func (s *LifiUnlockHandlerTestSuite) Test_HandleMessage_BorrowTokenEqualsInputToken() { sigChn := make(chan interface{}, 1) + repaymentChan := make(chan common.Hash, 1) ad := &message.LifiUnlockData{ - SigChn: sigChn, - OrderID: "id", - Settler: common.HexToAddress("abcd"), + SigChn: sigChn, + RepaymentAddressChn: repaymentChan, + OrderID: "id", + Settler: common.HexToAddress("abcd"), + BorrowToken: "UsDc", } s.mockCoordinator.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + inputTokenAddr := common.HexToHash("0x1") + s.mockApi.EXPECT().GetOrder(gomock.Any()).Return(&lifi.LifiOrder{ + GenericInputs: []order.Input{ + { + ChainID: order.ChainID("eip155:10"), + TokenAddress: &inputTokenAddr, + }, + }, + GenericOutputs: []order.Output{ + { + ChainID: order.ChainID("eip155:8453"), + }, + }, + }, nil) + s.mockTokenResolver.EXPECT().Token(order.ChainID("eip155:10"), inputTokenAddr).Return( + token.Token{ + Symbol: "usdc", + }, + nil, + ) m := &coreMessage.Message{ Data: ad, @@ -83,7 +118,55 @@ func (s *LifiUnlockHandlerTestSuite) Test_HandleMessage_ValidMessage() { } prop, err := s.handler.HandleMessage(m) + s.Nil(prop) + s.Nil(err) + + repaymentAddress := <-repaymentChan + s.Equal(repaymentAddress, common.HexToHash("0x5c7BCd6E7De5423a257D81B442095A1a6ced35C6")) +} + +func (s *LifiUnlockHandlerTestSuite) Test_HandleMessage_BorrowTokenIsDifferent() { + sigChn := make(chan interface{}, 1) + repaymentChan := make(chan common.Hash, 1) + ad := &message.LifiUnlockData{ + SigChn: sigChn, + RepaymentAddressChn: repaymentChan, + OrderID: "id", + Settler: common.HexToAddress("abcd"), + BorrowToken: "UsDc", + } + s.mockCoordinator.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + inputTokenAddr := common.HexToHash("0x1") + s.mockApi.EXPECT().GetOrder(gomock.Any()).Return(&lifi.LifiOrder{ + GenericInputs: []order.Input{ + { + ChainID: order.ChainID("eip155:10"), + TokenAddress: &inputTokenAddr, + }, + }, + GenericOutputs: []order.Output{ + { + ChainID: order.ChainID("eip155:8453"), + }, + }, + }, nil) + s.mockTokenResolver.EXPECT().Token(order.ChainID("eip155:10"), inputTokenAddr).Return( + token.Token{ + Symbol: "usdt", + }, + nil, + ) + + m := &coreMessage.Message{ + Data: ad, + Source: 0, + Destination: 10, + } + prop, err := s.handler.HandleMessage(m) s.Nil(prop) s.Nil(err) + + repaymentAddress := <-repaymentChan + s.Equal(repaymentAddress, common.HexToHash("0x5c7BCd6E7De5423a257D81B442095A1a6ced35C7")) } diff --git a/go.mod b/go.mod index 87198692..c1a5a00d 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,8 @@ require ( github.com/rs/zerolog v1.25.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.9.0 - github.com/sprintertech/lifi-solver v1.10.1-0.20260504134943-9bcb16937adf - github.com/sprintertech/solver-config/go v0.0.0-20260420164134-699e2fffda37 + github.com/sprintertech/lifi-solver v1.12.7-0.20260512144109-aff2bb29e55a + github.com/sprintertech/solver-config/go v0.0.0-20260511094556-3a4df18de523 github.com/stretchr/testify v1.11.1 github.com/sygmaprotocol/sygma-core v0.0.0-20250304150334-bd39ac4f7b82 go.opentelemetry.io/otel v1.16.0 diff --git a/go.sum b/go.sum index f8e0314c..15c33b01 100644 --- a/go.sum +++ b/go.sum @@ -860,10 +860,10 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= -github.com/sprintertech/lifi-solver v1.10.1-0.20260504134943-9bcb16937adf h1:VJ8iwyTEL0jJm9Mz+WUbUP/N5PeDynWUFT/LwvpGRDI= -github.com/sprintertech/lifi-solver v1.10.1-0.20260504134943-9bcb16937adf/go.mod h1:fGyHGX9kbFN7AtwCAvmQF+eu75vxr6JFLESfhXHlc70= -github.com/sprintertech/solver-config/go v0.0.0-20260420164134-699e2fffda37 h1:cV00hOEI+SpQnaDyVxAjK199IbDEP5ONjTAmfg+HEb0= -github.com/sprintertech/solver-config/go v0.0.0-20260420164134-699e2fffda37/go.mod h1:MrIGW6M815PSYKtWSeOd1Z7eiSeOIk/uA/6E2PhlQVQ= +github.com/sprintertech/lifi-solver v1.12.7-0.20260512144109-aff2bb29e55a h1:1mpBylUWbaf2nNJ/EY1aP7D8DTYUHFjDpZTXLPdkScY= +github.com/sprintertech/lifi-solver v1.12.7-0.20260512144109-aff2bb29e55a/go.mod h1:fGyHGX9kbFN7AtwCAvmQF+eu75vxr6JFLESfhXHlc70= +github.com/sprintertech/solver-config/go v0.0.0-20260511094556-3a4df18de523 h1:uACOHGeT3E5lqrgb5fWN3V6ueeu9OK5LiLcQGg3eHss= +github.com/sprintertech/solver-config/go v0.0.0-20260511094556-3a4df18de523/go.mod h1:MrIGW6M815PSYKtWSeOd1Z7eiSeOIk/uA/6E2PhlQVQ= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From ff681dc5996f7f5742e54d4d1eeb79719ddf6771 Mon Sep 17 00:00:00 2001 From: mpetrun5 Date: Wed, 13 May 2026 16:27:34 +0200 Subject: [PATCH 2/5] Fix conflicts --- app/app.go | 4 ++-- go.mod | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/app.go b/app/app.go index 5cb3a9a7..ef529fa4 100644 --- a/app/app.go +++ b/app/app.go @@ -179,6 +179,7 @@ func Run() error { err = usdPricer.Start(ctx) panicOnError(err) multiPricer := aggregator.New(usdPricer) + resolver := token.NewTokenResolver(solverConfig, multiPricer) var hubPoolContract across.TokenMatcher acrossPools := make(map[uint64]common.Address) @@ -284,7 +285,6 @@ func Run() error { } if c.LifiOutputSettler != "" { - if *c.GeneralChainConfig.Id == ETHEREUM { w3Client, err := w3.Dial(c.GeneralChainConfig.Endpoint) panicOnError(err) @@ -300,7 +300,7 @@ func Run() error { ) multiPricer.Add(vaultPricer, "srRoyUSDC") } - resolver := token.NewTokenResolver(solverConfig, multiPricer) + resolver = token.NewTokenResolver(solverConfig, multiPricer) lifiConfig, err := lifiConfig.GetSolverConfig(solverConfig, protocols.LifiEscrow, lifiConfig.PulsarSolver) panicOnError(err) diff --git a/go.mod b/go.mod index c1a5a00d..cb652720 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/imdario/mergo v0.3.12 github.com/jellydator/ttlcache/v3 v3.3.0 github.com/libp2p/go-libp2p v0.46.0 + github.com/lmittmann/w3 v0.20.2 github.com/mitchellh/mapstructure v1.4.2 github.com/multiformats/go-multiaddr v0.16.0 github.com/multiformats/go-multiaddr-dns v0.4.1 @@ -72,7 +73,6 @@ require ( github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/libp2p/go-yamux/v5 v5.0.1 // indirect - github.com/lmittmann/w3 v0.20.2 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect From 8af5d546fac7f43257cb26eeb09f6193cadf9828 Mon Sep 17 00:00:00 2001 From: mpetrun5 Date: Mon, 18 May 2026 11:17:17 +0200 Subject: [PATCH 3/5] Fix chan not being initialized --- chains/evm/message/unlock.go | 1 + 1 file changed, 1 insertion(+) diff --git a/chains/evm/message/unlock.go b/chains/evm/message/unlock.go index 52807530..390dff04 100644 --- a/chains/evm/message/unlock.go +++ b/chains/evm/message/unlock.go @@ -163,6 +163,7 @@ func (h *LifiUnlockHandler) Listen(ctx context.Context) { return } d.SigChn = make(chan interface{}, 1) + d.RepaymentAddressChn = make(chan common.Hash, 1) msg := NewLifiUnlockMessage(d.Source, d.Destination, d) _, err = h.HandleMessage(msg) From 6724f6f6544e984c3cb468a56dd4bb6050cfd92a Mon Sep 17 00:00:00 2001 From: mpetrun5 Date: Mon, 18 May 2026 16:35:48 +0200 Subject: [PATCH 4/5] Expect borrow token to be address --- chains/evm/message/lifiEscrow.go | 22 +++++++++++----------- chains/evm/message/unlock.go | 16 +++++++--------- chains/evm/message/unlock_test.go | 17 ++++++----------- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/chains/evm/message/lifiEscrow.go b/chains/evm/message/lifiEscrow.go index 1f2f1497..ffeb637f 100644 --- a/chains/evm/message/lifiEscrow.go +++ b/chains/evm/message/lifiEscrow.go @@ -220,7 +220,14 @@ func (h *LifiEscrowMessageHandler) borrowToken( return common.Address{}, destChainID, err } - if data.BorrowToken != tokenIn.Symbol && data.BorrowToken != tokenOut.Symbol { + borrowToken, err := h.tokenResolver.Token( + order.GenericOutputs[0].ChainID, + common.HexToHash(data.BorrowToken)) + if err != nil { + return common.Address{}, destChainID, err + } + + if borrowToken.Symbol != tokenIn.Symbol && borrowToken.Symbol != tokenOut.Symbol { return common.Address{}, destChainID, fmt.Errorf( "borrow token %s must be either input %s or output token symbol %s", data.BorrowToken, @@ -228,24 +235,17 @@ func (h *LifiEscrowMessageHandler) borrowToken( tokenOut.Symbol) } - if data.BorrowToken == tokenIn.Symbol { - dstToken, err := h.tokenResolver.TokenFromSymbol( - order.GenericOutputs[0].ChainID, - tokenIn.Symbol) - if err != nil { - return common.Address{}, destChainID, err - } - + if borrowToken.Symbol == tokenIn.Symbol { scaledInputAmount := chains.ScaleTokenAmount( order.GenericInputs[0].Amount, tokenIn.Decimals, - dstToken.Decimals) + borrowToken.Decimals) if scaledInputAmount.Cmp(data.BorrowAmount) == -1 { return common.Address{}, destChainID, fmt.Errorf( "order input is less than requested borrow amount") } - return common.BytesToAddress(dstToken.Address[:]), destChainID, nil + return common.BytesToAddress(borrowToken.Address[:]), destChainID, nil } else { amountOutValue := tokenOut.AmountToUSD(data.BorrowAmount) if amountInValue < amountOutValue { diff --git a/chains/evm/message/unlock.go b/chains/evm/message/unlock.go index 390dff04..219497ed 100644 --- a/chains/evm/message/unlock.go +++ b/chains/evm/message/unlock.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "math/big" - "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -125,18 +124,17 @@ func (h *LifiUnlockHandler) repaymentAddress( data *LifiUnlockData, order *lifi.LifiOrder, ) (common.Address, error) { - tokenIn, err := h.tokenResolver.Token( + if common.HexToHash(data.BorrowToken) == *order.GenericInputs[0].TokenAddress { + return h.repayer, nil + } + + borrowToken, err := h.tokenResolver.Token( order.GenericInputs[0].ChainID, - *order.GenericInputs[0].TokenAddress) + common.HexToHash(data.BorrowToken)) if err != nil { return common.Address{}, err } - - if strings.EqualFold(data.BorrowToken, tokenIn.Symbol) { - return h.repayer, nil - } - - processor, ok := h.processors[strings.ToLower(data.BorrowToken)] + processor, ok := h.processors[borrowToken.Symbol] if !ok { return common.Address{}, fmt.Errorf( "no processor specified for token %s", diff --git a/chains/evm/message/unlock_test.go b/chains/evm/message/unlock_test.go index c8ff4db5..68a454d1 100644 --- a/chains/evm/message/unlock_test.go +++ b/chains/evm/message/unlock_test.go @@ -82,15 +82,15 @@ func (s *LifiUnlockHandlerTestSuite) SetupTest() { func (s *LifiUnlockHandlerTestSuite) Test_HandleMessage_BorrowTokenEqualsInputToken() { sigChn := make(chan interface{}, 1) repaymentChan := make(chan common.Hash, 1) + inputTokenAddr := common.HexToHash("0x1") ad := &message.LifiUnlockData{ SigChn: sigChn, RepaymentAddressChn: repaymentChan, OrderID: "id", Settler: common.HexToAddress("abcd"), - BorrowToken: "UsDc", + BorrowToken: inputTokenAddr.Hex(), } s.mockCoordinator.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - inputTokenAddr := common.HexToHash("0x1") s.mockApi.EXPECT().GetOrder(gomock.Any()).Return(&lifi.LifiOrder{ GenericInputs: []order.Input{ { @@ -104,12 +104,6 @@ func (s *LifiUnlockHandlerTestSuite) Test_HandleMessage_BorrowTokenEqualsInputTo }, }, }, nil) - s.mockTokenResolver.EXPECT().Token(order.ChainID("eip155:10"), inputTokenAddr).Return( - token.Token{ - Symbol: "usdc", - }, - nil, - ) m := &coreMessage.Message{ Data: ad, @@ -128,12 +122,13 @@ func (s *LifiUnlockHandlerTestSuite) Test_HandleMessage_BorrowTokenEqualsInputTo func (s *LifiUnlockHandlerTestSuite) Test_HandleMessage_BorrowTokenIsDifferent() { sigChn := make(chan interface{}, 1) repaymentChan := make(chan common.Hash, 1) + borrowToken := common.HexToHash("0x2") ad := &message.LifiUnlockData{ SigChn: sigChn, RepaymentAddressChn: repaymentChan, OrderID: "id", Settler: common.HexToAddress("abcd"), - BorrowToken: "UsDc", + BorrowToken: borrowToken.Hex(), } s.mockCoordinator.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) inputTokenAddr := common.HexToHash("0x1") @@ -150,9 +145,9 @@ func (s *LifiUnlockHandlerTestSuite) Test_HandleMessage_BorrowTokenIsDifferent() }, }, }, nil) - s.mockTokenResolver.EXPECT().Token(order.ChainID("eip155:10"), inputTokenAddr).Return( + s.mockTokenResolver.EXPECT().Token(order.ChainID("eip155:10"), borrowToken).Return( token.Token{ - Symbol: "usdt", + Symbol: "usdc", }, nil, ) From 70361476f29dcf2082e43daff00a3d092ee7bb19 Mon Sep 17 00:00:00 2001 From: mpetrun5 Date: Mon, 18 May 2026 16:50:27 +0200 Subject: [PATCH 5/5] Fix log protocol --- chains/evm/message/unlock.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chains/evm/message/unlock.go b/chains/evm/message/unlock.go index 219497ed..0a633074 100644 --- a/chains/evm/message/unlock.go +++ b/chains/evm/message/unlock.go @@ -157,7 +157,7 @@ func (h *LifiUnlockHandler) Listen(ctx context.Context) { d := &LifiUnlockData{} err := json.Unmarshal(wMsg.Payload, d) if err != nil { - log.Warn().Msgf("Failed unmarshaling across message: %s", err) + log.Warn().Msgf("Failed unmarshaling lifi message: %s", err) return } d.SigChn = make(chan interface{}, 1) @@ -166,7 +166,7 @@ func (h *LifiUnlockHandler) Listen(ctx context.Context) { msg := NewLifiUnlockMessage(d.Source, d.Destination, d) _, err = h.HandleMessage(msg) if err != nil { - log.Err(err).Msgf("Failed handling across message %+v because of: %s", msg, err) + log.Err(err).Msgf("Failed handling lifi message %+v because of: %s", msg, err) } }(wMsg) }