Production Execution Flow

This commit is contained in:
ryanml
2020-07-14 15:11:38 -07:00
parent 10b130adea
commit 5ddb672580
11 changed files with 280 additions and 11 deletions
+29
View File
@@ -227,5 +227,34 @@ void GeminiGetOrderQuoteFunction::OnOrderQuoteResult(
Respond(OneArgument(std::move(quote)));
}
ExtensionFunction::ResponseAction
GeminiExecuteOrderFunction::Run() {
if (!IsGeminiAPIAvailable(browser_context())) {
return RespondNow(Error("Not available in Tor/incognito/guest profile"));
}
std::unique_ptr<gemini::ExecuteOrder::Params> params(
gemini::ExecuteOrder::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
auto* service = GetGeminiService(browser_context());
bool balance_success = service->ExecuteOrder(
params->symbol, params->side, params->quantity,
params->price, params->fee, params->quote_id,
base::BindOnce(
&GeminiExecuteOrderFunction::OnOrderExecuted,
this));
if (!balance_success) {
return RespondNow(Error("Could not send request to execute order"));
}
return RespondLater();
}
void GeminiExecuteOrderFunction::OnOrderExecuted(bool success) {
Respond(OneArgument(std::make_unique<base::Value>(success)));
}
} // namespace api
} // namespace extensions
+12
View File
@@ -104,6 +104,18 @@ class GeminiGetOrderQuoteFunction :
ResponseAction Run() override;
};
class GeminiExecuteOrderFunction :
public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("gemini.executeOrder", UNKNOWN)
protected:
~GeminiExecuteOrderFunction() override {}
void OnOrderExecuted(bool success);
ResponseAction Run() override;
};
} // namespace api
} // namespace extensions
+8 -1
View File
@@ -306,7 +306,7 @@ void CustomizeWebUIHTMLSource(const std::string &name,
{ "geminiWidgetConnectTitle", IDS_GEMINI_WIDGET_CONNECT_TITLE },
{ "geminiWidgetConnectCopy", IDS_GEMINI_WIDGET_CONNECT_COPY },
{ "geminiWidgetConnectButton", IDS_GEMINI_WIDGET_CONNECT_BUTTON },
{ "geminiWidgetFailedTrade", IDS_GEMINI_WIDGET_FAILED_TRADE },
{ "geminiWidgetFailedTrade", IDS_GEMINI_WIDGET_FAILED_TRADE },
{ "geminiWidgetInsufficientFunds", IDS_BINANCE_WIDGET_INSUFFICIENT_FUNDS }, // NOLINT
{ "geminiWidgetError", IDS_GEMINI_WIDGET_ERROR },
{ "geminiWidgetConfirmTrade", IDS_GEMINI_WIDGET_CONFIRM_TRADE },
@@ -320,6 +320,13 @@ void CustomizeWebUIHTMLSource(const std::string &name,
{ "geminiWidgetDepositLabel", IDS_BINANCE_WIDGET_DEPOSIT_LABEL },
{ "geminiWidgetTradeLabel", IDS_GEMINI_WIDGET_TRADE_LABEL },
{ "geminiWidgetBalanceLabel", IDS_GEMINI_WIDGET_BALANCE_LABEL },
{ "geminiWidgetBuying", IDS_GEMINI_WIDGET_BUYING },
{ "geminiWidgetSelling", IDS_GEMINI_WIDGET_SELLING },
{ "geminiWidgetContinue", IDS_BINANCE_WIDGET_CONTINUE },
{ "geminiWidgetBought", IDS_GEMINI_WIDGET_BOUGHT },
{ "geminiWidgetSold", IDS_GEMINI_WIDGET_SOLD },
{ "geminiWidgetFee", IDS_BINANCE_WIDGET_FEE },
{ "geminiWidgetPrice", IDS_GEMINI_WIDGET_PRICE }
}
}, {
std::string("wallet"), {
+41
View File
@@ -172,6 +172,47 @@
]
}
]
},
{
"name": "executeOrder",
"type": "function",
"description": "Gets a quote for a given order",
"parameters": [
{
"type": "string",
"name": "symbol"
},
{
"type": "string",
"name": "side"
},
{
"type": "string",
"name": "quantity"
},
{
"type": "string",
"name": "price"
},
{
"type": "string",
"name": "fee"
},
{
"type": "number",
"name": "quoteId"
},
{
"type": "function",
"name": "callback",
"parameters": [
{
"type": "boolean",
"name": "success"
}
]
}
]
}
],
"types": [
@@ -68,13 +68,19 @@ import {
DisconnectCopy,
InvalidWrapper,
InvalidTitle,
InvalidCopy
InvalidCopy,
TradeInfoWrapper,
TradeInfoItem,
TradeItemLabel,
TradeValue,
StyledParty
} from './style'
import {
SearchIcon,
QRIcon,
ShowIcon,
HideIcon
HideIcon,
PartyIcon
} from '../exchangeWidget/shared-assets'
import GeminiLogo from './assets/gemini-logo'
import { CaratLeftIcon, CaratDownIcon } from 'brave-ui/components/icons'
@@ -239,7 +245,8 @@ class Gemini extends React.PureComponent<Props, State> {
currentQRAsset,
insufficientFunds,
tradeFailed,
showTradePreview
showTradePreview,
tradeSuccess
} = this.state
const { authInvalid, disconnectInProgress } = this.props
@@ -253,6 +260,8 @@ class Gemini extends React.PureComponent<Props, State> {
return this.renderInsufficientFundsView()
} else if (tradeFailed) {
return this.renderUnableToTradeView()
} else if (tradeSuccess) {
return this.renderTradeSuccess()
} else if (showTradePreview) {
return this.renderTradeConfirm()
}
@@ -331,6 +340,10 @@ class Gemini extends React.PureComponent<Props, State> {
USDValue += price * assetBalance
}
if ('USD' in accountBalances) {
USDValue += parseFloat(accountBalances['USD'])
}
return USDValue.toFixed(2)
}
@@ -579,13 +592,43 @@ class Gemini extends React.PureComponent<Props, State> {
})
}
shouldShowTradePreview () {
processTrade = () => {
const {
currentTradeId,
currentTradeAsset,
currentTradeFee: fee,
currentTradePrice: price,
currentTradeMode: side,
currentTradeQuantityLive: quantity
} = this.state
const quoteId = parseInt(currentTradeId, 10)
const symbol = `${currentTradeAsset}usd`.toUpperCase()
chrome.gemini.executeOrder(symbol, side, quantity, price, fee, quoteId, (success: boolean) => {
if (success) {
this.setState({ tradeSuccess: true })
} else {
this.setState({ tradeFailed: true })
}
})
}
finishTrade = () => {
this.cancelTrade()
this.props.onSetSelectedView('balance')
}
shouldShowTradePreview = () => {
const {
currentTradeMode,
currentTradeAsset,
currentTradeQuantity
} = this.state
const { accountBalances } = this.props
if (!currentTradeQuantity || isNaN(parseFloat(currentTradeQuantity))) {
return
}
const compare = currentTradeMode === 'buy'
? (accountBalances['USD'] || '0')
: (accountBalances[currentTradeAsset] || '0')
@@ -661,21 +704,64 @@ class Gemini extends React.PureComponent<Props, State> {
)
}
renderTradeSuccess = () => {
const {
currentTradeAsset,
currentTradeQuantityLive,
currentTradeMode
} = this.state
const quantity = this.formatCryptoBalance(currentTradeQuantityLive)
const actionLabel = currentTradeMode === 'buy' ? 'geminiWidgetBought' : 'geminiWidgetSold'
return (
<InvalidWrapper>
<StyledParty>
<img src={PartyIcon} />
</StyledParty>
<InvalidTitle>
{`${getLocale(actionLabel)} ${quantity} ${currentTradeAsset}!`}
</InvalidTitle>
<ConnectButton isSmall={true} onClick={this.finishTrade}>
{getLocale('geminiWidgetContinue')}
</ConnectButton>
</InvalidWrapper>
)
}
renderTradeConfirm = () => {
const {
currentTradeQuantityLive,
currentTradeFee,
currentTradeExpiryTime
currentTradeAsset,
currentTradeQuantityLive,
currentTradeMode,
currentTradeExpiryTime,
currentTradePrice
} = this.state
console.log(currentTradeExpiryTime, currentTradeFee, currentTradeQuantityLive)
const tradeLabel = currentTradeMode === 'buy' ? 'geminiWidgetBuying' : 'geminiWidgetSelling'
const quantity = this.formatCryptoBalance(currentTradeQuantityLive)
const fee = this.formatCryptoBalance(currentTradeFee)
return (
<InvalidWrapper>
<InvalidTitle>
{getLocale('geminiWidgetConfirmTrade')}
</InvalidTitle>
<TradeInfoWrapper>
<TradeInfoItem>
<TradeItemLabel>{getLocale(tradeLabel)}</TradeItemLabel>
<TradeValue>{`${quantity} ${currentTradeAsset}`}</TradeValue>
</TradeInfoItem>
<TradeInfoItem>
<TradeItemLabel>{getLocale('geminiWidgetPrice')}</TradeItemLabel>
<TradeValue>{`${currentTradePrice} USD`}</TradeValue>
</TradeInfoItem>
<TradeInfoItem isLast={true}>
<TradeItemLabel>{getLocale('geminiWidgetFee')}</TradeItemLabel>
<TradeValue>{`${fee} USD`}</TradeValue>
</TradeInfoItem>
</TradeInfoWrapper>
<ActionsWrapper>
<ConnectButton isSmall={true}>
<ConnectButton isSmall={true} onClick={this.processTrade}>
{`${getLocale('geminiWidgetConfirm')} (${currentTradeExpiryTime}s)`}
</ConnectButton>
<DismissAction onClick={this.cancelTrade}>
@@ -507,3 +507,33 @@ export const InvalidCopy = styled(DisconnectCopy)`
export const InvalidWrapper = styled(DisconnectWrapper)`
min-width: 244px;
`
export const TradeItemLabel = styled<{}, 'span'>('span')`
float: left;
width: 45%;
text-align: left;
font-size: 15px;
`
export const TradeValue = styled<{}, 'span'>('span')`
font-weight: bold;
float: right;
width: 55%;
text-align: right;
font-size: 15px;
`
export const TradeInfoWrapper = styled<StyleProps, 'div'>('div')`
margin: 20px 0;
overflow-y: auto;
`
export const TradeInfoItem = styled<StyleProps, 'div'>('div')`
margin: 5px 0;
overflow-y: hidden;
margin-top: ${p => p.isLast ? '15' : '5'}px;
`
export const StyledParty = styled<{}, 'div'>('div')`
margin: 10px 0px;
`
@@ -50,7 +50,11 @@ const geminiReducer: Reducer<NewTab.State | undefined> = (state: NewTab.State, a
case types.SET_ACCOUNT_BALANCES:
const { balances } = payload
state = { ...state }
state.geminiState.accountBalances = balances
for (let balance in balances) {
if (balances[balance]) {
state.geminiState.accountBalances[balance] = balances[balance]
}
}
break
case types.ON_DEPOSIT_QR_FOR_ASSET:
+1
View File
@@ -175,6 +175,7 @@ declare namespace chrome.gemini {
const getDepositInfo: (asset: string, callback: (depositAddress: string, depositTag: string) => void) => {}
const revokeToken: (callback: (success: boolean) => void) => {}
const getOrderQuote: (side: string, symbol: string, spend: string, callback: (quote: any) => void) => {}
const executeOrder: (symbol: string, side: string, quantity: string, price: string, fee: string, quoteId: number, callback: (success: boolean) => void) => {}
}
declare namespace chrome.braveTogether {
@@ -81,6 +81,26 @@ namespace {
return encoded_payload;
}
std::string GetEncodedExecutePayload(std::string symbol,
std::string side,
std::string quantity,
std::string price,
std::string fee,
int quote_id) {
std::string encoded_payload;
std::string request_payload = "{\"request\": \"" +
std::string(api_path_execute_quote) +
"\",\"symbol\": \"" + symbol +
"\",\"side\": \"" + side +
"\",\"quantity\": \"" + quantity +
"\",\"price\": \"" + price +
"\",\"fee\": \"" + fee +
"\",\"quoteId\": \"" + std::to_string(quote_id) +
"\"}";
base::Base64Encode(request_payload, &encoded_payload);
return encoded_payload;
}
} // namespace
GeminiService::GeminiService(content::BrowserContext* context)
@@ -259,6 +279,29 @@ void GeminiService::OnGetOrderQuote(GetOrderQuoteCallback callback,
std::move(callback).Run(quote_id, quantity, fee, price);
}
bool GeminiService::ExecuteOrder(const std::string& symbol,
const std::string& side,
const std::string& quantity,
const std::string& price,
const std::string& fee,
const int quote_id,
ExecuteOrderCallback callback) {
auto internal_callback = base::BindOnce(&GeminiService::OnOrderExecuted,
base::Unretained(this), std::move(callback));
std::string payload = GetEncodedExecutePayload(
symbol, side, quantity, price, fee, quote_id);
GURL url = GetURLWithPath(api_host, api_path_execute_quote);
return OAuthRequest(
url, "POST", "", std::move(internal_callback), true, true, payload);
}
void GeminiService::OnOrderExecuted(ExecuteOrderCallback callback,
const int status, const std::string& body,
const std::map<std::string, std::string>& headers) {
bool success = (status >= 200 && status <= 299);
std::move(callback).Run(success);
}
bool GeminiService::SetAccessTokens(const std::string& access_token,
const std::string& refresh_token) {
access_token_ = access_token;
@@ -65,6 +65,7 @@ class GeminiService : public KeyedService {
const std::string&,
const std::string&,
const std::string&)>;
using ExecuteOrderCallback = base::OnceCallback<void(bool)>;
std::string GetOAuthClientUrl();
void SetAuthToken(const std::string& auth_token);
@@ -79,6 +80,13 @@ class GeminiService : public KeyedService {
const std::string& symbol,
const std::string& spend,
GetOrderQuoteCallback callback);
bool ExecuteOrder(const std::string& symbol,
const std::string& side,
const std::string& quantity,
const std::string& price,
const std::string& fee,
const int quote_id,
ExecuteOrderCallback callback);
private:
base::SequencedTaskRunner* io_task_runner();
@@ -109,6 +117,9 @@ class GeminiService : public KeyedService {
void OnGetOrderQuote(GetOrderQuoteCallback callback,
const int status, const std::string& body,
const std::map<std::string, std::string>& headers);
void OnOrderExecuted(ExecuteOrderCallback callback,
const int status, const std::string& body,
const std::map<std::string, std::string>& headers);
bool OAuthRequest(const GURL& url, const std::string& method,
const std::string& post_data, URLRequestCallback callback,
@@ -889,7 +889,7 @@
<message name="IDS_GEMINI_WIDGET_CONNECT_TITLE" desc="">Purchase and trade with Gemini</message>
<message name="IDS_GEMINI_WIDGET_CONNECT_COPY" desc="">Enable a Gemini connection to view your Gemini account balance and trade crypto.</message>
<message name="IDS_GEMINI_WIDGET_CONNECT_BUTTON" desc="">Connect to Gemini.</message>
<message name="IDS_GEMINI_WIDGET_CONNECT_BUTTON" desc="">Connect to Gemini</message>
<message name="IDS_GEMINI_WIDGET_AUTH_INVALID_COPY" desc="">Your Gemini access token has expired and could not be refreshed. Please reconnect.</message>
<message name="IDS_GEMINI_WIDGET_FAILED_TRADE" desc="">Unable to perform trade</message>
<message name="IDS_GEMINI_WIDGET_ERROR" desc="">Something went wrong</message>
@@ -898,6 +898,11 @@
<message name="IDS_GEMINI_WIDGET_GET_QUOTE" desc="">Get a Quote</message>
<message name="IDS_GEMINI_WIDGET_TRADE_LABEL" desc="">Trade</message>
<message name="IDS_GEMINI_WIDGET_BALANCE_LABEL" desc="">Balance</message>
<message name="IDS_GEMINI_WIDGET_BUYING" desc="">Buying</message>
<message name="IDS_GEMINI_WIDGET_SELLING" desc="">Selling</message>
<message name="IDS_GEMINI_WIDGET_BOUGHT" desc="">You bought</message>
<message name="IDS_GEMINI_WIDGET_SOLD" desc="">You sold</message>
<message name="IDS_GEMINI_WIDGET_PRICE" desc="">Price</message>
<!-- WebUI Brave Toolbar resources -->
<message name="IDS_WALLETS_TITLE" desc="The toolbar title for the brave://wallets page">Crypto Wallets</message>