Skip to content

Commit

Permalink
feat: add psbt functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarkhanzadian committed Dec 4, 2024
1 parent 8ef8c97 commit 5ce4ce9
Show file tree
Hide file tree
Showing 50 changed files with 1,169 additions and 438 deletions.
104 changes: 52 additions & 52 deletions apps/mobile/ios/Podfile.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { TextInput } from 'react-native';

import { usePsbtSigner } from '@/features/psbt-signer/psbt-signer';
import { usePsbtSigner } from '@/features/psbt-signer/use-psbt-signer';
import { useBitcoinAccounts } from '@/store/keychains/bitcoin/bitcoin-keychains.read';
import { BitcoinAccountLoader } from '@/store/keychains/keychains';
import { t } from '@lingui/macro';
Expand Down
8 changes: 4 additions & 4 deletions apps/mobile/src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ export default function RootLayout() {
<SplashScreenGuard>
<HapticsProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<SheetProvider>
<SheetNavigatorWrapper>
<SheetNavigatorWrapper>
<SheetProvider>
<ToastWrapper>
<AppRouter />
<SendSheet />
<ReceiveSheet />
</ToastWrapper>
</SheetNavigatorWrapper>
</SheetProvider>
</SheetProvider>
</SheetNavigatorWrapper>
</GestureHandlerRootView>
</HapticsProvider>
</SplashScreenGuard>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useCallback } from 'react';
import { useNetworkPreferenceBitcoinScureLibNetworkConfig } from '@/store/settings/settings.read';

import {
BtcSignerDefaultBip32Derivation,
CoinSelectionRecipient,
CoinSelectionUtxo,
generateBitcoinUnsignedTransactionNativeSegwit,
Expand All @@ -19,17 +20,25 @@ interface GenerateBtcUnsignedTransactionCallbackArgs {
isSendingMax: boolean;
utxos: CoinSelectionUtxo[];
values: BtcTransactionValues;
bip32Derivation: BtcSignerDefaultBip32Derivation[];
}

export function useGenerateBtcUnsignedTransactionNativeSegwit(address: string, publicKey: string) {
const network = useNetworkPreferenceBitcoinScureLibNetworkConfig();

return useCallback(
({ feeRate, isSendingMax, values, utxos }: GenerateBtcUnsignedTransactionCallbackArgs) =>
({
feeRate,
isSendingMax,
values,
utxos,
bip32Derivation,
}: GenerateBtcUnsignedTransactionCallbackArgs) =>
generateBitcoinUnsignedTransactionNativeSegwit({
feeRate,
isSendingMax,
network,
bip32Derivation,
payerAddress: address,
payerPublicKey: publicKey,
recipients: values.recipients,
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/components/balance/balance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface BalanceProps extends TextProps {
lockedBalance?: string;
}

function formatBalance(balance: Money, isFiat: boolean) {
export function formatBalance(balance: Money, isFiat: boolean) {
if (isFiat) {
const isLargeBalance = balance.amount.isGreaterThanOrEqualTo(100_000);
// i18nFormatCurrency is hardcoded to only accept USD
Expand Down
14 changes: 11 additions & 3 deletions apps/mobile/src/components/spinner-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Box, useOnMount } from '@leather.io/ui/native';
const AnimatedBox = Animated.createAnimatedComponent(Box);
const duration = 1000;
const easing = Easing.linear;
export function SpinnerIcon() {
export function SpinnerIcon({ invertColors }: { invertColors?: boolean }) {
const { whenTheme } = useSettings();
const spin = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
Expand All @@ -35,14 +35,22 @@ export function SpinnerIcon() {
}}
style={{ height: 24, width: 24 }}
contentFit="cover"
source={require('@/assets/spinner-light.png')}
source={
invertColors
? require('@/assets/spinner-dark.png')
: require('@/assets/spinner-light.png')
}
/>
),
dark: (
<Image
style={{ height: 24, width: 24 }}
contentFit="cover"
source={require('@/assets/spinner-dark.png')}
source={
invertColors
? require('@/assets/spinner-light.png')
: require('@/assets/spinner-dark.png')
}
/>
),
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Avatar, Flag, ItemLayout, Pressable } from '@leather.io/ui/native';
import { Avatar, Flag, ItemLayout, Pressable, PressableProps } from '@leather.io/ui/native';

interface AccountListItemProps {
interface AccountListItemProps extends PressableProps {
accountName: string;
address: React.ReactNode;
balance: React.ReactNode;
icon: React.ReactNode;
iconTestID?: string;
onPress?(): void;
testID?: string;
walletName?: string;
}
Expand All @@ -19,16 +18,10 @@ export function AccountListItem({
onPress,
testID,
walletName,
...rest
}: AccountListItemProps) {
return (
<Pressable
flexDirection="row"
disabled={!onPress}
onPress={onPress}
px="5"
py="3"
testID={testID}
>
<Pressable flexDirection="row" disabled={!onPress} onPress={onPress} testID={testID} {...rest}>
<Flag
img={
<Avatar bg="ink.text-primary" testID={iconTestID}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export function AccountList({ accounts, onPress, showWalletInfo }: AccountListPr
onPress={() => onPress(account)}
testID={TestId.walletListAccountCard}
walletName={showWalletInfo ? wallet.name : undefined}
px="5"
py="3"
/>
)}
</WalletLoader>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
import { useMemo } from 'react';

import { AvatarIcon } from '@/components/avatar-icon';
import { AccountListItem } from '@/features/accounts/account-list/account-list-item';
import { AccountAddress } from '@/features/accounts/components/account-address';
import { AccountBalance } from '@/features/accounts/components/account-balance';
import { useAccountsByFingerprint } from '@/store/accounts/accounts.read';
import { Account } from '@/store/accounts/accounts';
import { useWallets } from '@/store/wallets/wallets.read';
import { t } from '@lingui/macro';
import { useTheme } from '@shopify/restyle';

import { Text, Theme } from '@leather.io/ui/native';

export function ApproverAccountCard() {
const theme = useTheme<Theme>();
function AccountItem({ account }: { account: Account }) {
const { list: walletsList } = useWallets();
const wallet = walletsList[0];

if (!wallet) throw new Error('no wallet present in approver');

const { list: accountList } = useAccountsByFingerprint(wallet.fingerprint);
const account = accountList[0];
const walletName = useMemo(
() => walletsList.find(w => w.fingerprint === account.fingerprint)?.name ?? '',
[account.fingerprint, walletsList]
);

if (!account) throw new Error('no account present in approver');
const theme = useTheme<Theme>();
return (
<AccountListItem
key={account.id}
accountName={account.name}
address={
<AccountAddress accountIndex={account.accountIndex} fingerprint={account.fingerprint} />
}
balance={
<AccountBalance accountIndex={account.accountIndex} fingerprint={account.fingerprint} />
}
icon={<AvatarIcon color={theme.colors['ink.background-primary']} icon={account.icon} />}
walletName={walletName}
py="3"
/>
);
}

export function ApproverAccountCard({ accounts }: { accounts: Account[] }) {
return (
<>
<Text variant="label01">
Expand All @@ -29,17 +46,9 @@ export function ApproverAccountCard() {
message: 'With account',
})}
</Text>
<AccountListItem
accountName={account.name}
address={
<AccountAddress accountIndex={account.accountIndex} fingerprint={account.fingerprint} />
}
balance={
<AccountBalance accountIndex={account.accountIndex} fingerprint={account.fingerprint} />
}
icon={<AvatarIcon color={theme.colors['ink.background-primary']} icon={account.icon} />}
walletName={wallet.name}
/>
{accounts.map(account => (
<AccountItem key={account.id} account={account} />
))}
</>
);
}
27 changes: 27 additions & 0 deletions apps/mobile/src/features/approver/components/bitcoin-outcome.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BitcoinTokenBalance } from '@/features/balances/bitcoin/bitcoin-balance';
import { useBtcMarketDataQuery } from '@/queries/market-data/btc-market-data.query';
import { t } from '@lingui/macro';

import { Money } from '@leather.io/models';
import { Text } from '@leather.io/ui/native';
import { baseCurrencyAmountInQuote, createMoney } from '@leather.io/utils';

export function BitcoinOutcome({ amount }: { amount: Money }) {
const { data: btcMarketData } = useBtcMarketDataQuery();

const fiatBalance = btcMarketData
? baseCurrencyAmountInQuote(amount, btcMarketData)
: createMoney(0, 'USD');

return (
<>
<Text variant="label01">
{t({
id: 'approver.outcomes.title1',
message: "You'll send",
})}
</Text>
<BitcoinTokenBalance availableBalance={amount} fiatBalance={fiatBalance} py="3" />
</>
);
}
25 changes: 20 additions & 5 deletions apps/mobile/src/features/approver/components/fee-card.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ReactNode } from 'react';

import { Balance } from '@/components/balance/balance';
import { FeeBadge } from '@/features/send/fee-badge';
import { useBtcMarketDataQuery } from '@/queries/market-data/btc-market-data.query';
import { t } from '@lingui/macro';

import { FeeTypes } from '@leather.io/models';
import { FeeTypes, Money } from '@leather.io/models';
import {
AnimalChameleonIcon,
AnimalEagleIcon,
Expand All @@ -17,9 +19,14 @@ import {
Pressable,
Text,
} from '@leather.io/ui/native';
import { match } from '@leather.io/utils';
import { baseCurrencyAmountInQuote, createMoney, match } from '@leather.io/utils';

export function FeeCard({ feeType }: { feeType: FeeTypes }) {
interface FeeCardProps {
feeType: FeeTypes;
amount: Money;
}

export function FeeCard({ feeType, amount }: FeeCardProps) {
const matchFeeType = match<FeeTypes>();
const feeIcon = matchFeeType<ReactNode>(feeType, {
[FeeTypes.Low]: <AnimalSnailIcon />,
Expand All @@ -28,6 +35,12 @@ export function FeeCard({ feeType }: { feeType: FeeTypes }) {
[FeeTypes.Custom]: <AnimalChameleonIcon />,
[FeeTypes.Unknown]: <AnimalChameleonIcon />,
});

const { data: btcMarketData } = useBtcMarketDataQuery();

const fiatBalance = btcMarketData
? baseCurrencyAmountInQuote(amount, btcMarketData)
: createMoney(0, 'USD');
return (
<>
<Box flexDirection="row">
Expand Down Expand Up @@ -56,8 +69,10 @@ export function FeeCard({ feeType }: { feeType: FeeTypes }) {
id: 'approver.fee.speed.normal',
message: '~20 mins',
})}
titleRight="0.000034 BTC"
captionRight="$ 1.65"
titleRight={<Balance balance={amount} variant="label02" />}
captionRight={
<Balance balance={fiatBalance} variant="label02" color="ink.text-subdued" />
}
actionIcon={<ChevronRightIcon />}
/>
</Flag>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import { useBtcMarketDataQuery } from '@/queries/market-data/btc-market-data.query';
import { t } from '@lingui/macro';

import { PsbtInput, PsbtOutput } from '@leather.io/bitcoin';
import { Box, Text } from '@leather.io/ui/native';
import { baseCurrencyAmountInQuote, createMoney } from '@leather.io/utils';

import { UtxoRow } from './utxo-row';

export function InputsAndOutputsCard() {
interface InputsAndOutputsCardProps {
inputs: PsbtInput[];
outputs: PsbtOutput[];
}

export function InputsAndOutputsCard({ inputs, outputs }: InputsAndOutputsCardProps) {
const { data: btcMarketData } = useBtcMarketDataQuery();

function addBalance<T extends PsbtInput | PsbtOutput>(inputOutput: T) {
const btcBalance = createMoney(Number(inputOutput.value), 'BTC');
const usdBalance = btcMarketData
? baseCurrencyAmountInQuote(btcBalance, btcMarketData)
: createMoney(0, 'USD');
return {
...inputOutput,
btcBalance,
usdBalance,
};
}

const inputsWithBalance = inputs.map(addBalance);

const outputsWithBalance = outputs.map(addBalance);
return (
<>
<Text variant="label01">
Expand All @@ -20,8 +45,16 @@ export function InputsAndOutputsCard() {
})}
</Text>
<Box gap="4">
<UtxoRow isLocked />
<UtxoRow isLocked={false} />
{inputsWithBalance.map(input => (
<UtxoRow
key={input.txid + input.address + input.btcBalance.amount}
txid={input.txid}
address={input.address}
btcBalance={input.btcBalance}
usdBalance={input.usdBalance}
isLocked
/>
))}
</Box>
<Text variant="label01">
{t({
Expand All @@ -30,8 +63,15 @@ export function InputsAndOutputsCard() {
})}
</Text>
<Box gap="4">
<UtxoRow isLocked />
<UtxoRow isLocked={false} />
{outputsWithBalance.map(output => (
<UtxoRow
key={output.address + output.btcBalance.amount}
address={output.address}
btcBalance={output.btcBalance}
usdBalance={output.usdBalance}
isLocked
/>
))}
</Box>
</>
);
Expand Down
Loading

0 comments on commit 5ce4ce9

Please sign in to comment.