2024-08-28 13:17:59 +02:00
package requests
import (
"math/big"
2024-12-18 11:32:31 +01:00
"reflect"
"sort"
2024-08-28 13:17:59 +02:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/errors"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/router/fees"
"github.com/status-im/status-go/services/wallet/router/sendtype"
"github.com/status-im/status-go/services/wallet/token"
)
var (
ErrENSRegisterRequiresUsernameAndPubKey = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-001" ) , Details : "username and public key are required for ENSRegister" }
ErrENSRegisterTestnetSTTOnly = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-002" ) , Details : "only STT is supported for ENSRegister on testnet" }
ErrENSRegisterMainnetSNTOnly = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-003" ) , Details : "only SNT is supported for ENSRegister on mainnet" }
ErrENSReleaseRequiresUsername = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-004" ) , Details : "username is required for ENSRelease" }
ErrENSSetPubKeyRequiresUsernameAndPubKey = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-005" ) , Details : "username and public key are required for ENSSetPubKey" }
ErrStickersBuyRequiresPackID = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-006" ) , Details : "packID is required for StickersBuy" }
ErrSwapRequiresToTokenID = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-007" ) , Details : "toTokenID is required for Swap" }
ErrSwapTokenIDMustBeDifferent = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-008" ) , Details : "tokenID and toTokenID must be different" }
ErrSwapAmountInAmountOutMustBeExclusive = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-009" ) , Details : "only one of amountIn or amountOut can be set" }
ErrSwapAmountInMustBePositive = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-010" ) , Details : "amountIn must be positive" }
ErrSwapAmountOutMustBePositive = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-011" ) , Details : "amountOut must be positive" }
ErrLockedAmountNotSupportedForNetwork = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-012" ) , Details : "locked amount is not supported for the selected network" }
ErrLockedAmountNotNegative = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-013" ) , Details : "locked amount must not be negative" }
ErrLockedAmountExceedsTotalSendAmount = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-014" ) , Details : "locked amount exceeds the total amount to send" }
ErrLockedAmountLessThanSendAmountAllNetworks = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-015" ) , Details : "locked amount is less than the total amount to send, but all networks are locked" }
ErrDisabledChainFoundAmongLockedNetworks = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-016" ) , Details : "disabled chain found among locked networks" }
ErrENSSetPubKeyInvalidUsername = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-017" ) , Details : "a valid username, ending in '.eth', is required for ENSSetPubKey" }
ErrLockedAmountExcludesAllSupported = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-018" ) , Details : "all supported chains are excluded, routing impossible" }
ErrCannotCheckLockedAmounts = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-019" ) , Details : "cannot check locked amounts" }
2024-12-18 11:32:31 +01:00
ErrNoCommunityParametersProvided = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-020" ) , Details : "no community parameters provided" }
ErrNoFromChainProvided = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-021" ) , Details : "from chain not provided" }
ErrNoToChainProvided = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-022" ) , Details : "to chain not provided" }
ErrFromAndToChainMustBeTheSame = & errors . ErrorResponse { Code : errors . ErrorCode ( "WRR-023" ) , Details : "from and to chain IDs must be the same" }
2024-08-28 13:17:59 +02:00
)
type RouteInputParams struct {
Uuid string ` json:"uuid" `
SendType sendtype . SendType ` json:"sendType" validate:"required" `
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" `
2024-09-23 09:19:00 +02:00
TokenIDIsOwnerToken bool ` json:"tokenIDIsOwnerToken" `
2024-08-28 13:17:59 +02:00
ToTokenID string ` json:"toTokenID" `
DisabledFromChainIDs [ ] uint64 ` json:"disabledFromChainIDs" `
DisabledToChainIDs [ ] uint64 ` json:"disabledToChainIDs" `
GasFeeMode fees . GasFeeMode ` json:"gasFeeMode" validate:"required" `
FromLockedAmount map [ uint64 ] * hexutil . Big ` json:"fromLockedAmount" `
TestnetMode bool
// For send types like EnsRegister, EnsRelease, EnsSetPubKey, StickersBuy
Username string ` json:"username" `
PublicKey string ` json:"publicKey" `
PackID * hexutil . Big ` json:"packID" `
feat(wallet)!: allowing client to set custom values for fees, nonce, gas
Removed properties from `Path` type:
- `MaxFeesPerGas`, based on the sending flow progress appropriate new properties (`TxMaxFeesPerGas`, `ApprovalMaxFeesPerGas`) should be used
Added new properties to `Path` type:
- `RouterInputParamsUuid`, used to identify from which router input params this path was created
- `TxNonce`, used to set nonce for the tx
- `TxMaxFeesPerGas`, used to set max fees per gas for the tx
- `TxEstimatedTime`, used to estimate time for executing the tx
- `ApprovalTxNonce`, used to set nonce for the approval tx
- `ApprovalTxMaxFeesPerGas`, used to set max fees per gas for the approval tx
- `ApprovalTxEstimatedTime`, used to estimate time for executing the approval tx
New request types added:
- `PathTxCustomParams`, used to pass tx custom params from the client
- `PathTxIdentity`, used to uniquely identify path (tx) to which the custom params need to be applied
New endpoints added:
- `SetFeeMode` used to set fee mode (`GasFeeLow`, `GasFeeMedium` or `GasFeeHigh`)
- `SetCustomTxDetails` used to set custom fee mode (`SetCustomTxDetails`), if this mode is set, client needs to provide:
- Max fees per gas
- Max priority fee
- Nonce
- Gas amount
2024-11-26 12:29:00 +01:00
// Used internally
PathTxCustomParams map [ string ] * PathTxCustomParams ` json:"-" `
2024-12-18 11:32:31 +01:00
// Community related params
CommunityRouteInputParams * CommunityRouteInputParams ` json:"communityRouteInputParams" `
2024-08-28 13:17:59 +02:00
// TODO: Remove two fields below once we implement a better solution for tests
// Currently used for tests only
TestsMode bool
TestParams * RouterTestParams
}
type RouterTestParams struct {
TokenFrom * token . Token
TokenPrices map [ string ] float64
2024-11-05 22:20:27 +01:00
EstimationMap map [ string ] Estimation // [processor-name, estimation]
BonderFeeMap map [ string ] * big . Int // [token-symbol, bonder-fee]
2024-08-28 13:17:59 +02:00
SuggestedFees * fees . SuggestedFees
BaseFee * big . Int
BalanceMap map [ string ] * big . Int // [token-symbol, balance]
ApprovalGasEstimation uint64
ApprovalL1Fee uint64
}
2024-11-05 22:20:27 +01:00
type Estimation struct {
Value uint64
Err error
}
2024-12-18 11:32:31 +01:00
func slicesEqual ( a , b [ ] uint64 ) bool {
if len ( a ) != len ( b ) {
return false
}
aCopy := make ( [ ] uint64 , len ( a ) )
bCopy := make ( [ ] uint64 , len ( b ) )
copy ( aCopy , a )
copy ( bCopy , b )
sort . Slice ( aCopy , func ( i , j int ) bool { return aCopy [ i ] < aCopy [ j ] } )
sort . Slice ( bCopy , func ( i , j int ) bool { return bCopy [ i ] < bCopy [ j ] } )
return reflect . DeepEqual ( aCopy , bCopy )
}
func ( i * RouteInputParams ) UseCommunityTransferDetails ( ) bool {
if ! i . SendType . IsCommunityRelatedTransfer ( ) || i . CommunityRouteInputParams == nil {
return false
}
return i . CommunityRouteInputParams . UseTransferDetails ( )
}
2024-08-28 13:17:59 +02:00
func ( i * RouteInputParams ) Validate ( ) error {
if i . SendType == sendtype . ENSRegister {
if i . Username == "" || i . PublicKey == "" {
return ErrENSRegisterRequiresUsernameAndPubKey
}
if i . TestnetMode {
2024-11-05 22:20:27 +01:00
if i . TokenID != walletCommon . SttSymbol {
2024-08-28 13:17:59 +02:00
return ErrENSRegisterTestnetSTTOnly
}
} else {
2024-11-05 22:20:27 +01:00
if i . TokenID != walletCommon . SntSymbol {
2024-08-28 13:17:59 +02:00
return ErrENSRegisterMainnetSNTOnly
}
}
return nil
}
if i . SendType == sendtype . ENSRelease {
if i . Username == "" {
return ErrENSReleaseRequiresUsername
}
}
if i . SendType == sendtype . ENSSetPubKey {
if i . Username == "" || i . PublicKey == "" {
return ErrENSSetPubKeyRequiresUsernameAndPubKey
}
2024-11-05 12:05:23 +01:00
if walletCommon . ValidateENSUsername ( i . Username ) != nil {
2024-08-28 13:17:59 +02:00
return ErrENSSetPubKeyInvalidUsername
}
}
if i . SendType == sendtype . StickersBuy {
if i . PackID == nil {
return ErrStickersBuyRequiresPackID
}
}
if i . SendType == sendtype . Swap {
if i . ToTokenID == "" {
return ErrSwapRequiresToTokenID
}
if i . TokenID == i . ToTokenID {
return ErrSwapTokenIDMustBeDifferent
}
if i . AmountIn != nil &&
i . AmountOut != nil &&
2024-09-23 08:35:34 +02:00
i . AmountIn . ToInt ( ) . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 &&
i . AmountOut . ToInt ( ) . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 {
2024-08-28 13:17:59 +02:00
return ErrSwapAmountInAmountOutMustBeExclusive
}
if i . AmountIn != nil && i . AmountIn . ToInt ( ) . Sign ( ) < 0 {
return ErrSwapAmountInMustBePositive
}
if i . AmountOut != nil && i . AmountOut . ToInt ( ) . Sign ( ) < 0 {
return ErrSwapAmountOutMustBePositive
}
}
2024-12-18 11:32:31 +01:00
if i . SendType . IsCommunityRelatedTransfer ( ) {
if i . DisabledFromChainIDs == nil || len ( i . DisabledFromChainIDs ) == 0 {
return ErrNoFromChainProvided
}
if i . DisabledToChainIDs == nil || len ( i . DisabledToChainIDs ) == 0 {
return ErrNoToChainProvided
}
if ! slicesEqual ( i . DisabledFromChainIDs , i . DisabledToChainIDs ) {
return ErrFromAndToChainMustBeTheSame
}
if i . CommunityRouteInputParams == nil {
return ErrNoCommunityParametersProvided
}
return i . CommunityRouteInputParams . validateCommunityRelatedInputs ( i . SendType )
}
2024-08-28 13:17:59 +02:00
return i . validateFromLockedAmount ( )
}
func ( i * RouteInputParams ) validateFromLockedAmount ( ) error {
if i . FromLockedAmount == nil || len ( i . FromLockedAmount ) == 0 {
return nil
}
var suppNetworks map [ uint64 ] bool
if i . TestnetMode {
suppNetworks = walletCommon . CopyMapGeneric ( walletCommon . SupportedTestNetworks , nil ) . ( map [ uint64 ] bool )
} else {
suppNetworks = walletCommon . CopyMapGeneric ( walletCommon . SupportedNetworks , nil ) . ( map [ uint64 ] bool )
}
if suppNetworks == nil {
return ErrCannotCheckLockedAmounts
}
totalLockedAmount := big . NewInt ( 0 )
excludedChainCount := 0
for chainID , amount := range i . FromLockedAmount {
if walletCommon . ArrayContainsElement ( chainID , i . DisabledFromChainIDs ) {
return ErrDisabledChainFoundAmongLockedNetworks
}
if i . TestnetMode {
if ! walletCommon . SupportedTestNetworks [ chainID ] {
return ErrLockedAmountNotSupportedForNetwork
}
} else {
if ! walletCommon . SupportedNetworks [ chainID ] {
return ErrLockedAmountNotSupportedForNetwork
}
}
if amount == nil || amount . ToInt ( ) . Sign ( ) < 0 {
return ErrLockedAmountNotNegative
}
if ! ( amount . ToInt ( ) . Sign ( ) > 0 ) {
excludedChainCount ++
}
delete ( suppNetworks , chainID )
totalLockedAmount = new ( big . Int ) . Add ( totalLockedAmount , amount . ToInt ( ) )
}
if ( ! i . TestnetMode && excludedChainCount == len ( walletCommon . SupportedNetworks ) ) ||
( i . TestnetMode && excludedChainCount == len ( walletCommon . SupportedTestNetworks ) ) {
return ErrLockedAmountExcludesAllSupported
}
if totalLockedAmount . Cmp ( i . AmountIn . ToInt ( ) ) > 0 {
return ErrLockedAmountExceedsTotalSendAmount
}
if totalLockedAmount . Cmp ( i . AmountIn . ToInt ( ) ) < 0 && len ( suppNetworks ) == 0 {
return ErrLockedAmountLessThanSendAmountAllNetworks
}
return nil
}