Skip to content

Commit 9f3118d

Browse files
committed
feat: integrate solana token extensions into LLM
1 parent 7682034 commit 9f3118d

8 files changed

+644
-25
lines changed

apps/ledger-live-mobile/src/families/solana/AccountSubHeader.tsx

+11-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Box, Alert, Text } from "@ledgerhq/native-ui";
55
import { isTokenAccountFrozen } from "@ledgerhq/live-common/families/solana/token";
66
import { SolanaAccount, SolanaTokenAccount } from "@ledgerhq/live-common/families/solana/types";
77
import AccountSubHeader from "~/components/AccountSubHeader";
8+
import TokenExtensionsInfoBox from "./Token2022/TokenExtensionsInfoBox";
89

910
type Account = SolanaAccount | SolanaTokenAccount | SubAccount;
1011

@@ -13,18 +14,26 @@ type Props = {
1314
};
1415

1516
function SolanaAccountSubHeader({ account }: Props) {
17+
const tokenExtensions =
18+
account.type === "TokenAccount" ? (account as SolanaTokenAccount)?.extensions : undefined;
1619
return (
1720
<>
21+
<AccountSubHeader family="Solana" team="Solana Labs" />
1822
{isTokenAccountFrozen(account) && (
19-
<Box mt={6}>
23+
<Box mb={6}>
2024
<Alert type="warning">
2125
<Text variant="body">
2226
<Trans i18nKey="solana.token.frozenStateWarning" />
2327
</Text>
2428
</Alert>
2529
</Box>
2630
)}
27-
<AccountSubHeader family="Solana" team="Solana Labs" />
31+
{!!tokenExtensions && (
32+
<TokenExtensionsInfoBox
33+
tokenAccount={account as SolanaTokenAccount}
34+
extensions={tokenExtensions}
35+
/>
36+
)}
2837
</>
2938
);
3039
}

apps/ledger-live-mobile/src/families/solana/SendRowsFee.tsx

+44-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { getAccountCurrency } from "@ledgerhq/live-common/account/index";
22
import { Transaction, TransactionStatus } from "@ledgerhq/live-common/generated/types";
3-
import { TransactionStatus as SolanaTransactionStatus } from "@ledgerhq/live-common/families/solana/types";
3+
import {
4+
TransactionStatus as SolanaTransactionStatus,
5+
Transaction as SolanaTransaction,
6+
SolanaAccount,
7+
} from "@ledgerhq/live-common/families/solana/types";
48
import { Account, AccountLike } from "@ledgerhq/types-live";
59
import { Text } from "@ledgerhq/native-ui";
610
import { CompositeScreenProps, useTheme } from "@react-navigation/native";
@@ -19,11 +23,13 @@ import { ScreenName } from "~/const";
1923
import { SignTransactionNavigatorParamList } from "~/components/RootNavigator/types/SignTransactionNavigator";
2024
import { SwapNavigatorParamList } from "~/components/RootNavigator/types/SwapNavigator";
2125
import { useAccountUnit } from "~/hooks/useAccountUnit";
26+
import TokenTransferFeesWarning from "./Token2022/TokenTransferFeesWarning";
2227

2328
type Props = {
2429
account: AccountLike;
2530
parentAccount?: Account | null;
2631
transaction: Transaction;
32+
setTransaction: (..._: Array<Transaction>) => void;
2733
status?: TransactionStatus;
2834
} & CompositeScreenProps<
2935
| StackNavigatorProps<SendFundsNavigatorStackParamList, ScreenName.SendSummary>
@@ -32,38 +38,54 @@ type Props = {
3238
StackNavigatorProps<BaseNavigatorStackParamList>
3339
>;
3440

35-
export default function SolanaFeeRow({ account, parentAccount, status }: Props) {
41+
export default function SolanaFeeRow({
42+
account,
43+
parentAccount,
44+
status,
45+
transaction,
46+
setTransaction,
47+
}: Props) {
3648
const { colors } = useTheme();
3749
const extraInfoFees = useCallback(() => {
3850
Linking.openURL(urls.solana.supportPage);
3951
}, []);
4052

4153
const fees = (status as SolanaTransactionStatus).estimatedFees;
42-
43-
const unit = useAccountUnit(account.type === "TokenAccount" ? parentAccount || account : account);
54+
const isTokenAccount = account.type === "TokenAccount";
55+
const unit = useAccountUnit(isTokenAccount ? parentAccount || account : account);
4456
const currency = getAccountCurrency(account);
4557

4658
return (
47-
<SummaryRow
48-
onPress={extraInfoFees}
49-
title={<Trans i18nKey="send.fees.title" />}
50-
additionalInfo={
51-
<View>
52-
<ExternalLink size={12} color={colors.grey} />
53-
</View>
54-
}
55-
>
56-
<View style={{ alignItems: "flex-end" }}>
57-
<View style={styles.accountContainer}>
58-
<Text style={styles.valueText}>
59-
<CurrencyUnitValue unit={unit} value={fees} />
59+
<>
60+
<SummaryRow
61+
onPress={extraInfoFees}
62+
title={<Trans i18nKey="send.fees.title" />}
63+
additionalInfo={
64+
<View>
65+
<ExternalLink size={12} color={colors.grey} />
66+
</View>
67+
}
68+
>
69+
<View style={{ alignItems: "flex-end" }}>
70+
<View style={styles.accountContainer}>
71+
<Text style={styles.valueText}>
72+
<CurrencyUnitValue unit={unit} value={fees} />
73+
</Text>
74+
</View>
75+
<Text style={styles.countervalue} color="grey">
76+
<CounterValue before="≈ " value={fees} currency={currency} />
6077
</Text>
6178
</View>
62-
<Text style={styles.countervalue} color="grey">
63-
<CounterValue before="≈ " value={fees} currency={currency} />
64-
</Text>
65-
</View>
66-
</SummaryRow>
79+
</SummaryRow>
80+
{isTokenAccount && (
81+
<TokenTransferFeesWarning
82+
account={parentAccount as SolanaAccount}
83+
tokenAccount={account}
84+
transaction={transaction as SolanaTransaction}
85+
setTransaction={setTransaction}
86+
/>
87+
)}
88+
</>
6789
);
6890
}
6991

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import React from "react";
2+
import { View } from "react-native";
3+
import BigNumber from "bignumber.js";
4+
import { Trans } from "react-i18next";
5+
import { Flex, Text, Alert, Box } from "@ledgerhq/native-ui";
6+
import {
7+
SolanaTokenAccount,
8+
SolanaTokenAccountExtensions,
9+
} from "@ledgerhq/live-common/families/solana/types";
10+
import { bpsToPercent } from "@ledgerhq/live-common/families/solana/token";
11+
import TooltipLabel from "~/components/TooltipLabel";
12+
import TokenExtensionsInfoDrawer from "./TokenExtensionsInfoDrawer";
13+
import { InfoMedium } from "@ledgerhq/native-ui/assets/icons";
14+
import CopyButton from "../shared/CopyButton";
15+
import Button from "~/components/Button";
16+
17+
export default function TokenExtensionsInfoBox({
18+
tokenAccount,
19+
extensions,
20+
}: {
21+
tokenAccount: SolanaTokenAccount;
22+
extensions: SolanaTokenAccountExtensions;
23+
}) {
24+
const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
25+
26+
const extensionsSize = Object.values(extensions);
27+
if (!extensionsSize.length) return null;
28+
29+
function openDrawer() {
30+
setIsDrawerOpen(true);
31+
}
32+
33+
function closeDrawer() {
34+
setIsDrawerOpen(false);
35+
}
36+
37+
return (
38+
<View>
39+
<Alert showIcon={extensionsSize.length === 1} type="info">
40+
<Flex width="100%" flexDirection="column" columnGap={4} alignItems="flex-start">
41+
{!!extensions.nonTransferable && (
42+
<Text>
43+
<Trans i18nKey="solana.token.nonTransferable.notice" />
44+
</Text>
45+
)}
46+
47+
{!!extensions.interestRate && (
48+
<TooltipLabel
49+
label={
50+
<Text>
51+
<Trans
52+
i18nKey="solana.token.interestRate.notice"
53+
values={{
54+
rate: BigNumber(extensions.interestRate.rateBps).div(100).toNumber(),
55+
}}
56+
/>
57+
</Text>
58+
}
59+
tooltip={<Trans i18nKey="solana.token.interestRate.tooltipHint" />}
60+
/>
61+
)}
62+
63+
{extensions.permanentDelegate ? (
64+
extensions.permanentDelegate.delegateAddress ? (
65+
<TooltipLabel
66+
label={
67+
<Text>
68+
<Trans i18nKey="solana.token.permanentDelegate.notice" />
69+
</Text>
70+
}
71+
tooltip={
72+
<Trans
73+
i18nKey="solana.token.permanentDelegate.tooltipHint"
74+
values={{ delegateAddress: extensions.permanentDelegate.delegateAddress }}
75+
components={[
76+
<CopyButton
77+
key="SolanaCopyDelegateAddress"
78+
copyString={extensions.permanentDelegate.delegateAddress}
79+
/>,
80+
]}
81+
/>
82+
}
83+
/>
84+
) : (
85+
<TooltipLabel
86+
label={
87+
<Text>
88+
<Trans i18nKey="solana.token.permanentDelegate.initializationNotice" />
89+
</Text>
90+
}
91+
tooltip={
92+
<Trans i18nKey="solana.token.permanentDelegate.initializationNoticeTooltipHint" />
93+
}
94+
/>
95+
)
96+
) : null}
97+
98+
{!!extensions.transferFee && (
99+
<TooltipLabel
100+
label={
101+
<Text>
102+
<Trans
103+
i18nKey="solana.token.transferFees.notice"
104+
values={{ fee: bpsToPercent(extensions.transferFee.feeBps) }}
105+
/>
106+
</Text>
107+
}
108+
tooltip={<Trans i18nKey="solana.token.transferFees.tooltipHint" />}
109+
/>
110+
)}
111+
112+
{extensions.transferHook ? (
113+
extensions.transferHook.programAddress ? (
114+
<TooltipLabel
115+
label={
116+
<Text>
117+
<Trans i18nKey="solana.token.transferHook.notice" />
118+
</Text>
119+
}
120+
tooltip={
121+
<Trans
122+
i18nKey="solana.token.transferHook.tooltipHint"
123+
values={{ programAddress: extensions.transferHook.programAddress }}
124+
components={[
125+
<CopyButton
126+
key="SolanaCopyHookAddress"
127+
copyString={extensions.transferHook.programAddress}
128+
/>,
129+
]}
130+
/>
131+
}
132+
/>
133+
) : (
134+
<Text>
135+
<Trans i18nKey="solana.token.transferHook.initializationNotice" />
136+
</Text>
137+
)
138+
) : null}
139+
140+
{!!extensions.requiredMemoOnTransfer && (
141+
<TooltipLabel
142+
label={
143+
<Text>
144+
<Trans i18nKey="solana.token.requiredMemoOnTransfer.notice" />
145+
</Text>
146+
}
147+
tooltip={<Trans i18nKey="solana.token.requiredMemoOnTransfer.tooltipHint" />}
148+
/>
149+
)}
150+
<Box width="100%" mt={4}>
151+
{/* <Link type="color" size="medium" Icon={InfoMedium} onPress={openDrawer}>
152+
<Trans i18nKey="common.moreInfo" />
153+
</Link> */}
154+
<Button
155+
type="color"
156+
size="small"
157+
maxWidth={150}
158+
alignSelf="center"
159+
outline
160+
Icon={InfoMedium}
161+
onPress={openDrawer}
162+
>
163+
<Trans i18nKey="common.moreInfo" />
164+
</Button>
165+
</Box>
166+
</Flex>
167+
</Alert>
168+
<TokenExtensionsInfoDrawer
169+
extensions={extensions}
170+
tokenAccount={tokenAccount}
171+
isOpen={isDrawerOpen}
172+
closeDrawer={closeDrawer}
173+
/>
174+
</View>
175+
);
176+
}

0 commit comments

Comments
 (0)