chore_: swap via paraswap improvements

This commit is contained in:
Sale Djenic 2024-06-12 22:14:30 +02:00 committed by saledjenic
parent 9c3b49b866
commit 462013520f
9 changed files with 102 additions and 25 deletions

View File

@ -37,6 +37,7 @@ type ProcessorInputParams struct {
FromToken *token.Token
ToToken *token.Token
AmountIn *big.Int
AmountOut *big.Int
// extra params
BonderFee *big.Int

View File

@ -20,7 +20,8 @@ import (
type SwapParaswapTxArgs struct {
transactions.SendTxArgs
ChainID uint64 `json:"chainId"`
ChainID uint64 `json:"chainId"`
SlippagePercentage float32 `json:"slippagePercentage"`
}
type SwapParaswapProcessor struct {
@ -95,8 +96,13 @@ func (s *SwapParaswapProcessor) PackTxInputData(params ProcessorInputParams) ([]
}
func (s *SwapParaswapProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) {
swapSide := paraswap.SellSide
if params.AmountOut != nil && params.AmountOut.Cmp(ZeroBigIntValue) > 0 {
swapSide = paraswap.BuySide
}
priceRoute, err := s.paraswapClient.FetchPriceRoute(context.Background(), params.FromToken.Address, params.FromToken.Decimals,
params.ToToken.Address, params.ToToken.Decimals, params.AmountIn, params.FromAddr, params.ToAddr)
params.ToToken.Address, params.ToToken.Decimals, params.AmountIn, params.FromAddr, params.ToAddr, swapSide)
if err != nil {
return 0, err
}
@ -138,8 +144,12 @@ func (s *SwapParaswapProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.
}
func (s *SwapParaswapProcessor) prepareTransaction(sendArgs *MultipathProcessorTxArgs) error {
slippageBP := uint(sendArgs.SwapTx.SlippagePercentage * 100) // convert to basis points
tx, err := s.paraswapClient.BuildTransaction(context.Background(), s.priceRoute.SrcTokenAddress, s.priceRoute.SrcTokenDecimals, s.priceRoute.SrcAmount.Int,
s.priceRoute.DestTokenAddress, s.priceRoute.DestTokenDecimals, s.priceRoute.DestAmount.Int, common.Address(sendArgs.SwapTx.From), common.Address(*sendArgs.SwapTx.To), s.priceRoute.RawPriceRoute)
s.priceRoute.DestTokenAddress, s.priceRoute.DestTokenDecimals, s.priceRoute.DestAmount.Int, slippageBP,
common.Address(sendArgs.SwapTx.From), common.Address(*sendArgs.SwapTx.To),
s.priceRoute.RawPriceRoute, s.priceRoute.Side)
if err != nil {
return err
}

View File

@ -589,7 +589,7 @@ func (r *Router) SuggestedRoutes(
continue
}
ProcessorInputParams := pathprocessor.ProcessorInputParams{
processorInputParams := pathprocessor.ProcessorInputParams{
FromChain: network,
ToChain: dest,
FromToken: token,
@ -599,7 +599,7 @@ func (r *Router) SuggestedRoutes(
AmountIn: amountIn,
}
can, err := pProcessor.AvailableFor(ProcessorInputParams)
can, err := pProcessor.AvailableFor(processorInputParams)
if err != nil || !can {
continue
}
@ -607,7 +607,7 @@ func (r *Router) SuggestedRoutes(
continue
}
bonderFees, tokenFees, err := pProcessor.CalculateFees(ProcessorInputParams)
bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams)
if err != nil {
continue
}
@ -624,7 +624,7 @@ func (r *Router) SuggestedRoutes(
}
gasLimit := uint64(0)
if sendType.isTransfer(false) {
gasLimit, err = pProcessor.EstimateGas(ProcessorInputParams)
gasLimit, err = pProcessor.EstimateGas(processorInputParams)
if err != nil {
continue
}
@ -632,7 +632,7 @@ func (r *Router) SuggestedRoutes(
gasLimit = sendType.EstimateGas(r.ensService, r.stickersService, network, addrFrom, tokenID)
}
approvalContractAddress, err := pProcessor.GetContractAddress(ProcessorInputParams)
approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams)
if err != nil {
continue
}
@ -643,7 +643,7 @@ func (r *Router) SuggestedRoutes(
var l1GasFeeWei uint64
if sendType.needL1Fee() {
txInputData, err := pProcessor.PackTxInputData(ProcessorInputParams)
txInputData, err := pProcessor.PackTxInputData(processorInputParams)
if err != nil {
continue
}
@ -723,7 +723,7 @@ func (r *Router) SuggestedRoutes(
suggestedRoutes.TokenPrice = prices[tokenID]
suggestedRoutes.NativeChainTokenPrice = prices["ETH"]
for _, path := range suggestedRoutes.Best {
ProcessorInputParams := pathprocessor.ProcessorInputParams{
processorInputParams := pathprocessor.ProcessorInputParams{
FromChain: path.From,
ToChain: path.To,
AmountIn: path.AmountIn.ToInt(),
@ -732,7 +732,7 @@ func (r *Router) SuggestedRoutes(
},
}
amountOut, err := r.pathProcessors[path.BridgeName].CalculateAmountOut(ProcessorInputParams)
amountOut, err := r.pathProcessors[path.BridgeName].CalculateAmountOut(processorInputParams)
if err != nil {
continue
}

View File

@ -103,9 +103,15 @@ func (s SendType) needL1Fee() bool {
return !s.IsEnsTransfer() && !s.IsStickersTransfer()
}
// canUseProcessor is used to check if certain SendType can be used with a given path processor
func (s SendType) canUseProcessor(p pathprocessor.PathProcessor) bool {
pathProcessorName := p.Name()
switch s {
case Bridge:
return pathProcessorName == pathprocessor.ProcessorBridgeHopName ||
pathProcessorName == pathprocessor.ProcessorBridgeCelerName
case Swap:
return pathProcessorName == pathprocessor.ProcessorSwapParaswapName
case ERC721Transfer:
return pathProcessorName == pathprocessor.ProcessorERC721Name
case ERC1155Transfer:

View File

@ -38,6 +38,7 @@ type RouteInputParams struct {
AddrFrom common.Address `json:"addrFrom" validate:"required"`
AddrTo common.Address `json:"addrTo" validate:"required"`
AmountIn *hexutil.Big `json:"amountIn" validate:"required"`
AmountOut *hexutil.Big `json:"amountOut"`
TokenID string `json:"tokenID" validate:"required"`
ToTokenID string `json:"toTokenID"`
DisabledFromChainIDs []uint64 `json:"disabledFromChainIDs"`
@ -326,6 +327,30 @@ func validateInputData(input *RouteInputParams) error {
}
}
if input.SendType == Swap {
if input.ToTokenID == "" {
return errors.New("toTokenID is required for Swap")
}
if input.TokenID == input.ToTokenID {
return errors.New("tokenID and toTokenID must be different")
}
// we can do this check, cause AmountIn is required in `RouteInputParams`
if input.AmountIn.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 &&
input.AmountOut != nil &&
input.AmountOut.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
return errors.New("only one of amountIn or amountOut can be set")
}
if input.AmountIn.ToInt().Sign() < 0 {
return errors.New("amountIn must be positive")
}
if input.AmountOut != nil && input.AmountOut.ToInt().Sign() < 0 {
return errors.New("amountOut must be positive")
}
}
if input.FromLockedAmount != nil && len(input.FromLockedAmount) > 0 {
for chainID, amount := range input.FromLockedAmount {
if input.TestnetMode {
@ -414,6 +439,22 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
}
for _, pProcessor := range r.pathProcessors {
// With the condition below we're eliminating `Swap` as potential path that can participate in calculating the best route
// once we decide to inlcude `Swap` in the calculation we need to update `canUseProcessor` function.
// This also applies to including another (Celer) bridge in the calculation.
// TODO:
// this algorithm, includeing finding the best route, has to be updated to include more bridges and one (for now) or more swap options
// it means that candidates should not be treated linearly, but improve the logic to have multiple routes with different processors of the same type.
// Example:
// Routes for sending SNT from Ethereum to Optimism can be:
// 1. Swap SNT(mainnet) to ETH(mainnet); then bridge via Hop ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination
// 2. Swap SNT(mainnet) to ETH(mainnet); then bridge via Celer ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination
// 3. Swap SNT(mainnet) to USDC(mainnet); then bridge via Hop USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination
// 4. Swap SNT(mainnet) to USDC(mainnet); then bridge via Celer USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination
// 5. ...
// 6. ...
//
// With the current routing algorithm atm we're not able to generate all possible routes.
if !input.SendType.canUseProcessor(pProcessor) {
continue
}
@ -439,7 +480,7 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
continue
}
ProcessorInputParams := pathprocessor.ProcessorInputParams{
processorInputParams := pathprocessor.ProcessorInputParams{
FromChain: network,
ToChain: dest,
FromToken: token,
@ -447,28 +488,29 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
ToAddr: input.AddrTo,
FromAddr: input.AddrFrom,
AmountIn: amountToSend,
AmountOut: input.AmountOut.ToInt(),
Username: input.Username,
PublicKey: input.PublicKey,
PackID: input.PackID.Int,
}
can, err := pProcessor.AvailableFor(ProcessorInputParams)
can, err := pProcessor.AvailableFor(processorInputParams)
if err != nil || !can {
continue
}
bonderFees, tokenFees, err := pProcessor.CalculateFees(ProcessorInputParams)
bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams)
if err != nil {
continue
}
gasLimit, err := pProcessor.EstimateGas(ProcessorInputParams)
gasLimit, err := pProcessor.EstimateGas(processorInputParams)
if err != nil {
continue
}
approvalContractAddress, err := pProcessor.GetContractAddress(ProcessorInputParams)
approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams)
if err != nil {
continue
}
@ -480,7 +522,7 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
var l1FeeWei uint64
if input.SendType.needL1Fee() {
txInputData, err := pProcessor.PackTxInputData(ProcessorInputParams)
txInputData, err := pProcessor.PackTxInputData(processorInputParams)
if err != nil {
continue
}
@ -504,7 +546,7 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
selctedPriorityFee = priorityFees.Low
}
amountOut, err := pProcessor.CalculateAmountOut(ProcessorInputParams)
amountOut, err := pProcessor.CalculateAmountOut(processorInputParams)
if err != nil {
continue
}

View File

@ -2,6 +2,13 @@ package paraswap
import "github.com/status-im/status-go/services/wallet/thirdparty"
type SwapSide string
const (
SellSide = SwapSide("SELL")
BuySide = SwapSide("BUY")
)
type ClientV5 struct {
httpClient *thirdparty.HTTPClient
chainID uint64

View File

@ -24,21 +24,30 @@ type Transaction struct {
}
func (c *ClientV5) BuildTransaction(ctx context.Context, srcTokenAddress common.Address, srcTokenDecimals uint, srcAmountWei *big.Int,
destTokenAddress common.Address, destTokenDecimals uint, destAmountWei *big.Int,
addressFrom common.Address, addressTo common.Address, priceRoute json.RawMessage) (Transaction, error) {
destTokenAddress common.Address, destTokenDecimals uint, destAmountWei *big.Int, slippageBasisPoints uint,
addressFrom common.Address, addressTo common.Address, priceRoute json.RawMessage, side SwapSide) (Transaction, error) {
params := map[string]interface{}{}
params["srcToken"] = srcTokenAddress.Hex()
params["srcDecimals"] = srcTokenDecimals
params["srcAmount"] = srcAmountWei.String()
params["destToken"] = destTokenAddress.Hex()
params["destDecimals"] = destTokenDecimals
// params["destAmount"] = destAmountWei.String()
params["userAddress"] = addressFrom.Hex()
// params["receiver"] = addressTo.Hex() // at this point paraswap doesn't allow swap and transfer transaction
params["slippage"] = "500"
params["priceRoute"] = priceRoute
if slippageBasisPoints > 0 {
params["slippage"] = slippageBasisPoints
if side == SellSide {
params["srcAmount"] = srcAmountWei.String()
} else {
params["destAmount"] = destAmountWei.String()
}
} else {
params["srcAmount"] = srcAmountWei.String()
params["destAmount"] = destAmountWei.String()
}
url := fmt.Sprintf(transactionsURL, c.chainID)
response, err := c.httpClient.DoPostRequest(ctx, url, params)
if err != nil {

View File

@ -24,6 +24,7 @@ type Route struct {
DestTokenAddress common.Address `json:"destToken"`
DestTokenDecimals uint `json:"destDecimals"`
RawPriceRoute json.RawMessage `json:"rawPriceRoute"`
Side SwapSide `json:"side"`
}
type PriceRouteResponse struct {
@ -33,7 +34,7 @@ type PriceRouteResponse struct {
func (c *ClientV5) FetchPriceRoute(ctx context.Context, srcTokenAddress common.Address, srcTokenDecimals uint,
destTokenAddress common.Address, destTokenDecimals uint, amountWei *big.Int, addressFrom common.Address,
addressTo common.Address) (Route, error) {
addressTo common.Address, side SwapSide) (Route, error) {
params := netUrl.Values{}
params.Add("srcToken", srcTokenAddress.Hex())
@ -44,7 +45,7 @@ func (c *ClientV5) FetchPriceRoute(ctx context.Context, srcTokenAddress common.A
// params.Add("receiver", addressTo.Hex()) // at this point paraswap doesn't allow swap and transfer transaction
params.Add("network", strconv.FormatUint(c.chainID, 10))
params.Add("amount", amountWei.String())
params.Add("side", "SELL")
params.Add("side", string(side))
url := pricesURL
response, err := c.httpClient.DoGetRequest(ctx, url, params)

View File

@ -108,6 +108,7 @@ func TestUnmarshallPriceRoute(t *testing.T) {
DestAmount: &bigint.BigInt{Int: big.NewInt(1000000000000000000)},
DestTokenAddress: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
DestTokenDecimals: 18,
Side: SellSide,
RawPriceRoute: data,
}