diff --git a/services/wallet/common/const.go b/services/wallet/common/const.go index c742bb976..9d819b3e0 100644 --- a/services/wallet/common/const.go +++ b/services/wallet/common/const.go @@ -3,6 +3,8 @@ package common import ( "strconv" "time" + + ethCommon "github.com/ethereum/go-ethereum/common" ) type MultiTransactionIDType int64 @@ -28,6 +30,10 @@ const ( BinanceTestChainID uint64 = 97 // obsolete? ) +var ( + ZeroAddress = ethCommon.HexToAddress("0x0000000000000000000000000000000000000000") +) + type ContractType byte const ( diff --git a/services/wallet/router/pathprocessor/processor_swap_paraswap.go b/services/wallet/router/pathprocessor/processor_swap_paraswap.go index d64f43616..9dcce15da 100644 --- a/services/wallet/router/pathprocessor/processor_swap_paraswap.go +++ b/services/wallet/router/pathprocessor/processor_swap_paraswap.go @@ -33,11 +33,37 @@ type SwapParaswapProcessor struct { priceRoute sync.Map // [fromChainName-toChainName-fromTokenSymbol-toTokenSymbol, paraswap.Route] } +const ( + partnerID = "status.app" +) + +func getPartnerAddressAndFeePcnt(chainID uint64) (common.Address, float64) { + const partnerFeePcnt = 0.7 + + switch chainID { + case walletCommon.EthereumMainnet: + return common.HexToAddress("0xd9abc564bfabefa88a6C2723d78124579600F568"), partnerFeePcnt + case walletCommon.OptimismMainnet: + return common.HexToAddress("0xE9B59dC0b30cd4646430c25de0111D651c395775"), partnerFeePcnt + case walletCommon.ArbitrumMainnet: + return common.HexToAddress("0x9a8278e856C0B191B9daa2d7DD1f7B28268E4DA2"), partnerFeePcnt + } + return common.Address{}, 0 +} + func NewSwapParaswapProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface, tokenManager *walletToken.Manager) *SwapParaswapProcessor { + defaultChainID := walletCommon.EthereumMainnet + partnerAddress, partnerFeePcnt := getPartnerAddressAndFeePcnt(defaultChainID) + return &SwapParaswapProcessor{ - paraswapClient: paraswap.NewClientV5(walletCommon.EthereumMainnet), - transactor: transactor, - priceRoute: sync.Map{}, + paraswapClient: paraswap.NewClientV5( + defaultChainID, + partnerID, + partnerAddress, + partnerFeePcnt, + ), + transactor: transactor, + priceRoute: sync.Map{}, } } @@ -75,7 +101,11 @@ func (s *SwapParaswapProcessor) AvailableFor(params ProcessorInputParams) (bool, return false, ErrFromAndToTokensMustBeDifferent } - s.paraswapClient.SetChainID(params.FromChain.ChainID) + chainID := params.FromChain.ChainID + partnerAddress, partnerFeePcnt := getPartnerAddressAndFeePcnt(chainID) + s.paraswapClient.SetChainID(chainID) + s.paraswapClient.SetPartnerAddress(partnerAddress) + s.paraswapClient.SetPartnerFeePcnt(partnerFeePcnt) searchForToken := params.FromToken.Address == ZeroAddress searchForToToken := params.ToToken.Address == ZeroAddress @@ -111,6 +141,23 @@ func (s *SwapParaswapProcessor) AvailableFor(params ProcessorInputParams) (bool, return true, nil } +func calcReceivedAmountAndFee(baseDestAmount *big.Int, feePcnt float64) (destAmount *big.Int, destFee *big.Int) { + destAmount = new(big.Int).Set(baseDestAmount) + destFee = new(big.Int).SetUint64(0) + + if feePcnt > 0 { + baseDestAmountFloat := new(big.Float).SetInt(baseDestAmount) + feePcntFloat := big.NewFloat(feePcnt / 100.0) + + destFeeFloat := new(big.Float).Set(baseDestAmountFloat) + destFeeFloat = destFeeFloat.Mul(destFeeFloat, feePcntFloat) + destFeeFloat.Int(destFee) + + destAmount = destAmount.Sub(destAmount, destFee) + } + return +} + func (s *SwapParaswapProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { return ZeroBigIntValue, ZeroBigIntValue, nil } @@ -252,5 +299,8 @@ func (s *SwapParaswapProcessor) CalculateAmountOut(params ProcessorInputParams) } priceRoute := priceRouteIns.(*paraswap.Route) - return priceRoute.DestAmount.Int, nil + _, partnerFeePcnt := getPartnerAddressAndFeePcnt(params.FromChain.ChainID) + destAmount, _ := calcReceivedAmountAndFee(priceRoute.DestAmount.Int, partnerFeePcnt) + + return destAmount, nil } diff --git a/services/wallet/router/pathprocessor/processor_swap_paraswap_test.go b/services/wallet/router/pathprocessor/processor_swap_paraswap_test.go new file mode 100644 index 000000000..5ad1d91c6 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_swap_paraswap_test.go @@ -0,0 +1,71 @@ +package pathprocessor + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/status-im/status-go/params" + "github.com/status-im/status-go/services/wallet/bigint" + walletCommon "github.com/status-im/status-go/services/wallet/common" + "github.com/status-im/status-go/services/wallet/thirdparty/paraswap" + "github.com/status-im/status-go/services/wallet/token" + + "github.com/stretchr/testify/require" +) + +func TestParaswapWithPartnerFee(t *testing.T) { + testPriceRoute := ¶swap.Route{ + GasCost: &bigint.BigInt{Int: big.NewInt(500)}, + SrcAmount: &bigint.BigInt{Int: big.NewInt(1000)}, + SrcTokenAddress: common.HexToAddress("0x123"), + SrcTokenDecimals: 18, + DestAmount: &bigint.BigInt{Int: big.NewInt(2000)}, + DestTokenAddress: common.HexToAddress("0x465"), + DestTokenDecimals: 6, + Side: paraswap.SellSide, + } + + processor := NewSwapParaswapProcessor(nil, nil, nil) + + fromToken := token.Token{ + Symbol: EthSymbol, + } + toToken := token.Token{ + Symbol: UsdcSymbol, + } + chainIDs := []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet, walletCommon.OptimismMainnet, walletCommon.UnknownChainID} + + for _, chainID := range chainIDs { + key := makeKey(chainID, chainID, fromToken.Symbol, toToken.Symbol) + processor.priceRoute.Store(key, testPriceRoute) + + testInputParams := ProcessorInputParams{ + FromChain: ¶ms.Network{ChainID: chainID}, + ToChain: ¶ms.Network{ChainID: chainID}, + FromToken: &fromToken, + ToToken: &toToken, + } + + partnerAddress, partnerFeePcnt := getPartnerAddressAndFeePcnt(chainID) + + if partnerAddress != walletCommon.ZeroAddress { + require.Greater(t, partnerFeePcnt, 0.0) + + expectedFee := uint64(float64(testPriceRoute.DestAmount.Uint64()) * partnerFeePcnt / 100.0) + expectedDestAmount := testPriceRoute.DestAmount.Uint64() - expectedFee + + amountOut, err := processor.CalculateAmountOut(testInputParams) + require.NoError(t, err) + require.NotNil(t, amountOut) + require.InEpsilon(t, expectedDestAmount, amountOut.Uint64(), 2.0) + } else { + require.Equal(t, 0.0, partnerFeePcnt) + + amountOut, err := processor.CalculateAmountOut(testInputParams) + require.NoError(t, err) + require.NotNil(t, amountOut) + require.Equal(t, testPriceRoute.DestAmount.Uint64(), amountOut.Uint64()) + } + } +} diff --git a/services/wallet/thirdparty/paraswap/client_v5.go b/services/wallet/thirdparty/paraswap/client_v5.go index 8bd8578af..38f8e4ca0 100644 --- a/services/wallet/thirdparty/paraswap/client_v5.go +++ b/services/wallet/thirdparty/paraswap/client_v5.go @@ -1,6 +1,9 @@ package paraswap -import "github.com/status-im/status-go/services/wallet/thirdparty" +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/status-im/status-go/services/wallet/thirdparty" +) type SwapSide string @@ -10,17 +13,35 @@ const ( ) type ClientV5 struct { - httpClient *thirdparty.HTTPClient - chainID uint64 + httpClient *thirdparty.HTTPClient + chainID uint64 + partnerID string + partnerAddress common.Address + partnerFeePcnt float64 } -func NewClientV5(chainID uint64) *ClientV5 { +func NewClientV5( + chainID uint64, + partnerID string, + partnerAddress common.Address, + partnerFeePcnt float64) *ClientV5 { return &ClientV5{ - httpClient: thirdparty.NewHTTPClient(), - chainID: chainID, + httpClient: thirdparty.NewHTTPClient(), + chainID: chainID, + partnerID: partnerID, + partnerAddress: partnerAddress, + partnerFeePcnt: partnerFeePcnt, } } func (c *ClientV5) SetChainID(chainID uint64) { c.chainID = chainID } + +func (c *ClientV5) SetPartnerAddress(partnerAddress common.Address) { + c.partnerAddress = partnerAddress +} + +func (c *ClientV5) SetPartnerFeePcnt(partnerFeePcnt float64) { + c.partnerFeePcnt = partnerFeePcnt +} diff --git a/services/wallet/thirdparty/paraswap/request_build_transaction.go b/services/wallet/thirdparty/paraswap/request_build_transaction.go index 247ccbc44..1104ca3b4 100644 --- a/services/wallet/thirdparty/paraswap/request_build_transaction.go +++ b/services/wallet/thirdparty/paraswap/request_build_transaction.go @@ -8,6 +8,8 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + + walletCommon "github.com/status-im/status-go/services/wallet/common" ) const transactionsURL = "https://apiv5.paraswap.io/transactions/%d" @@ -47,6 +49,11 @@ func (c *ClientV5) BuildTransaction(ctx context.Context, srcTokenAddress common. params["srcAmount"] = srcAmountWei.String() params["destAmount"] = destAmountWei.String() } + params["partner"] = c.partnerID + if c.partnerAddress != walletCommon.ZeroAddress && c.partnerFeePcnt > 0 { + params["partnerAddress"] = c.partnerAddress.Hex() + params["partnerFeeBps"] = uint(c.partnerFeePcnt * 100) + } url := fmt.Sprintf(transactionsURL, c.chainID) response, err := c.httpClient.DoPostRequest(ctx, url, params) diff --git a/services/wallet/thirdparty/paraswap/request_price_route.go b/services/wallet/thirdparty/paraswap/request_price_route.go index 155fd8cda..1e4425522 100644 --- a/services/wallet/thirdparty/paraswap/request_price_route.go +++ b/services/wallet/thirdparty/paraswap/request_price_route.go @@ -46,6 +46,7 @@ func (c *ClientV5) FetchPriceRoute(ctx context.Context, srcTokenAddress common.A params.Add("network", strconv.FormatUint(c.chainID, 10)) params.Add("amount", amountWei.String()) params.Add("side", string(side)) + params.Add("partner", c.partnerID) url := pricesURL response, err := c.httpClient.DoGetRequest(ctx, url, params)