diff --git a/backend/accounts/account.go b/backend/accounts/account.go index 93bec34dac..a8936e51ac 100644 --- a/backend/accounts/account.go +++ b/backend/accounts/account.go @@ -21,7 +21,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/notes" - "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/safello" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/coin" "github.com/digitalbitbox/bitbox-wallet-app/backend/signing" "github.com/digitalbitbox/bitbox-wallet-app/util/observable" @@ -82,12 +81,6 @@ type Interface interface { CanVerifyAddresses() (bool, bool, error) VerifyAddress(addressID string) (bool, error) - // SafelloBuySupported returns true if the Safello Buy widget can be used with this account. - SafelloBuySupported() bool - // Safello returns the infos needed to load the Safello Buy widget. panics() if Safello is not - // supported for this coin. Check support with `SafelloBuySupported()` before calling this. - SafelloBuy() *safello.Buy - Notes() *notes.Notes // ProposeTxnote stores a note. The note is is persisted in the notes database upon calling // SendTx(). This function must be called before `SendTx()`. diff --git a/backend/accounts/safello/safello.go b/backend/accounts/safello/safello.go deleted file mode 100644 index f5425b1102..0000000000 --- a/backend/accounts/safello/safello.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 Shift Devices AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package safello - -import ( - "encoding/json" - "fmt" - "io/ioutil" - - "github.com/digitalbitbox/bitbox-wallet-app/util/errp" -) - -// Buy holds the infos needed to load the Safello Buy widget. -type Buy struct { - URL string `json:"url"` - AddressID string `json:"addressID"` - Address string `json:"address"` -} - -// NewBuy creates a Buy instance. -func NewBuy(testnet bool, receiveAddressID, receiveAddress string) *Buy { - const shiftAppID = "6302f999-1f53-4428-871c-7538a1007d8c" - appID := shiftAppID - country := "" - host := "app.safello.com" - if testnet { - // Predefined values for the Safello test sandbox. - appID = "1234-5678" - country = "no" - host = "app.s4f3.io" - } - return &Buy{ - URL: fmt.Sprintf("https://%s/sdk/quickbuy.html?appId=%s&country=%s&tab=buy&border=false&source=Shift+Cryptosecurity+AG&crypto=btc&address=%s", - host, - appID, - country, - receiveAddress, - ), - AddressID: receiveAddressID, - Address: receiveAddress, - } -} - -// StoreCallbackJSONMessage a raw safello JSON object as sent by the Safello widget. -// It is appended at the end of an array of Safello messages. -// file structure: [sallelo object, sallelo object, ....]. -// The directory the filename is in must exist beforehand. -// The message must contain a "type" key with a value of either `"ORDER_DONE"` or `"TRANSACTION_ISSUED"`. -func StoreCallbackJSONMessage(filename string, message map[string]json.RawMessage) error { - val, ok := message["type"] - if !ok || !(string(val) == `"ORDER_DONE"` || string(val) == `"TRANSACTION_ISSUED"`) { - return errp.New("message needs to contain a valid type entry") - } - messages := []map[string]json.RawMessage{} - jsonBytes, err := ioutil.ReadFile(filename) // #nosec G304 - if err == nil { - if err := json.Unmarshal(jsonBytes, &messages); err != nil { - return errp.WithStack(err) - } - } - messages = append(messages, message) - writeJSONBytes, err := json.Marshal(messages) - if err != nil { - return errp.WithStack(err) - } - return ioutil.WriteFile(filename, writeJSONBytes, 0600) -} diff --git a/backend/accounts/safello/safello_test.go b/backend/accounts/safello/safello_test.go deleted file mode 100644 index 369b1449e2..0000000000 --- a/backend/accounts/safello/safello_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 Shift Devices AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package safello_test - -import ( - "encoding/json" - "io/ioutil" - "testing" - - "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/safello" - "github.com/digitalbitbox/bitbox-wallet-app/util/test" - "github.com/stretchr/testify/require" -) - -func TestStoreCallbackJSONMessage(t *testing.T) { - filename := test.TstTempFile("safello") - - require.Error(t, safello.StoreCallbackJSONMessage(filename, nil)) - require.Error(t, safello.StoreCallbackJSONMessage(filename, map[string]json.RawMessage{"key": json.RawMessage(`"value"`)})) - - msg1 := map[string]json.RawMessage{ - "type": json.RawMessage(`"ORDER_DONE"`), - "key1": json.RawMessage(`"value1"`), - "key2": json.RawMessage(`"value2"`), - } - msg2 := map[string]json.RawMessage{ - "type": json.RawMessage(`"ORDER_DONE"`), - } - msg3 := map[string]json.RawMessage{ - "type": json.RawMessage(`"TRANSACTION_ISSUED"`), - "foo": json.RawMessage(`"bar"`), - } - require.NoError(t, safello.StoreCallbackJSONMessage(filename, msg1)) - require.NoError(t, safello.StoreCallbackJSONMessage(filename, msg2)) - require.NoError(t, safello.StoreCallbackJSONMessage(filename, msg3)) - - result, err := ioutil.ReadFile(filename) // #nosec G304 - require.NoError(t, err) - require.JSONEq(t, - `[{"key1":"value1","key2":"value2","type":"ORDER_DONE"},{"type":"ORDER_DONE"},{"foo":"bar","type":"TRANSACTION_ISSUED"}]`, - string(result), - ) -} diff --git a/backend/backend.go b/backend/backend.go index 593beff7f1..92609c8ec1 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -1114,7 +1114,12 @@ func (backend *Backend) SystemOpen(url string) error { "https://www.coingecko.com", "https://bitcoincore.org/en/2016/01/26/segwit-benefits/", "https://en.bitcoin.it/wiki/Bech32_adoption", - "https://help.safello.com", + // Moonpay onramp + "https://www.moonpay.com", + "https://support.moonpay.com", + "https://support.moonpay.io", + "https://help.moonpay.io", + "https://help.moonpay.com", } { if url == whitelistedURL { blocked = false @@ -1133,6 +1138,7 @@ func (backend *Backend) SystemOpen(url string) error { "^https://etherscan\\.io/tx/", "^https://rinkeby\\.etherscan\\.io/tx/", "^https://ropsten\\.etherscan\\.io/tx/", + "^https://support.moonpay.com/", } if runtime.GOOS != "android" { // TODO: fix DownloadsDir() for android diff --git a/backend/coins/btc/exchange.go b/backend/coins/btc/exchange.go deleted file mode 100644 index 23dbef2a48..0000000000 --- a/backend/coins/btc/exchange.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2018 Shift Devices AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package btc - -import ( - "github.com/btcsuite/btcd/chaincfg" - "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/safello" -) - -// SafelloBuySupported implements accounts.Interface. -func (account *Account) SafelloBuySupported() bool { - switch account.coin.Net().Net { - case chaincfg.MainNetParams.Net, chaincfg.TestNet3Params.Net: - return false // Safello suspended services, maybe temporarily, so we keep this around for a bit. - } - return false -} - -// SafelloBuy implements accounts.Interface. -func (account *Account) SafelloBuy() *safello.Buy { - switch account.coin.Net().Net { - case chaincfg.MainNetParams.Net: - address := account.GetUnusedReceiveAddresses()[0][0] - return safello.NewBuy(false, address.ID(), address.EncodeForHumans()) - case chaincfg.TestNet3Params.Net: - address := account.GetUnusedReceiveAddresses()[0][0] - return safello.NewBuy(true, address.ID(), address.EncodeForHumans()) - default: - panic("SafelloBuy is not supported by this account") - } -} diff --git a/backend/coins/btc/handlers/handlers.go b/backend/coins/btc/handlers/handlers.go index d04d1758e4..f8ff47016f 100644 --- a/backend/coins/btc/handlers/handlers.go +++ b/backend/coins/btc/handlers/handlers.go @@ -20,7 +20,6 @@ import ( "encoding/json" "net/http" "os" - "path" "path/filepath" "strconv" "strings" @@ -30,11 +29,11 @@ import ( "github.com/btcsuite/btcutil" "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts" "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/errors" - "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/safello" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc/util" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/coin" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/eth" + "github.com/digitalbitbox/bitbox-wallet-app/backend/exchanges" "github.com/digitalbitbox/bitbox-wallet-app/backend/keystore" "github.com/digitalbitbox/bitbox-wallet-app/util/config" "github.com/digitalbitbox/bitbox-wallet-app/util/errp" @@ -68,9 +67,8 @@ func NewHandlers( handleFunc("/can-verify-extended-public-key", handlers.ensureAccountInitialized(handlers.getCanVerifyExtendedPublicKey)).Methods("GET") handleFunc("/verify-extended-public-key", handlers.ensureAccountInitialized(handlers.postVerifyExtendedPublicKey)).Methods("POST") handleFunc("/has-secure-output", handlers.ensureAccountInitialized(handlers.getHasSecureOutput)).Methods("GET") - handleFunc("/exchange/safello/buy-supported", handlers.ensureAccountInitialized(handlers.getExchangeSafelloBuySupported)).Methods("GET") - handleFunc("/exchange/safello/buy", handlers.ensureAccountInitialized(handlers.getExchangeSafelloBuy)).Methods("GET") - handleFunc("/exchange/safello/process-message", handlers.ensureAccountInitialized(handlers.postExchangeSafelloProcessMessage)).Methods("POST") + handleFunc("/exchange/moonpay/buy-supported", handlers.ensureAccountInitialized(handlers.getExchangeMoonpayBuySupported)).Methods("GET") + handleFunc("/exchange/moonpay/buy", handlers.ensureAccountInitialized(handlers.getExchangeMoonpayBuy)).Methods("GET") handleFunc("/propose-tx-note", handlers.ensureAccountInitialized(handlers.postProposeTxNote)).Methods("POST") handleFunc("/notes/tx", handlers.ensureAccountInitialized(handlers.postSetTxNote)).Methods("POST") return handlers @@ -486,24 +484,27 @@ func (handlers *Handlers) getHasSecureOutput(r *http.Request) (interface{}, erro }, nil } -func (handlers *Handlers) getExchangeSafelloBuySupported(r *http.Request) (interface{}, error) { - return handlers.account.SafelloBuySupported(), nil +func (handlers *Handlers) getExchangeMoonpayBuySupported(r *http.Request) (interface{}, error) { + return exchanges.IsMoonpaySupported(handlers.account.Coin().Code()), nil } -func (handlers *Handlers) getExchangeSafelloBuy(r *http.Request) (interface{}, error) { - return handlers.account.SafelloBuy(), nil -} - -func (handlers *Handlers) postExchangeSafelloProcessMessage(r *http.Request) (interface{}, error) { - var message map[string]json.RawMessage - if err := json.NewDecoder(r.Body).Decode(&message); err != nil { - return nil, errp.WithStack(err) +func (handlers *Handlers) getExchangeMoonpayBuy(r *http.Request) (interface{}, error) { + params := exchanges.BuyMoonpayParams{ + Fiat: "CHF", // TODO: Get this from app config + Lang: "en", // TODO: Get this from the backend } - - return nil, safello.StoreCallbackJSONMessage( - path.Join(handlers.account.FilesFolder(), "safello-buy.json"), - message, - ) + buy, err := exchanges.BuyMoonpay(handlers.account, params) + if err != nil { + return nil, err + } + resp := struct { + URL string `json:"url"` + Address string `json:"address"` + }{ + URL: buy.URL, + Address: buy.Address, + } + return resp, nil } func (handlers *Handlers) postProposeTxNote(r *http.Request) (interface{}, error) { diff --git a/backend/coins/eth/exchange.go b/backend/coins/eth/exchange.go deleted file mode 100644 index 3a6926ffc5..0000000000 --- a/backend/coins/eth/exchange.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 Shift Devices AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package eth - -import "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/safello" - -// SafelloBuySupported implements accounts.Interface. -func (account *Account) SafelloBuySupported() bool { - return false -} - -// SafelloBuy implements accounts.Interface. -func (account *Account) SafelloBuy() *safello.Buy { - panic("SafelloBuy is not supported by this account") -} diff --git a/backend/config/config.go b/backend/config/config.go index e4224d7ef2..acdb4ba66e 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -80,14 +80,9 @@ func (proxy proxyConfig) ProxyAddressOrDefault() string { return defaultProxyAddress } -type servicesConfig struct { - Safello bool `json:"safello"` -} - // Backend holds the backend specific configuration. type Backend struct { - Proxy proxyConfig `json:"proxy"` - Services servicesConfig `json:"services"` + Proxy proxyConfig `json:"proxy"` BitcoinActive bool `json:"bitcoinActive"` LitecoinActive bool `json:"litecoinActive"` @@ -158,9 +153,6 @@ func NewDefaultAppConfig() AppConfig { UseProxy: false, ProxyAddress: defaultProxyAddress, }, - Services: servicesConfig{ - Safello: true, - }, BitcoinActive: true, LitecoinActive: true, EthereumActive: true, diff --git a/backend/exchanges/moonpay.go b/backend/exchanges/moonpay.go new file mode 100644 index 0000000000..49a3eb3715 --- /dev/null +++ b/backend/exchanges/moonpay.go @@ -0,0 +1,90 @@ +package exchanges + +import ( + "fmt" + "net/url" + + "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts" + "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/coin" +) + +// moonpayAPITestPubKey is the public key of Shift Crypto Moonpay account. +// Test assets and notes: +// - working fake CC: 4000 0231 0466 2535, 12/2020, 123 +// - always declined CC: 4008 3708 9666 2369, 12/2020, 123 +// - TBTC and TETH work; send them back after a purchase to +// tb1q45h8zexwztmz3nyd8gmkxhpavdsva4znwwhzvs and +// 0xc216eD2D6c295579718dbd4a797845CdA70B3C36, respectively +// - KYC always succeeds; simply click on "submit anyway" +// - need to provide a valid email addr to receive a check code; +// any temp email service like fakermail.com will do +const moonpayAPITestPubKey = "pk_test_e9i4oaa4J7eKo8UI3Wm8QLagoskWGjXN" + +// BuyMoonpayInfo contains a starting point for initiating an onramp flow. +type BuyMoonpayInfo struct { + URL string // moonpay's buy widget URL + Address string // which address to send coins to +} + +// BuyMoonpayParams specifies parameters to iniate a cryptocurrency purchase flow. +type BuyMoonpayParams struct { + Fiat string // fiat currency code, like "CHF" or "USD" + Lang string // user preferred language in ISO 639-1; falls back to "en" +} + +// BuyMoonpay returns info for the frontend to initiate an onramp flow. +func BuyMoonpay(acct accounts.Interface, params BuyMoonpayParams) (BuyMoonpayInfo, error) { + // Here's the list of all supported currencies: + // https://api.moonpay.com/v3/currencies?apiKey=pk_test_e9i4oaa4J7eKo8UI3Wm8QLagoskWGjXN + // Note that it may be different for prod account. + var cryptoCode = map[coin.Code]string{ // -> moonpay crypto currency code + coin.CodeBTC: "btc", + coin.CodeLTC: "ltc", // moonpay doesn't seem to support TLTC + coin.CodeETH: "eth", + // TODO: ERC20 tokens + + // Supported testnet coins. + coin.CodeTBTC: "btc", // moonpay uses testnet in test mode + coin.CodeTETH: "eth", // moonpay uses ropsten in test mode + } + + if !IsMoonpaySupported(acct.Coin().Code()) { + return BuyMoonpayInfo{}, fmt.Errorf("unsupported cryptocurrency code %q", acct.Coin().Code()) + } + ccode, ok := cryptoCode[acct.Coin().Code()] + if !ok { + return BuyMoonpayInfo{}, fmt.Errorf("unknown cryptocurrency code %q", acct.Coin().Code()) + } + unused := acct.GetUnusedReceiveAddresses() + addr := unused[0][0] // TODO: Let them choose sub acct? + val := url.Values{ + // TODO: Honor -testnet flag to switch between test/prod + "apiKey": {moonpayAPITestPubKey}, + "walletAddress": {addr.EncodeForHumans()}, + "currencyCode": {ccode}, + "language": {params.Lang}, + "baseCurrencyCode": {params.Fiat}, + } + return BuyMoonpayInfo{ + // TODO: Honor -testnet flag for the base URL here as well + URL: fmt.Sprintf("https://buy-staging.moonpay.com?%s", val.Encode()), + Address: addr.EncodeForHumans(), + }, nil +} + +// IsMoonpaySupported reports whether moonpay.com supports onramp. +// TODO: Check country/state? +func IsMoonpaySupported(code coin.Code) bool { + // TODO: ERC20 tokens + switch code { + default: + return false + case coin.CodeBTC, coin.CodeLTC, coin.CodeETH: + return true + // Test networks. + // Moonpay doesn't seem to support TLTC. + // TODO: Disable if -testnet flag isn't set. + case coin.CodeTBTC, coin.CodeTETH: + return true + } +} diff --git a/frontends/android/BitBoxApp/app/src/main/java/ch/shiftcrypto/bitboxapp/MainActivity.java b/frontends/android/BitBoxApp/app/src/main/java/ch/shiftcrypto/bitboxapp/MainActivity.java index b67729335f..dc461bbfe4 100644 --- a/frontends/android/BitBoxApp/app/src/main/java/ch/shiftcrypto/bitboxapp/MainActivity.java +++ b/frontends/android/BitBoxApp/app/src/main/java/ch/shiftcrypto/bitboxapp/MainActivity.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.Message; import android.os.Bundle; +import android.webkit.CookieManager; import android.webkit.JavascriptInterface; import android.webkit.MimeTypeMap; import android.webkit.PermissionRequest; @@ -98,6 +99,8 @@ protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().hide(); // hide title bar with app name. setContentView(R.layout.activity_main); final WebView vw = (WebView)findViewById(R.id.vw); + // For onramp iframe'd widgets like MoonPay. + CookieManager.getInstance().setAcceptThirdPartyCookies(vw, true); // GoModel invokes the Go backend. It is in a ViewModel so it only runs once, not every time // onCreate is called (on a configuration change like orientation change) @@ -154,7 +157,7 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { // Block navigating to any external site inside the app. // This is only called if the whole page is about to change. Changes inside an iframe proceed normally. try { - // Allow opening in external browser instead, for links clicked in the buy page (e.g. links inside the Safello widget). + // Allow opening in external browser instead, for links clicked in an onramp widget. Pattern pattern = Pattern.compile("^file:///account/[^/]+/buy$"); if (pattern.matcher(view.getUrl()).matches()) { Util.systemOpen(getApplication(), url); diff --git a/frontends/qt/main.cpp b/frontends/qt/main.cpp index 362f0dc384..288e4d321f 100644 --- a/frontends/qt/main.cpp +++ b/frontends/qt/main.cpp @@ -53,11 +53,12 @@ class RequestInterceptor : public QWebEngineUrlRequestInterceptor { if (info.requestUrl().scheme() == "qrc" || info.requestUrl().scheme() == "blob") { return; } -#if 0 // Safello suspended services, maybe temporarily, so we keep this around for a bit. + + // We treat the onramp page specially because we need to allow onramp + // widgets to load in an iframe as well as let them open external links + // in a browser. auto currentUrl = mainPage->requestedUrl().toString(); - bool onBuyPage = currentUrl.contains(QRegularExpression("^qrc:/account/[^/]+?/buy$")); - // We treat the buy page specially, as we need to allow Safello to load in the iframe, as - // well as open Safello link externally in the browser. + bool onBuyPage = currentUrl.contains(QRegularExpression("^qrc:/buy/moonpay/[^/]+?$")); if (onBuyPage) { if (info.firstPartyUrl().toString() == info.requestUrl().toString()) { // A link with target=_blank was clicked. @@ -67,7 +68,7 @@ class RequestInterceptor : public QWebEngineUrlRequestInterceptor { } return; } -#endif + std::cerr << "Blocked: " << info.requestUrl().toString().toStdString() << std::endl; info.block(true); }; diff --git a/frontends/web/src/app.tsx b/frontends/web/src/app.tsx index c4bf3e18bb..590f5ec9af 100644 --- a/frontends/web/src/app.tsx +++ b/frontends/web/src/app.tsx @@ -1,5 +1,6 @@ /** * Copyright 2018 Shift Devices AG + * Copyright 2020 Shift Crypto AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +31,8 @@ import { translate, TranslateProps } from './decorators/translate'; import { i18nEditorActive } from './i18n/i18n'; import { Account, AccountInterface } from './routes/account/account'; import { AddAccount } from './routes/account/add/addaccount'; -import { Buy } from './routes/account/buy/buy'; +import { Moonpay } from './routes/buy/moonpay'; +import { BuyInfo } from './routes/buy/info'; import Info from './routes/account/info/info'; import { Receive } from './routes/account/receive/receive'; import { Send } from './routes/account/send/send'; @@ -199,8 +201,12 @@ class App extends Component { devices={devices} accounts={accounts} deviceIDs={deviceIDs} /> - + ): JSX.Element { return ( { - apiPost('open', href); e.preventDefault(); + const { hostname, origin } = new URL(href, location.href); + if (origin === 'qrc:' || (debug && hostname === location.hostname)) { + hide(); + route(href); + } else { + apiPost('open', href); + } }} title={props.title || href} {...props}> {icon ? icon : null} {children} diff --git a/frontends/web/src/components/forms/select.css b/frontends/web/src/components/forms/select.css index 4cffbcc227..34b4bdc99e 100644 --- a/frontends/web/src/components/forms/select.css +++ b/frontends/web/src/components/forms/select.css @@ -37,3 +37,7 @@ border: solid 1px var(--color-lightgray); background-color: white; } + +.select option[disabled] { + color: var(--color-mediumgray); +} diff --git a/frontends/web/src/components/forms/select.jsx b/frontends/web/src/components/forms/select.jsx index 2ed59cd887..73926c8ab1 100644 --- a/frontends/web/src/components/forms/select.jsx +++ b/frontends/web/src/components/forms/select.jsx @@ -28,10 +28,11 @@ export default function Select({
{label && } this.setState({ selected: e.target.value})} + value={selected} + defaultValue={'choose'} + id="coinAndAccountCode" + /> +
+ +
+
+ ) + )} + + + + + ); + } +} + +const loadHOC = load({ + config: 'config' +})(BuyInfo); + +const HOC = translate()(loadHOC); +export { HOC as BuyInfo }; diff --git a/frontends/web/src/routes/account/buy/buy.css b/frontends/web/src/routes/buy/moonpay.css similarity index 69% rename from frontends/web/src/routes/account/buy/buy.css rename to frontends/web/src/routes/buy/moonpay.css index 957758734b..6618080a65 100644 --- a/frontends/web/src/routes/account/buy/buy.css +++ b/frontends/web/src/routes/buy/moonpay.css @@ -4,4 +4,6 @@ .iframe { max-width: 100%; + position: relative; + z-index: 4001; } diff --git a/frontends/web/src/routes/buy/moonpay.tsx b/frontends/web/src/routes/buy/moonpay.tsx new file mode 100644 index 0000000000..268e1694de --- /dev/null +++ b/frontends/web/src/routes/buy/moonpay.tsx @@ -0,0 +1,115 @@ +/** + * Copyright 2018 Shift Devices AG + * Copyright 2020 Shift Crypto AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, createRef, h, RenderableProps } from 'preact'; +import Guide from './guide'; +import { Header } from '../../components/layout'; +import { load } from '../../decorators/load'; +import { translate, TranslateProps } from '../../decorators/translate'; +import { Devices } from '../device/deviceswitch'; +import { AccountInterface } from '../account/account'; +import { Spinner } from '../../components/spinner/Spinner'; +import * as style from './moonpay.css'; + +interface BuyProps { + accounts: AccountInterface[]; + code?: string; + devices: Devices; +} + +interface LoadedBuyProps { + moonpay: { url: string, address: string; }; +} + +interface State { + height: number; +} + +type Props = LoadedBuyProps & BuyProps & TranslateProps; + +class Moonpay extends Component { + private ref = createRef(); + private resizeTimerID?: any; + + public componentDidMount() { + this.onResize(); + window.addEventListener('resize', this.onResize); + } + + public componentWillUnmount() { + window.removeEventListener('resize', this.onResize); + } + + private onResize = () => { + if (this.resizeTimerID) { + clearTimeout(this.resizeTimerID); + } + this.resizeTimerID = setTimeout(() => { + if (!this.ref.current) { + return; + } + this.setState({ height: this.ref.current.offsetHeight }); + }, 200); + } + + private getAccount = () => { + if (!this.props.accounts) { + return undefined; + } + return this.props.accounts.find(({ code }) => code === this.props.code); + } + + public render( + { code, + moonpay, + t }: RenderableProps, + { height }: State, + ) { + const account = this.getAccount(); + if (!account) { + return null; + } + const name = code === 'btc' || code === 'tbtc' ? 'Bitcoin' : 'crypto'; + return ( +
+
+
+
+
+ + +
+
+
+ +
+ ); + } +} + +const loadHOC = load(({ code }) => ({ + moonpay: `account/${code}/exchange/moonpay/buy`, +}))(Moonpay); +const HOC = translate()(loadHOC); +export { HOC as Moonpay }; diff --git a/frontends/web/src/routes/settings/settings.tsx b/frontends/web/src/routes/settings/settings.tsx index b063807dfb..cb53ca43ef 100644 --- a/frontends/web/src/routes/settings/settings.tsx +++ b/frontends/web/src/routes/settings/settings.tsx @@ -206,27 +206,6 @@ class Settings extends Component { this.setState({ activeProxyDialog: false }); } - /* - private setServicesConfig = servicesConfig => { - setConfig({ - backend: { services: servicesConfig }, - }).then(config => { - this.setState({ config }); - }); - } - - private handleToggleSafello = (event: Event) => { - const config = this.state.config; - if (!config) { - return; - } - const target = (event.target as HTMLInputElement); - const services = config.backend.services; - services.safello = target.checked; - this.setServicesConfig(services); - } - */ - private handleRestartDismissMessage = () => { this.setState({ restart: false }); } @@ -434,31 +413,6 @@ class Settings extends Component { } - - {/* Safello suspended services, maybe temporarily, so we keep this around for a bit. -
-
-
-

{t('settings.services.title')}

-
-
-
-
-
-

Safello

-

- BTC -

-
- -
- -
-
- */} { restart && ( diff --git a/frontends/web/src/style/layout.css b/frontends/web/src/style/layout.css index ac1f9af8a8..1c64345d50 100644 --- a/frontends/web/src/style/layout.css +++ b/frontends/web/src/style/layout.css @@ -349,6 +349,8 @@ .m-bottom-half { margin-bottom: var(--space-half) !important; } .m-bottom-default { margin-bottom: var(--space-default) !important; } .m-bottom-large { margin-bottom: calc(var(--space-half) * 1.5) !important; } +.m-bottom-xlarge { margin-bottom: calc(var(--space-half) * 3) !important; } +.m-bottom-xxlarge { margin-bottom: calc(var(--space-half) * 6) !important; } .m-left-quarter { margin-left: var(--space-quarter) !important; } .m-left-half { margin-left: var(--space-half) !important; } .m-left-default { margin-left: var(--space-default) !important; } diff --git a/frontends/web/src/utils/custom.d.ts b/frontends/web/src/utils/custom.d.ts index a7f92706a8..cbda05102e 100644 --- a/frontends/web/src/utils/custom.d.ts +++ b/frontends/web/src/utils/custom.d.ts @@ -30,6 +30,7 @@ declare module '*.svg'; declare namespace JSX { // tslint:disable-line:no-namespace interface HTMLAttributes { align?: 'left' | 'right' | 'center'; + allow?: 'payment'; autocorrect?: 'on' | 'off'; spellcheck?: boolean; }