Skip to content
This repository has been archived by the owner on Jun 16, 2022. It is now read-only.

LIVE-784 Sell and Fund flow #2232

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ios/ledgerlivemobile.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@
DEVELOPMENT_TEAM = 5HK2Q4J4X4;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 ";
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
JunichiSugiura marked this conversation as resolved.
Show resolved Hide resolved
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand Down Expand Up @@ -1041,7 +1041,7 @@
DEVELOPMENT_TEAM = 5HK2Q4J4X4;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 ";
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
Expand Down Expand Up @@ -1108,7 +1108,7 @@
DEVELOPMENT_TEAM = 5HK2Q4J4X4;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 ";
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand Down
18 changes: 18 additions & 0 deletions src/components/DeviceAction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
renderInWrongAppForAccount,
renderError,
renderBootloaderStep,
renderExchange,
renderConfirmSwap,
renderConfirmSell,
LoadingAppInstall,
Expand Down Expand Up @@ -79,6 +80,9 @@ export default function DeviceAction<R, H, P>({
initSwapResult,
signMessageRequested,
allowOpeningGranted,
completeExchangeStarted,
completeExchangeResult,
completeExchangeError,
initSellRequested,
initSellResult,
initSellError,
Expand Down Expand Up @@ -168,6 +172,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
48 changes: 48 additions & 0 deletions src/components/DeviceAction/rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,54 @@ export function renderLoading({
);
}

export function renderExchange({
exchangeType,
t,
device,
theme,
}: RawProps & {
exchangeType: number,
device: Device,
}) {
switch (exchangeType) {
case 0x00:
return <div>{"Confirm swap on your device"}</div>;
case 0x01:
case 0x02:
JunichiSugiura marked this conversation as resolved.
Show resolved Hide resolved
return renderSecureTransferDeviceConfirmation({
exchangeTypeName: exchangeType === 0x01 ? "confirmSell" : "confirmFund",
t,
device,
theme,
});
default:
return <LText>{"Confirm exchange on your device"}</LText>;
}
}

export function renderSecureTransferDeviceConfirmation({
t,
exchangeTypeName,
device,
}: RawProps & {
exchangeTypeName: string,
device: Device,
}) {
return (
<View style={styles.wrapper}>
<View style={[styles.animationContainer]}>
<Animation source={getDeviceAnimation({ device, key: "validate" })} />
</View>
<LText style={[styles.text, styles.title, { marginBottom: 32 }]} semiBold>
{t(`DeviceAction.${exchangeTypeName}.title`)}
</LText>
<Alert type="primary" learnMoreUrl={urls.swap.learnMore}>
{t(`DeviceAction.${exchangeTypeName}.alert`)}
</Alert>
</View>
);
}

export function LoadingAppInstall({
analyticsPropertyFlow = "unknown",
request,
Expand Down
6 changes: 6 additions & 0 deletions src/components/RootNavigator/BaseNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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 @@ -287,6 +288,11 @@ export default function BaseNavigator() {
component={ExchangeSellFlowNavigator}
options={{ headerShown: false }}
/>
<Stack.Screen
name={NavigatorName.PlatformExchange}
component={PlatformExchangeNavigator}
options={{ headerShown: false }}
/>
<Stack.Screen
name={ScreenName.OperationDetails}
component={OperationDetails}
Expand Down
44 changes: 44 additions & 0 deletions src/components/RootNavigator/PlatformExchangeNavigator.js
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();
152 changes: 151 additions & 1 deletion src/components/WebPlatformPlayer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@ import Color from "color";

import { JSONRPCRequest } from "json-rpc-2.0";

import type { Operation } from "@ledgerhq/live-common/lib/types";

import type {
RawPlatformTransaction,
RawPlatformSignedTransaction,
} from "@ledgerhq/live-common/lib/platform/rawTypes";

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 +117,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 +404,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 +552,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 +562,8 @@ const WebPlatformPlayer = ({ manifest, inputs }: Props) => {
receiveOnAccount,
signTransaction,
broadcastTransaction,
startExchange,
completeExchange,
],
);

Expand Down
Loading