2024-05-28 13:16:15 +00:00
|
|
|
package onramp
|
|
|
|
|
2024-08-12 12:53:32 +00:00
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/sha512"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
2024-05-28 13:16:15 +00:00
|
|
|
|
2024-08-12 12:53:32 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
|
|
|
|
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
|
|
|
"github.com/status-im/status-go/services/wallet/thirdparty/mercuryo"
|
|
|
|
"github.com/status-im/status-go/services/wallet/token"
|
|
|
|
)
|
|
|
|
|
|
|
|
const mercuryoID = "mercuryo"
|
|
|
|
const mercuryioNoFeesBaseURL = "https://exchange.mercuryo.io/?type=buy&networks=ETHEREUM,ARBITRUM,OPTIMISM¤cy=ETH"
|
|
|
|
const supportedAssetsUpdateInterval = 24 * time.Hour
|
|
|
|
|
|
|
|
type MercuryoProvider struct {
|
|
|
|
supportedTokens []*token.Token
|
|
|
|
supportedTokensTimestamp time.Time
|
|
|
|
supportedTokensLock sync.RWMutex
|
|
|
|
httpClient *mercuryo.Client
|
|
|
|
tokenManager token.ManagerInterface
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewMercuryoProvider(tokenManager token.ManagerInterface) *MercuryoProvider {
|
|
|
|
return &MercuryoProvider{
|
|
|
|
httpClient: mercuryo.NewClient(),
|
|
|
|
tokenManager: tokenManager,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *MercuryoProvider) ID() string {
|
|
|
|
return mercuryoID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *MercuryoProvider) GetCryptoOnRamp(ctx context.Context) (CryptoOnRamp, error) {
|
2024-05-28 13:16:15 +00:00
|
|
|
const (
|
2024-06-03 02:31:57 +00:00
|
|
|
logoMercuryo = "data:image/jpeg;base64,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
|
2024-08-12 12:53:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
provider := CryptoOnRamp{
|
|
|
|
ID: mercuryoID,
|
|
|
|
Name: "Mercuryo",
|
|
|
|
Description: "Buy crypto within 15 seconds",
|
|
|
|
Fees: "4.5%",
|
|
|
|
LogoURL: logoMercuryo,
|
|
|
|
Hostname: "mercuryo.io",
|
|
|
|
SupportsSinglePurchase: true,
|
|
|
|
SupportsRecurrentPurchase: true,
|
|
|
|
SupportedChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet, walletCommon.OptimismMainnet},
|
|
|
|
URLsNeedParameters: true,
|
|
|
|
SiteURL: mercuryioNoFeesBaseURL,
|
|
|
|
RecurrentSiteURL: mercuryioNoFeesBaseURL + "&widget_flow=recurrent",
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
provider.SupportedTokens, err = p.getSupportedCurrencies(ctx)
|
|
|
|
return provider, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *MercuryoProvider) getSupportedCurrencies(ctx context.Context) ([]*token.Token, error) {
|
|
|
|
p.supportedTokensLock.Lock()
|
|
|
|
defer p.supportedTokensLock.Unlock()
|
|
|
|
|
|
|
|
if time.Since(p.supportedTokensTimestamp) < supportedAssetsUpdateInterval {
|
|
|
|
return p.supportedTokens, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
newSupportedCurrencies, err := p.httpClient.FetchCurrencies(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return p.supportedTokens, err
|
|
|
|
}
|
|
|
|
|
|
|
|
newSupportedTokens := make([]*token.Token, 0, len(newSupportedCurrencies))
|
|
|
|
for _, currency := range newSupportedCurrencies {
|
|
|
|
chainID := mercuryo.NetworkToCommonChainID(currency.Network)
|
|
|
|
if chainID == walletCommon.UnknownChainID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
token, isNative := p.tokenManager.LookupToken(&chainID, currency.Symbol)
|
|
|
|
if token == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !isNative {
|
|
|
|
contractAddress := common.HexToAddress(currency.Contract)
|
|
|
|
if contractAddress != token.Address {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
newSupportedTokens = append(newSupportedTokens, token)
|
|
|
|
}
|
2024-06-03 02:31:57 +00:00
|
|
|
|
2024-08-12 12:53:32 +00:00
|
|
|
p.supportedTokens = newSupportedTokens
|
|
|
|
p.supportedTokensTimestamp = time.Now()
|
|
|
|
|
|
|
|
return p.supportedTokens, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should generate the SHA512 hash of the string "AddressKey"
|
|
|
|
func getMercuryoSignature(address common.Address, key string) string {
|
|
|
|
addressString := address.Hex()
|
|
|
|
|
|
|
|
hash := sha512.New()
|
|
|
|
hash.Write([]byte(addressString[:] + key))
|
|
|
|
return fmt.Sprintf("%x", hash.Sum(nil))
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMercuryoCurrency(symbol string) string {
|
|
|
|
return strings.ToUpper(symbol)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *MercuryoProvider) GetURL(ctx context.Context, parameters Parameters) (string, error) {
|
|
|
|
const (
|
|
|
|
baseURL = "https://exchange.mercuryo.io/?type=buy"
|
|
|
|
widgetID = "6a7eb330-2b09-49b7-8fd3-1c77cfb6cd47"
|
|
|
|
widgetSecret = "AZ5fmxmrgyrXH3zre6yHU2Vw9fPqEw82" // #nosec G101
|
2024-05-28 13:16:15 +00:00
|
|
|
)
|
|
|
|
|
2024-08-12 12:53:32 +00:00
|
|
|
if parameters.DestAddress == nil || *parameters.DestAddress == walletCommon.ZeroAddress {
|
|
|
|
return "", errors.New("destination address is required")
|
|
|
|
}
|
|
|
|
|
|
|
|
if parameters.ChainID == nil || *parameters.ChainID == walletCommon.UnknownChainID {
|
|
|
|
return "", errors.New("chainID is required")
|
|
|
|
}
|
|
|
|
|
|
|
|
if parameters.Symbol == nil || *parameters.Symbol == "" {
|
|
|
|
return "", errors.New("symbol is required")
|
|
|
|
}
|
|
|
|
|
|
|
|
network := mercuryo.CommonChainIDToNetwork(*parameters.ChainID)
|
|
|
|
if network == "" {
|
|
|
|
return "", errors.New("unsupported chainID")
|
|
|
|
}
|
|
|
|
|
|
|
|
currency := getMercuryoCurrency(*parameters.Symbol)
|
|
|
|
if currency == "" {
|
|
|
|
return "", errors.New("unsupported symbol")
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO #16005: Move signature generation to proxy server
|
|
|
|
signature := getMercuryoSignature(*parameters.DestAddress, widgetSecret)
|
|
|
|
url := fmt.Sprintf("%s&network=%s¤cy=%s&address=%s&hide_address=false&fix_address=true&signature=%s&widget_id=%s",
|
|
|
|
baseURL, network, currency, parameters.DestAddress.Hex(), signature, widgetID)
|
|
|
|
|
|
|
|
if parameters.IsRecurrent {
|
|
|
|
url = url + "&widget_flow=recurrent"
|
|
|
|
}
|
|
|
|
|
|
|
|
return url, nil
|
2021-01-26 13:00:32 +00:00
|
|
|
}
|