status-go/services/wallet/onramp/provider_mercuryo.go

158 lines
24 KiB
Go
Raw Normal View History

package onramp
import (
"context"
"crypto/sha512"
"errors"
"fmt"
"strings"
"sync"
"time"
"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&currency=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) {
const (
logoMercuryo = "data:image/jpeg;base64,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
)
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)
}
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
)
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&currency=%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
}