New swaps (#704)
* adding basic template for swaps * adding chain icons * update tx details page to handle chain swaps * add stablecoin amount format function * updating UX + fixing styles * creating select stablecoin currency page * adding confirm page for stablecoin swaps * finalizing sendpage for stablecoin swaps * process stablecoin status * adding translations * fix solana and tron mismatch * fixed paymentId missing from tx details for flashnet swaps * fix eslint error * making sure button text doesn't ruin layout * fix quote reset while sending * adding opacity to transition * fixing estimated out format * updating activesupport * Update locales/pt-BR/translation.json Co-authored-by: bitcoinuser <bitcoinuser@users.noreply.github.com> * Update locales/pt-BR/translation.json Co-authored-by: bitcoinuser <bitcoinuser@users.noreply.github.com> * Update locales/pt-BR/translation.json Co-authored-by: bitcoinuser <bitcoinuser@users.noreply.github.com> * Update locales/pt-BR/translation.json Co-authored-by: bitcoinuser <bitcoinuser@users.noreply.github.com> * Update locales/pt-BR/translation.json Co-authored-by: bitcoinuser <bitcoinuser@users.noreply.github.com> * Update locales/pt-BR/translation.json Co-authored-by: bitcoinuser <bitcoinuser@users.noreply.github.com> * Update locales/pt-BR/translation.json Co-authored-by: bitcoinuser <bitcoinuser@users.noreply.github.com> * Update locales/pt-BR/translation.json Co-authored-by: bitcoinuser <bitcoinuser@users.noreply.github.com> --------- Co-authored-by: bitcoinuser <bitcoinuser@users.noreply.github.com>
@@ -5,7 +5,7 @@ ruby ">= 2.6.10"
|
||||
|
||||
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
|
||||
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
|
||||
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
|
||||
gem 'activesupport', '>= 7.2.3.1'
|
||||
gem 'xcodeproj', '< 1.26.0'
|
||||
gem 'concurrent-ruby', '< 1.3.4'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ GEM
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
activesupport (7.2.2.2)
|
||||
activesupport (7.2.3.1)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
@@ -14,7 +14,7 @@ GEM
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
minitest (>= 5.1, < 6)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.7)
|
||||
@@ -24,8 +24,8 @@ GEM
|
||||
json (>= 1.5.1)
|
||||
atomos (0.1.3)
|
||||
base64 (0.3.0)
|
||||
benchmark (0.4.1)
|
||||
bigdecimal (3.2.3)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (4.1.0)
|
||||
claide (1.1.0)
|
||||
cocoapods (1.15.2)
|
||||
addressable (~> 2.8)
|
||||
@@ -66,7 +66,7 @@ GEM
|
||||
cocoapods-try (1.2.0)
|
||||
colored2 (3.1.2)
|
||||
concurrent-ruby (1.3.3)
|
||||
connection_pool (2.5.4)
|
||||
connection_pool (3.0.2)
|
||||
drb (2.2.3)
|
||||
escape (0.0.4)
|
||||
ethon (0.15.0)
|
||||
@@ -77,11 +77,11 @@ GEM
|
||||
gh_inspector (1.1.3)
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
i18n (1.14.7)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.15.0)
|
||||
logger (1.7.0)
|
||||
minitest (5.25.5)
|
||||
minitest (5.27.0)
|
||||
molinillo (0.8.0)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.3.0)
|
||||
@@ -108,7 +108,7 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
activesupport (>= 6.1.7.5, != 7.1.0)
|
||||
activesupport (>= 7.2.3.1)
|
||||
benchmark
|
||||
bigdecimal
|
||||
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><clipPath id="a"><circle cx="16" cy="16" r="16"/></clipPath></defs><g clip-path="url(#a)"><rect width="32" height="32" fill="#213147"/><path fill="#12AAFF" d="m17.604 17.621-1.18 3.187a.4.4 0 0 0 0 .274l2.03 5.483 2.348-1.335-2.818-7.609a.203.203 0 0 0-.38 0m2.365-5.359a.203.203 0 0 0-.38 0l-1.18 3.187a.4.4 0 0 0 0 .274l3.326 8.976 2.348-1.335z"/><path fill="#9DCCED" d="M16 5.487a.4.4 0 0 1 .168.043l8.953 5.09a.33.33 0 0 1 .168.286v10.18a.33.33 0 0 1-.168.286l-8.953 5.09a.33.33 0 0 1-.168.043.4.4 0 0 1-.167-.043l-8.953-5.087a.33.33 0 0 1-.168-.286v-10.18a.33.33 0 0 1 .168-.287l8.953-5.09A.35.35 0 0 1 16 5.487m0-1.487c-.318 0-.637.08-.923.244L6.124 9.334A1.81 1.81 0 0 0 5.2 10.91v10.18c0 .65.352 1.25.924 1.575l8.953 5.09a1.88 1.88 0 0 0 1.848 0l8.952-5.09a1.81 1.81 0 0 0 .923-1.575V10.91a1.81 1.81 0 0 0-.923-1.576L17.724 4.244A1.87 1.87 0 0 0 16 4"/><path fill="#213147" d="m10.079 24.913.822-2.221 1.659 1.357-1.551 1.395z"/><path fill="#FFF" d="M15.244 10.18h-2.268a.4.4 0 0 0-.38.263l-4.865 13.136 2.348 1.335 5.358-14.465a.2.2 0 0 0-.193-.268m3.972-.001h-2.27a.4.4 0 0 0-.38.263l-5.556 15 2.348 1.334 6.047-16.328a.2.2 0 0 0-.19-.268"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 111 111" fill="none">
|
||||
<circle cx="55.5" cy="55.5" r="55.5" fill="#0052FF"/>
|
||||
<path d="M55.7 94C76.8 94 94 76.6 94 55.5S76.8 17 55.7 17C35.8 17 19.2 32.5 17.3 52.2h50.9v6.6H17.3C19.2 78.5 35.8 94 55.7 94z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 282 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#627EEA"/><g fill="#FFF" fill-rule="nonzero"><path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/><path d="M16.498 4L9 16.22l7.498-3.35z"/><path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/><path d="M16.498 27.995v-6.028L9 17.616z"/><path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/><path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 526 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><clipPath id="a"><circle cx="16" cy="16" r="16"/></clipPath></defs><g clip-path="url(#a)"><rect width="32" height="32" fill="#FF0420"/><path fill="#FFF" fill-rule="evenodd" d="M5.288 21.067q1.305.933 3.35.933 2.472 0 3.949-1.117 1.477-1.133 2.079-3.416.36-1.4.619-2.884.084-.53.085-.884 0-1.165-.601-1.999a3.6 3.6 0 0 0-1.65-1.266Q12.07 10 10.749 10q-4.858 0-6.026 4.583a53 53 0 0 0-.636 2.884q-.077.447-.087.898 0 1.752 1.288 2.702m6.2-3.7c-.33 1.276-1.235 2.107-2.611 2.107-1.36 0-1.824-.92-1.579-2.107a36 36 0 0 1 .619-2.733c.353-1.379 1.187-2.107 2.608-2.107 1.356 0 1.797.906 1.564 2.107a40 40 0 0 1-.601 2.733m4.625 4.01q.101.12.285.12h2.272a.5.5 0 0 0 .317-.12.48.48 0 0 0 .184-.309l.718-3.36h2.31c1.459 0 2.6-.707 3.435-1.336q1.271-.943 1.688-2.914.1-.465.1-.894 0-1.489-1.135-2.28Q25.16 10 23.68 10h-4.444a.5.5 0 0 0-.317.12.5.5 0 0 0-.184.31l-2.307 11.14a.4.4 0 0 0 .05.31m7.217-7.955c-.21.919-1.01 1.758-1.95 1.758h-1.92l.662-3.158h2.004c.683 0 1.253.135 1.253.886q0 .22-.049.514" clip-rule="evenodd"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><g fill="none"><circle fill="#EF0027" cx="16" cy="16" r="16"/><path d="M21.932 9.913L7.5 7.257l7.595 19.112 10.583-12.894-3.746-3.562zm-.232 1.17l2.208 2.099-6.038 1.093 3.83-3.192zm-5.142 2.973l-6.364-5.278 10.402 1.914-4.038 3.364zm-.453.934l-1.038 8.58L9.472 9.487l6.633 5.502zm.96.455l6.687-1.21-7.67 9.343.983-8.133z" fill="#FFF"/></g></svg>
|
||||
|
After Width: | Height: | Size: 407 B |
@@ -0,0 +1,5 @@
|
||||
<svg data-name="86977684-12db-4850-8f30-233a7c267d11" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000">
|
||||
<path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/>
|
||||
<path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/>
|
||||
<path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#26A17B"/><path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/></g></svg>
|
||||
|
After Width: | Height: | Size: 649 B |
@@ -37,6 +37,8 @@ import Animated, {
|
||||
useHandler,
|
||||
useEvent,
|
||||
LinearTransition,
|
||||
FadeIn,
|
||||
FadeOut,
|
||||
} from 'react-native-reanimated';
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||
import { TAB_ITEM_HEIGHT } from '../../../../navigation/tabs';
|
||||
@@ -676,6 +678,8 @@ export default function HomeLightning({ navigation }) {
|
||||
return (
|
||||
<Animated.View
|
||||
key={tx.key}
|
||||
entering={FadeIn.duration(150)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
layout={LinearTransition.duration(300)}
|
||||
>
|
||||
{tx.item}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
navigateToSendUsingClipboard,
|
||||
getQRImage,
|
||||
} from '../../../../functions';
|
||||
import handlePreSendPageParsing from '../../../../functions/sendBitcoin/handlePreSendPageParsing';
|
||||
import { ThemeText } from '../../../../functions/CustomElements';
|
||||
import { useGlobalContacts } from '../../../../../context-store/globalContacts';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -373,8 +374,27 @@ export default function HalfModalSendOptions({
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = handlePreSendPageParsing(input);
|
||||
if (parsed.error) {
|
||||
navigate.navigate('ErrorScreen', { errorMessage: parsed.error });
|
||||
return;
|
||||
}
|
||||
if (parsed.navigateToWebView) {
|
||||
navigate.navigate('CustomWebView', {
|
||||
headerText: '',
|
||||
webViewURL: parsed.webViewURL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (parsed.isExternalChain) {
|
||||
navigate.replace('SelectStablecoinParamsScreen', {
|
||||
address: parsed.address,
|
||||
chainFamily: parsed.chainFamily,
|
||||
});
|
||||
return;
|
||||
}
|
||||
navigate.replace('ConfirmPaymentScreen', {
|
||||
btcAdress: input,
|
||||
btcAdress: parsed.btcAdress,
|
||||
fromPage: '',
|
||||
});
|
||||
}, [
|
||||
@@ -406,6 +426,15 @@ export default function HalfModalSendOptions({
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.isExternalChain) {
|
||||
navigate.replace('SelectStablecoinParamsScreen', {
|
||||
address: response.address,
|
||||
chainFamily: response.chainFamily,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.didWork || !response.btcAdress) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -257,11 +257,19 @@ export default function TxFilterHalfModal({
|
||||
{/* --- Sticky bottom bar --- */}
|
||||
<View style={[styles.bottomBar, { borderTopColor: inactiveBorderColor }]}>
|
||||
<TouchableOpacity
|
||||
style={{ opacity: hasItemsSeleceted ? 1 : HIDDEN_OPACITY }}
|
||||
style={{
|
||||
opacity: hasItemsSeleceted ? 1 : HIDDEN_OPACITY,
|
||||
flexShrink: 1,
|
||||
}}
|
||||
onPress={handleClear}
|
||||
activeOpacity={hasItemsSeleceted ? 0.2 : HIDDEN_OPACITY}
|
||||
>
|
||||
<ThemeText content={t(`${ns}.filterClearAll`)} />
|
||||
<ThemeText
|
||||
CustomNumberOfLines={2}
|
||||
adjustsFontSizeToFit={true}
|
||||
allowFontScaling={true}
|
||||
content={t(`${ns}.filterClearAll`)}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<CustomButton
|
||||
buttonStyles={styles.applyButton}
|
||||
@@ -352,6 +360,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 14,
|
||||
borderTopWidth: 1,
|
||||
gap: 10,
|
||||
},
|
||||
clearText: {
|
||||
fontSize: SIZES.medium,
|
||||
|
||||
@@ -34,6 +34,8 @@ import GetThemeColors from '../../../../../hooks/themeColors';
|
||||
import { copyToClipboard } from '../../../../../functions';
|
||||
import { useToast } from '../../../../../../context-store/toastManager';
|
||||
import ThemeIcon from '../../../../../functions/CustomElements/themeIcon';
|
||||
import { satsToDollars } from '../../../../../functions/spark/flashnet';
|
||||
import { useFlashnet } from '../../../../../../context-store/flashnetContext';
|
||||
|
||||
const confirmTxAnimation = require('../../../../../assets/confirmTxAnimation');
|
||||
|
||||
|
||||
@@ -22,6 +22,15 @@ import dollarIcon from '../assets/icons/dollarIcon.png';
|
||||
import bitcoinIcon from '../assets/icons/bitcoinIcon.png';
|
||||
import giftCardIcon from '../assets/icons/giftCardIcon.png';
|
||||
import nwcLogo from '../assets/icons/nwcLogo.png';
|
||||
import chain_arbitrum from '../assets/icons/chain-arbitrum.svg';
|
||||
import chain_base from '../assets/icons/chain-base.svg';
|
||||
import chain_ethereum from '../assets/icons/chain-ethereum.svg';
|
||||
import chain_optimism from '../assets/icons/chain-optimism.svg';
|
||||
import chain_plasma from '../assets/icons/chain-plasma.svg';
|
||||
import chain_tron from '../assets/icons/chain-tron.svg';
|
||||
import chain_polygon from '../assets/icons/chain-polygon.png';
|
||||
import usdcLogo from '../assets/icons/usdc.svg';
|
||||
import usdtLogo from '../assets/icons/usdt.svg';
|
||||
|
||||
export default {
|
||||
logoIcon,
|
||||
@@ -61,4 +70,15 @@ export default {
|
||||
dollarIcon,
|
||||
bitcoinIcon,
|
||||
nwcLogo,
|
||||
|
||||
// chain logos
|
||||
chain_arbitrum,
|
||||
chain_base,
|
||||
chain_ethereum,
|
||||
chain_optimism,
|
||||
chain_plasma,
|
||||
chain_tron,
|
||||
chain_polygon,
|
||||
usdcLogo,
|
||||
usdtLogo,
|
||||
};
|
||||
|
||||
@@ -70,6 +70,7 @@ import ClaimGiftHomeHalfModal from '../../components/admin/homeComponents/gifts/
|
||||
import SwapFlowHalfModal from '../../components/admin/homeComponents/swaps/swapFlowHalfModal';
|
||||
import TxFilterHalfModal from '../../components/admin/homeComponents/homeLightning/txFilterHalfModal';
|
||||
import PayLinkCurrencySelect from '../../components/admin/homeComponents/payLinks/components/payLinkCurrencySelect';
|
||||
import StablecoinAssetPickerHalfModal from './stablecoinAssetPickerHalfModal';
|
||||
|
||||
export default function CustomHalfModal(props) {
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
@@ -557,6 +558,15 @@ export default function CustomHalfModal(props) {
|
||||
setContentHeight={setContentHeight}
|
||||
/>
|
||||
);
|
||||
case 'stablecoinAssetPicker':
|
||||
return (
|
||||
<StablecoinAssetPickerHalfModal
|
||||
selectedChain={props?.route?.params?.selectedChain}
|
||||
address={props?.route?.params?.address}
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setContentHeight={setContentHeight}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <ThemeText content={'TST'} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import { useEffect } from 'react';
|
||||
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { Image } from 'expo-image';
|
||||
import { ThemeText } from '.';
|
||||
import GetThemeColors from '../../hooks/themeColors';
|
||||
import { ICONS, SIZES } from '../../constants';
|
||||
import { useGlobalThemeContext } from '../../../context-store/theme';
|
||||
import { ChevronRight } from 'lucide-react-native';
|
||||
import { HIDDEN_OPACITY } from '../../constants/theme';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const CHAIN_TO_FLASHNET = {
|
||||
Ethereum: 'ethereum',
|
||||
Arbitrum: 'arbitrum',
|
||||
Base: 'base',
|
||||
Optimism: 'optimism',
|
||||
Polygon: 'polygon',
|
||||
Tron: 'tron',
|
||||
};
|
||||
|
||||
const USDT_SUPPORTED_CHAINS = new Set([
|
||||
'Ethereum',
|
||||
'Arbitrum',
|
||||
'Optimism',
|
||||
'Tron',
|
||||
]);
|
||||
|
||||
const USDC_SUPPORTED_CHAINS = new Set([
|
||||
'Ethereum',
|
||||
'Arbitrum',
|
||||
'Base',
|
||||
'Optimism',
|
||||
'Polygon',
|
||||
]);
|
||||
|
||||
const ASSETS = [
|
||||
{
|
||||
id: 'USDC',
|
||||
name: 'USDC',
|
||||
icon: ICONS.usdcLogo,
|
||||
},
|
||||
{
|
||||
id: 'USDT',
|
||||
name: 'USDT',
|
||||
icon: ICONS.usdtLogo,
|
||||
},
|
||||
];
|
||||
|
||||
export default function StablecoinAssetPickerHalfModal({
|
||||
selectedChain,
|
||||
address,
|
||||
handleBackPressFunction,
|
||||
setContentHeight,
|
||||
}) {
|
||||
const navigate = useNavigation();
|
||||
const { backgroundOffset, backgroundColor, textColor } = GetThemeColors();
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setContentHeight(400);
|
||||
}, []);
|
||||
|
||||
const rowBg = theme && darkModeType ? backgroundColor : backgroundOffset;
|
||||
|
||||
const handleAssetSelect = asset => {
|
||||
handleBackPressFunction(() => {
|
||||
navigate.replace('StablecoinSendScreen', {
|
||||
address,
|
||||
chain: CHAIN_TO_FLASHNET[selectedChain],
|
||||
chainLabel: selectedChain,
|
||||
asset,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ThemeText
|
||||
styles={styles.title}
|
||||
content={t('wallet.stablecoinSend.assetPicker.header')}
|
||||
/>
|
||||
|
||||
{ASSETS.map(item => {
|
||||
const isUnsupported =
|
||||
(item.id === 'USDT' && !USDT_SUPPORTED_CHAINS.has(selectedChain)) ||
|
||||
(item.id === 'USDC' && !USDC_SUPPORTED_CHAINS.has(selectedChain));
|
||||
|
||||
const rowContent = (
|
||||
<>
|
||||
<Image
|
||||
style={styles.assetIcon}
|
||||
source={item.icon}
|
||||
contentFit="contain"
|
||||
/>
|
||||
<View style={styles.assetInfo}>
|
||||
<ThemeText styles={styles.assetName} content={item.name} />
|
||||
{isUnsupported && (
|
||||
<ThemeText
|
||||
styles={styles.unsupportedLabel}
|
||||
content={t('wallet.stablecoinSend.assetPicker.notAvailable', {
|
||||
chain: selectedChain,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{!isUnsupported && (
|
||||
<ChevronRight
|
||||
size={20}
|
||||
color={textColor}
|
||||
style={{ opacity: 0.5 }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
if (isUnsupported) {
|
||||
return (
|
||||
<View
|
||||
key={item.id}
|
||||
style={[styles.row, { backgroundColor: rowBg, opacity: 0.4 }]}
|
||||
pointerEvents="none"
|
||||
>
|
||||
{rowContent}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={item.id}
|
||||
style={[styles.row, { backgroundColor: rowBg }]}
|
||||
onPress={() => handleAssetSelect(item.id)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{rowContent}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 16,
|
||||
gap: 10,
|
||||
},
|
||||
title: {
|
||||
fontSize: SIZES.large,
|
||||
fontWeight: 500,
|
||||
marginBottom: 8,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
gap: 12,
|
||||
},
|
||||
assetIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
},
|
||||
assetInfo: {
|
||||
flex: 1,
|
||||
gap: 2,
|
||||
},
|
||||
assetName: {
|
||||
fontSize: SIZES.large,
|
||||
fontWeight: 500,
|
||||
},
|
||||
|
||||
unsupportedLabel: {
|
||||
fontSize: SIZES.small,
|
||||
opacity: HIDDEN_OPACITY,
|
||||
},
|
||||
});
|
||||
@@ -3,10 +3,35 @@ import { IS_BLITZ_URL_REGEX, WEBSITE_REGEX } from '../../constants';
|
||||
import testURLForInvoice from '../testURLForInvoice';
|
||||
import { convertMerchantQRToLightningAddress } from './getMerchantAddress';
|
||||
|
||||
const EVM_REGEX = /^0x[0-9a-fA-F]{40}$/;
|
||||
const TRON_REGEX = /^T[1-9A-HJ-NP-Za-km-z]{33}$/;
|
||||
const SOLANA_REGEX = /^[1-9A-HJ-NP-Za-km-z]{43,44}$/;
|
||||
|
||||
export default function handlePreSendPageParsing(data) {
|
||||
try {
|
||||
if (!data) throw new Error(i18next.t('errormessages.invalidData'));
|
||||
|
||||
const trimmed = data.trim();
|
||||
|
||||
if (EVM_REGEX.test(trimmed)) {
|
||||
return {
|
||||
didWork: true,
|
||||
error: null,
|
||||
isExternalChain: true,
|
||||
address: trimmed,
|
||||
chainFamily: 'EVM',
|
||||
};
|
||||
}
|
||||
if (TRON_REGEX.test(trimmed)) {
|
||||
return {
|
||||
didWork: true,
|
||||
error: null,
|
||||
isExternalChain: true,
|
||||
address: trimmed,
|
||||
chainFamily: 'Tron',
|
||||
};
|
||||
}
|
||||
|
||||
if (WEBSITE_REGEX.test(data)) {
|
||||
if (IS_BLITZ_URL_REGEX.test(data))
|
||||
throw new Error(i18next.t('errormessages.invalidData'));
|
||||
@@ -28,6 +53,16 @@ export default function handlePreSendPageParsing(data) {
|
||||
network: process.env.BOLTZ_ENVIRONMENT,
|
||||
});
|
||||
|
||||
if (!merchantLNAddress && SOLANA_REGEX.test(trimmed)) {
|
||||
return {
|
||||
didWork: true,
|
||||
error: null,
|
||||
isExternalChain: true,
|
||||
address: trimmed,
|
||||
chainFamily: 'Solana',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
didWork: true,
|
||||
error: null,
|
||||
|
||||
@@ -29,6 +29,20 @@ async function navigateToSendUsingClipboard(navigate, callLocation, from, t) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preParsingResponse.isExternalChain) {
|
||||
if (from === 'home')
|
||||
navigate.navigate('SelectStablecoinParamsScreen', {
|
||||
address: preParsingResponse.address,
|
||||
chainFamily: preParsingResponse.chainFamily,
|
||||
});
|
||||
else
|
||||
navigate.replace('SelectStablecoinParamsScreen', {
|
||||
address: preParsingResponse.address,
|
||||
chainFamily: preParsingResponse.chainFamily,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (from === 'home')
|
||||
navigate.navigate('ConfirmPaymentScreen', {
|
||||
btcAdress: preParsingResponse.btcAdress,
|
||||
@@ -95,7 +109,22 @@ async function getQRImage() {
|
||||
};
|
||||
}
|
||||
|
||||
if (preParsingResponse.isExternalChain) {
|
||||
return {
|
||||
isExternalChain: true,
|
||||
address: preParsingResponse.address,
|
||||
chainFamily: preParsingResponse.chainFamily,
|
||||
didWork: true,
|
||||
error: '',
|
||||
};
|
||||
}
|
||||
|
||||
return { btcAdress: preParsingResponse.btcAdress, didWork: true, error: '' };
|
||||
}
|
||||
|
||||
export { navigateToSendUsingClipboard, getQRImage };
|
||||
function formatStablecoinAmount(rawAmount, decimals = 2) {
|
||||
const value = Number(rawAmount) / Math.pow(10, 6);
|
||||
return value.toFixed(decimals);
|
||||
}
|
||||
|
||||
export { navigateToSendUsingClipboard, getQRImage, formatStablecoinAmount };
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from './transactions';
|
||||
import { transformTxToPaymentObject } from './transformTxToPayment';
|
||||
import sha256Hash from '../hash';
|
||||
import fetchBackend from '../../../db/handleBackend';
|
||||
|
||||
const RESTORE_STATE_KEY = 'spark_tx_restore_state';
|
||||
const MAX_BATCH_SIZE = 400;
|
||||
@@ -490,12 +491,54 @@ export async function fullRestoreSparkState({
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkFlashnetStablecoinStatusLogic(
|
||||
tx,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
) {
|
||||
try {
|
||||
const details =
|
||||
typeof tx.details === 'string' ? JSON.parse(tx.details) : tx.details;
|
||||
if (!details?.isFlashnetStablecoin || !details?.quoteId) return null;
|
||||
|
||||
const statusResult = await fetchBackend(
|
||||
'checkFlashnetStablecoinStatus',
|
||||
{ quoteId: details.quoteId },
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
);
|
||||
|
||||
if (!statusResult || statusResult.error) return null;
|
||||
|
||||
const newStatus =
|
||||
statusResult.status === 'completed'
|
||||
? 'completed'
|
||||
: statusResult.status === 'failed'
|
||||
? 'failed'
|
||||
: null;
|
||||
|
||||
if (!newStatus) return null;
|
||||
|
||||
return {
|
||||
id: tx.sparkID,
|
||||
paymentStatus: newStatus,
|
||||
paymentType: tx.paymentType,
|
||||
accountId: tx.accountId,
|
||||
details,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let isUpdatingSparkTxStatus = false;
|
||||
export const updateSparkTxStatus = async (
|
||||
mnemoninc,
|
||||
accountId,
|
||||
sendWebViewRequest,
|
||||
forceRefresh = false,
|
||||
contactsPrivateKey = null,
|
||||
publicKey = null,
|
||||
) => {
|
||||
try {
|
||||
if (isUpdatingSparkTxStatus) {
|
||||
@@ -547,7 +590,13 @@ export const updateSparkTxStatus = async (
|
||||
accountId,
|
||||
forceRefresh,
|
||||
),
|
||||
processSparkTransactions(txsByType.spark, mnemoninc, sendWebViewRequest),
|
||||
processSparkTransactions(
|
||||
txsByType.spark,
|
||||
mnemoninc,
|
||||
sendWebViewRequest,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
),
|
||||
]);
|
||||
|
||||
const updatedTxs = [
|
||||
@@ -942,11 +991,27 @@ async function processSparkTransactions(
|
||||
sparkTxs,
|
||||
mnemonic,
|
||||
sendWebViewRequest,
|
||||
contactsPrivateKey = null,
|
||||
publicKey = null,
|
||||
) {
|
||||
let includesGift = false;
|
||||
let updatedTxs = [];
|
||||
for (const txStateUpdate of sparkTxs) {
|
||||
const details = JSON.parse(txStateUpdate.details);
|
||||
|
||||
// Stablecoin sends via Flashnet orchestration — delegate to status checker
|
||||
if (details.isFlashnetStablecoin) {
|
||||
if (contactsPrivateKey && publicKey) {
|
||||
const update = await checkFlashnetStablecoinStatusLogic(
|
||||
txStateUpdate,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
);
|
||||
if (update) updatedTxs.push(update);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IS_SPARK_ID.test(txStateUpdate.sparkID)) {
|
||||
const findTxResponse = await getSingleTxDetails(
|
||||
mnemonic,
|
||||
|
||||
@@ -27,7 +27,7 @@ import customUUID from '../../functions/customUUID';
|
||||
import { useGlobalContactsInfo } from '../../../context-store/globalContacts';
|
||||
import { getSingleContact } from '../../../db';
|
||||
import { getCachedProfileImage } from '../../functions/cachedImage';
|
||||
import { INSET_WINDOW_WIDTH } from '../../constants/theme';
|
||||
import { HIDDEN_OPACITY, INSET_WINDOW_WIDTH } from '../../constants/theme';
|
||||
import normalizeLNURLAddress from '../../functions/lnurl/normalizeLNURLAddress';
|
||||
|
||||
const BLITZ_DOMAINS = [
|
||||
@@ -92,6 +92,9 @@ export default function ConfirmTxPage(props) {
|
||||
: transaction?.paymentType;
|
||||
|
||||
const showPendingMessage = transaction?.paymentStatus === 'pending';
|
||||
const isFlashnetStablecoinPending =
|
||||
paymentInformation?.isFlashnetStablecoin &&
|
||||
transaction?.paymentStatus === 'pending';
|
||||
|
||||
const paymentFee = paymentInformation?.fee;
|
||||
|
||||
@@ -127,6 +130,87 @@ export default function ConfirmTxPage(props) {
|
||||
animationRef.current?.play();
|
||||
}, []);
|
||||
|
||||
if (isFlashnetStablecoinPending) {
|
||||
return (
|
||||
<GlobalThemeView useStandardWidth={true} styles={styles.globalConatianer}>
|
||||
<LottieView
|
||||
ref={animationRef}
|
||||
source={didSucceed ? confirmAnimation : errorAnimation}
|
||||
loop={false}
|
||||
style={{
|
||||
width: screenDimensions.width / 1.5,
|
||||
height: screenDimensions.width / 1.5,
|
||||
maxWidth: 400,
|
||||
maxHeight: 400,
|
||||
}}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={{ fontSize: SIZES.large, marginBottom: 10 }}
|
||||
content={t('wallet.stablecoinSend.swapInProgress')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={{
|
||||
opacity: HIDDEN_OPACITY,
|
||||
width: '95%',
|
||||
maxWidth: 300,
|
||||
textAlign: 'center',
|
||||
marginBottom: 50,
|
||||
fontSize: SIZES.smedium,
|
||||
}}
|
||||
content={t('wallet.stablecoinSend.swapInProgressDescription')}
|
||||
/>
|
||||
<View style={styles.paymentTable}>
|
||||
<View style={styles.paymentTableRow}>
|
||||
<ThemeText
|
||||
CustomNumberOfLines={1}
|
||||
styles={styles.labelText}
|
||||
content={t('constants.fee')}
|
||||
/>
|
||||
<ThemeText
|
||||
content={displayCorrectDenomination({
|
||||
amount: paymentFee,
|
||||
masterInfoObject,
|
||||
fiatStats,
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.paymentTableRow}>
|
||||
<ThemeText
|
||||
CustomNumberOfLines={1}
|
||||
styles={styles.labelText}
|
||||
content={t('constants.type')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={{ textTransform: 'capitalize' }}
|
||||
content={t('wallet.stablecoinSend.chainSwap')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={{
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
backgroundColor: !theme ? COLORS.primary : COLORS.darkModeText,
|
||||
marginTop: 'auto',
|
||||
paddingHorizontal: 15,
|
||||
}}
|
||||
textStyles={{
|
||||
...styles.buttonText,
|
||||
color: !theme ? COLORS.darkModeText : COLORS.lightModeText,
|
||||
}}
|
||||
actionFunction={() => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
navigate.popToTop();
|
||||
});
|
||||
});
|
||||
}}
|
||||
textContent={t('constants.continue')}
|
||||
/>
|
||||
</GlobalThemeView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GlobalThemeView useStandardWidth={true} styles={styles.globalConatianer}>
|
||||
<LottieView
|
||||
|
||||
@@ -240,6 +240,8 @@ export default function ExpandedTx(props) {
|
||||
}
|
||||
};
|
||||
|
||||
const isFlashnetStablecoin = !!transaction.details?.isFlashnetStablecoin;
|
||||
|
||||
const isLRC20Payment = transaction.details.isLRC20Payment;
|
||||
const selectedToken = isLRC20Payment
|
||||
? sparkInformation.tokens?.[transaction.details.LRC20Token]
|
||||
@@ -520,7 +522,9 @@ export default function ExpandedTx(props) {
|
||||
|
||||
{renderInfoRow(
|
||||
t('constants.type'),
|
||||
transactionPaymentType,
|
||||
isFlashnetStablecoin
|
||||
? t('screens.inAccount.expandedTxPage.chainSwap')
|
||||
: transactionPaymentType,
|
||||
true,
|
||||
{
|
||||
textTransform: 'capitalize',
|
||||
@@ -529,6 +533,15 @@ export default function ExpandedTx(props) {
|
||||
|
||||
{renderLRC20TokenRow()}
|
||||
|
||||
{isFlashnetStablecoin &&
|
||||
transaction.paymentStatus !== 'completed' &&
|
||||
renderInfoRow(
|
||||
t('wallet.stablecoinSend.swapStatus'),
|
||||
transaction.paymentStatus,
|
||||
true,
|
||||
{ textTransform: 'capitalize' },
|
||||
)}
|
||||
|
||||
{showConversionLine &&
|
||||
renderInfoRow(
|
||||
t('constants.rate'),
|
||||
@@ -962,7 +975,6 @@ const styles = StyleSheet.create({
|
||||
// fontSize: SIZES.large,
|
||||
},
|
||||
tokenText: {
|
||||
fontSize: SIZES.large,
|
||||
textTransform: 'uppercase',
|
||||
flex: 1,
|
||||
textAlign: 'right',
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GlobalThemeView, ThemeText } from '../../functions/CustomElements';
|
||||
import CustomButton from '../../functions/CustomElements/button';
|
||||
import CustomSettingsTopBar from '../../functions/CustomElements/settingsTopBar';
|
||||
import GetThemeColors from '../../hooks/themeColors';
|
||||
import {
|
||||
CENTER,
|
||||
COLORS,
|
||||
CONTENT_KEYBOARD_OFFSET,
|
||||
FONT,
|
||||
ICONS,
|
||||
SIZES,
|
||||
} from '../../constants';
|
||||
import { useGlobalThemeContext } from '../../../context-store/theme';
|
||||
import { useGlobalInsets } from '../../../context-store/insetsProvider';
|
||||
import { Image } from 'expo-image';
|
||||
import { HIDDEN_OPACITY, INSET_WINDOW_WIDTH } from '../../constants/theme';
|
||||
|
||||
const EVM_CHAINS = ['Base', 'Ethereum', 'Arbitrum', 'Optimism', 'Polygon'];
|
||||
const TRON_ASSETS = [{ id: 'USDT', label: 'USDT', icon: ICONS.usdtLogo }];
|
||||
const SOLANA_ASSETS = [{ id: 'USDC', label: 'USDC', icon: ICONS.usdcLogo }];
|
||||
|
||||
export default function SelectStablecoinParamsScreen() {
|
||||
const navigate = useNavigation();
|
||||
const route = useRoute();
|
||||
const { address, chainFamily } = route.params;
|
||||
const { t } = useTranslation();
|
||||
const { backgroundOffset, backgroundColor } = GetThemeColors();
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { bottomPadding } = useGlobalInsets();
|
||||
|
||||
const isTron = chainFamily === 'Tron';
|
||||
const isSolana = chainFamily === 'Solana';
|
||||
const chains = isTron || isSolana ? [] : EVM_CHAINS;
|
||||
|
||||
const [selectedChain, setSelectedChain] = useState(null);
|
||||
|
||||
const handleContinue = useCallback(() => {
|
||||
navigate.navigate('CustomHalfModal', {
|
||||
wantedContent: 'stablecoinAssetPicker',
|
||||
selectedChain,
|
||||
address,
|
||||
sliderHight: 0.5,
|
||||
});
|
||||
}, [navigate, address, selectedChain]);
|
||||
|
||||
const handleNonEvmAssetSelect = useCallback(
|
||||
asset => {
|
||||
navigate.navigate('StablecoinSendScreen', {
|
||||
address,
|
||||
chain: chainFamily.toLowerCase(),
|
||||
chainLabel: chainFamily,
|
||||
asset,
|
||||
});
|
||||
},
|
||||
[navigate, address],
|
||||
);
|
||||
|
||||
const cardBg = backgroundOffset;
|
||||
|
||||
return (
|
||||
<GlobalThemeView useStandardWidth={true}>
|
||||
<CustomSettingsTopBar
|
||||
label={t('wallet.stablecoinSend.sendStablecoins')}
|
||||
containerStyles={{ marginBottom: 0 }}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.sectionTitle}
|
||||
content={t(
|
||||
isTron || isSolana
|
||||
? 'wallet.stablecoinSend.selectAsset'
|
||||
: 'wallet.stablecoinSend.selectChain',
|
||||
)}
|
||||
/>
|
||||
<View style={styles.innerContainer}>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={[
|
||||
styles.scrollContent,
|
||||
{ paddingBottom: bottomPadding + 20 },
|
||||
]}
|
||||
>
|
||||
<View style={styles.section}>
|
||||
<View style={styles.grid}>
|
||||
{isTron || isSolana
|
||||
? (isTron ? TRON_ASSETS : SOLANA_ASSETS).map(asset => (
|
||||
<TouchableOpacity
|
||||
key={asset.id}
|
||||
style={[
|
||||
styles.card,
|
||||
{ backgroundColor: cardBg, borderColor: cardBg },
|
||||
]}
|
||||
onPress={() => handleNonEvmAssetSelect(asset.id)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Image
|
||||
style={styles.cardIcon}
|
||||
source={asset.icon}
|
||||
contentFit="contain"
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.cardLabel}
|
||||
content={asset.label}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
))
|
||||
: chains.map(chain => {
|
||||
const isSelected = selectedChain === chain;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={chain}
|
||||
style={[
|
||||
styles.card,
|
||||
{ backgroundColor: cardBg, borderColor: cardBg },
|
||||
isSelected && {
|
||||
borderColor:
|
||||
theme && darkModeType
|
||||
? COLORS.darkModeText
|
||||
: COLORS.primary,
|
||||
},
|
||||
]}
|
||||
onPress={() => setSelectedChain(chain)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Image
|
||||
style={styles.cardIcon}
|
||||
source={ICONS[`chain_${chain.toLowerCase()}`]}
|
||||
contentFit="contain"
|
||||
/>
|
||||
<ThemeText styles={styles.cardLabel} content={chain} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{!isTron && !isSolana && (
|
||||
<CustomButton
|
||||
textContent={t('constants.continue')}
|
||||
actionFunction={handleContinue}
|
||||
disabled={!selectedChain}
|
||||
buttonStyles={!selectedChain ? { opacity: 0.5 } : undefined}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</GlobalThemeView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
innerContainer: {
|
||||
flex: 1,
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
...CENTER,
|
||||
},
|
||||
scrollContent: {
|
||||
gap: 16,
|
||||
paddingTop: 8,
|
||||
},
|
||||
section: {
|
||||
gap: 8,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: SIZES.medium,
|
||||
opacity: HIDDEN_OPACITY,
|
||||
textAlign: 'center',
|
||||
marginBottom: CONTENT_KEYBOARD_OFFSET,
|
||||
},
|
||||
grid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 12,
|
||||
},
|
||||
card: {
|
||||
width: '47%',
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
borderColor: 'transparent',
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
|
||||
cardIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
},
|
||||
cardLabel: {
|
||||
fontSize: SIZES.medium,
|
||||
fontFamily: FONT.Descriptoin_Medium,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
@@ -129,6 +129,21 @@ export default function SendPaymentHome({ pageViewPage, from }) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.isExternalChain) {
|
||||
if (from === 'home')
|
||||
navigate.navigate('SelectStablecoinParamsScreen', {
|
||||
address: response.address,
|
||||
chainFamily: response.chainFamily,
|
||||
});
|
||||
else
|
||||
navigate.replace('SelectStablecoinParamsScreen', {
|
||||
address: response.address,
|
||||
chainFamily: response.chainFamily,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (from === 'home')
|
||||
navigate.navigate('ConfirmPaymentScreen', {
|
||||
btcAdress: response.btcAdress,
|
||||
@@ -205,6 +220,22 @@ export default function SendPaymentHome({ pageViewPage, from }) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.isExternalChain) {
|
||||
if (from === 'home')
|
||||
navigate.navigate('SelectStablecoinParamsScreen', {
|
||||
address: response.address,
|
||||
chainFamily: response.chainFamily,
|
||||
});
|
||||
else
|
||||
navigate.replace('SelectStablecoinParamsScreen', {
|
||||
address: response.address,
|
||||
chainFamily: response.chainFamily,
|
||||
});
|
||||
|
||||
isPhotoeLibraryOpen.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.didWork || !response.btcAdress) {
|
||||
isPhotoeLibraryOpen.current = false;
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,777 @@
|
||||
import {
|
||||
ActivityIndicator,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
CustomKeyboardAvoidingView,
|
||||
ThemeText,
|
||||
} from '../../functions/CustomElements';
|
||||
import CustomButton from '../../functions/CustomElements/button';
|
||||
import CustomSettingsTopBar from '../../functions/CustomElements/settingsTopBar';
|
||||
import FormattedBalanceInput from '../../functions/CustomElements/formattedBalanceInput';
|
||||
import FormattedSatText from '../../functions/CustomElements/satTextDisplay';
|
||||
import CustomNumberKeyboard from '../../functions/CustomElements/customNumberKeyboard';
|
||||
import CustomSearchInput from '../../functions/CustomElements/searchInput';
|
||||
import ChoosePaymentMethod from '../../components/admin/homeComponents/sendBitcoin/components/choosePaymentMethodContainer';
|
||||
import SwipeButtonNew from '../../functions/CustomElements/sliderButton';
|
||||
|
||||
import GetThemeColors from '../../hooks/themeColors';
|
||||
import {
|
||||
APPROXIMATE_SYMBOL,
|
||||
CENTER,
|
||||
COLORS,
|
||||
SIZES,
|
||||
USDB_TOKEN_ID,
|
||||
} from '../../constants';
|
||||
import {
|
||||
HIDDEN_OPACITY,
|
||||
INSET_WINDOW_WIDTH,
|
||||
WINDOWWIDTH,
|
||||
} from '../../constants/theme';
|
||||
import { useGlobalThemeContext } from '../../../context-store/theme';
|
||||
import { useGlobalInsets } from '../../../context-store/insetsProvider';
|
||||
import { useNodeContext } from '../../../context-store/nodeContext';
|
||||
import { useKeysContext } from '../../../context-store/keys';
|
||||
import { useActiveCustodyAccount } from '../../../context-store/activeAccount';
|
||||
import { useSparkWallet } from '../../../context-store/sparkContext';
|
||||
import { useUserBalanceContext } from '../../../context-store/userBalanceContext';
|
||||
import { useFlashnet } from '../../../context-store/flashnetContext';
|
||||
import { useGlobalContextProvider } from '../../../context-store/context';
|
||||
|
||||
import fetchBackend from '../../../db/handleBackend';
|
||||
import { sendSparkPayment, sendSparkTokens } from '../../functions/spark';
|
||||
import { bulkUpdateSparkTransactions } from '../../functions/spark/transactions';
|
||||
import { dollarsToSats, satsToDollars } from '../../functions/spark/flashnet';
|
||||
import EmojiQuickBar from '../../functions/CustomElements/emojiBar';
|
||||
import usePaymentInputDisplay from '../../hooks/usePaymentInputDisplay';
|
||||
import convertTextInputValue from '../../functions/textInputConvertValue';
|
||||
import SendTransactionFeeInfo from '../../components/admin/homeComponents/sendBitcoin/components/feeInfo';
|
||||
import { formatStablecoinAmount } from '../../functions/sendBitcoin';
|
||||
import { SliderProgressAnimation } from '../../functions/CustomElements/sendPaymentAnimation';
|
||||
import { formatBalanceAmount } from '../../functions';
|
||||
|
||||
const QUOTE_TTL_MS = 115_000;
|
||||
|
||||
function truncateAddress(addr) {
|
||||
if (!addr || addr.length <= 16) return addr || '';
|
||||
return `${addr.slice(0, 8)}...${addr.slice(-6)}`;
|
||||
}
|
||||
|
||||
function formatCountdown(ms) {
|
||||
const secs = Math.max(0, Math.floor(ms / 1000));
|
||||
const m = Math.floor(secs / 60);
|
||||
const s = secs % 60;
|
||||
return `${m}:${s.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
export default function StablecoinSendScreen() {
|
||||
const navigate = useNavigation();
|
||||
const route = useRoute();
|
||||
const { address, chain, chainLabel, asset, selectedPaymentMethod } =
|
||||
route.params;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { backgroundOffset, backgroundColor } = GetThemeColors();
|
||||
const { bottomPadding } = useGlobalInsets();
|
||||
const { fiatStats } = useNodeContext();
|
||||
const { masterInfoObject } = useGlobalContextProvider();
|
||||
const { contactsPrivateKey, publicKey } = useKeysContext();
|
||||
const { currentWalletMnemoinc } = useActiveCustodyAccount();
|
||||
const { sparkInformation } = useSparkWallet();
|
||||
const { bitcoinBalance, dollarBalanceToken } = useUserBalanceContext();
|
||||
const { swapUSDPriceDollars, poolInfoRef } = useFlashnet();
|
||||
|
||||
const [screenMode, setScreenMode] = useState('EDIT_AMOUNT'); // 'EDIT_AMOUNT' | 'CONFIRM_PAYMENT'
|
||||
const [rawInput, setRawInput] = useState('');
|
||||
const [inputDenomination, setInputDenomination] = useState('fiat');
|
||||
console.log(rawInput);
|
||||
const [description, setDescription] = useState('');
|
||||
|
||||
const [quote, setQuote] = useState(null);
|
||||
const [quoteLoading, setQuoteLoading] = useState(false);
|
||||
const [quoteError, setQuoteError] = useState(null);
|
||||
const [countdown, setCountdown] = useState(null);
|
||||
const [sending, setSending] = useState(false);
|
||||
const [isAmountFocused, setIsAmountFocused] = useState(true);
|
||||
const [retriggerQuoteFetch, setRetriggerQuoteFetch] = useState(0);
|
||||
|
||||
const debounceRef = useRef(null);
|
||||
const countdownRef = useRef(null);
|
||||
const quoteExpiresAt = useRef(null);
|
||||
const isSendingPayment = useRef(null);
|
||||
const progressAnimationRef = useRef(null);
|
||||
|
||||
const sourceMethod = selectedPaymentMethod || 'BTC';
|
||||
|
||||
useEffect(() => {
|
||||
isSendingPayment.current = sending;
|
||||
}, [sending]);
|
||||
|
||||
const {
|
||||
primaryDisplay,
|
||||
secondaryDisplay,
|
||||
conversionFiatStats,
|
||||
convertSatsToDisplay,
|
||||
convertDisplayToSats,
|
||||
getNextDenomination,
|
||||
convertForToggle,
|
||||
} = usePaymentInputDisplay({
|
||||
paymentMode: sourceMethod,
|
||||
inputDenomination,
|
||||
fiatStats,
|
||||
usdFiatStats: { coin: 'USD', value: swapUSDPriceDollars },
|
||||
masterInfoObject,
|
||||
isSendingPayment: isSendingPayment.current,
|
||||
});
|
||||
|
||||
const convertedSendAmount = convertDisplayToSats(rawInput);
|
||||
|
||||
const clearCountdown = useCallback(() => {
|
||||
if (countdownRef.current) clearInterval(countdownRef.current);
|
||||
countdownRef.current = null;
|
||||
setCountdown(null);
|
||||
}, []);
|
||||
|
||||
const startCountdown = useCallback(
|
||||
expiresAt => {
|
||||
clearCountdown();
|
||||
quoteExpiresAt.current = expiresAt;
|
||||
countdownRef.current = setInterval(() => {
|
||||
const remaining = quoteExpiresAt.current - Date.now();
|
||||
if (remaining <= 0) {
|
||||
clearInterval(countdownRef.current);
|
||||
countdownRef.current = null;
|
||||
setCountdown(null);
|
||||
setQuote(null);
|
||||
setRetriggerQuoteFetch(prev => prev + 1);
|
||||
} else {
|
||||
setCountdown(remaining);
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
[clearCountdown, t],
|
||||
);
|
||||
|
||||
const fetchQuote = useCallback(
|
||||
async sats => {
|
||||
if (sats <= 0 || !contactsPrivateKey || !publicKey) return;
|
||||
setQuoteError(null);
|
||||
setQuote(null);
|
||||
clearCountdown();
|
||||
try {
|
||||
const apiSourceMethod = sourceMethod === 'BTC' ? 'spark' : 'usdb';
|
||||
const result = await fetchBackend(
|
||||
'createFlashnetStablecoinQuote',
|
||||
{
|
||||
recipientAddress: address,
|
||||
destinationChain: chain,
|
||||
destinationAsset: asset,
|
||||
amountSats: sats,
|
||||
sourceMethod: apiSourceMethod,
|
||||
refundAddress: sparkInformation.sparkAddress,
|
||||
},
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
);
|
||||
if (!result || result.error) {
|
||||
throw new Error(
|
||||
result?.error || t('wallet.stablecoinSend.quoteError'),
|
||||
);
|
||||
}
|
||||
|
||||
const expiresAt = result.expiresAt || Date.now() + QUOTE_TTL_MS;
|
||||
const formattedFee =
|
||||
sourceMethod === 'BTC'
|
||||
? result.fee
|
||||
: dollarsToSats(
|
||||
result?.fee / Math.pow(10, 6),
|
||||
poolInfoRef.currentPriceAInB,
|
||||
);
|
||||
setQuote({ ...result, fee: formattedFee, expiresAt });
|
||||
startCountdown(expiresAt);
|
||||
} catch (err) {
|
||||
setQuoteError(err.message || t('wallet.stablecoinSend.quoteError'));
|
||||
} finally {
|
||||
setQuoteLoading(false);
|
||||
}
|
||||
},
|
||||
[
|
||||
address,
|
||||
chain,
|
||||
asset,
|
||||
sourceMethod,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
clearCountdown,
|
||||
startCountdown,
|
||||
t,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||
if (rawInput > 0) {
|
||||
setQuoteLoading(true);
|
||||
const fetchAmount =
|
||||
sourceMethod === 'BTC'
|
||||
? convertDisplayToSats(rawInput)
|
||||
: rawInput * Math.pow(10, 6);
|
||||
debounceRef.current = setTimeout(() => fetchQuote(fetchAmount), 800);
|
||||
} else {
|
||||
setQuoteLoading(false);
|
||||
setQuote(null);
|
||||
setQuoteError(null);
|
||||
clearCountdown();
|
||||
}
|
||||
return () => {
|
||||
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||
};
|
||||
}, [rawInput, sourceMethod, fetchQuote, clearCountdown, retriggerQuoteFetch]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearCountdown();
|
||||
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||
};
|
||||
}, [clearCountdown]);
|
||||
|
||||
// Handle back press from CONFIRM_PAYMENT — return to EDIT_AMOUNT
|
||||
useEffect(() => {
|
||||
const unsubscribe = navigate.addListener('beforeRemove', e => {
|
||||
if (screenMode !== 'CONFIRM_PAYMENT') return;
|
||||
if (isSendingPayment.current) return;
|
||||
e.preventDefault();
|
||||
setScreenMode('EDIT_AMOUNT');
|
||||
});
|
||||
return unsubscribe;
|
||||
}, [navigate, screenMode]);
|
||||
|
||||
const handleDenominationToggle = () => {
|
||||
if (!isAmountFocused) return;
|
||||
if (!convertedSendAmount) {
|
||||
setInputDenomination(prev => (prev === 'fiat' ? 'sats' : 'fiat'));
|
||||
return;
|
||||
}
|
||||
const nextDenom = getNextDenomination();
|
||||
const convertedValue = convertForToggle(rawInput, convertTextInputValue);
|
||||
|
||||
setInputDenomination(nextDenom);
|
||||
setRawInput(String(convertedValue));
|
||||
};
|
||||
|
||||
const handleMethodToggle = () => {
|
||||
navigate.navigate('CustomHalfModal', {
|
||||
wantedContent: 'SelectPaymentMethod',
|
||||
selectedPaymentMethod: sourceMethod,
|
||||
fromPage: 'StablecoinSendScreen',
|
||||
});
|
||||
};
|
||||
|
||||
const handleSend = useCallback(async () => {
|
||||
if (!quote || sending) return;
|
||||
if (Date.now() >= quote.expiresAt) {
|
||||
setQuoteError(t('wallet.stablecoinSend.quoteExpired'));
|
||||
setQuote(null);
|
||||
clearCountdown();
|
||||
setScreenMode('EDIT_AMOUNT');
|
||||
return;
|
||||
}
|
||||
|
||||
setSending(true);
|
||||
clearCountdown();
|
||||
|
||||
try {
|
||||
let result;
|
||||
if (sourceMethod === 'BTC') {
|
||||
result = await sendSparkPayment({
|
||||
receiverSparkAddress: quote.depositAddress,
|
||||
amountSats: Number(quote.amountIn),
|
||||
mnemonic: currentWalletMnemoinc,
|
||||
});
|
||||
} else {
|
||||
// USDb: quote.amountIn is in token micro-units (e.g. 1_000_000 = $1)
|
||||
result = await sendSparkTokens({
|
||||
tokenIdentifier: USDB_TOKEN_ID,
|
||||
tokenAmount: Number(quote.amountIn),
|
||||
receiverSparkAddress: quote.depositAddress,
|
||||
mnemonic: currentWalletMnemoinc,
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.didWork) throw new Error(result.error);
|
||||
|
||||
const sparkTransferId =
|
||||
sourceMethod === 'USD' ? result.response : result.response?.id;
|
||||
|
||||
const pendingTx = {
|
||||
id: sparkTransferId,
|
||||
paymentStatus: 'pending',
|
||||
paymentType: 'spark',
|
||||
accountId: sparkInformation.identityPubKey,
|
||||
details: {
|
||||
amount: quote.amountIn,
|
||||
fee: quote?.fee,
|
||||
totalFee: 0,
|
||||
supportFee: 0,
|
||||
description: description || `Send ${asset}`,
|
||||
address: quote.depositAddress,
|
||||
time: Date.now(),
|
||||
createdAt: Date.now(),
|
||||
direction: 'OUTGOING',
|
||||
isFlashnetStablecoin: true,
|
||||
quoteId: quote.quoteId,
|
||||
destinationAddress: address,
|
||||
destinationChain: chain,
|
||||
destinationAsset: asset,
|
||||
sourceMethod,
|
||||
isLRC20Payment: sourceMethod === 'USD',
|
||||
...(sourceMethod === 'USD' ? { LRC20Token: USDB_TOKEN_ID } : {}),
|
||||
},
|
||||
};
|
||||
|
||||
await bulkUpdateSparkTransactions([pendingTx], 'fullUpdate');
|
||||
|
||||
await fetchBackend(
|
||||
'submitFlashnetStablecoinOrder',
|
||||
{
|
||||
quoteId: quote.quoteId,
|
||||
sparkTxHash: sparkTransferId,
|
||||
sourceSparkAddress: sparkInformation.sparkAddress,
|
||||
},
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
);
|
||||
|
||||
if (progressAnimationRef.current) {
|
||||
progressAnimationRef.current.completeProgress();
|
||||
await new Promise(res => setTimeout(res, 600));
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
navigate.reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: 'HomeAdmin',
|
||||
params: {
|
||||
screen: 'Home',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ConfirmTxPage',
|
||||
params: {
|
||||
transaction: pendingTx,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err, 'error navigating to bla bla bla');
|
||||
navigate.navigate('ErrorScreen', { errorMessage: err.message });
|
||||
}
|
||||
}, [
|
||||
quote,
|
||||
sending,
|
||||
sourceMethod,
|
||||
navigate,
|
||||
currentWalletMnemoinc,
|
||||
sparkInformation,
|
||||
description,
|
||||
address,
|
||||
chain,
|
||||
asset,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
clearCountdown,
|
||||
t,
|
||||
]);
|
||||
|
||||
const handleEmoji = newDescription => {
|
||||
setDescription(newDescription);
|
||||
};
|
||||
|
||||
const isQuoteLoading =
|
||||
(quoteLoading || (quote && countdown === null)) && !sending;
|
||||
|
||||
const canConfirm =
|
||||
!!quote && !quoteLoading && !sending && convertedSendAmount > 0;
|
||||
|
||||
const handleReview = useCallback(() => {
|
||||
if (!convertedSendAmount || convertedSendAmount <= 0) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('wallet.stablecoinSend.noAmount'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (isQuoteLoading) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('wallet.stablecoinSend.quoteStillLoading'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!quote) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('wallet.stablecoinSend.noQuote'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const hasEnoughBalance =
|
||||
sourceMethod === 'BTC'
|
||||
? convertedSendAmount <= Number(bitcoinBalance)
|
||||
: Number(rawInput) <= Number(dollarBalanceToken);
|
||||
|
||||
if (!hasEnoughBalance) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('screens.inAccount.swapsPage.insufficientBalance'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (Date.now() >= quote.expiresAt) {
|
||||
setQuoteError(t('wallet.stablecoinSend.quoteExpired'));
|
||||
setQuote(null);
|
||||
clearCountdown();
|
||||
return;
|
||||
}
|
||||
setIsAmountFocused(true);
|
||||
setScreenMode('CONFIRM_PAYMENT');
|
||||
}, [
|
||||
canConfirm,
|
||||
quote,
|
||||
clearCountdown,
|
||||
t,
|
||||
convertedSendAmount,
|
||||
isQuoteLoading,
|
||||
sourceMethod,
|
||||
rawInput,
|
||||
bitcoinBalance,
|
||||
dollarBalanceToken,
|
||||
navigate,
|
||||
]);
|
||||
|
||||
const rowBg = backgroundOffset;
|
||||
|
||||
const memorizedKeyboardStyle = useMemo(() => {
|
||||
return {
|
||||
paddingBottom: !isAmountFocused ? 0 : bottomPadding,
|
||||
};
|
||||
}, [isAmountFocused]);
|
||||
|
||||
const isConfirmMode = screenMode === 'CONFIRM_PAYMENT';
|
||||
|
||||
return (
|
||||
<CustomKeyboardAvoidingView globalThemeViewStyles={memorizedKeyboardStyle}>
|
||||
<View style={styles.replacementContainer}>
|
||||
<CustomSettingsTopBar label={`${t('constants.send')} ${asset}`} />
|
||||
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{/* Amount display — tap to toggle denomination (edit mode only) */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={handleDenominationToggle}
|
||||
style={CENTER}
|
||||
disabled={!isAmountFocused || isConfirmMode}
|
||||
>
|
||||
<FormattedBalanceInput
|
||||
maxWidth={0.9}
|
||||
amountValue={rawInput}
|
||||
inputDenomination={primaryDisplay.denomination}
|
||||
forceCurrency={primaryDisplay.forceCurrency}
|
||||
forceFiatStats={primaryDisplay.forceFiatStats}
|
||||
activeOpacity={!convertedSendAmount ? 0.5 : 1}
|
||||
/>
|
||||
<FormattedSatText
|
||||
neverHideBalance={true}
|
||||
containerStyles={{
|
||||
opacity: !convertedSendAmount ? HIDDEN_OPACITY : 1,
|
||||
}}
|
||||
styles={{ includeFontPadding: false, ...styles.satValue }}
|
||||
globalBalanceDenomination={secondaryDisplay.denomination}
|
||||
forceCurrency={secondaryDisplay.forceCurrency}
|
||||
balance={convertedSendAmount}
|
||||
forceFiatStats={secondaryDisplay.forceFiatStats}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Confirm mode: fee info */}
|
||||
{isConfirmMode && (
|
||||
<SendTransactionFeeInfo
|
||||
paymentFee={quote?.fee}
|
||||
isLightningPayment={true}
|
||||
isDecoding={isQuoteLoading}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Confirm mode: destination info */}
|
||||
{isConfirmMode && (
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: address,
|
||||
})
|
||||
}
|
||||
style={[styles.destinationBox, { backgroundColor: rowBg }]}
|
||||
>
|
||||
<ThemeText
|
||||
styles={styles.quoteValue}
|
||||
content={`${truncateAddress(address)}`}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{/* Source method picker: shown in edit mode only */}
|
||||
{!isConfirmMode && (
|
||||
<ChoosePaymentMethod
|
||||
theme={theme}
|
||||
darkModeType={darkModeType}
|
||||
determinePaymentMethod={sourceMethod}
|
||||
handleSelectPaymentMethod={handleMethodToggle}
|
||||
bitcoinBalance={bitcoinBalance}
|
||||
dollarBalanceToken={dollarBalanceToken}
|
||||
masterInfoObject={masterInfoObject}
|
||||
fiatStats={fiatStats}
|
||||
uiState="EDIT_AMOUNT"
|
||||
t={t}
|
||||
containerStyles={{ marginTop: 5 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Description input: edit mode only */}
|
||||
{!isConfirmMode && (
|
||||
<CustomSearchInput
|
||||
onFocusFunction={() => setIsAmountFocused(false)}
|
||||
onBlurFunction={() => setIsAmountFocused(true)}
|
||||
placeholderText={t('constants.paymentDescriptionPlaceholder')}
|
||||
setInputText={setDescription}
|
||||
inputText={description}
|
||||
textInputMultiline={true}
|
||||
textAlignVertical="baseline"
|
||||
maxLength={150}
|
||||
containerStyles={{
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
marginTop: 10,
|
||||
maxWidth: 350,
|
||||
...CENTER,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Quote summary */}
|
||||
{convertedSendAmount > 0 && isAmountFocused && !sending && (
|
||||
<View style={[styles.quoteBox, { backgroundColor: rowBg }]}>
|
||||
{isQuoteLoading && (
|
||||
<View style={styles.quoteLoadingRow}>
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={
|
||||
theme && darkModeType ? COLORS.darkModeText : COLORS.primary
|
||||
}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.quoteLoadingText}
|
||||
content={t('wallet.stablecoinSend.gettingQuote')}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{!isQuoteLoading && quoteError && (
|
||||
<ThemeText
|
||||
styles={[
|
||||
styles.quoteErrorText,
|
||||
{
|
||||
color:
|
||||
theme && darkModeType
|
||||
? COLORS.darkModeText
|
||||
: COLORS.cancelRed,
|
||||
},
|
||||
]}
|
||||
content={quoteError}
|
||||
/>
|
||||
)}
|
||||
{!isQuoteLoading && quote && countdown !== null && (
|
||||
<>
|
||||
<View style={styles.quoteRow}>
|
||||
<ThemeText
|
||||
styles={styles.quoteLabel}
|
||||
content={t('wallet.stablecoinSend.recipientGets')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.quoteValue}
|
||||
content={`${APPROXIMATE_SYMBOL}${formatBalanceAmount(
|
||||
formatStablecoinAmount(quote.estimatedOut),
|
||||
false,
|
||||
masterInfoObject,
|
||||
)} ${asset}`}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.quoteRow}>
|
||||
<ThemeText
|
||||
styles={styles.quoteLabel}
|
||||
content={t('wallet.stablecoinSend.quoteExpiresIn')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={[
|
||||
styles.quoteValue,
|
||||
countdown < 30000 && {
|
||||
color:
|
||||
theme && darkModeType
|
||||
? COLORS.darkModeText
|
||||
: COLORS.cancelRed,
|
||||
},
|
||||
]}
|
||||
content={formatCountdown(countdown)}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* EDIT_AMOUNT: keyboard + Review button */}
|
||||
{!isConfirmMode && isAmountFocused && (
|
||||
<CustomNumberKeyboard
|
||||
setInputValue={setRawInput}
|
||||
showDot={inputDenomination === 'fiat'}
|
||||
fiatStats={conversionFiatStats}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isConfirmMode && isAmountFocused && (
|
||||
<CustomButton
|
||||
textContent={t('constants.review')}
|
||||
actionFunction={handleReview}
|
||||
buttonStyles={{ ...CENTER }}
|
||||
useLoading={isQuoteLoading}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* CONFIRM_PAYMENT: swipe button */}
|
||||
{isConfirmMode && (
|
||||
<View style={styles.buttonContainer}>
|
||||
{sending ? (
|
||||
<SliderProgressAnimation
|
||||
ref={progressAnimationRef}
|
||||
isVisible={true}
|
||||
textColor={COLORS.darkModeText}
|
||||
backgroundColor={
|
||||
theme && darkModeType ? backgroundOffset : COLORS.primary
|
||||
}
|
||||
width={0.95}
|
||||
/>
|
||||
) : (
|
||||
<SwipeButtonNew
|
||||
onSwipeSuccess={handleSend}
|
||||
width={0.85}
|
||||
resetAfterSuccessAnimDuration={true}
|
||||
shouldResetAfterSuccess={!sending}
|
||||
containerStyles={{
|
||||
opacity: canConfirm ? 1 : HIDDEN_OPACITY,
|
||||
}}
|
||||
thumbIconStyles={{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
borderColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
}}
|
||||
railStyles={{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
borderColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Emoji bar for description input */}
|
||||
{!isAmountFocused && !isConfirmMode && (
|
||||
<EmojiQuickBar description={description} onEmojiSelect={handleEmoji} />
|
||||
)}
|
||||
</CustomKeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
globalContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
replacementContainer: {
|
||||
flexGrow: 1,
|
||||
width: WINDOWWIDTH,
|
||||
...CENTER,
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 10,
|
||||
},
|
||||
satValue: {
|
||||
fontSize: SIZES.medium,
|
||||
marginTop: 4,
|
||||
},
|
||||
destinationBox: {
|
||||
width: '80%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 8,
|
||||
borderRadius: 8,
|
||||
...CENTER,
|
||||
marginTop: 30,
|
||||
},
|
||||
quoteBox: {
|
||||
marginTop: 12,
|
||||
marginHorizontal: 16,
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
gap: 10,
|
||||
},
|
||||
quoteLoadingRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
quoteLoadingText: {
|
||||
fontSize: SIZES.medium,
|
||||
opacity: 0.65,
|
||||
},
|
||||
quoteErrorText: {
|
||||
fontSize: SIZES.small,
|
||||
},
|
||||
quoteRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
quoteLabel: {
|
||||
fontSize: SIZES.medium,
|
||||
opacity: 0.65,
|
||||
},
|
||||
quoteValue: {
|
||||
fontSize: SIZES.medium,
|
||||
},
|
||||
buttonContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 10,
|
||||
},
|
||||
});
|
||||
@@ -10,52 +10,78 @@ import ThemeIcon from '../../functions/CustomElements/themeIcon';
|
||||
import { INSET_WINDOW_WIDTH } from '../../constants/theme';
|
||||
|
||||
export default function TechnicalTransactionDetails(props) {
|
||||
console.log('Transaction Detials Page');
|
||||
console.log('Transaction Details Page');
|
||||
const { showToast } = useToast();
|
||||
const navigate = useNavigation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { transaction } = props.route.params;
|
||||
|
||||
const { details, sparkID } = transaction;
|
||||
|
||||
const isPending = transaction.paymentStatus === 'pending';
|
||||
|
||||
const paymentDetails =
|
||||
transaction.paymentType === 'spark'
|
||||
? details.isLRC20Payment
|
||||
? [t('screens.inAccount.technicalTransactionDetails.txHash')]
|
||||
: [t('screens.inAccount.technicalTransactionDetails.paymentId')]
|
||||
: transaction.paymentType === 'lightning'
|
||||
? [
|
||||
t('screens.inAccount.technicalTransactionDetails.paymentId'),
|
||||
t('screens.inAccount.technicalTransactionDetails.preimage'),
|
||||
t('screens.inAccount.technicalTransactionDetails.address'),
|
||||
]
|
||||
: [
|
||||
t('screens.inAccount.technicalTransactionDetails.paymentId'),
|
||||
t('screens.inAccount.technicalTransactionDetails.bitcoinTxId'),
|
||||
t('screens.inAccount.technicalTransactionDetails.address'),
|
||||
t('screens.inAccount.technicalTransactionDetails.paymentExp'),
|
||||
].slice(0, details.direction === 'OUTGOING' && isPending ? 4 : 3);
|
||||
let paymentDetails = [];
|
||||
let infoValues = [];
|
||||
|
||||
if (transaction.paymentType === 'spark') {
|
||||
if (details.isFlashnetStablecoin) {
|
||||
paymentDetails = [
|
||||
t('screens.inAccount.technicalTransactionDetails.paymentId'),
|
||||
t('screens.inAccount.technicalTransactionDetails.quoteId'),
|
||||
t('screens.inAccount.technicalTransactionDetails.destinationAddress'),
|
||||
t('screens.inAccount.technicalTransactionDetails.destinationAsset'),
|
||||
t('screens.inAccount.technicalTransactionDetails.destinationChain'),
|
||||
];
|
||||
infoValues = [
|
||||
sparkID,
|
||||
details.quoteId,
|
||||
details.destinationAddress,
|
||||
details.destinationAsset,
|
||||
details.destinationChain,
|
||||
];
|
||||
} else if (details.isLRC20Payment) {
|
||||
paymentDetails = [
|
||||
t('screens.inAccount.technicalTransactionDetails.txHash'),
|
||||
];
|
||||
infoValues = [sparkID];
|
||||
} else {
|
||||
paymentDetails = [
|
||||
t('screens.inAccount.technicalTransactionDetails.paymentId'),
|
||||
];
|
||||
infoValues = [sparkID];
|
||||
}
|
||||
} else if (transaction.paymentType === 'lightning') {
|
||||
paymentDetails = [
|
||||
t('screens.inAccount.technicalTransactionDetails.paymentId'),
|
||||
t('screens.inAccount.technicalTransactionDetails.preimage'),
|
||||
t('screens.inAccount.technicalTransactionDetails.address'),
|
||||
];
|
||||
infoValues = [sparkID, details.preimage, details.address];
|
||||
} else {
|
||||
// bitcoin
|
||||
const bitcoinLabels = [
|
||||
t('screens.inAccount.technicalTransactionDetails.paymentId'),
|
||||
t('screens.inAccount.technicalTransactionDetails.bitcoinTxId'),
|
||||
t('screens.inAccount.technicalTransactionDetails.address'),
|
||||
t('screens.inAccount.technicalTransactionDetails.paymentExp'),
|
||||
];
|
||||
const bitcoinValues = [
|
||||
sparkID,
|
||||
details.onChainTxid,
|
||||
details.address,
|
||||
details.expiresAt || 'N/A',
|
||||
];
|
||||
const count = details.direction === 'OUTGOING' && isPending ? 4 : 3;
|
||||
paymentDetails = bitcoinLabels.slice(0, count);
|
||||
infoValues = bitcoinValues.slice(0, count);
|
||||
}
|
||||
|
||||
const infoElements = paymentDetails.map((item, id) => {
|
||||
const txItem =
|
||||
transaction.paymentType === 'spark'
|
||||
? sparkID
|
||||
: transaction.paymentType === 'lightning'
|
||||
? id === 0
|
||||
? sparkID
|
||||
: id === 1
|
||||
? details.preimage
|
||||
: details.address
|
||||
: id === 0
|
||||
? sparkID
|
||||
: id === 1
|
||||
? details.onChainTxid
|
||||
: id === 2
|
||||
? details.address
|
||||
: details.expiresAt || 'N/A';
|
||||
const txItem = infoValues[id];
|
||||
|
||||
const isBitcoinTxId =
|
||||
transaction.paymentType === 'bitcoin' &&
|
||||
item === t('screens.inAccount.technicalTransactionDetails.bitcoinTxId');
|
||||
|
||||
return (
|
||||
<View key={id}>
|
||||
@@ -79,11 +105,7 @@ export default function TechnicalTransactionDetails(props) {
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (
|
||||
transaction.paymentType === 'bitcoin' &&
|
||||
item ===
|
||||
t('screens.inAccount.technicalTransactionDetails.bitcoinTxId')
|
||||
) {
|
||||
if (isBitcoinTxId) {
|
||||
navigate.navigate('CustomWebView', {
|
||||
webViewURL: `https://mempool.space/tx/${txItem}`,
|
||||
});
|
||||
@@ -110,20 +132,6 @@ export default function TechnicalTransactionDetails(props) {
|
||||
style={styles.innerContainer}
|
||||
>
|
||||
{infoElements}
|
||||
{/* {transaction.paymentType === 'spark' && (
|
||||
<ThemeText
|
||||
content={t(
|
||||
'screens.inAccount.technicalTransactionDetails.privacyMessage',
|
||||
{
|
||||
type: t(
|
||||
`screens.inAccount.technicalTransactionDetails.${
|
||||
details?.direction === 'OUTGOING' ? 'receivers' : 'senders'
|
||||
}`,
|
||||
),
|
||||
},
|
||||
)}
|
||||
/>
|
||||
)} */}
|
||||
</ScrollView>
|
||||
</GlobalThemeView>
|
||||
);
|
||||
|
||||
@@ -697,6 +697,8 @@ const SparkWalletProvider = ({ children }) => {
|
||||
sparkInfoRef.current.identityPubKey,
|
||||
sendWebViewRequest,
|
||||
true,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
);
|
||||
const didUpdateStatus = updated.find(
|
||||
tx =>
|
||||
@@ -1216,6 +1218,9 @@ const SparkWalletProvider = ({ children }) => {
|
||||
currentMnemonicRef.current,
|
||||
sparkInfoRef.current.identityPubKey,
|
||||
sendWebViewRequest,
|
||||
false,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
);
|
||||
|
||||
if (updatePendingPaymentsIntervalRef.current) {
|
||||
@@ -1255,6 +1260,9 @@ const SparkWalletProvider = ({ children }) => {
|
||||
currentMnemonicRef.current,
|
||||
sparkInfoRef.current.identityPubKey,
|
||||
sendWebViewRequest,
|
||||
false,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
);
|
||||
|
||||
if (response.shouldCheck) {
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
"accounts": "Konten",
|
||||
"to": "An",
|
||||
"from": "Von",
|
||||
"asset": "Asset",
|
||||
"chain": "Kette",
|
||||
"description": "Beschreibung",
|
||||
"regenerate": "Erneut generieren",
|
||||
"restore": "Wiederherstellen",
|
||||
@@ -1161,6 +1163,33 @@
|
||||
"paymentsCounter": "{{number}} Zahlungen",
|
||||
"paymentsCounterTracker": "{{number}} von {{total}}",
|
||||
"loadingMessage": "Gespeicherte Transaktionen werden geladen..."
|
||||
},
|
||||
"stablecoinSend": {
|
||||
"sendStablecoins": "Stablecoins senden",
|
||||
"selectChain": "Netzwerk auswählen",
|
||||
"network": "Netzwerk",
|
||||
"selectAsset": "Asset auswählen",
|
||||
"recipient": "Empfänger",
|
||||
"gettingQuote": "Angebot wird abgerufen…",
|
||||
"quoteExpired": "Angebot abgelaufen. Bitte versuche es erneut.",
|
||||
"quoteError": "Angebot konnte nicht abgerufen werden. Bitte versuche es erneut.",
|
||||
"recipientGets": "Empfänger erhält",
|
||||
"youSend": "Du sendest",
|
||||
"quoteExpiresIn": "Angebot läuft ab in",
|
||||
"payWithLightning": "Mit Lightning bezahlen",
|
||||
"payWithUSDb": "Mit USDb bezahlen",
|
||||
"chainNotSupportedForAsset": "Nicht verfügbar in diesem Netzwerk",
|
||||
"swapInProgress": "Tausch läuft",
|
||||
"swapInProgressDescription": "Deine Mittel wurden gesendet. Der Stablecoin-Tausch wird verarbeitet und kann einige Minuten dauern.",
|
||||
"noAmount": "Bitte gib einen Betrag zum Senden ein.",
|
||||
"quoteStillLoading": "Angebot wird abgerufen, bitte einen Moment warten.",
|
||||
"noQuote": "Angebot konnte nicht abgerufen werden. Bitte versuche es erneut.",
|
||||
"swapStatus": "Tauschstatus",
|
||||
"chainSwap": "Chain-Tausch",
|
||||
"assetPicker": {
|
||||
"header": "Wähle die Währung aus, die du senden möchtest.",
|
||||
"notAvailable": "Nicht verfügbar auf {{chain}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts": {
|
||||
@@ -1843,7 +1872,8 @@
|
||||
"contactPaymentType": "Kontakt",
|
||||
"converstionRate": "{{satAmount}} pro {{USDAmount}}",
|
||||
"gift": "Geschenk",
|
||||
"claimPayment": "Zahlung einfordern"
|
||||
"claimPayment": "Zahlung einfordern",
|
||||
"chainSwap": "Chain-Tausch"
|
||||
},
|
||||
"explorePage": {
|
||||
"timeLeft": "({{time}} verbleibend)",
|
||||
@@ -1944,7 +1974,11 @@
|
||||
"noInformationLabel": "{{item}} wird nicht angezeigt, da diese Zahlung aus dem Verlauf wiederhergestellt wurde oder eine Rechnung mit Nullbetrag verwendet wurde.",
|
||||
"privacyMessage": "Um die Privatsphäre des {{type}} zu wahren, sind alle weiteren Informationen ausgeblendet.",
|
||||
"receivers": "Empfänger",
|
||||
"senders": "Absender"
|
||||
"senders": "Absender",
|
||||
"quoteId": "Angebots-ID",
|
||||
"destinationAddress": "Zieladresse",
|
||||
"destinationAsset": "Ziel-Asset",
|
||||
"destinationChain": "Ziel-Kette"
|
||||
},
|
||||
"viewAllTxPage": {
|
||||
"title": "Transaktionen",
|
||||
|
||||
@@ -81,6 +81,8 @@
|
||||
"accounts": "Accounts",
|
||||
"to": "To",
|
||||
"from": "From",
|
||||
"asset": "Asset",
|
||||
"chain": "Chain",
|
||||
"description": "Description",
|
||||
"regenerate": "Regenerate",
|
||||
"restore": "Restore",
|
||||
@@ -1160,6 +1162,33 @@
|
||||
"paymentsCounter": "{{number}} payments",
|
||||
"paymentsCounterTracker": "{{number}} of {{total}}",
|
||||
"loadingMessage": "Loading saved transactions"
|
||||
},
|
||||
"stablecoinSend": {
|
||||
"sendStablecoins": "Send Stablecoins",
|
||||
"selectChain": "Select Network",
|
||||
"network": "Network",
|
||||
"selectAsset": "Select Asset",
|
||||
"recipient": "Recipient",
|
||||
"gettingQuote": "Getting quote…",
|
||||
"quoteExpired": "Quote expired. Please try again.",
|
||||
"quoteError": "Failed to get quote. Please try again.",
|
||||
"recipientGets": "Recipient gets",
|
||||
"youSend": "You send",
|
||||
"quoteExpiresIn": "Quote expires in",
|
||||
"payWithLightning": "Pay with Lightning",
|
||||
"payWithUSDb": "Pay with USDb",
|
||||
"chainNotSupportedForAsset": "Not available on this network",
|
||||
"swapInProgress": "Swap in Progress",
|
||||
"swapInProgressDescription": "Your funds have been sent. The stablecoin swap is processing and may take a few minutes.",
|
||||
"noAmount": "Please enter an amount to send.",
|
||||
"quoteStillLoading": "Getting a quote, please wait a moment.",
|
||||
"noQuote": "Could not get a quote. Please try again.",
|
||||
"swapStatus": "Swap Status",
|
||||
"chainSwap": "Chain Swap",
|
||||
"assetPicker": {
|
||||
"header": "Select the currency you want to send.",
|
||||
"notAvailable": "Not available on {{chain}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts": {
|
||||
@@ -1842,7 +1871,8 @@
|
||||
"contactPaymentType": "Contact",
|
||||
"converstionRate": "{{satAmount}} per {{dollarAmount}}",
|
||||
"gift": "Gift",
|
||||
"claimPayment": "Claim payment"
|
||||
"claimPayment": "Claim payment",
|
||||
"chainSwap": "Chain Swap"
|
||||
},
|
||||
"explorePage": {
|
||||
"timeLeft": "({{time}} left)",
|
||||
@@ -1944,7 +1974,11 @@
|
||||
"noInformationLabel": "{{item}} is not shown since this payment was restored from history or used a zero amount invoice.",
|
||||
"privacyMessage": "To preserve the {{type}} privacy, all other information is hidden.",
|
||||
"receivers": "receiver's",
|
||||
"senders": "sender's"
|
||||
"senders": "sender's",
|
||||
"quoteId": "Quote ID",
|
||||
"destinationAddress": "Destination Address",
|
||||
"destinationAsset": "Destination Asset",
|
||||
"destinationChain": "Destination Chain"
|
||||
},
|
||||
"viewAllTxPage": {
|
||||
"title": "Transactions",
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
"accounts": "Cuentas",
|
||||
"to": "A",
|
||||
"from": "De",
|
||||
"asset": "Activo",
|
||||
"chain": "Cadena",
|
||||
"description": "Descripción",
|
||||
"regenerate": "Regenerar",
|
||||
"restore": "Restaurar",
|
||||
@@ -1020,6 +1022,33 @@
|
||||
"paymentsCounter": "{{number}} pagos",
|
||||
"paymentsCounterTracker": "{{number}} de {{total}}",
|
||||
"loadingMessage": "Cargando transacciones guardadas"
|
||||
},
|
||||
"stablecoinSend": {
|
||||
"sendStablecoins": "Enviar Stablecoins",
|
||||
"selectChain": "Seleccionar Red",
|
||||
"network": "Red",
|
||||
"selectAsset": "Seleccionar Activo",
|
||||
"recipient": "Destinatario",
|
||||
"gettingQuote": "Obteniendo cotización…",
|
||||
"quoteExpired": "Cotización vencida. Por favor, inténtalo de nuevo.",
|
||||
"quoteError": "No se pudo obtener la cotización. Por favor, inténtalo de nuevo.",
|
||||
"recipientGets": "El destinatario recibe",
|
||||
"youSend": "Tú envías",
|
||||
"quoteExpiresIn": "La cotización vence en",
|
||||
"payWithLightning": "Pagar con Lightning",
|
||||
"payWithUSDb": "Pagar con USDb",
|
||||
"chainNotSupportedForAsset": "No disponible en esta red",
|
||||
"swapInProgress": "Intercambio en Proceso",
|
||||
"swapInProgressDescription": "Tus fondos han sido enviados. El intercambio de stablecoin está procesándose y puede tardar unos minutos.",
|
||||
"noAmount": "Por favor, ingresa un monto para enviar.",
|
||||
"quoteStillLoading": "Obteniendo cotización, por favor espera un momento.",
|
||||
"noQuote": "No se pudo obtener una cotización. Por favor, inténtalo de nuevo.",
|
||||
"swapStatus": "Estado del Intercambio",
|
||||
"chainSwap": "Intercambio en Cadena",
|
||||
"assetPicker": {
|
||||
"header": "Selecciona la moneda que deseas enviar.",
|
||||
"notAvailable": "No disponible en {{chain}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts": {
|
||||
@@ -1703,7 +1732,8 @@
|
||||
"contactPaymentType": "Contacto",
|
||||
"converstionRate": "{{satAmount}} por {{dollarAmount}}",
|
||||
"gift": "Regalo",
|
||||
"claimPayment": "Reclamar pago"
|
||||
"claimPayment": "Reclamar pago",
|
||||
"chainSwap": "Intercambio en Cadena"
|
||||
},
|
||||
"explorePage": {
|
||||
"timeLeft": "({{time}} restantes)",
|
||||
@@ -1804,7 +1834,11 @@
|
||||
"noInformationLabel": "{{item}} no se muestra ya que este pago fue restaurado desde el historial o usó una factura de monto cero.",
|
||||
"privacyMessage": "Para preservar la privacidad {{type}}, toda la demás información se oculta.",
|
||||
"receivers": "del receptor",
|
||||
"senders": "del remitente"
|
||||
"senders": "del remitente",
|
||||
"quoteId": "ID de Cotización",
|
||||
"destinationAddress": "Dirección de Destino",
|
||||
"destinationAsset": "Activo de Destino",
|
||||
"destinationChain": "Cadena de Destino"
|
||||
},
|
||||
"viewAllTxPage": {
|
||||
"title": "Transacciones",
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
"accounts": "Comptes",
|
||||
"to": "Pour",
|
||||
"from": "De",
|
||||
"asset": "Actif",
|
||||
"chain": "Chaîne",
|
||||
"description": "Description",
|
||||
"regenerate": "Régénérer",
|
||||
"restore": "Restaurer",
|
||||
@@ -1159,6 +1161,33 @@
|
||||
"paymentsCounter": "{{number}} paiements",
|
||||
"paymentsCounterTracker": "{{number}} de {{total}}",
|
||||
"loadingMessage": "Chargement des transactions enregistrées"
|
||||
},
|
||||
"stablecoinSend": {
|
||||
"sendStablecoins": "Envoyer des Stablecoins",
|
||||
"selectChain": "Sélectionner le Réseau",
|
||||
"network": "Réseau",
|
||||
"selectAsset": "Sélectionner l'Actif",
|
||||
"recipient": "Destinataire",
|
||||
"gettingQuote": "Obtention du devis…",
|
||||
"quoteExpired": "Devis expiré. Veuillez réessayer.",
|
||||
"quoteError": "Impossible d'obtenir un devis. Veuillez réessayer.",
|
||||
"recipientGets": "Le destinataire reçoit",
|
||||
"youSend": "Vous envoyez",
|
||||
"quoteExpiresIn": "Le devis expire dans",
|
||||
"payWithLightning": "Payer avec Lightning",
|
||||
"payWithUSDb": "Payer avec USDb",
|
||||
"chainNotSupportedForAsset": "Non disponible sur ce réseau",
|
||||
"swapInProgress": "Échange en Cours",
|
||||
"swapInProgressDescription": "Vos fonds ont été envoyés. L'échange de stablecoin est en cours de traitement et peut prendre quelques minutes.",
|
||||
"noAmount": "Veuillez saisir un montant à envoyer.",
|
||||
"quoteStillLoading": "Obtention d'un devis, veuillez patienter un instant.",
|
||||
"noQuote": "Impossible d'obtenir un devis. Veuillez réessayer.",
|
||||
"swapStatus": "Statut de l'Échange",
|
||||
"chainSwap": "Échange de Chaîne",
|
||||
"assetPicker": {
|
||||
"header": "Sélectionnez la devise que vous souhaitez envoyer.",
|
||||
"notAvailable": "Non disponible sur {{chain}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts": {
|
||||
@@ -1841,7 +1870,8 @@
|
||||
"contactPaymentType": "Contact",
|
||||
"converstionRate": "{{satAmount}} par {{dollarAmount}}",
|
||||
"gift": "Cadeau",
|
||||
"claimPayment": "Réclamer le paiement"
|
||||
"claimPayment": "Réclamer le paiement",
|
||||
"chainSwap": "Échange de Chaîne"
|
||||
},
|
||||
"explorePage": {
|
||||
"timeLeft": "({{time}} restantes)",
|
||||
@@ -1942,7 +1972,11 @@
|
||||
"noInformationLabel": "{{item}} n'apparaît pas car ce paiement a été restauré à partir de l'historique ou a utilisé une facture d'un montant nul.",
|
||||
"privacyMessage": "Pour préserver la vie privée de {{type}}, toutes les autres informations sont cachées.",
|
||||
"receivers": "du destinataire",
|
||||
"senders": "de l'expéditeur"
|
||||
"senders": "de l'expéditeur",
|
||||
"quoteId": "ID du Devis",
|
||||
"destinationAddress": "Adresse de Destination",
|
||||
"destinationAsset": "Actif de Destination",
|
||||
"destinationChain": "Chaîne de Destination"
|
||||
},
|
||||
"viewAllTxPage": {
|
||||
"title": "Transactions",
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
"accounts": "Accounts",
|
||||
"to": "A",
|
||||
"from": "Da",
|
||||
"asset": "Asset",
|
||||
"chain": "Catena",
|
||||
"description": "Descrizione",
|
||||
"regenerate": "Rigenera",
|
||||
"restore": "Ripristina",
|
||||
@@ -1159,6 +1161,33 @@
|
||||
"paymentsCounter": "{{number}} pagamenti",
|
||||
"paymentsCounterTracker": "{{number}} di {{total}}",
|
||||
"loadingMessage": "Caricamento delle transazioni salvate"
|
||||
},
|
||||
"stablecoinSend": {
|
||||
"sendStablecoins": "Invia Stablecoin",
|
||||
"selectChain": "Seleziona Rete",
|
||||
"network": "Rete",
|
||||
"selectAsset": "Seleziona Asset",
|
||||
"recipient": "Destinatario",
|
||||
"gettingQuote": "Ottenendo quotazione…",
|
||||
"quoteExpired": "Quotazione scaduta. Riprova.",
|
||||
"quoteError": "Impossibile ottenere la quotazione. Riprova.",
|
||||
"recipientGets": "Il destinatario riceve",
|
||||
"youSend": "Tu invii",
|
||||
"quoteExpiresIn": "La quotazione scade tra",
|
||||
"payWithLightning": "Paga con Lightning",
|
||||
"payWithUSDb": "Paga con USDb",
|
||||
"chainNotSupportedForAsset": "Non disponibile su questa rete",
|
||||
"swapInProgress": "Scambio in Corso",
|
||||
"swapInProgressDescription": "I tuoi fondi sono stati inviati. Lo scambio di stablecoin è in elaborazione e potrebbe richiedere alcuni minuti.",
|
||||
"noAmount": "Inserisci un importo da inviare.",
|
||||
"quoteStillLoading": "Ottenendo una quotazione, attendi un momento.",
|
||||
"noQuote": "Impossibile ottenere una quotazione. Riprova.",
|
||||
"swapStatus": "Stato Scambio",
|
||||
"chainSwap": "Chain Swap",
|
||||
"assetPicker": {
|
||||
"header": "Seleziona la valuta che vuoi inviare.",
|
||||
"notAvailable": "Non disponibile su {{chain}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts": {
|
||||
@@ -1841,7 +1870,8 @@
|
||||
"converstionRate": "{{satAmount}} per {{dollarAmount}}",
|
||||
"confirmMessage_sent": "Importo inviato",
|
||||
"gift": "Regalo",
|
||||
"claimPayment": "Richiedi pagamento"
|
||||
"claimPayment": "Richiedi pagamento",
|
||||
"chainSwap": "Chain Swap"
|
||||
},
|
||||
"explorePage": {
|
||||
"timeLeft": "({{time}} rimanenti)",
|
||||
@@ -1942,7 +1972,11 @@
|
||||
"noInformationLabel": "{{item}} non è mostrato poiché questo pagamento è stato ripristinato dalla cronologia o ha utilizzato una fattura con importo zero.",
|
||||
"privacyMessage": "Per preservare la privacy {{type}}, tutte le altre informazioni sono nascoste.",
|
||||
"receivers": "del destinatario",
|
||||
"senders": "del mittente"
|
||||
"senders": "del mittente",
|
||||
"quoteId": "ID Quotazione",
|
||||
"destinationAddress": "Indirizzo di Destinazione",
|
||||
"destinationAsset": "Asset di Destinazione",
|
||||
"destinationChain": "Catena di Destinazione"
|
||||
},
|
||||
"viewAllTxPage": {
|
||||
"title": "Transazioni",
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
"accounts": "Contas",
|
||||
"to": "Para",
|
||||
"from": "De",
|
||||
"asset": "Ativo",
|
||||
"chain": "Rede",
|
||||
"description": "Descrição",
|
||||
"regenerate": "Gerar",
|
||||
"restore": "Restaurar",
|
||||
@@ -1159,6 +1161,33 @@
|
||||
"paymentsCounter": "{{number}} pagamentos",
|
||||
"paymentsCounterTracker": "{{number}} do {{total}}",
|
||||
"loadingMessage": "Carregando transações salvas"
|
||||
},
|
||||
"stablecoinSend": {
|
||||
"sendStablecoins": "Enviar dólar digital",
|
||||
"selectChain": "Selecionar rede",
|
||||
"network": "Rede",
|
||||
"selectAsset": "Selecionar ativo",
|
||||
"recipient": "Destinatário",
|
||||
"gettingQuote": "Obtendo cotação…",
|
||||
"quoteExpired": "Cotação expirada. Por favor, tente novamente.",
|
||||
"quoteError": "Falha ao obter cotação. Por favor, tente novamente.",
|
||||
"recipientGets": "O destinatário recebe",
|
||||
"youSend": "Você envia",
|
||||
"quoteExpiresIn": "Cotação expira em",
|
||||
"payWithLightning": "Pagar com Bitcoin",
|
||||
"payWithUSDb": "Pagar com USDB",
|
||||
"chainNotSupportedForAsset": "Não disponível nesta rede",
|
||||
"swapInProgress": "Troca em Andamento",
|
||||
"swapInProgressDescription": "Seus fundos foram enviados. A troca de dólar digital está sendo processada e pode levar alguns minutos.",
|
||||
"noAmount": "Por favor, insira um valor para enviar.",
|
||||
"quoteStillLoading": "Obtendo cotação, aguarde um momento.",
|
||||
"noQuote": "Não foi possível obter uma cotação. Por favor, tente novamente.",
|
||||
"swapStatus": "Status da troca",
|
||||
"chainSwap": "Troca entre redes",
|
||||
"assetPicker": {
|
||||
"header": "Selecione a moeda que deseja enviar.",
|
||||
"notAvailable": "Não disponível em {{chain}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts": {
|
||||
@@ -1841,7 +1870,8 @@
|
||||
"contactPaymentType": "Contato",
|
||||
"converstionRate": "{{satAmount}} por {{dollarAmount}}",
|
||||
"gift": "Vale-Blitz",
|
||||
"claimPayment": "Reivindicar pagamento"
|
||||
"claimPayment": "Reivindicar pagamento",
|
||||
"chainSwap": "Troca em Cadeia"
|
||||
},
|
||||
"explorePage": {
|
||||
"timeLeft": "({{time}} restantes)",
|
||||
@@ -1942,7 +1972,11 @@
|
||||
"noInformationLabel": "{{item}} não é mostrado porque este pagamento foi restaurado do histórico ou usou uma fatura de valor zero.",
|
||||
"privacyMessage": "Para preservar a privacidade {{type}} ,todas as outras informações ficam ocultas.",
|
||||
"receivers": "do recebedor",
|
||||
"senders": "do pagador"
|
||||
"senders": "do pagador",
|
||||
"quoteId": "ID da Cotação",
|
||||
"destinationAddress": "Endereço de Destino",
|
||||
"destinationAsset": "Ativo de Destino",
|
||||
"destinationChain": "Rede de Destino"
|
||||
},
|
||||
"viewAllTxPage": {
|
||||
"title": "Transações",
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
"accounts": "Счета",
|
||||
"to": "Куда",
|
||||
"from": "Откуда",
|
||||
"asset": "Актив",
|
||||
"chain": "Сеть",
|
||||
"description": "Описание",
|
||||
"regenerate": "Сгенерировать снова",
|
||||
"restore": "Восстановить",
|
||||
@@ -1160,6 +1162,33 @@
|
||||
"paymentsCounter": "{{number}} платежей",
|
||||
"paymentsCounterTracker": "{{number}} из {{total}}",
|
||||
"loadingMessage": "Загрузка транзакций"
|
||||
},
|
||||
"stablecoinSend": {
|
||||
"sendStablecoins": "Отправить стейблкоины",
|
||||
"selectChain": "Выбрать сеть",
|
||||
"network": "Сеть",
|
||||
"selectAsset": "Выбрать актив",
|
||||
"recipient": "Получатель",
|
||||
"gettingQuote": "Получение котировки…",
|
||||
"quoteExpired": "Срок котировки истёк. Попробуйте снова.",
|
||||
"quoteError": "Не удалось получить котировку. Попробуйте снова.",
|
||||
"recipientGets": "Получатель получает",
|
||||
"youSend": "Вы отправляете",
|
||||
"quoteExpiresIn": "Котировка истекает через",
|
||||
"payWithLightning": "Оплатить через Lightning",
|
||||
"payWithUSDb": "Оплатить через USDb",
|
||||
"chainNotSupportedForAsset": "Недоступно в этой сети",
|
||||
"swapInProgress": "Обмен выполняется",
|
||||
"swapInProgressDescription": "Ваши средства отправлены. Обмен стейблкоина обрабатывается и может занять несколько минут.",
|
||||
"noAmount": "Пожалуйста, введите сумму для отправки.",
|
||||
"quoteStillLoading": "Получение котировки, подождите немного.",
|
||||
"noQuote": "Не удалось получить котировку. Попробуйте снова.",
|
||||
"swapStatus": "Статус обмена",
|
||||
"chainSwap": "Обмен в сети",
|
||||
"assetPicker": {
|
||||
"header": "Выберите валюту для отправки.",
|
||||
"notAvailable": "Недоступно в сети {{chain}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts": {
|
||||
@@ -1842,7 +1871,8 @@
|
||||
"contactPaymentType": "Контакт",
|
||||
"converstionRate": "{{satAmount}} за {{dollarAmount}}",
|
||||
"gift": "Подарок",
|
||||
"claimPayment": "Получить платёж"
|
||||
"claimPayment": "Получить платёж",
|
||||
"chainSwap": "Обмен в сети"
|
||||
},
|
||||
"explorePage": {
|
||||
"timeLeft": "(осталось {{time}})",
|
||||
@@ -1943,7 +1973,11 @@
|
||||
"noInformationLabel": "{{item}} не показан (платеж восстановлен из истории или нулевой инвойс).",
|
||||
"privacyMessage": "Для сохранения приватности {{type}}, остальная информация скрыта.",
|
||||
"receivers": "получателя",
|
||||
"senders": "отправителя"
|
||||
"senders": "отправителя",
|
||||
"quoteId": "ID котировки",
|
||||
"destinationAddress": "Адрес назначения",
|
||||
"destinationAsset": "Актив назначения",
|
||||
"destinationChain": "Сеть назначения"
|
||||
},
|
||||
"viewAllTxPage": {
|
||||
"title": "Транзакции",
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
"accounts": "Konton",
|
||||
"to": "Till",
|
||||
"from": "Från",
|
||||
"asset": "Tillgång",
|
||||
"chain": "Kedja",
|
||||
"description": "Beskrivning",
|
||||
"regenerate": "Regenerera",
|
||||
"restore": "Återställ",
|
||||
@@ -1159,6 +1161,33 @@
|
||||
"paymentsCounter": "{{number}} Betalningar",
|
||||
"paymentsCounterTracker": "{{number}} av {{total}}",
|
||||
"loadingMessage": "Läser in sparade transaktioner"
|
||||
},
|
||||
"stablecoinSend": {
|
||||
"sendStablecoins": "Skicka Stablecoins",
|
||||
"selectChain": "Välj Nätverk",
|
||||
"network": "Nätverk",
|
||||
"selectAsset": "Välj Tillgång",
|
||||
"recipient": "Mottagare",
|
||||
"gettingQuote": "Hämtar offert…",
|
||||
"quoteExpired": "Offerten har gått ut. Försök igen.",
|
||||
"quoteError": "Det gick inte att hämta en offert. Försök igen.",
|
||||
"recipientGets": "Mottagaren får",
|
||||
"youSend": "Du skickar",
|
||||
"quoteExpiresIn": "Offerten löper ut om",
|
||||
"payWithLightning": "Betala med Lightning",
|
||||
"payWithUSDb": "Betala med USDb",
|
||||
"chainNotSupportedForAsset": "Inte tillgängligt i detta nätverk",
|
||||
"swapInProgress": "Byte pågår",
|
||||
"swapInProgressDescription": "Dina pengar har skickats. Stablecoin-bytet bearbetas och kan ta några minuter.",
|
||||
"noAmount": "Ange ett belopp att skicka.",
|
||||
"quoteStillLoading": "Hämtar offert, vänta ett ögonblick.",
|
||||
"noQuote": "Det gick inte att hämta en offert. Försök igen.",
|
||||
"swapStatus": "Bytestatus",
|
||||
"chainSwap": "Kedjeswap",
|
||||
"assetPicker": {
|
||||
"header": "Välj den valuta du vill skicka.",
|
||||
"notAvailable": "Inte tillgängligt på {{chain}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts": {
|
||||
@@ -1841,7 +1870,8 @@
|
||||
"contactPaymentType": "Kontakt",
|
||||
"converstionRate": "{{satAmount}} per {{dollarAmount}}",
|
||||
"gift": "Gåva",
|
||||
"claimPayment": "Ta emot betalning"
|
||||
"claimPayment": "Ta emot betalning",
|
||||
"chainSwap": "Kedjeswap"
|
||||
},
|
||||
"explorePage": {
|
||||
"timeLeft": "({{time}} kvar)",
|
||||
@@ -1942,7 +1972,11 @@
|
||||
"noInformationLabel": "{{item}} visas inte eftersom denna betalning återställdes från historiken eller använde en faktura med nollbelopp.",
|
||||
"privacyMessage": "För att bevara {{type}}:s integritet är all annan information dold.",
|
||||
"receivers": "mottagarens",
|
||||
"senders": "avsändarens"
|
||||
"senders": "avsändarens",
|
||||
"quoteId": "Offert-ID",
|
||||
"destinationAddress": "Destinationsadress",
|
||||
"destinationAsset": "Destinations-tillgång",
|
||||
"destinationChain": "Destinations-kedja"
|
||||
},
|
||||
"viewAllTxPage": {
|
||||
"title": "Transaktioner",
|
||||
|
||||
@@ -101,6 +101,8 @@ import {
|
||||
import ConversionHistory from '../app/components/admin/homeComponents/swaps/swapHistory';
|
||||
import GiftsPageHome from '../app/screens/inAccount/giftsHome';
|
||||
import ReclaimGift from '../app/components/admin/homeComponents/gifts/reclaimGift';
|
||||
import SelectStablecoinParamsScreen from '../app/screens/inAccount/selectStablecoinParamsScreen';
|
||||
import StablecoinSendScreen from '../app/screens/inAccount/stablecoinSendScreen';
|
||||
|
||||
const SLIDE_FROM_BOTTOM_SCREENS = [
|
||||
{ name: 'CustomWebView', component: CustomWebView },
|
||||
@@ -202,6 +204,15 @@ const SLIDE_FROM_RIGHT_SCREENS = [
|
||||
{ name: 'SavingsGoalDetails', component: SavingsGoalDetails },
|
||||
{ name: 'GiftsPageHome', component: GiftsPageHome },
|
||||
{ name: 'ReclaimGift', component: ReclaimGift },
|
||||
{
|
||||
name: 'SelectStablecoinParamsScreen',
|
||||
component: SelectStablecoinParamsScreen,
|
||||
},
|
||||
{
|
||||
name: 'StablecoinSendScreen',
|
||||
component: StablecoinSendScreen,
|
||||
options: { gestureEnabled: false },
|
||||
},
|
||||
];
|
||||
|
||||
const FADE_SCREENS = [
|
||||
|
||||