Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sell and fund flow [LIVE-784] #91

Merged
merged 9 commits into from
Jun 15, 2022
18 changes: 18 additions & 0 deletions apps/ledger-live-mobile/src/components/DeviceAction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
renderInWrongAppForAccount,
renderError,
renderBootloaderStep,
renderExchange,
renderConfirmSwap,
renderConfirmSell,
LoadingAppInstall,
Expand Down Expand Up @@ -81,6 +82,9 @@ export default function DeviceAction<R, H, P>({
initSwapResult,
signMessageRequested,
allowOpeningGranted,
completeExchangeStarted,
completeExchangeResult,
completeExchangeError,
initSellRequested,
initSellResult,
initSellError,
Expand Down Expand Up @@ -170,6 +174,20 @@ export default function DeviceAction<R, H, P>({
});
}

if (
completeExchangeStarted &&
!completeExchangeResult &&
!completeExchangeError
) {
return renderExchange({
// $FlowFixMe
exchangeType: request?.exchangeType,
t,
device,
theme,
});
}

if (initSwapRequested && !initSwapResult && !initSwapError) {
return renderConfirmSwap({ t, device: selectedDevice, colors, theme });
}
Expand Down
49 changes: 49 additions & 0 deletions apps/ledger-live-mobile/src/components/DeviceAction/rendering.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,55 @@ export function renderLoading({
);
}

export function renderExchange({
exchangeType,
t,
device,
theme,
}: RawProps & {
exchangeType: number;
device: Device;
}) {
switch (exchangeType) {
case 0x00: // swap
return <div>{"Confirm swap on your device"}</div>;
case 0x01: // sell
case 0x02: // fund
return renderSecureTransferDeviceConfirmation({
exchangeTypeName: exchangeType === 0x01 ? "confirmSell" : "confirmFund",
t,
device,
theme,
});
default:
return <CenteredText>{"Confirm exchange on your device"}</CenteredText>;
}
}

export function renderSecureTransferDeviceConfirmation({
t,
exchangeTypeName,
device,
theme,
}: RawProps & {
exchangeTypeName: string;
device: Device;
}) {
return (
<Wrapper>
<AnimationContainer>
<Animation
source={getDeviceAnimation({ device, key: "validate", theme })}
/>
</AnimationContainer>
<TitleText>{t(`DeviceAction.${exchangeTypeName}.title`)}</TitleText>
<Alert type="primary" learnMoreUrl={urls.swap.learnMore}>
{t(`DeviceAction.${exchangeTypeName}.alert`)}
</Alert>
</Wrapper>
);
}

export function LoadingAppInstall({
analyticsPropertyFlow = "unknown",
request,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import AddAccountsNavigator from "./AddAccountsNavigator";
import ExchangeBuyFlowNavigator from "./ExchangeBuyFlowNavigator";
import ExchangeSellFlowNavigator from "./ExchangeSellFlowNavigator";
import ExchangeNavigator from "./ExchangeNavigator";
import PlatformExchangeNavigator from "./PlatformExchangeNavigator";
import FirmwareUpdateNavigator from "./FirmwareUpdateNavigator";
import AccountSettingsNavigator from "./AccountSettingsNavigator";
import ImportAccountsNavigator from "./ImportAccountsNavigator";
Expand Down Expand Up @@ -341,6 +342,11 @@ export default function BaseNavigator() {
}
options={{ headerShown: false }}
/>
<Stack.Screen
name={NavigatorName.PlatformExchange}
component={PlatformExchangeNavigator}
options={{ headerShown: false }}
/>
<Stack.Screen
name={ScreenName.OperationDetails}
component={OperationDetails}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// @flow
import React, { useMemo } from "react";
import { useTheme } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { useTranslation } from "react-i18next";
import { getStackNavigatorConfig } from "../../navigation/navigatorConfig";
import styles from "../../navigation/styles";
import { ScreenName } from "../../const";
import PlatformStartExchange from "../../screens/Platform/exchange/StartExchange";
import PlatformCompleteExchange from "../../screens/Platform/exchange/CompleteExchange";

export default function PlatformExchangeNavigator() {
const { t } = useTranslation();
const { colors } = useTheme();
const stackNavigationConfig = useMemo(
() => getStackNavigatorConfig(colors, true),
[colors],
);

return (
<Stack.Navigator
screenOptions={{ ...stackNavigationConfig, headerShown: false }}
>
<Stack.Screen
name={ScreenName.PlatformStartExchange}
component={PlatformStartExchange}
options={{
headerStyle: styles.headerNoShadow,
title: t("transfer.swap.landing.header"),
}}
/>
<Stack.Screen
name={ScreenName.PlatformCompleteExchange}
component={PlatformCompleteExchange}
options={{
headerStyle: styles.headerNoShadow,
title: t("transfer.swap.landing.header"),
}}
/>
</Stack.Navigator>
);
}

const Stack = createStackNavigator();
150 changes: 149 additions & 1 deletion apps/ledger-live-mobile/src/components/WebPlatformPlayer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ import type {

import { getEnv } from "@ledgerhq/live-common/lib/env";
import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge";
import { getMainAccount } from "@ledgerhq/live-common/lib/account";
import type { Device } from "@ledgerhq/live-common/lib/hw/actions/types";
import {
listCryptoCurrencies,
findCryptoCurrencyById,
} from "@ledgerhq/live-common/lib/currencies/index";
} from "@ledgerhq/live-common/lib/currencies";
import type { AppManifest } from "@ledgerhq/live-common/lib/platform/types";

import { useJSONRPCServer } from "@ledgerhq/live-common/lib/platform/JSONRPCServer";
Expand Down Expand Up @@ -113,6 +115,8 @@ const WebPlatformPlayer = ({ manifest, inputs }: Props) => {
const [widgetLoaded, setWidgetLoaded] = useState(false);
const [isInfoPanelOpened, setIsInfoPanelOpened] = useState(false);

const [device, setDevice] = useState();

const uri = useMemo(() => {
const url = new URL(manifest.url);

Expand Down Expand Up @@ -398,6 +402,146 @@ const WebPlatformPlayer = ({ manifest, inputs }: Props) => {
[manifest, accounts],
);

const startExchange = useCallback(
({ exchangeType }: { exchangeType: number }) => {
tracking.platformStartExchangeRequested(manifest);

return new Promise((resolve, reject) => {
navigation.navigate(NavigatorName.PlatformExchange, {
screen: ScreenName.PlatformStartExchange,
params: {
request: {
exchangeType,
},
onResult: (result: {
startExchangeResult?: number,
startExchangeError?: Error,
device: Device,
}) => {
if (result.startExchangeError) {
tracking.platformStartExchangeFail(manifest);
reject(result.startExchangeError);
}

if (result.startExchangeResult) {
tracking.platformStartExchangeSuccess(manifest);
setDevice(result.device);
resolve(result.startExchangeResult);
}

const n = navigation.getParent() || navigation;
n.pop();
},
},
});
});
},
[manifest, navigation],
);

const completeExchange = useCallback(
({
provider,
fromAccountId,
toAccountId,
transaction,
binaryPayload,
signature,
feesStrategy,
exchangeType,
}: {
provider: string,
fromAccountId: string,
toAccountId: string,
transaction: RawPlatformTransaction,
binaryPayload: string,
signature: string,
feesStrategy: string,
exchangeType: number,
}) => {
// Nb get a hold of the actual accounts, and parent accounts
const fromAccount = accounts.find(a => a.id === fromAccountId);
let fromParentAccount;

const toAccount = accounts.find(a => a.id === toAccountId);
let toParentAccount;

if (!fromAccount) {
return null;
}

if (exchangeType === 0x00 && !toAccount) {
// if we do a swap, a destination account must be provided
return null;
}

if (fromAccount.type === "TokenAccount") {
fromParentAccount = accounts.find(a => a.id === fromAccount.parentId);
}
if (toAccount && toAccount.type === "TokenAccount") {
toParentAccount = accounts.find(a => a.id === toAccount.parentId);
}

const accountBridge = getAccountBridge(fromAccount, fromParentAccount);
const mainFromAccount = getMainAccount(fromAccount, fromParentAccount);

// eslint-disable-next-line no-param-reassign
transaction.family = mainFromAccount.currency.family;

const platformTransaction = deserializePlatformTransaction(transaction);

platformTransaction.feesStrategy = feesStrategy;

let processedTransaction = accountBridge.createTransaction(
mainFromAccount,
);
processedTransaction = accountBridge.updateTransaction(
processedTransaction,
platformTransaction,
);

tracking.platformCompleteExchangeRequested(manifest);
return new Promise((resolve, reject) => {
navigation.navigate(NavigatorName.PlatformExchange, {
screen: ScreenName.PlatformCompleteExchange,
params: {
request: {
exchangeType,
provider,
exchange: {
fromAccount,
fromParentAccount,
toAccount,
toParentAccount,
},
transaction: processedTransaction,
binaryPayload,
signature,
feesStrategy,
},
device,
onResult: (result: { operation?: Operation, error?: Error }) => {
if (result.error) {
tracking.platformStartExchangeFail(manifest);
reject(result.error);
}

if (result.operation) {
tracking.platformStartExchangeSuccess(manifest);
resolve(result.operation);
}

setDevice();
const n = navigation.getParent() || navigation;
n.pop();
},
},
});
});
},
[accounts, manifest, navigation, device],
);

const handlers = useMemo(
() => ({
"account.list": listAccounts,
Expand All @@ -406,6 +550,8 @@ const WebPlatformPlayer = ({ manifest, inputs }: Props) => {
"account.receive": receiveOnAccount,
"transaction.sign": signTransaction,
"transaction.broadcast": broadcastTransaction,
"exchange.start": startExchange,
"exchange.complete": completeExchange,
}),
[
listAccounts,
Expand All @@ -414,6 +560,8 @@ const WebPlatformPlayer = ({ manifest, inputs }: Props) => {
receiveOnAccount,
signTransaction,
broadcastTransaction,
startExchange,
completeExchange,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,32 @@ export function platformBroadcastSuccess(manifest: AppManifest) {
export function platformBroadcastOperationDetailsClick(manifest: AppManifest) {
track("Platform Broadcast OpD Clicked", getEventData(manifest));
}

// Generate Exchange nonce modal open
export function platformStartExchangeRequested(manifest: AppManifest) {
track("Platform start Exchange Nonce request", getEventData(manifest));
}

// Successfully generated an Exchange app nonce
export function platformStartExchangeSuccess(manifest: AppManifest) {
track("Platform start Exchange Nonce success", getEventData(manifest));
}

// Failed to generate an Exchange app nonce
export function platformStartExchangeFail(manifest: AppManifest) {
track("Platform start Exchange Nonce fail", getEventData(manifest));
}

export function platformCompleteExchangeRequested(manifest: AppManifest) {
track("Platform complete Exchange requested", getEventData(manifest));
}

// Successfully completed an Exchange
export function platformCompleteExchangeSuccess(manifest: AppManifest) {
track("Platform complete Exchange success", getEventData(manifest));
}

// Failed to complete an Exchange
export function platformCompleteExchangeFail(manifest: AppManifest) {
track("Platform complete Exchange Nonce fail", getEventData(manifest));
}
3 changes: 3 additions & 0 deletions apps/ledger-live-mobile/src/const/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ export const ScreenName = {

PlatformCatalog: "PlatformCatalog",
PlatformApp: "PlatformApp",
PlatformStartExchange: "PlatformStartExchange",
PlatformCompleteExchange: "PlatformCompleteExchange",

WalletConnectScan: "WalletConnectScan",
WalletConnectConnect: "WalletConnectConnect",
Expand Down Expand Up @@ -389,6 +391,7 @@ export const NavigatorName = {
PasswordAddFlow: "PasswordAddFlow",
PasswordModifyFlow: "PasswordModifyFlow",
Platform: "Platform",
PlatformExchange: "PlatformExchange",
ReceiveFunds: "ReceiveFunds",
SendFunds: "SendFunds",
Settings: "Settings",
Expand Down
Loading