Compare commits

..

7 Commits

Author SHA1 Message Date
Ivan Vershigora
79262eaf0b
fix: translate 2026-05-13 11:28:30 +01:00
Ivan Vershigora
a2e1024639
fix: vocabularies 2026-05-12 17:28:22 +01:00
Ivan Vershigora
c099ed9394
fix: ua 2026-05-12 15:59:49 +01:00
Ivan Vershigora
5fcba7dc32
fix: ua 2026-05-12 15:58:15 +01:00
Ivan Vershigora
01aaec05c0
fix: reference 2026-05-12 14:50:46 +01:00
Ivan Vershigora
63f0c0f7bf
feat: i18n vocabulary 2026-05-12 14:23:09 +01:00
Ivan Vershigora
75ee120ee9
feat: i18n vocabulary 2026-05-12 13:42:10 +01:00
282 changed files with 6647 additions and 16210 deletions

View File

@ -30,7 +30,7 @@ jobs:
steps:
- name: Checkout Project
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # Ensures the full Git history is
@ -210,7 +210,7 @@ jobs:
- name: Set Up Ruby
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
with:
ruby-version: 3.4.9
ruby-version: 3.4.8
- name: System Debug Information
run: |
@ -490,12 +490,12 @@ jobs:
BRANCH_NAME: ${{ needs.build.outputs.branch_name }}
steps:
- name: Checkout Project
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set Up Ruby
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
with:
ruby-version: 3.4.9
ruby-version: 3.4.8
- name: Install Dependencies with Bundler
run: |

View File

@ -49,7 +49,7 @@ jobs:
- name: Checkout project
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
@ -70,7 +70,7 @@ jobs:
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
with:
ruby-version: 3.4.9
ruby-version: 3.4.8
bundler-cache: true
- name: Install Node modules

View File

@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout project
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: "0"
@ -72,7 +72,7 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
with:
ruby-version: 3.4.9
ruby-version: 3.4.8
bundler-cache: true
- name: Generate Build Number based on timestamp
@ -135,12 +135,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Ruby
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
with:
ruby-version: 3.4.9
ruby-version: 3.4.8
bundler-cache: true
- name: Install dependencies with Bundler

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout project
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout project
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
@ -53,7 +53,6 @@ jobs:
BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}}
HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }}
HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }}
HD_MNEMONIC_OLD: ${{ secrets.HD_MNEMONIC_OLD }}
HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }}
HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }}
HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }}
@ -65,7 +64,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout project
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
@ -84,7 +83,6 @@ jobs:
BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}}
HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }}
HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }}
HD_MNEMONIC_OLD: ${{ secrets.HD_MNEMONIC_OLD }}
HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }}
HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }}
HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }}

View File

@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Free disk space (Ubuntu)
run: |
@ -86,7 +86,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Free disk space (Ubuntu)
run: |

View File

@ -19,7 +19,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@ -30,7 +30,7 @@ jobs:
- name: Setup Ruby
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
with:
ruby-version: "3.4.9"
ruby-version: "3.4.8"
bundler-cache: true
- name: Install Node dependencies
@ -168,7 +168,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@ -194,6 +194,9 @@ jobs:
mkdir -p ios/build/Build/Products/Release-iphonesimulator
tar -xzf BlueWallet.app.tar.gz -C ios/build/Build/Products/Release-iphonesimulator
- name: Disable simulator animations
run: defaults write com.apple.iphonesimulator SlowMotionAnimation -bool NO
# Pre-boot simulator so first detox launchApp lands warm.
- name: Pre-boot iOS simulator
run: |
@ -207,13 +210,6 @@ jobs:
xcrun simctl bootstatus "$UDID" -b
xcrun simctl launch "$UDID" com.apple.springboard >/dev/null 2>&1 || true
# Cut animations so detox sync stays steady on slow CI VMs; Reduce Motion makes reanimated skip to final value.
- name: Disable simulator animations
run: |
defaults write com.apple.iphonesimulator SlowMotionAnimation -bool NO
xcrun simctl spawn booted defaults write com.apple.Accessibility ReduceMotionEnabled -bool true
xcrun simctl spawn booted notifyutil -p com.apple.Accessibility.ReduceMotionStatusDidChange
- name: Run detox tests
timeout-minutes: 360
run: |

View File

@ -1 +1 @@
3.4.9
3.4.8

199
BlueComponents.tsx Normal file
View File

@ -0,0 +1,199 @@
import { useLocale } from '@react-navigation/native';
import React, { forwardRef } from 'react';
import {
Dimensions,
Platform,
Pressable,
PressableProps,
StyleProp,
StyleSheet,
Text,
TextInput,
TextInputProps,
TextProps,
View,
ViewProps,
ViewStyle,
} from 'react-native';
import Icon from './components/Icon';
import { useTheme } from './components/themes';
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
const isIpad = aspectRatio <= 1.6;
interface BlueButtonLinkProps extends PressableProps {
title: string;
}
export const BlueButtonLink = forwardRef<React.ElementRef<typeof Pressable>, BlueButtonLinkProps>((props, ref) => {
const { colors } = useTheme();
return (
<Pressable accessibilityRole="button" style={({ pressed }) => [styles.blueButtonLink, pressed && styles.pressed]} {...props} ref={ref}>
<Text style={[styles.blueButtonLinkText, { color: colors.foregroundColor }]}>{props.title}</Text>
</Pressable>
);
});
export const BlueCard: React.FC<ViewProps> = props => {
return <View {...props} style={styles.blueCard} />;
};
interface BlueTextProps extends TextProps {
bold?: boolean;
h1?: boolean;
h2?: boolean;
h3?: boolean;
h4?: boolean;
}
export const BlueText: React.FC<BlueTextProps> = ({ bold = false, h1, h2, h3, h4, style: passedStyle, ...props }) => {
const { colors } = useTheme();
const { direction } = useLocale();
let headingStyle = {};
if (h1) {
headingStyle = styles.h1;
} else if (h2) {
headingStyle = styles.h2;
} else if (h3) {
headingStyle = styles.h3;
} else if (h4) {
headingStyle = styles.h4;
}
const hasHeading = h1 || h2 || h3 || h4;
const style = StyleSheet.compose(
{
color: colors.foregroundColor,
writingDirection: direction,
fontWeight: hasHeading ? undefined : bold ? 'bold' : 'normal',
...headingStyle,
},
passedStyle,
);
return <Text style={style} {...props} />;
};
export const BlueTextCentered: React.FC<TextProps> = props => {
const { colors } = useTheme();
return <Text {...props} style={[styles.blueTextCentered, { color: colors.foregroundColor }]} />;
};
export const BlueFormLabel: React.FC<TextProps> = props => {
const { colors } = useTheme();
const { direction } = useLocale();
return <Text {...props} style={[styles.blueFormLabel, { color: colors.foregroundColor, writingDirection: direction }]} />;
};
export const BlueFormMultiInput: React.FC<TextInputProps> = props => {
const { colors } = useTheme();
const { style, editable, ...restProps } = props;
return (
<TextInput
multiline
underlineColorAndroid="transparent"
numberOfLines={4}
editable={editable}
style={[
styles.blueFormMultiInput,
{
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
color: colors.foregroundColor,
},
style,
]}
autoCorrect={false}
autoCapitalize="none"
spellCheck={false}
{...restProps}
selectTextOnFocus={false}
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
/>
);
};
export class is {
static ipad() {
return isIpad;
}
}
interface BlueBigCheckmarkProps {
style?: StyleProp<ViewStyle>;
}
export function BlueBigCheckmark({ style }: BlueBigCheckmarkProps) {
const mergedStyles = [styles.checkmarkContainer, style];
return (
<View style={mergedStyles}>
<Icon name="check" size={50} type="font-awesome" color="#0f5cc0" />
</View>
);
}
const styles = StyleSheet.create({
blueButtonLink: {
minWidth: 100,
minHeight: 36,
justifyContent: 'center',
},
blueButtonLinkText: {
textAlign: 'center',
fontSize: 16,
},
blueCard: {
padding: 20,
},
h1: {
fontSize: 40,
fontWeight: 'bold',
},
h2: {
fontSize: 34,
fontWeight: 'bold',
},
h3: {
fontSize: 28,
fontWeight: 'bold',
},
h4: {
fontSize: 22,
fontWeight: 'bold',
},
blueTextCentered: {
textAlign: 'center',
},
blueFormLabel: {
fontWeight: '400',
marginHorizontal: 20,
},
blueFormMultiInput: {
paddingHorizontal: 8,
paddingVertical: 16,
flex: 1,
marginTop: 5,
marginHorizontal: 20,
borderWidth: 1,
borderBottomWidth: 0.5,
borderRadius: 4,
textAlignVertical: 'top',
},
pressed: {
opacity: 0.6,
},
checkmarkContainer: {
backgroundColor: '#ccddf9',
width: 120,
height: 120,
borderRadius: 60,
alignSelf: 'center',
justifyContent: 'center',
alignItems: 'center',
},
});

View File

@ -60,8 +60,6 @@ React Navigation 7.x with native stack. Typed params in `navigation/DetailViewSt
**Dependencies:** Do not add new dependencies without strong justification. Bonus for removing dependencies.
**Patches:** Local fixes to `node_modules` live in `patches/` and are applied by `patch-package` on `postinstall`. Each patch is documented in `patches/README.md` (what/why + upstream issue link); update it when adding or removing a patch.
**Components:** New components go in `components/`, not legacy `BlueComponents.js`.
**Linting Rules:**

View File

@ -1,13 +1,13 @@
source "https://rubygems.org"
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby "3.4.9"
gem "fastlane", "~> 2.234.0"
ruby "3.4.8"
gem "fastlane", "~> 2.232.0"
# 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 'xcodeproj', '< 1.26.0'
gem 'concurrent-ruby', '< 1.3.8'
gem 'concurrent-ruby', '< 1.3.4'
# Ruby 3.4.0 removed these from the standard library
gem 'bigdecimal'

View File

@ -15,7 +15,7 @@ GEM
minitest (>= 5.1, < 6)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.9.0)
addressable (2.8.9)
public_suffix (>= 2.0.2, < 8.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
@ -23,8 +23,8 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1252.0)
aws-sdk-core (3.247.0)
aws-partitions (1.1227.0)
aws-sdk-core (3.244.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@ -32,19 +32,19 @@ GEM
bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.127.0)
aws-sdk-core (~> 3, >= 3.247.0)
aws-sdk-kms (1.123.0)
aws-sdk-core (~> 3, >= 3.244.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.223.0)
aws-sdk-core (~> 3, >= 3.247.0)
aws-sdk-s3 (1.217.0)
aws-sdk-core (~> 3, >= 3.244.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.3.0)
base64 (0.2.0)
benchmark (0.5.0)
bigdecimal (4.1.2)
bigdecimal (4.0.1)
claide (1.1.0)
cocoapods (1.15.2)
addressable (~> 2.8)
@ -87,7 +87,7 @@ GEM
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
concurrent-ruby (1.3.7)
concurrent-ruby (1.3.3)
connection_pool (3.0.2)
csv (3.3.5)
declarative (0.0.20)
@ -131,14 +131,14 @@ GEM
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.1)
fastlane (2.234.0)
CFPropertyList (>= 2.3, < 5.0.0)
abbrev (~> 0.1)
fastlane (2.232.2)
CFPropertyList (>= 2.3, < 4.0.0)
abbrev (~> 0.1.2)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.197)
babosa (>= 1.0.3, < 2.0.0)
base64 (~> 0.2)
base64 (~> 0.2.0)
benchmark (>= 0.1.0)
bundler (>= 1.17.3, < 5.0.0)
colored (~> 1.2)
@ -151,7 +151,7 @@ GEM
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.1.0)
fastlane-sirp (>= 1.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
@ -164,9 +164,9 @@ GEM
logger (>= 1.6, < 2.0)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
mutex_m (~> 0.3)
mutex_m (~> 0.3.0)
naturally (~> 2.2)
nkf (~> 0.2)
nkf (~> 0.2.0)
optparse (>= 0.1.1, < 1.0.0)
ostruct (>= 0.1.0)
plist (>= 3.1.0, < 4.0.0)
@ -188,7 +188,8 @@ GEM
git
xml-simple
fastlane-plugin-bugsnag_sourcemaps_upload (0.2.0)
fastlane-sirp (1.1.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
ffi (1.17.3)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
@ -198,7 +199,7 @@ GEM
addressable (~> 2.8)
process_executer (~> 4.0)
rchardet (~> 1.9)
google-apis-androidpublisher_v3 (0.100.0)
google-apis-androidpublisher_v3 (0.97.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-core (0.18.0)
addressable (~> 2.5, >= 2.5.1)
@ -208,19 +209,19 @@ GEM
mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
google-apis-iamcredentials_v1 (0.27.0)
google-apis-iamcredentials_v1 (0.26.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-playcustomapp_v1 (0.17.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-storage_v1 (0.62.0)
google-apis-storage_v1 (0.61.0)
google-apis-core (>= 0.15.0, < 2.a)
google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (2.1.1)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.6.0)
google-cloud-storage (1.60.0)
google-cloud-errors (1.5.0)
google-cloud-storage (1.58.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-core (>= 0.18, < 2)
@ -245,7 +246,7 @@ GEM
i18n (1.14.8)
concurrent-ruby (~> 1.0)
jmespath (1.6.2)
json (2.19.5)
json (2.19.2)
jwt (2.10.2)
base64
logger (1.7.0)
@ -257,7 +258,7 @@ GEM
mini_mime (1.1.5)
minitest (5.27.0)
molinillo (0.8.0)
multi_json (1.21.1)
multi_json (1.19.1)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.3.0)
@ -272,7 +273,7 @@ GEM
process_executer (4.0.2)
track_open_instances (~> 0.1)
public_suffix (4.0.7)
rake (13.4.2)
rake (13.3.1)
rchardet (1.10.0)
representable (3.2.0)
declarative (< 0.1.0)
@ -299,6 +300,7 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
@ -337,8 +339,8 @@ DEPENDENCIES
benchmark
bigdecimal
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
concurrent-ruby (< 1.3.8)
fastlane (~> 2.234.0)
concurrent-ruby (< 1.3.4)
fastlane (~> 2.232.0)
fastlane-plugin-browserstack
fastlane-plugin-bugsnag
fastlane-plugin-bugsnag_sourcemaps_upload
@ -351,20 +353,20 @@ CHECKSUMS
CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261
abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242
activesupport (7.2.3.1) sha256=11ebed516a43a0bb47346227a35ebae4d9427465a7c9eb197a03d5c8d283cb34
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
algoliasearch (1.27.5) sha256=26c1cddf3c2ec4bd60c148389e42702c98fdac862881dc6b07a4c0b89ffec853
artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263
atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f
aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
aws-partitions (1.1252.0) sha256=b44c74136ebd634d35f3fb8fd37def5214db21b9375f22c6954dbe7a7f2a449d
aws-sdk-core (3.247.0) sha256=789864594ce8cef05ee3d81fa8ed506099280bda6ea12a7612b8b7c5e5e62851
aws-sdk-kms (1.127.0) sha256=5d540b6afb9574327202989db2217741211e1cce3fb443ad0e1e37de730202e5
aws-sdk-s3 (1.223.0) sha256=655e382af34926caa76b77cf0171caed5f61ff52b8b58ae50f6f3e22c39e6cbc
aws-partitions (1.1227.0) sha256=122dd20fe108cb38d38cccbc1f2592408bc1b30ca6e0d05797a7af2501567e29
aws-sdk-core (3.244.0) sha256=3e458c078b0c5bdee95bc370c3a483374b3224cf730c1f9f0faf849a5d9a18ea
aws-sdk-kms (1.123.0) sha256=d405f37e82f8fa32045ca8980be266c0b45b37aaf2012afe0254321a1e811f20
aws-sdk-s3 (1.217.0) sha256=6ea709272c666888b14e9c62345abd9a6a967759ae13667c28f01fde6823c24b
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e
cocoapods (1.15.2) sha256=f0f5153de8d028d133b96f423e04f37fb97a1da0d11dda581a9f46c0cba4090a
cocoapods-core (1.15.2) sha256=322650d97fe1ad4c0831a09669764b888bd91c6d79d0f6bb07281a17667a2136
@ -377,7 +379,7 @@ CHECKSUMS
colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c
colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a
commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9
concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
concurrent-ruby (1.3.3) sha256=4f9cd28965c4dcf83ffd3ea7304f9323277be8525819cb18a3b61edcb56a7c6a
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9
@ -403,25 +405,25 @@ CHECKSUMS
faraday-retry (1.0.4) sha256=dc659233777fabf96c69c2ffe56c0a5d2c102af90321a42cc6c90157bcd716aa
faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9
fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20
fastlane (2.234.0) sha256=b74835681ad9a8e9c0931a5727dad1bab433895ac534c864a1ed5749625d26e9
fastlane (2.232.2) sha256=978689f60f0fc3d54699de86ef12be4eda9f5b52217c1798965257c390d2b112
fastlane-plugin-browserstack (0.3.4) sha256=a4f3e4a552e2390a4733570857512571535912100ffada177d5374413f2c1333
fastlane-plugin-bugsnag (3.0.0) sha256=8ddac4b79cb4b5d00432cccd5789a9e1a1119c29f7773a27d01b1d8a2363915d
fastlane-plugin-bugsnag_sourcemaps_upload (0.2.0) sha256=a05afaefa81a7bf56c36386dddeb0931db31ead6886e3eae24f9683bda1a064d
fastlane-sirp (1.1.0) sha256=10bc94f9682efd8e1badfb31452a76dd8981f1f3a33717c765fde6d75b54d847
fastlane-sirp (1.0.0) sha256=66478f25bcd039ec02ccf65625373fca29646fa73d655eb533c915f106c5e641
ffi (1.17.3) sha256=0e9f39f7bb3934f77ad6feab49662be77e87eedcdeb2a3f5c0234c2938563d4c
fourflusher (2.3.1) sha256=1b3de61c7c791b6a4e64f31e3719eb25203d151746bb519a0292bff1065ccaa9
fuzzy_match (2.0.4) sha256=b5de4f95816589c5b5c3ad13770c0af539b75131c158135b3f3bbba75d0cfca5
gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939
git (4.3.1) sha256=91ca566c39766a033e61a148c8f470908bd4786b818f8f3ff566d3a9a0200c50
google-apis-androidpublisher_v3 (0.100.0) sha256=7a82935bee985190e8fe23bf5e53df3a27d65dd084114bb71b846b617de16489
google-apis-androidpublisher_v3 (0.97.0) sha256=0f3859844872ec09b64dde3bff6dee84458eb61d664337402adcbb4ac912322a
google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee
google-apis-iamcredentials_v1 (0.27.0) sha256=9289f29968610754ef11d98b9ec627f0153f3e2616fef839aef096de529f6d1e
google-apis-iamcredentials_v1 (0.26.0) sha256=3ff70a10a1d6cddf2554e95b7c5df2c26afdeaeb64100048a355194da19e48a3
google-apis-playcustomapp_v1 (0.17.0) sha256=d5bc90b705f3f862bab4998086449b0abe704ee1685a84821daa90ca7fa95a78
google-apis-storage_v1 (0.62.0) sha256=f62467c36df53287fb0252ebb4da85f9e25d7b4c5809d045c2aab1fc307760c1
google-apis-storage_v1 (0.61.0) sha256=b330e599b58e6a01533c189525398d6dbdbaf101ffb0c60145940b57e1c982e8
google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf
google-cloud-env (2.1.1) sha256=cf4bb8c7d517ee1ea692baedf06e0b56ce68007549d8d5a66481aa9f97f46999
google-cloud-errors (1.6.0) sha256=1da8476dd706ad04b9d32e3c4b90d07d3463b37d6407cb56d41342ea7647d0a1
google-cloud-storage (1.60.0) sha256=b21b752d37945d678a4533be5ef4303f15d33a964d8bc709c7c41c3600f650db
google-cloud-errors (1.5.0) sha256=b56be28b8c10628125214dde571b925cfcebdbc58619e598250c37a2114f7b4b
google-cloud-storage (1.58.0) sha256=1bedc07a9c75af169e1ede1dd306b9f941f9ffa9e7095d0364c0803c468fdffd
googleauth (1.11.2) sha256=7e6bacaeed7aea3dd66dcea985266839816af6633e9f5983c3c2e0e40a44731e
highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479
http-accept (1.7.0) sha256=c626860682bfbb3b46462f8c39cd470fd7b0584f61b3cc9df5b2e9eb9972a126
@ -429,7 +431,7 @@ CHECKSUMS
httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59
json (2.19.2) sha256=e7e1bd318b2c37c4ceee2444841c86539bc462e81f40d134cf97826cb14e83cf
jwt (2.10.2) sha256=31e1ee46f7359883d5e622446969fe9c118c3da87a0b1dca765ce269c3a0c4f4
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56
@ -438,7 +440,7 @@ CHECKSUMS
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
molinillo (0.8.0) sha256=efbff2716324e2a30bccd3eba1ff3a735f4d5d53ffddbc6a2f32c0ca9433045d
multi_json (1.21.1) sha256=e6126a31808e3b4d19f483c775ceac34df190dffa62adfb63a165ee14ba68080
multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7
multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
nanaimo (0.3.0) sha256=aaaedc60497070b864a7e220f7c4b4cad3a0daddda2c30055ba8dae306342376
@ -452,7 +454,7 @@ CHECKSUMS
plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42
process_executer (4.0.2) sha256=c73eb646d450044241c973a8360f6326e33ec5ad933f7acf503f6f3579873a71
public_suffix (4.0.7) sha256=8be161e2421f8d45b0098c042c06486789731ea93dc3a896d30554ee38b573b8
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
rchardet (1.10.0) sha256=d5ea2ed61a720a220f1914778208e718a0c7ed2a484b6d357ba695aa7001390f
representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace
rest-client (2.1.0) sha256=35a6400bdb14fae28596618e312776c158f7ebbb0ccad752ff4fa142bf2747e3
@ -466,6 +468,7 @@ CHECKSUMS
security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7
signet (0.21.0) sha256=d617e9fbf24928280d39dcfefba9a0372d1c38187ffffd0a9283957a10a8cd5b
simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b
sysrandom (1.0.5) sha256=5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75
terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea
terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91
track_open_instances (0.1.15) sha256=7f0e48821e6b4c881daaa40fb1583e308937c22a9c84883c150b399c3b5c3029
@ -484,7 +487,7 @@ CHECKSUMS
xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d
RUBY VERSION
ruby 3.4.9
ruby 3.4.8
BUNDLED WITH
4.0.7

View File

@ -116,10 +116,6 @@ Please note the values in curly braces should not be translated. These are the n
Transifex automatically creates Pull Request when language reaches 100% translation. We also trigger this by hand before each release, so don't worry if you can't translate everything, every word counts.
### Vocabulary glossaries
[`loc/vocabulary.md`](loc/vocabulary.md) + the per-language files under [`loc/vocabulary/`](loc/vocabulary/) are the canonical glossary of Bitcoin/Lightning terms (Wallet, Vault, Seed, Mnemonic, Passphrase, Multisig, Payment Code, Coin Control, …) and their chosen rendering in each locale, with the reasoning behind each choice and ⚠️ anti-meaning callouts (e.g. Passcode ≠ Password, Change-output ≠ verb "to change"). Use them as ground truth when translating by hand or when feeding `loc/<lang>.json` to an LLM — terminology consistency across screens is the difference between "looks translated" and "is correct for a Bitcoin wallet". When you change a shipped string, update the matching row in the same PR.
## Q&A
Builds automated and tested with BrowserStack

View File

@ -87,7 +87,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "8.0.1"
versionName "8.0.0"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
// Keep compatibility across react-native-capture-protection flavor changes.

View File

@ -14,8 +14,6 @@ import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.react.modules.fresco.FrescoModule
import com.facebook.react.modules.i18nmanager.I18nUtil
import io.bluewallet.bluewallet.components.segmentedcontrol.SegmentedControlPackage
@ -99,13 +97,6 @@ class MainApplication : Application(), ReactApplication {
val sharedI18nUtilInstance = I18nUtil.getInstance()
sharedI18nUtilInstance.allowRTL(applicationContext, true)
// Initialize Fresco before RN mounts views. FrescoModule init can lag behind the first
// frame (e.g. UnlockWith logo) when OkHttp/SSL warms up network security config.
if (!FrescoModule.hasBeenInitialized()) {
Fresco.initialize(this)
}
loadReactNative(this)
initializeDeviceUID()

View File

@ -57,13 +57,6 @@ allprojects {
maven {
url("$rootDir/../node_modules/detox/Detox-android")
}
// react-native-background-fetch ships com.transistorsoft:tsbackgroundfetch
// as a bundled local Maven repo; the package's own build.gradle adds it
// for itself, but :app's runtime classpath resolution needs it visible
// at the root level too.
maven {
url("$rootDir/../node_modules/react-native-background-fetch/android/libs")
}
mavenCentral {
// We don't want to fetch react-native from Maven Central as there are
@ -92,17 +85,6 @@ if (buildscript != null) {
}
subprojects { project ->
// react-native-device-info's androidTest classpath pulls
// play-services-iid:16.0.1 -> play-services-base:16.0.1 -> support-v4:26.1.0,
// which collides with androidx.core:core:1.13.1 (Duplicate class
// android.support.v4.app.INotificationSideChannel). Exclude the pre-AndroidX
// support-* modules so the AndroidX equivalents in core win.
configurations.all {
exclude group: 'com.android.support', module: 'support-compat'
exclude group: 'com.android.support', module: 'support-annotations'
exclude group: 'com.android.support', module: 'support-core-utils'
}
// Remove and block any jcenter() repositories at both project and buildscript levels
def scrub = { repoContainer ->
repoContainer.all { repo ->

View File

@ -1,7 +1,4 @@
module.exports = {
// Pin the @babel/runtime version so Metro resolves a single copy instead of
// bundling duplicate helpers, which bloats the bundle.
// See https://github.com/babel/babel/issues/18050
presets: [['module:@react-native/babel-preset', { enableBabelRuntime: '^7.26.0' }]],
presets: ['module:@react-native/babel-preset'],
plugins: ['react-native-worklets/plugin'],
};

View File

@ -30,7 +30,7 @@ type Utxo = {
wif?: string;
};
export type ElectrumTransaction = {
type ElectrumTransaction = {
txid: string;
hash: string;
version: number;
@ -58,14 +58,13 @@ export type ElectrumTransaction = {
addresses: string[];
};
}[];
// Confirmation-only fields: absent on mempool (unconfirmed) responses.
blockhash?: string;
confirmations?: number;
time?: number;
blocktime?: number;
blockhash: string;
confirmations: number;
time: number;
blocktime: number;
};
export type ElectrumTransactionWithHex = ElectrumTransaction & {
type ElectrumTransactionWithHex = ElectrumTransaction & {
hex: string;
};
@ -101,84 +100,24 @@ export const suggestedServers: Peer[] = hardcodedPeers.map(peer => ({
}));
let mainClient: typeof ElectrumClient | undefined;
let mainConnected: boolean = false;
let wasConnectedAtLeastOnce: boolean = false;
let serverName: string | false = false;
let disableBatching: boolean = false;
let connectionAttempt: number = 0;
let currentPeerIndex = hardcodedPeers.findIndex(peer => peer.host === defaultPeer.host && peer.ssl === defaultPeer.ssl);
if (currentPeerIndex < 0) currentPeerIndex = 0;
let latestBlock: { height: number; time: number } | { height: undefined; time: undefined } = { height: undefined, time: undefined };
// --- Single source of truth for connection liveness -----------------------------
// We previously tracked `mainConnected` (boolean) separately from the client's own
// `mainClient.status`. They drifted on iOS suspend/resume: a transient `ping()`
// failure cleared the flag while the socket was still alive, then `waitTillConnected`
// blocked for ~30s on the stale flag and surfaced a false network-error alert. The
// state machine + `ensureConnected()` below is the only place that mutates the
// connection lifecycle, and UI is driven by subscribing to state changes.
const WAIT_TILL_CONNECTED_TICK_MS = 100;
/** After at least one successful Electrum session: wall ~30s before timeout (slow reconnect). */
const WAIT_TILL_CONNECTED_MAX_TICKS_AFTER_FIRST_CONNECT = 300;
/** First-ever connect: wall ~60s before timeout (cold start / slow TLS / flaky network). */
const WAIT_TILL_CONNECTED_MAX_TICKS_NEVER_CONNECTED = 600;
export type ConnectionState = 'disabled' | 'disconnected' | 'connecting' | 'connected';
let connState: ConnectionState = 'disconnected';
type ConnectionListener = (state: ConnectionState) => void;
const connectionListeners = new Set<ConnectionListener>();
function setConnectionState(next: ConnectionState): void {
if (connState === next) return;
connState = next;
for (const l of connectionListeners) {
try {
l(next);
} catch (e) {
console.warn('[electrum] connection listener threw:', e);
}
}
}
/** Current connection state for UI. */
export function getConnectionState(): ConnectionState {
return connState;
}
/** Subscribe to state changes. Returns an unsubscribe function. */
export function subscribeConnectionState(listener: ConnectionListener): () => void {
connectionListeners.add(listener);
return () => {
connectionListeners.delete(listener);
};
}
/** Convenience: `true` iff a usable Electrum connection is currently believed to exist. */
export function isConnected(): boolean {
return connState === 'connected';
}
// --- Connection lifecycle internals ---------------------------------------------
/** One liveness check (`server_ping`) wall-time before giving up and marking the socket dead. */
const PING_TIMEOUT_MS = 5_000;
/** One full connect attempt (TLS + `server_version` handshake) wall-time before retrying. */
const CONNECT_ATTEMPT_TIMEOUT_MS = 10_000;
/** Reconnect attempts inside a single `ensureConnected()` call before declaring failure. */
const CONNECT_MAX_ATTEMPTS = 5;
/** Backoff between attempts to avoid hammering a flaky server. */
const CONNECT_BACKOFF_MS = 500;
/** Delay before the auto-reconnect triggered by a live-socket `onError`. Onions are slower. */
const RECONNECT_ONION_DELAY_MS = 4_000;
const RECONNECT_TCP_DELAY_MS = 500;
/** Max wall time one `ensureConnected()` call may take when no live socket exists. */
export const ENSURE_CONNECTED_MAX_WALL_MS =
CONNECT_MAX_ATTEMPTS * CONNECT_ATTEMPT_TIMEOUT_MS + (CONNECT_MAX_ATTEMPTS - 1) * CONNECT_BACKOFF_MS;
/** Coalesces concurrent `ensureConnected()` callers — at most one connect attempt at a time. */
let ensureInFlight: Promise<boolean> | null = null;
/** If any coalesced caller asked for the failure alert, honour it once the in-flight attempt finishes. */
let ensureInFlightShowAlert = false;
/**
* Bumps every time the caller asks us to abandon the current connection
* (`forceDisconnect()` or user disabling Electrum). In-flight `ensureConnected()`
* checks this between attempts so it can bail out promptly instead of racing back
* to `connected` after a disconnect was requested.
*/
let disconnectGeneration = 0;
/** Max wall time for one `waitTillConnected` wait (ms); derived from ticks above for callers (e.g. refresh fetch race). */
export const WAIT_TILL_CONNECTED_MAX_WALL_MS_AFTER_FIRST = WAIT_TILL_CONNECTED_MAX_TICKS_AFTER_FIRST_CONNECT * WAIT_TILL_CONNECTED_TICK_MS;
export const WAIT_TILL_CONNECTED_MAX_WALL_MS_NEVER = WAIT_TILL_CONNECTED_MAX_TICKS_NEVER_CONNECTED * WAIT_TILL_CONNECTED_TICK_MS;
const txhashHeightCache: Record<string, number> = {};
let _realm: Realm | undefined;
@ -224,10 +163,10 @@ export const getPreferredServer = async (): Promise<ElectrumServerItem | undefin
const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT);
const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT);
console.log('[electrum] Getting preferred server:', { host, tcpPort, sslPort });
console.log('Getting preferred server:', { host, tcpPort, sslPort });
if (!host) {
console.warn('[electrum] Preferred server host is undefined');
console.warn('Preferred server host is undefined');
return;
}
@ -237,7 +176,7 @@ export const getPreferredServer = async (): Promise<ElectrumServerItem | undefin
ssl: sslPort ? Number(sslPort) : undefined,
};
} catch (error) {
console.error('[electrum] Error in getPreferredServer:', error);
console.error('Error in getPreferredServer:', error);
return undefined;
}
};
@ -245,12 +184,12 @@ export const getPreferredServer = async (): Promise<ElectrumServerItem | undefin
export const removePreferredServer = async () => {
try {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
console.log('[electrum] Removing preferred server');
console.log('Removing preferred server');
await DefaultPreference.clear(ELECTRUM_HOST);
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
} catch (error) {
console.error('[electrum] Error in removePreferredServer:', error);
console.error('Error in removePreferredServer:', error);
}
};
@ -259,14 +198,14 @@ export async function isDisabled(): Promise<boolean> {
try {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
const savedValue = await DefaultPreference.get(ELECTRUM_CONNECTION_DISABLED);
console.log('[electrum] Getting Electrum connection disabled state:', savedValue);
console.log('Getting Electrum connection disabled state:', savedValue);
if (savedValue === null) {
result = false;
} else {
result = savedValue;
}
} catch (error) {
console.error('[electrum] Error getting Electrum connection disabled state:', error);
console.error('Error getting Electrum connection disabled state:', error);
result = false;
}
return !!result;
@ -274,23 +213,8 @@ export async function isDisabled(): Promise<boolean> {
export async function setDisabled(disabled = true) {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
console.log('[electrum] Setting Electrum connection disabled state to:', disabled);
const result = await DefaultPreference.set(ELECTRUM_CONNECTION_DISABLED, disabled ? '1' : '');
// Disabling must abort any in-flight ensureConnected() and tear down the live
// socket so callers don't have to remember to pair this with forceDisconnect().
// Without bumping the generation, an in-flight connect could race back to
// 'connected' after the user toggled Electrum off.
if (disabled) {
disconnectGeneration += 1;
if (mainClient) {
try {
mainClient.close();
} catch {}
mainClient = undefined;
}
setConnectionState('disabled');
}
return result;
console.log('Setting Electrum connection disabled state to:', disabled);
return DefaultPreference.set(ELECTRUM_CONNECTION_DISABLED, disabled ? '1' : '');
}
function getCurrentPeer() {
@ -314,7 +238,7 @@ async function getSavedPeer(): Promise<Peer | null> {
const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT);
const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT);
console.log('[electrum] Getting saved peer:', { host, tcpPort, sslPort });
console.log('Getting saved peer:', { host, tcpPort, sslPort });
if (!host) {
return null;
@ -330,98 +254,53 @@ async function getSavedPeer(): Promise<Peer | null> {
return null;
} catch (error) {
console.error('[electrum] Error in getSavedPeer:', error);
console.error('Error in getSavedPeer:', error);
return null;
}
}
/** Resolve to the peer this attempt should target (preferred saved peer, or rotate hardcoded list). */
async function pickPeer(): Promise<Peer> {
export async function connectMain(): Promise<void> {
if (await isDisabled()) {
console.log('Electrum connection disabled by user. Skipping connectMain call');
return;
}
let usingPeer = getNextPeer();
const savedPeer = await getSavedPeer();
if (savedPeer && savedPeer.host && (savedPeer.tcp || savedPeer.ssl)) {
usingPeer = savedPeer;
}
return usingPeer;
}
function scheduleReconnectFromClient(client: typeof ElectrumClient, usingPeer: Peer, reason: string): void {
if (connState !== 'connected' || mainClient !== client) return;
console.log(`[electrum] scheduling Electrum reconnect after ${reason}`);
try {
// Also neutralises electrum-client's own timers/reconnect hooks for this instance.
client.close();
} catch {}
if (mainClient === client) mainClient = undefined;
setConnectionState('disconnected');
const delay = usingPeer.host.endsWith('.onion') ? RECONNECT_ONION_DELAY_MS : RECONNECT_TCP_DELAY_MS;
const generationAtSchedule = disconnectGeneration;
setTimeout(() => {
if (generationAtSchedule !== disconnectGeneration) return;
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- defined later in file
ensureConnected().catch(() => {
/* ensureConnected never throws, but be defensive */
});
}, delay);
}
/**
* One connect attempt: build a fresh `ElectrumClient`, run the version handshake,
* subscribe to headers. No retries, no UI side effects. Returns the peer used
* (for caller-side telemetry/alerts) and whether the attempt succeeded.
*/
async function attemptConnectOnce(): Promise<{ ok: boolean; peer: Peer }> {
const usingPeer = await pickPeer();
console.log('[electrum] Using peer:', JSON.stringify(usingPeer));
// Drop any prior client before allocating a new one. Closing also neutralises
// electrum-client's internal `reconnect()` loop on the old instance.
if (mainClient) {
try {
mainClient.close();
} catch {}
mainClient = undefined;
}
console.log('Using peer:', JSON.stringify(usingPeer));
try {
console.log('[electrum] begin connection:', JSON.stringify(usingPeer));
const client = new ElectrumClient(net, tls, usingPeer.ssl || usingPeer.tcp, usingPeer.host, usingPeer.ssl ? 'tls' : 'tcp');
mainClient = client;
console.log('begin connection:', JSON.stringify(usingPeer));
mainClient = new ElectrumClient(net, tls, usingPeer.ssl || usingPeer.tcp, usingPeer.host, usingPeer.ssl ? 'tls' : 'tcp');
// Live-socket errors after a successful handshake: schedule a single
// `ensureConnected()` (deduped). Errors during this attempt's own handshake
// are caught below — we must not double-handle them here.
client.onError = function (e: { message: string }) {
console.log('[electrum] electrum mainClient.onError():', e.message);
scheduleReconnectFromClient(client, usingPeer, 'socket error');
mainClient.onError = function (e: { message: string }) {
console.log('electrum mainClient.onError():', e.message);
if (mainConnected) {
// most likely got a timeout from electrum ping. lets reconnect
// but only if we were previously connected (mainConnected), otherwise theres other
// code which does connection retries
mainClient?.close();
mainClient = undefined;
mainConnected = false;
// dropping `mainConnected` flag ensures there wont be reconnection race condition if several
// errors triggered
console.log('reconnecting after socket error');
setTimeout(connectMain, usingPeer.host.endsWith('.onion') ? 4000 : 500);
}
};
const ver = await Promise.race([
client.initElectrum(
{ client: 'bluewallet', version: '1.4' },
{
maxRetry: 0,
callback: () => scheduleReconnectFromClient(client, usingPeer, 'socket close'),
},
),
new Promise<never>((_resolve, reject) => setTimeout(() => reject(new Error('connect timeout')), CONNECT_ATTEMPT_TIMEOUT_MS)),
]);
if (mainClient !== client) {
// Caller raced `forceDisconnect()` while we were awaiting. Bail.
try {
client.close();
} catch {}
return { ok: false, peer: usingPeer };
}
const ver = await mainClient.initElectrum({ client: 'bluewallet', version: '1.4' });
if (ver && ver[0]) {
console.log('[electrum] connected to ', ver);
console.log('connected to ', ver);
serverName = ver[0];
mainConnected = true;
wasConnectedAtLeastOnce = true;
if (ver[0].startsWith('ElectrumPersonalServer') || ver[0].startsWith('electrs') || ver[0].startsWith('Fulcrum')) {
disableBatching = true;
// exeptions for versions:
const [electrumImplementation, electrumVersion] = ver[0].split(' ');
switch (electrumImplementation) {
case 'electrs':
@ -430,6 +309,8 @@ async function attemptConnectOnce(): Promise<{ ok: boolean; peer: Peer }> {
}
break;
case 'electrs-esplora':
// its a different one, and it does NOT support batching
// nop
break;
case 'Fulcrum':
if (semVerToInt(electrumVersion) >= semVerToInt('1.9.0')) {
@ -438,156 +319,38 @@ async function attemptConnectOnce(): Promise<{ ok: boolean; peer: Peer }> {
break;
}
}
const header = await client.blockchainHeaders_subscribe();
const header = await mainClient.blockchainHeaders_subscribe();
if (header && header.height) {
latestBlock = {
height: header.height,
time: Math.floor(+new Date() / 1000),
};
}
return { ok: true, peer: usingPeer };
// AsyncStorage.setItem(storageKey, JSON.stringify(peers)); TODO: refactor
}
return { ok: false, peer: usingPeer };
} catch (e) {
console.log('[electrum] bad connection:', JSON.stringify(usingPeer), e);
if (mainClient) {
try {
mainClient.close();
} catch {}
mainClient = undefined;
mainConnected = false;
console.log('bad connection:', JSON.stringify(usingPeer), e);
mainClient?.close();
mainClient = undefined;
}
if (!mainConnected) {
console.log('retry');
connectionAttempt = connectionAttempt + 1;
mainClient?.close();
mainClient = undefined;
if (connectionAttempt >= 5) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- `presentNetworkErrorAlert` is defined below after `connectMain`
presentNetworkErrorAlert(usingPeer);
} else {
console.log('reconnection attempt #', connectionAttempt);
await new Promise(resolve => setTimeout(resolve, 500)); // sleep
return connectMain();
}
return { ok: false, peer: usingPeer };
}
}
/** Single liveness check on the current `mainClient`, bounded by `PING_TIMEOUT_MS`. */
async function pingWithTimeout(timeoutMs: number = PING_TIMEOUT_MS): Promise<boolean> {
if (!mainClient) return false;
const client = mainClient;
try {
await Promise.race([
client.server_ping(),
new Promise<never>((_resolve, reject) => setTimeout(() => reject(new Error('ping timeout')), timeoutMs)),
]);
return mainClient === client; // server replied AND client wasn't swapped while we waited
} catch {
return false;
}
}
export type EnsureConnectedOptions = {
/**
* Show the legacy "couldn't connect" alert (Try again / Reset / Cancel) on failure.
* Used by initial bootstrap (`SettingsProvider` re-enabling Electrum) and the manual
* help alert. Off-hot-path callers (refresh, broadcast, etc.) should leave this false
* and surface their own UI.
*/
showAlertOnFailure?: boolean;
};
/**
* Make sure a usable Electrum connection exists, healing if needed.
*
* - If we already think we're connected, run one fast `ping` to verify. If the ping
* succeeds, we're done. If it fails the client is torn down and we fall through
* to a reconnect.
* - Otherwise run up to `CONNECT_MAX_ATTEMPTS` connect attempts (each with its own
* timeout + backoff).
*
* Concurrent callers share the same in-flight promise there is at most one connect
* attempt at a time per process. This replaces the old `mainConnected`-flag-polling
* `waitTillConnected()`, which could block ~30s on a stale flag while the socket was
* still alive.
*/
export async function ensureConnected(opts: EnsureConnectedOptions = {}): Promise<boolean> {
const { showAlertOnFailure = false } = opts;
if (await isDisabled()) {
setConnectionState('disabled');
return false;
}
if (ensureInFlight) {
if (showAlertOnFailure) ensureInFlightShowAlert = true;
return ensureInFlight;
}
ensureInFlightShowAlert = showAlertOnFailure;
ensureInFlight = (async (): Promise<boolean> => {
const myGeneration = disconnectGeneration;
/** True iff the current generation no longer matches ours (i.e. `forceDisconnect()` ran). */
const aborted = (where: string): boolean => {
if (myGeneration === disconnectGeneration) return false;
console.log(`[electrum] ensureConnected aborted by forceDisconnect at ${where} (gen ${myGeneration}${disconnectGeneration})`);
return true;
};
let lastPeer: Peer | undefined;
try {
// Fast path: live ping on the existing client.
if (mainClient && connState === 'connected') {
if (await pingWithTimeout()) {
// If a disconnect/disable raced us, the bumper already set the right
// state ('disconnected' or 'disabled'); don't clobber it from here.
if (aborted('post-ping')) return false;
return true;
}
// Stale socket. Tear it down so the attempt loop starts fresh.
try {
mainClient.close();
} catch {}
mainClient = undefined;
setConnectionState('disconnected');
}
if (aborted('pre-loop')) return false;
setConnectionState('connecting');
for (let i = 0; i < CONNECT_MAX_ATTEMPTS; i++) {
if (await isDisabled()) {
setConnectionState('disabled');
return false;
}
// Generation-bumper (`forceDisconnect` or `setDisabled(true)`) already
// set the appropriate terminal state; we must not clobber 'disabled'
// back to 'disconnected' here.
if (aborted(`attempt ${i} start`)) return false;
const { ok, peer } = await attemptConnectOnce();
lastPeer = peer;
if (aborted(`attempt ${i} end`)) {
if (mainClient) {
try {
mainClient.close();
} catch {}
mainClient = undefined;
}
return false;
}
if (ok) {
setConnectionState('connected');
return true;
}
if (i < CONNECT_MAX_ATTEMPTS - 1) {
await new Promise(resolve => setTimeout(resolve, CONNECT_BACKOFF_MS));
}
}
setConnectionState('disconnected');
if (ensureInFlightShowAlert) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- defined later in file
presentNetworkErrorAlert(lastPeer);
}
return false;
} finally {
ensureInFlight = null;
ensureInFlightShowAlert = false;
}
})();
return ensureInFlight;
}
export async function presentResetToDefaultsAlert(): Promise<boolean> {
const hasPreferredServer = await getPreferredServer();
const serverHistoryStr = await DefaultPreference.get(ELECTRUM_SERVER_HISTORY);
@ -607,7 +370,7 @@ export async function presentResetToDefaultsAlert(): Promise<boolean> {
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
} catch (e) {
console.log('[electrum]', e); // Must be running on Android
console.log(e); // Must be running on Android
}
resolve(true);
},
@ -626,7 +389,7 @@ export async function presentResetToDefaultsAlert(): Promise<boolean> {
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
} catch (e) {
console.log('[electrum]', e); // Must be running on Android
console.log(e); // Must be running on Android
}
resolve(true);
},
@ -652,7 +415,7 @@ export async function presentResetToDefaultsAlert(): Promise<boolean> {
async function presentNetworkErrorAlert(usingPeer?: Peer, allowRepeat = false) {
if (await isDisabled()) {
console.log(
'[electrum] Electrum connection disabled by user. Perhaps we are attempting to show this network error alert after the user disabled connections.',
'Electrum connection disabled by user. Perhaps we are attempting to show this network error alert after the user disabled connections.',
);
return;
}
@ -668,10 +431,10 @@ async function presentNetworkErrorAlert(usingPeer?: Peer, allowRepeat = false) {
{
text: loc.wallets.list_tryagain,
onPress: () => {
forceDisconnect();
setTimeout(() => {
ensureConnected({ showAlertOnFailure: true }).catch(() => {});
}, 500);
connectionAttempt = 0;
mainClient?.close();
mainClient = undefined;
setTimeout(connectMain, 500);
},
style: 'default',
},
@ -680,10 +443,10 @@ async function presentNetworkErrorAlert(usingPeer?: Peer, allowRepeat = false) {
onPress: () => {
presentResetToDefaultsAlert().then(result => {
if (result) {
forceDisconnect();
setTimeout(() => {
ensureConnected({ showAlertOnFailure: true }).catch(() => {});
}, 500);
connectionAttempt = 0;
mainClient?.close();
mainClient = undefined;
setTimeout(connectMain, 500);
}
});
},
@ -692,7 +455,9 @@ async function presentNetworkErrorAlert(usingPeer?: Peer, allowRepeat = false) {
{
text: loc._.cancel,
onPress: () => {
forceDisconnect();
connectionAttempt = 0;
mainClient?.close();
mainClient = undefined;
},
style: 'cancel',
},
@ -753,27 +518,18 @@ export const getBalanceByAddress = async function (address: string): Promise<{ c
balance.addr = address;
return balance;
} catch (error) {
console.error('[electrum] Error in getBalanceByAddress:', error);
console.error('Error in getBalanceByAddress:', error);
throw error;
}
};
export const getConfig = async function () {
if (!mainClient) {
return {
host: undefined,
port: undefined,
serverName: false as typeof serverName,
connected: connState === 'connected' ? 1 : 0,
};
}
if (!mainClient) throw new Error('Electrum client is not connected');
return {
host: mainClient.host,
port: mainClient.port,
serverName,
// Drive UI "connected" indicator from the single state machine so the settings
// screen agrees with the wallets-list header pill and with `ensureConnected()`.
connected: connState === 'connected' ? 1 : 0,
connected: mainClient.timeLastCall !== 0 && mainClient.status,
};
};
@ -802,24 +558,14 @@ export const getMempoolTransactionsByAddress = async function (address: string):
return mainClient.blockchainScripthash_getMempool(uint8ArrayToHex(reversedHash));
};
/**
* Read-only liveness probe. Does NOT trigger reconnects (use `ensureConnected()`
* for that). Updates the connection state machine to reflect the probe result so
* subscribers (UI pill, settings screen) stay in sync.
*
* - `true`: server replied within `PING_TIMEOUT_MS`.
* - `false`: client missing, timed out, or server errored.
*/
export const ping = async function (): Promise<boolean> {
if (await isDisabled()) return false;
const ok = await pingWithTimeout();
if (ok) {
// Heal stale `disconnected` state from a transient ping failure earlier.
if (connState !== 'connected') setConnectionState('connected');
} else if (connState === 'connected') {
setConnectionState('disconnected');
}
return ok;
export const ping = async function () {
try {
await mainClient.server_ping();
return true;
} catch (_) {}
mainConnected = false;
return false;
};
// exported only to be used in unit tests
@ -1032,7 +778,7 @@ export const multiGetBalanceByAddress = async (addresses: string[], batchsize: n
}
for (const bal of balances) {
if (bal.error) console.warn('[electrum] multiGetBalanceByAddress():', bal.error);
if (bal.error) console.warn('multiGetBalanceByAddress():', bal.error);
ret.balance += +bal.result.confirmed;
ret.unconfirmed_balance += +bal.result.unconfirmed;
ret.addresses[scripthash2addr[bal.param]] = bal.result;
@ -1126,7 +872,7 @@ export const multiGetHistoryByAddress = async function (
}
for (const history of results) {
if (history.error) console.warn('[electrum] multiGetHistoryByAddress():', history.error);
if (history.error) console.warn('multiGetHistoryByAddress():', history.error);
ret[scripthash2addr[history.param]] = history.result || [];
for (const result of history.result || []) {
if (result.tx_hash) txhashHeightCache[result.tx_hash] = result.height; // cache tx height
@ -1167,7 +913,7 @@ export async function multiGetTransactionByTxid<T extends boolean>(
try {
ret[txid] = JSON.parse(jsonString.cache_value as string);
} catch (error) {
console.log('[electrum]', error, 'cache failed to parse', jsonString.cache_value);
console.log(error, 'cache failed to parse', jsonString.cache_value);
}
}
@ -1217,7 +963,7 @@ export async function multiGetTransactionByTxid<T extends boolean>(
tx = txhexToElectrumTransaction(tx);
results.push({ result: tx, param: txid });
} catch (err) {
console.log('[electrum]', err);
console.log(err);
}
}
} else {
@ -1233,7 +979,7 @@ export async function multiGetTransactionByTxid<T extends boolean>(
}
results.push({ result: tx, param: txid });
} catch (err) {
console.log('[electrum]', err);
console.log(err);
}
}
}
@ -1288,12 +1034,40 @@ export async function multiGetTransactionByTxid<T extends boolean>(
}
});
} catch (writeError) {
console.error('[electrum] Failed to write transaction cache:', writeError);
console.error('Failed to write transaction cache:', writeError);
}
return ret;
}
export const waitTillConnected = async function (): Promise<boolean> {
let waitTillConnectedInterval: NodeJS.Timeout | undefined;
let retriesCounter = 0;
if (await isDisabled()) {
console.warn('Electrum connections disabled by user. waitTillConnected skipping...');
return false;
}
return new Promise(function (resolve, reject) {
waitTillConnectedInterval = setInterval(() => {
if (mainConnected) {
clearInterval(waitTillConnectedInterval);
return resolve(true);
}
retriesCounter += 1;
const maxTicks = wasConnectedAtLeastOnce
? WAIT_TILL_CONNECTED_MAX_TICKS_AFTER_FIRST_CONNECT
: WAIT_TILL_CONNECTED_MAX_TICKS_NEVER_CONNECTED;
if (retriesCounter >= maxTicks) {
clearInterval(waitTillConnectedInterval);
presentNetworkErrorAlert();
reject(new Error('Waiting for Electrum connection timeout'));
}
}, WAIT_TILL_CONNECTED_TICK_MS);
});
};
// Returns the value at a given percentile in a sorted numeric array.
// "Linear interpolation between closest ranks" method
function percentile(arr: number[], p: number) {
@ -1445,11 +1219,10 @@ export const testConnection = async function (host: string, tcpPort?: number, ss
client.onError = () => {}; // mute
let timeoutId: NodeJS.Timeout | undefined;
const timeoutMs = host.endsWith('.onion') ? 21_000 : 5_000;
try {
const rez = await Promise.race([
new Promise(resolve => {
timeoutId = setTimeout(() => resolve('timeout'), timeoutMs);
timeoutId = setTimeout(() => resolve('timeout'), 5000);
}),
client.connect(),
]);
@ -1467,19 +1240,8 @@ export const testConnection = async function (host: string, tcpPort?: number, ss
return false;
};
/**
* Drop the current connection and tell any in-flight `ensureConnected()` to abort
* (so it doesn't race the disconnect by setting state back to `connected`).
*/
export const forceDisconnect = (): void => {
disconnectGeneration += 1;
if (mainClient) {
try {
mainClient.close();
} catch {}
mainClient = undefined;
}
setConnectionState('disconnected');
mainClient?.close();
};
export const setBatchingDisabled = () => {

View File

@ -1,71 +0,0 @@
// Per-wallet Realm storage for notification-suppression entries.
//
// Lives inside the per-wallet Arkade Realm so suppression state is
// bucket-scoped, encrypted by the wallet's existing Realm key, and removed
// automatically when the wallet is deleted (deleteArkadeRealm tears down the
// whole file). Avoids leaking a stable per-wallet handle into a global
// AsyncStorage key.
export type ArkSwapNotificationAction = 'claim' | 'refund';
// Realm schema. `realm` is a peer dependency we don't import here directly;
// the schema is a plain object consumed by realmInstance.ts via the schemas
// array. Pattern matches BoltzSwapSchema in @arkade-os/boltz-swap.
export const ArkSwapNotificationSuppressionSchema = {
name: 'ArkSwapNotificationSuppression',
primaryKey: 'id',
properties: {
id: 'string',
swapId: 'string',
action: 'string',
postedAt: 'int',
},
};
const compositeId = (swapId: string, action: ArkSwapNotificationAction): string => `${swapId}:${action}`;
interface ArkSwapNotificationSuppressionRow {
id: string;
swapId: string;
action: ArkSwapNotificationAction;
postedAt: number;
}
export class RealmNotificationSuppressionRepository {
private readonly realm: any;
constructor(realm: any) {
this.realm = realm;
}
has(swapId: string, action: ArkSwapNotificationAction): boolean {
const row = this.realm.objectForPrimaryKey('ArkSwapNotificationSuppression', compositeId(swapId, action));
return Boolean(row);
}
record(swapId: string, action: ArkSwapNotificationAction): void {
this.realm.write(() => {
const row: ArkSwapNotificationSuppressionRow = {
id: compositeId(swapId, action),
swapId,
action,
postedAt: Date.now(),
};
this.realm.create('ArkSwapNotificationSuppression', row, 'modified');
});
}
clearForSwap(swapId: string): void {
this.realm.write(() => {
const matches = this.realm.objects('ArkSwapNotificationSuppression').filtered('swapId == $0', swapId);
this.realm.delete(matches);
});
}
clearForSwapAction(swapId: string, action: ArkSwapNotificationAction): void {
this.realm.write(() => {
const row = this.realm.objectForPrimaryKey('ArkSwapNotificationSuppression', compositeId(swapId, action));
if (row) this.realm.delete(row);
});
}
}

View File

@ -1,197 +0,0 @@
import RNFS from 'react-native-fs';
import Realm from 'realm';
import Keychain, { ACCESSIBLE, SECURITY_LEVEL } from 'react-native-keychain';
import { ArkRealmSchemas, ARK_REALM_SCHEMA_VERSION, runArkRealmMigrations } from '@arkade-os/sdk/repositories/realm';
import { BoltzRealmSchemas } from '@arkade-os/boltz-swap/repositories/realm';
import { randomBytes } from '../../../class/rng';
import { uint8ArrayToHex, hexToUint8Array } from '../../uint8array-extras';
import { ArkSwapNotificationSuppressionSchema } from './notificationSuppressionRepository';
const AllArkadeSchemas = [...ArkRealmSchemas, ...BoltzRealmSchemas, ArkSwapNotificationSuppressionSchema];
// App-owned schemas added on top of the SDK's. Bump when an app-owned schema
// changes; SDK bumps are handled by ARK_REALM_SCHEMA_VERSION. Realm requires
// a strictly increasing schemaVersion when objects are added; computing
// `SDK + offset` keeps the local additions ahead of any future SDK bump.
const LOCAL_ARK_SCHEMA_OFFSET = 1;
const ARKADE_REALM_SCHEMA_VERSION = ARK_REALM_SCHEMA_VERSION + LOCAL_ARK_SCHEMA_OFFSET;
const realmInstances: Map<string, Realm> = new Map();
const openInFlight: Map<string, Promise<Realm>> = new Map();
// Files live in a dedicated subdirectory so BlueApp.moveRealmFilesToCacheDirectory()
// — which sweeps top-level *.realm files from Documents into the OS-purgeable cache
// — never sees them. RNFS.readDir is non-recursive, so the subdirectory is invisible
// to that scan. Ark Realm holds non-recoverable swap/claim data and must stay in
// Documents.
const arkadeDir = (): string => `${RNFS.DocumentDirectoryPath}/arkade`;
const realmPathFor = (namespace: string): string => `${arkadeDir()}/arkade-${namespace}.realm`;
const keychainServiceFor = (namespace: string): string => `arkade_realm_${namespace}`;
async function ensureArkadeDir(): Promise<void> {
const dir = arkadeDir();
if (!(await RNFS.exists(dir))) await RNFS.mkdir(dir);
}
async function loadOrCreateEncryptionKey(namespace: string): Promise<Uint8Array> {
const service = keychainServiceFor(namespace);
const credentials = await Keychain.getGenericPassword({ service });
if (credentials) return hexToUint8Array(credentials.password);
const buf = await randomBytes(64);
const password = uint8ArrayToHex(buf);
// Accessibility: match the rest of the app's secret accessibility. RNSecureKeyStore
// in class/blue-app.ts and hooks/useBiometrics.ts both use WHEN_UNLOCKED_THIS_DEVICE_ONLY;
// the default of AFTER_FIRST_UNLOCK would expose the Realm key while the device is locked.
//
// Security level: preflight via getSecurityLevel() rather than try/catch around
// SECURE_HARDWARE. getSecurityLevel returns null on iOS (where the option is moot)
// and the highest supported level on Android. We only opt into SECURE_HARDWARE when
// the device actually backs it; otherwise let react-native-keychain pick its default.
// Catching every setGenericPassword error and silently retrying with ANY (the previous
// shape) downgrades on unrelated failures — preflight surfaces those instead.
const supportedLevel = await Keychain.getSecurityLevel();
const opts: Parameters<typeof Keychain.setGenericPassword>[2] = {
service,
accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
};
if (supportedLevel === SECURITY_LEVEL.SECURE_HARDWARE) {
opts.securityLevel = SECURITY_LEVEL.SECURE_HARDWARE;
}
await Keychain.setGenericPassword(service, password, opts);
return hexToUint8Array(password);
}
/**
* Returns a per-wallet Realm instance keyed by `namespace`. Each Ark wallet
* gets its own encrypted Realm file and its own Keychain entry so wallets
* never collide on WalletState/contracts/swaps and storage buckets stay
* isolated.
*
* Concurrent callers for the same namespace receive the same in-flight
* promise. Errors are surfaced to the caller; the in-flight entry is cleared
* so a later retry can succeed.
*/
export async function getArkadeRealm(namespace: string): Promise<Realm> {
const cached = realmInstances.get(namespace);
if (cached && !cached.isClosed) return cached;
if (cached && cached.isClosed) realmInstances.delete(namespace);
const inFlight = openInFlight.get(namespace);
if (inFlight) return inFlight;
const opening = (async () => {
await ensureArkadeDir();
const encryptionKey = await loadOrCreateEncryptionKey(namespace);
const realm = await Realm.open({
schema: AllArkadeSchemas as unknown as Realm.ObjectSchema[],
schemaVersion: ARKADE_REALM_SCHEMA_VERSION,
onMigration: (oldRealm, newRealm) => {
runArkRealmMigrations(oldRealm, newRealm);
},
path: realmPathFor(namespace),
encryptionKey,
excludeFromIcloudBackup: true,
});
realmInstances.set(namespace, realm);
return realm;
})();
openInFlight.set(namespace, opening);
try {
return await opening;
} finally {
openInFlight.delete(namespace);
}
}
/**
* Close the cached Realm for `namespace`, if any. The file and Keychain
* entry are preserved.
*/
export function closeArkadeRealm(namespace: string): void {
const realm = realmInstances.get(namespace);
if (realm && !realm.isClosed) {
realm.removeAllListeners();
realm.close();
}
realmInstances.delete(namespace);
}
/**
* Close every cached Arkade Realm instance. Used on app shutdown / sign out.
*/
export function closeAllArkadeRealms(): void {
for (const ns of Array.from(realmInstances.keys())) {
closeArkadeRealm(ns);
}
}
/**
* Delete the Realm file and the Keychain entry for `namespace`. Used when
* an Ark wallet is removed. Failures are logged but do not throw leaving
* an orphan file or Keychain entry is preferable to crashing the app's
* delete path. Ark Realm failures stay scoped to the Ark wallet path.
*
* The Keychain encryption key is reset only when the Realm file is gone
* (or never existed). Resetting the key while the encrypted file remains
* would leave the user unable to open the orphan on a future re-import:
* a fresh random key would be generated and the old file's ciphertext
* could not be decrypted.
*/
export async function deleteArkadeRealm(namespace: string): Promise<void> {
closeArkadeRealm(namespace);
const path = realmPathFor(namespace);
let realmRemoved = false;
try {
// Realm.deleteFile is sync and removes the .realm + .lock + .management
// siblings in one call. It is forgiving when the file does not exist
// (no-op), but we guard via Realm.exists to keep behavior explicit.
if (Realm.exists(path)) {
Realm.deleteFile({ path });
}
realmRemoved = true;
} catch (e: any) {
console.log(`[ArkadeRealm] Realm.deleteFile failed for ${path}:`, e?.message ?? e);
}
// Best-effort sweep of any sibling files Realm.deleteFile might have left
// behind. These are not load-bearing for re-import; failures are tolerated.
for (const suffix of ['.note']) {
const sibling = `${path}${suffix}`;
try {
if (await RNFS.exists(sibling)) await RNFS.unlink(sibling);
} catch (e: any) {
console.log(`[ArkadeRealm] failed to delete ${sibling}:`, e?.message ?? e);
}
}
if (!realmRemoved) {
console.log(
`[ArkadeRealm] keeping encryption key for ${namespace} because Realm file cleanup failed; key preserved so a future delete retry can still decrypt the orphan`,
);
return;
}
try {
await Keychain.resetGenericPassword({ service: keychainServiceFor(namespace) });
} catch (e: any) {
console.log(`[ArkadeRealm] failed to reset keychain for ${namespace}:`, e?.message ?? e);
}
}
// Exported for tests only.
export const __testing__ = {
realmInstances,
openInFlight,
realmPathFor,
keychainServiceFor,
};

View File

@ -1,423 +0,0 @@
// Background task module for Ark swap monitoring.
//
// Responsibilities:
// - Passive monitoring: poll Boltz swap status for non-terminal swaps in
// every Ark wallet's per-wallet Realm and persist remote changes through
// the SDK update helpers.
// - Post a local notification when an SDK predicate flags a swap as
// claimable/refundable. No claim, refund, recover, or signing happens in
// background — those remain foreground-only.
//
// State here is in-process: it survives configure→fetch→fetch ticks within a
// single JS runtime but is gone after process kill. Realm remains the
// durable source of truth for swap status and notification suppression.
import BackgroundFetch from 'react-native-background-fetch';
import {
BoltzSwapProvider,
isChainFinalStatus,
isReverseFinalStatus,
isSubmarineFinalStatus,
updateChainSwapStatus,
updateReverseSwapStatus,
updateSubmarineSwapStatus,
} from '@arkade-os/boltz-swap';
import type { BoltzChainSwap, BoltzReverseSwap, BoltzSubmarineSwap, BoltzSwap } from '@arkade-os/boltz-swap';
import { RealmSwapRepository } from '@arkade-os/boltz-swap/repositories/realm';
import { BlueApp as BlueAppClass } from '../class/blue-app';
import { LightningArkWallet } from '../class/wallets/lightning-ark-wallet';
import { getArkadeRealm } from './arkade-adapters/realm/realmInstance';
import {
RealmNotificationSuppressionRepository,
type ArkSwapNotificationAction,
} from './arkade-adapters/realm/notificationSuppressionRepository';
import { notifyArkSwapActionable, resolveActionableAction } from './arkade-notifications';
const BlueApp = BlueAppClass.getInstance();
// Single shared provider. The constructor only stores config; it does not
// open sockets. Re-using one instance avoids per-poll allocation.
const swapProvider = new BoltzSwapProvider({ network: 'bitcoin' });
const DEFAULT_MAX_RUN_MS = 25_000;
let maxRunMs = DEFAULT_MAX_RUN_MS;
interface ArkTaskState {
lastRegisteredAt: number | null;
lastUnregisteredAt: number | null;
lastRunStartedAt: number | null;
lastRunFinishedAt: number | null;
walletsScanned: number;
swapsPolled: number;
swapsUpdated: number;
lastError: string | null;
exitedDueToUnavailableStorage: boolean;
availability: 'unknown' | 'available' | 'denied' | 'restricted';
// Set whenever swapsUpdated is incremented. Used by reconcile() to detect
// updates that crossed run boundaries (per-run swapsUpdated is reset).
lastSwapUpdateAt: number;
lastReconciledAt: number;
}
const state: ArkTaskState = {
lastRegisteredAt: null,
lastUnregisteredAt: null,
lastRunStartedAt: null,
lastRunFinishedAt: null,
walletsScanned: 0,
swapsPolled: 0,
swapsUpdated: 0,
lastError: null,
exitedDueToUnavailableStorage: false,
availability: 'unknown',
lastSwapUpdateAt: 0,
lastReconciledAt: 0,
};
// Per-wallet last-seen status cache. Outer key: wallet namespace; inner key:
// swap ID; value: last status this background module observed. Diagnostic +
// reconciliation hint only — Realm is durable.
const swapStatusCache: Map<string, Map<string, string>> = new Map();
// Per-poll last-seen actionable action keyed by `${namespace}:${swapId}`.
// Used to detect predicate flips (true → false or claim ↔ refund) so we can
// clear the corresponding Realm suppression row even when the swap status
// has not yet reached a terminal state. In-process only; cleared by
// stopArkBackgroundTask so a later run does not falsely diagnose a flip on
// the first poll after restart.
const lastSeenActionMap: Map<string, ArkSwapNotificationAction> = new Map();
let configured = false;
let running = false;
let cancelRequested = false;
let runDeadline: number | null = null;
export function getArkTaskState(): Readonly<ArkTaskState> {
return Object.freeze({ ...state });
}
function recordError(message: string): void {
state.lastError = message;
}
function shouldStopRun(): boolean {
return cancelRequested || (runDeadline !== null && Date.now() >= runDeadline);
}
function remainingRunMs(): number {
if (runDeadline === null) return maxRunMs;
return Math.max(runDeadline - Date.now(), 0);
}
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
promise,
new Promise<never>((_resolve, reject) => {
timer = setTimeout(() => reject(new Error('deadline exceeded')), ms);
}),
]);
} finally {
if (timer) clearTimeout(timer);
}
}
function isFinalStatus(swap: BoltzSwap): boolean {
switch (swap.type) {
case 'reverse':
return isReverseFinalStatus(swap.status);
case 'submarine':
return isSubmarineFinalStatus(swap.status);
case 'chain':
return isChainFinalStatus(swap.status);
}
}
async function persistStatusChange(swap: BoltzSwap, newStatus: BoltzSwap['status'], repo: RealmSwapRepository): Promise<void> {
if (swap.type === 'reverse') {
await updateReverseSwapStatus(swap as BoltzReverseSwap, newStatus, s => repo.saveSwap(s));
} else if (swap.type === 'submarine') {
await updateSubmarineSwapStatus(swap as BoltzSubmarineSwap, newStatus, s => repo.saveSwap(s));
} else {
await updateChainSwapStatus(swap as BoltzChainSwap, newStatus, s => repo.saveSwap(s));
}
}
async function pollSwap(
swap: BoltzSwap,
namespace: string,
repo: RealmSwapRepository,
suppression: RealmNotificationSuppressionRepository,
walletID: string,
walletLabel: string,
): Promise<void> {
if (shouldStopRun()) return;
state.swapsPolled += 1;
let response;
try {
response = await withTimeout(swapProvider.getSwapStatus(swap.id), remainingRunMs());
} catch (e: any) {
recordError(`getSwapStatus(${swap.id}): ${e?.message ?? e}`);
if (e?.message === 'deadline exceeded' || remainingRunMs() <= 0) cancelRequested = true;
return;
}
if (shouldStopRun()) return;
const remoteStatus = response.status;
const statusChanged = remoteStatus !== swap.status;
// The SDK update helpers (updateReverseSwapStatus etc.) save a copy and do
// not mutate `swap`, so any post-persist predicate or terminal check on
// `swap` would read the pre-update status. effectiveSwap carries the
// status we want subsequent checks to evaluate against.
const effectiveSwap: BoltzSwap = statusChanged ? ({ ...swap, status: remoteStatus } as BoltzSwap) : swap;
if (statusChanged) {
try {
await persistStatusChange(swap, remoteStatus, repo);
} catch (e: any) {
recordError(`persistStatusChange(${swap.id}): ${e?.message ?? e}`);
return;
}
state.swapsUpdated += 1;
state.lastSwapUpdateAt = Date.now();
let perWallet = swapStatusCache.get(namespace);
if (!perWallet) {
perWallet = new Map();
swapStatusCache.set(namespace, perWallet);
}
perWallet.set(swap.id, remoteStatus);
}
// Actionable evaluation runs on every non-terminal poll, NOT only after a
// status change. Otherwise a swap that became actionable in a previous run
// but never received a successful post (notify failed mid-run, OS-level
// drop, permission-denied skip, app cold-started with already-actionable
// Realm state) would never be re-checked because subsequent polls observe
// remoteStatus === swap.status and would otherwise exit. The Realm
// suppression repo is the dedup layer.
const lastKey = `${namespace}:${effectiveSwap.id}`;
if (isFinalStatus(effectiveSwap)) {
try {
suppression.clearForSwap(effectiveSwap.id);
} catch (e: any) {
recordError(`suppression.clearForSwap(${effectiveSwap.id}): ${e?.message ?? e}`);
}
lastSeenActionMap.delete(lastKey);
return;
}
const action = resolveActionableAction(effectiveSwap);
const lastSeen = lastSeenActionMap.get(lastKey);
if (lastSeen && lastSeen !== action) {
// Predicate flipped out of `lastSeen` (either to null or to the other
// action). Clear the stale suppression so the next observed flip back
// re-fires.
try {
suppression.clearForSwapAction(effectiveSwap.id, lastSeen);
} catch (e: any) {
recordError(`suppression.clearForSwapAction(${effectiveSwap.id}): ${e?.message ?? e}`);
}
}
if (action) {
try {
await notifyArkSwapActionable(effectiveSwap, suppression, walletID, walletLabel);
} catch (e: any) {
recordError(`notifyArkSwapActionable(${effectiveSwap.id}): ${e?.message ?? e}`);
}
lastSeenActionMap.set(lastKey, action);
} else {
lastSeenActionMap.delete(lastKey);
}
}
async function processWallet(wallet: LightningArkWallet): Promise<void> {
state.walletsScanned += 1;
const namespace = wallet.getNamespace();
const walletID = wallet.getID();
const walletLabel = wallet.getLabel();
let realm;
try {
realm = await getArkadeRealm(namespace);
} catch (e: any) {
// Most likely the Keychain is locked (WHEN_UNLOCKED_THIS_DEVICE_ONLY) or
// the Realm file is unreachable. Either way the background task no-ops
// for this wallet — claim/refund is foreground-only anyway.
state.exitedDueToUnavailableStorage = true;
recordError(`getArkadeRealm(${namespace}): ${e?.message ?? e}`);
return;
}
let swaps: BoltzSwap[];
const repo = new RealmSwapRepository(realm as any);
const suppression = new RealmNotificationSuppressionRepository(realm);
try {
swaps = await repo.getAllSwaps<BoltzSwap>();
} catch (e: any) {
recordError(`getAllSwaps(${namespace}): ${e?.message ?? e}`);
return;
}
for (const swap of swaps) {
if (isFinalStatus(swap)) continue;
if (shouldStopRun()) return;
await pollSwap(swap, namespace, repo, suppression, walletID, walletLabel);
}
}
export async function runArkBackgroundTask(taskId: string): Promise<void> {
if (running) {
BackgroundFetch.finish(taskId);
return;
}
running = true;
cancelRequested = false;
runDeadline = Date.now() + maxRunMs;
state.lastRunStartedAt = Date.now();
state.walletsScanned = 0;
state.swapsPolled = 0;
state.swapsUpdated = 0;
state.exitedDueToUnavailableStorage = false;
try {
const wallets = BlueApp.getWallets().filter((w): w is LightningArkWallet => w instanceof LightningArkWallet);
if (wallets.length === 0) return;
for (const wallet of wallets) {
if (shouldStopRun()) break;
try {
await processWallet(wallet);
} catch (e: any) {
recordError(`processWallet: ${e?.message ?? e}`);
}
}
} finally {
state.lastRunFinishedAt = Date.now();
runDeadline = null;
cancelRequested = false;
running = false;
BackgroundFetch.finish(taskId);
}
}
export function onArkBackgroundTaskTimeout(taskId: string): void {
cancelRequested = true;
state.lastError = 'timeout';
state.lastRunFinishedAt = Date.now();
BackgroundFetch.finish(taskId);
}
function availabilityFromStatus(status: number): ArkTaskState['availability'] {
if (status === BackgroundFetch.STATUS_AVAILABLE) return 'available';
if (status === BackgroundFetch.STATUS_DENIED) return 'denied';
if (status === BackgroundFetch.STATUS_RESTRICTED) return 'restricted';
return 'unknown';
}
export async function registerArkBackgroundTask(): Promise<void> {
if (configured) {
await BackgroundFetch.start();
state.lastRegisteredAt = Date.now();
return;
}
const config: Parameters<typeof BackgroundFetch.configure>[0] = {
minimumFetchInterval: 15,
stopOnTerminate: false,
startOnBoot: true,
enableHeadless: true,
requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY,
};
try {
const status = await BackgroundFetch.configure(config, runArkBackgroundTask, onArkBackgroundTaskTimeout);
state.availability = availabilityFromStatus(status);
if (state.availability === 'available') {
configured = true;
state.lastRegisteredAt = Date.now();
} else {
console.warn(`[ArkBackground] Background fetch unavailable: ${state.availability}`);
}
} catch (e: any) {
recordError(`configure: ${e?.message ?? e}`);
}
}
export async function stopArkBackgroundTask(): Promise<void> {
cancelRequested = true;
try {
await BackgroundFetch.stop();
} catch (e: any) {
recordError(`stop: ${e?.message ?? e}`);
}
// Await in-flight run completion (draining). A live background run keeps
// Detox's FabricTimersIdlingResource busy and disconnects the JS bridge.
const start = Date.now();
// eslint-disable-next-line no-unmodified-loop-condition
while (running && Date.now() - start < 30_000) {
await new Promise(resolve => setTimeout(resolve, 50));
}
swapStatusCache.clear();
// Clear in-process predicate-flip tracker so a later run does not
// diagnose a flip on the first poll after restart. Persistent suppression
// (Realm) is intentionally untouched — re-registering must keep history.
lastSeenActionMap.clear();
state.lastUnregisteredAt = Date.now();
}
export function reconcileArkBackgroundTaskResults(triggerRefreshForWallet: (walletId: string) => void): void {
if (state.lastSwapUpdateAt <= state.lastReconciledAt) return;
const wallets = BlueApp.getWallets().filter((w): w is LightningArkWallet => w instanceof LightningArkWallet);
for (const wallet of wallets) {
const namespace = wallet.getNamespace();
const perWallet = swapStatusCache.get(namespace);
if (perWallet && perWallet.size > 0) {
triggerRefreshForWallet(wallet.getID());
}
}
state.lastReconciledAt = Date.now();
}
// Exported for tests only.
export const __testing__ = {
state,
swapStatusCache,
lastSeenActionMap,
resetConfigured: (): void => {
configured = false;
},
setMaxRunMs: (ms: number): void => {
maxRunMs = ms;
},
reset: (): void => {
state.lastRegisteredAt = null;
state.lastUnregisteredAt = null;
state.lastRunStartedAt = null;
state.lastRunFinishedAt = null;
state.walletsScanned = 0;
state.swapsPolled = 0;
state.swapsUpdated = 0;
state.lastError = null;
state.exitedDueToUnavailableStorage = false;
state.availability = 'unknown';
state.lastSwapUpdateAt = 0;
state.lastReconciledAt = 0;
swapStatusCache.clear();
lastSeenActionMap.clear();
configured = false;
running = false;
cancelRequested = false;
runDeadline = null;
maxRunMs = DEFAULT_MAX_RUN_MS;
},
};

View File

@ -1,163 +0,0 @@
// Local-notification posting for actionable Ark swaps. Imported from headless
// background runtimes (no React dependency).
//
// Design notes:
// - Suppression state lives per-wallet in the Arkade Realm
// (RealmNotificationSuppressionRepository), not in a global AsyncStorage
// key — bucket-scoped and encrypted, so the suppression record never
// leaks a stable handle outside the wallet's encryption boundary.
// - Permission and app-level opt-out are checked read-only before each post
// (no prompting from headless context). Suppression is NOT recorded when
// the post is skipped, so a later state where the user grants permission
// triggers a fresh post on the next wake.
// - Notification payload deliberately does NOT include `namespace`. The OS
// notification database persists payloads and is global across BlueWallet
// encryption buckets; embedding a deterministic per-wallet identifier
// would tie a stable handle to the OS-visible record.
import AsyncStorage from '@react-native-async-storage/async-storage';
import { AppState, Platform } from 'react-native';
import { Notification, Notifications } from 'react-native-notifications';
import { checkNotifications, RESULTS } from 'react-native-permissions';
import { isChainSwapClaimable, isChainSwapRefundable, isReverseSwapClaimable, isSubmarineSwapRefundable } from '@arkade-os/boltz-swap';
import type { BoltzSwap } from '@arkade-os/boltz-swap';
import loc from '../loc';
import { NOTIFICATIONS_NO_AND_DONT_ASK_FLAG } from './notifications';
import type {
RealmNotificationSuppressionRepository,
ArkSwapNotificationAction,
} from './arkade-adapters/realm/notificationSuppressionRepository';
export const ARK_SWAP_NOTIFICATION_TYPE = 100;
const ANDROID_NOTIFICATION_CHANNEL_ID = 'channel_01';
let channelEnsured = false;
export function ensureArkNotificationChannel(): void {
if (Platform.OS !== 'android') return;
if (channelEnsured) return;
channelEnsured = true;
// Reuses the BlueWallet channel from blue_modules/notifications.ts:80-91 so
// headless runs do not register a second channel under a different name.
Notifications.setNotificationChannel({
channelId: ANDROID_NOTIFICATION_CHANNEL_ID,
name: 'BlueWallet notifications',
description: 'Notifications about incoming payments',
importance: 4,
enableVibration: true,
showBadge: true,
});
}
// Channel registration runs lazily on the first post (see notifyArkSwapActionable).
// Calling it at module-top would invoke the native bridge during JS bundle
// evaluation, which racy-blocks RN bootstrap on some devices and breaks
// Detox's RN-context wait. The existing blue_modules/notifications.ts pattern
// also defers channel setup to lazy invocation.
export function resolveActionableAction(swap: BoltzSwap): ArkSwapNotificationAction | null {
if (isReverseSwapClaimable(swap) || isChainSwapClaimable(swap)) return 'claim';
if (isSubmarineSwapRefundable(swap) || isChainSwapRefundable(swap)) return 'refund';
return null;
}
const interpolate = (template: string, walletLabel: string): string => template.replace('{walletLabel}', walletLabel);
// Static references so scripts/find-unused-loc.js can detect these keys.
const titleFor = (): string => loc.lndViewInvoice.notification_action_title;
const bodyFor = (action: ArkSwapNotificationAction): string =>
action === 'claim' ? loc.lndViewInvoice.notification_claim_body : loc.lndViewInvoice.notification_refund_body;
let appStateOverrideForTest: string | null = null;
let permissionResultOverrideForTest: string | null = null;
let optOutFlagOverrideForTest: string | null | undefined;
function currentAppState(): string {
return appStateOverrideForTest ?? AppState.currentState;
}
async function isOsNotificationPermissionGranted(): Promise<boolean> {
if (permissionResultOverrideForTest !== null) {
return permissionResultOverrideForTest === RESULTS.GRANTED;
}
try {
const { status } = await checkNotifications();
return status === RESULTS.GRANTED;
} catch {
return false;
}
}
async function isAppLevelOptedOut(): Promise<boolean> {
if (optOutFlagOverrideForTest !== undefined) {
return optOutFlagOverrideForTest === 'true';
}
try {
const flag = await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG);
return flag === 'true';
} catch {
return false;
}
}
export async function notifyArkSwapActionable(
swap: BoltzSwap,
suppression: RealmNotificationSuppressionRepository,
walletID: string,
walletLabel: string,
): Promise<void> {
const action = resolveActionableAction(swap);
if (!action) return;
if (currentAppState() === 'active') return;
if (suppression.has(swap.id, action)) return;
if (!(await isOsNotificationPermissionGranted())) return;
if (await isAppLevelOptedOut()) return;
ensureArkNotificationChannel();
const title = titleFor();
const body = interpolate(bodyFor(action), walletLabel);
try {
Notifications.postLocalNotification(
// namespace is intentionally omitted; tap routing re-derives it from the loaded wallet.
new Notification({
title,
body,
type: ARK_SWAP_NOTIFICATION_TYPE,
walletID,
swapId: swap.id,
action,
}),
);
} catch (e: any) {
console.warn('[ArkNotifications] postLocalNotification failed:', e?.message ?? e);
return;
}
try {
suppression.record(swap.id, action);
} catch (e: any) {
console.warn('[ArkNotifications] suppression.record failed:', e?.message ?? e);
}
}
export const __testing__ = {
resetChannel: (): void => {
channelEnsured = false;
},
setAppStateForTest: (state: string | null): void => {
appStateOverrideForTest = state;
},
setPermissionResultForTest: (result: string | null): void => {
permissionResultOverrideForTest = result;
},
setOptOutFlagForTest: (value: string | null | undefined): void => {
optOutFlagOverrideForTest = value;
},
};

View File

@ -2,7 +2,4 @@
* Let's keep config vars, constants and definitions here
*/
export const groundControlUri: string = 'https://groundcontrol.bluewallet.io';
/** bitcoin-payment-push-service base URL, no trailing slash. Empty = disabled. */
export const arkadePaymentPushUri: string = 'https://electrum2.bluewallet.io:444';
export const groundControlUri: string = 'https://groundcontrol-bluewallet.herokuapp.com';

View File

@ -1,98 +1,23 @@
import { cbc } from '@noble/ciphers/aes';
import { md5 } from '@noble/hashes/legacy';
import { randomBytes } from '@noble/hashes/utils';
import AES from 'crypto-js/aes';
import Utf8 from 'crypto-js/enc-utf8';
import { areUint8ArraysEqual, base64ToUint8Array, concatUint8Arrays, stringToUint8Array, uint8ArrayToBase64 } from './uint8array-extras';
/**
* OpenSSL EVP_BytesToKey using MD5 with 1 iteration.
*
* Reproduces the default key+IV derivation used by CryptoJS@4.x's
* `AES.encrypt(string, password)` so the on-disk wire format stays
* bit-identical after we swap the underlying library.
*
* D1 = MD5( password || salt )
* Di = MD5( D(i-1) || password || salt ) for i 2
* key||iv = D1 || D2 || ... (take first `byteLength` bytes)
*
* MD5 is intentional: it matches the legacy OpenSSL format. The
* cryptographic weakness of MD5 is not relevant here the function is
* only used as a deterministic byte-stretcher; the password's entropy is
* what protects the wallet, not MD5.
*/
export function evpBytesToKeyMd5(password: Uint8Array, salt: Uint8Array, byteLength: number): Uint8Array {
if (!Number.isInteger(byteLength) || byteLength < 0) {
throw new Error('evpBytesToKeyMd5: byteLength must be a non-negative integer');
}
const out = new Uint8Array(byteLength);
let written = 0;
let prev: Uint8Array = new Uint8Array(0);
while (written < byteLength) {
prev = md5(concatUint8Arrays([prev, password, salt]));
const take = Math.min(prev.length, byteLength - written);
out.set(prev.subarray(0, take), written);
written += take;
}
return out;
}
// "Salted__" — OpenSSL envelope magic. Hardcoded as bytes so the wire
// format cannot drift through any encoder.
const SALT_MAGIC = new Uint8Array([0x53, 0x61, 0x6c, 0x74, 0x65, 0x64, 0x5f, 0x5f]);
const SALT_LEN = 8;
const KEY_LEN = 32;
const IV_LEN = 16;
const BLOCK_LEN = 16;
/**
* AES-256-CBC encrypt with the OpenSSL "Salted__" envelope, EVP_BytesToKey-MD5
* key derivation and PKCS7 padding. Output is base64-encoded.
*
* Wire format is bit-identical to CryptoJS@4.x's default
* `AES.encrypt(data, password).toString()` we kept the swap-the-library
* change a drop-in replacement so existing encrypted wallets on user
* devices remain readable, with no migration step.
*/
export function encrypt(data: string, password: string): string {
if (data.length < 10) throw new Error('data length cant be < 10');
const salt = randomBytes(SALT_LEN);
const kdf = evpBytesToKeyMd5(stringToUint8Array(password), salt, KEY_LEN + IV_LEN);
const key = kdf.subarray(0, KEY_LEN);
const iv = kdf.subarray(KEY_LEN);
const ciphertext = cbc(key, iv).encrypt(stringToUint8Array(data));
return uint8ArrayToBase64(concatUint8Arrays([SALT_MAGIC, salt, ciphertext]));
const ciphertext = AES.encrypt(data, password);
return ciphertext.toString();
}
/**
* Inverse of `encrypt`. Accepts the legacy CryptoJS wire format and returns
* the original UTF-8 plaintext. Any error (bad base64, missing magic, wrong
* password, bad padding) collapses to `false`.
*/
export function decrypt(data: string, password: string): string | false {
const bytes = AES.decrypt(data, password);
let str: string | false = false;
try {
// crypto-js's base64 decoder ignored whitespace. Some old encrypted-backup
// export/import flows (manual file paste, clipboard transit, email-based
// wallet transfer) introduced stray newlines or padding spaces. Strip them
// before strict base64 decode so legacy backups still open. `\s` does not
// include `=`, so base64 padding survives.
const envelope = base64ToUint8Array(data.replace(/\s+/g, ''));
if (envelope.length < SALT_MAGIC.length + SALT_LEN + BLOCK_LEN) return false;
if (!areUint8ArraysEqual(envelope.subarray(0, SALT_MAGIC.length), SALT_MAGIC)) return false;
const salt = envelope.subarray(SALT_MAGIC.length, SALT_MAGIC.length + SALT_LEN);
const ciphertext = envelope.subarray(SALT_MAGIC.length + SALT_LEN);
const kdf = evpBytesToKeyMd5(stringToUint8Array(password), salt, KEY_LEN + IV_LEN);
const key = kdf.subarray(0, KEY_LEN);
const iv = kdf.subarray(KEY_LEN);
const plain = cbc(key, iv).decrypt(ciphertext);
// Strict UTF-8 decode — wrong-password decrypts that happen to survive
// PKCS7 unpadding overwhelmingly fail here (crypto-js's `enc.Utf8` was
// strict too; we preserve that gate by using `fatal: true`).
const str = new TextDecoder('utf-8', { fatal: true }).decode(plain);
// Belt-and-suspenders: legitimate plaintext is always ≥ 10 chars
// (enforced by encrypt()), so anything shorter is rejected.
if (str.length < 10) return false;
return str;
} catch (e) {
return false;
}
str = bytes.toString(Utf8);
} catch (e) {}
// For some reason, sometimes decrypt would succeed with an incorrect password and return random characters.
// In this TypeScript version, we are not allowing the encryption of data that is shorter than
// 10 characters. If the decrypted data is less than 10 characters, we assume that the decrypt actually failed.
if (str && str.length < 10) return false;
return str;
}

View File

@ -26,93 +26,44 @@ export interface TinySecp256k1InterfaceExtended {
signDER(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
}
// @noble/hashes types differ slightly from @noble/secp256k1 v3 hash slot typings.
necc.hashes.sha256 = sha256 as NonNullable<typeof necc.hashes.sha256>;
necc.hashes.hmacSha256 = ((key: Uint8Array, message: Uint8Array) => hmac(sha256, key, message)) as NonNullable<
typeof necc.hashes.hmacSha256
>;
// Removed from @noble/secp256k1 v1.7; vendored from noble test vectors.
// @see https://github.com/paulmillr/noble-secp256k1/blob/1.7.2/test/index.ts
type Hex = string | Uint8Array;
const { mod, secretKeyToScalar, numberToBytesBE, bytesToNumberBE, hexToBytes } = necc.etc;
const CURVE_N = necc.Point.CURVE().n;
function pointFromBytes(p: Uint8Array): necc.Point {
if (p.length === 32) {
const prefixed = new Uint8Array(33);
prefixed[0] = 0x02;
prefixed.set(p, 1);
return necc.Point.fromBytes(prefixed);
}
return necc.Point.fromBytes(p);
}
const tweakUtils = {
privateAdd: (privateKey: Hex, tweak: Hex): Uint8Array => {
const p = secretKeyToScalar(typeof privateKey === 'string' ? hexToBytes(privateKey) : privateKey);
const t = secretKeyToScalar(typeof tweak === 'string' ? hexToBytes(tweak) : tweak);
return numberToBytesBE(mod(p + t, CURVE_N));
},
privateNegate: (privateKey: Hex): Uint8Array => {
const p = secretKeyToScalar(typeof privateKey === 'string' ? hexToBytes(privateKey) : privateKey);
return numberToBytesBE(CURVE_N - p);
},
pointAddScalar: (p: Hex, tweak: Hex, isCompressed?: boolean): Uint8Array => {
const P = typeof p === 'string' ? necc.Point.fromHex(p) : pointFromBytes(p);
const t = secretKeyToScalar(typeof tweak === 'string' ? hexToBytes(tweak) : tweak);
const Q = P.add(necc.Point.BASE.multiply(t));
if (Q.is0()) throw new Error('Tweaked point at infinity');
return Q.toBytes(isCompressed);
},
pointMultiply: (p: Hex, tweak: Hex, isCompressed?: boolean): Uint8Array => {
const P = typeof p === 'string' ? necc.Point.fromHex(p) : pointFromBytes(p);
const tweakBytes = typeof tweak === 'string' ? hexToBytes(tweak) : tweak;
const t = mod(bytesToNumberBE(tweakBytes), CURVE_N);
if (t === 0n) throw new Error('Point at infinity');
return P.multiply(t).toBytes(isCompressed);
},
necc.utils.sha256Sync = (...messages: Uint8Array[]): Uint8Array => {
const combinedMessages = messages.reduce((acc, msg) => {
const newArray = new Uint8Array(acc.length + msg.length);
newArray.set(acc);
newArray.set(msg, acc.length);
return newArray;
}, new Uint8Array(0));
return sha256(combinedMessages);
};
necc.utils.hmacSha256Sync = (key: Uint8Array, ...messages: Uint8Array[]): Uint8Array => {
const combinedMessages = messages.reduce((acc, msg) => {
const newArray = new Uint8Array(acc.length + msg.length);
newArray.set(acc);
newArray.set(msg, acc.length);
return newArray;
}, new Uint8Array(0));
return hmac(sha256, key, combinedMessages);
};
/* const normal = necc.utils._normalizePrivateKey;
type Hex = string | Uint8Array;
type PrivKey = Hex | bigint | number;
necc.utils.privateAdd = (privateKey: PrivKey, tweak: Hex) => {
console.log({ privateKey, tweak });
const p = normal(privateKey);
const t = normal(tweak);
return necc.utils.privateAdd(necc.utils.mod(p + t, necc.CURVE.n));
}; */
const defaultTrue = (param?: boolean): boolean => param !== false;
function compactToDER(sig: Uint8Array): Uint8Array {
const encodeInt = (bytes: Uint8Array): Uint8Array => {
let i = 0;
while (i < bytes.length - 1 && bytes[i] === 0) i++;
let trimmed = bytes.subarray(i);
if (trimmed[0] >= 0x80) {
const prefixed = new Uint8Array(trimmed.length + 1);
prefixed[0] = 0;
prefixed.set(trimmed, 1);
trimmed = prefixed;
}
const encoded = new Uint8Array(2 + trimmed.length);
encoded[0] = 0x02;
encoded[1] = trimmed.length;
encoded.set(trimmed, 2);
return encoded;
};
const rDer = encodeInt(sig.subarray(0, 32));
const sDer = encodeInt(sig.subarray(32, 64));
const seqLen = rDer.length + sDer.length;
const der = new Uint8Array(2 + seqLen);
der[0] = 0x30;
der[1] = seqLen;
der.set(rDer, 2);
der.set(sDer, 2 + rDer.length);
return der;
}
function throwToNull<Type>(fn: () => Type): Type | null {
try {
return fn();
} catch (e) {
// console.log(e);
return null;
}
}
@ -120,8 +71,7 @@ function throwToNull<Type>(fn: () => Type): Type | null {
function isPoint(p: Uint8Array, xOnly: boolean): boolean {
if ((p.length === 32) !== xOnly) return false;
try {
pointFromBytes(p);
return true;
return !!necc.Point.fromHex(p);
} catch (e) {
return false;
}
@ -129,12 +79,23 @@ function isPoint(p: Uint8Array, xOnly: boolean): boolean {
const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256k1InterfaceBIP32 = {
isPoint: (p: Uint8Array): boolean => isPoint(p, false),
isPrivate: (d: Uint8Array): boolean => necc.utils.isValidSecretKey(d),
isPrivate: (d: Uint8Array): boolean => {
/* if (
[
'0000000000000000000000000000000000000000000000000000000000000000',
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141',
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142',
].includes(d.toString('hex'))
) {
return false;
} */
return necc.utils.isValidPrivateKey(d);
},
isXOnlyPoint: (p: Uint8Array): boolean => isPoint(p, true),
xOnlyPointAddTweak: (p: Uint8Array, tweak: Uint8Array): { parity: 0 | 1; xOnlyPubkey: Uint8Array } | null =>
throwToNull(() => {
const P = tweakUtils.pointAddScalar(p, tweak, true);
const P = necc.utils.pointAddScalar(p, tweak, true);
const parity = P[0] % 2 === 1 ? 1 : 0;
return { parity, xOnlyPubkey: P.slice(1) };
}),
@ -143,56 +104,60 @@ const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256
throwToNull(() => necc.getPublicKey(sk, defaultTrue(compressed))),
pointCompress: (p: Uint8Array, compressed?: boolean): Uint8Array => {
return pointFromBytes(p).toBytes(defaultTrue(compressed));
return necc.Point.fromHex(p).toRawBytes(defaultTrue(compressed));
},
pointMultiply: (a: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null =>
throwToNull(() => tweakUtils.pointMultiply(a, tweak, defaultTrue(compressed))),
throwToNull(() => necc.utils.pointMultiply(a, tweak, defaultTrue(compressed))),
pointAdd: (a: Uint8Array, b: Uint8Array, compressed?: boolean): Uint8Array | null =>
throwToNull(() => {
const A = pointFromBytes(a);
const B = pointFromBytes(b);
return A.add(B).toBytes(defaultTrue(compressed));
const A = necc.Point.fromHex(a);
const B = necc.Point.fromHex(b);
return A.add(B).toRawBytes(defaultTrue(compressed));
}),
pointAddScalar: (p: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null =>
throwToNull(() => tweakUtils.pointAddScalar(p, tweak, defaultTrue(compressed))),
throwToNull(() => necc.utils.pointAddScalar(p, tweak, defaultTrue(compressed))),
privateAdd: (d: Uint8Array, tweak: Uint8Array): Uint8Array | null =>
throwToNull(() => {
// console.log({ d, tweak });
if (d.join('') === '00000000000000000000000000000001' && tweak.join('') === '00000000000000000000000000000000') {
return new Uint8Array(d); // make test_ecc happy
}
const ret = tweakUtils.privateAdd(d, tweak);
const ret = necc.utils.privateAdd(d, tweak);
// console.log(ret);
if (ret.join('') === '00000000000000000000000000000000') {
return null;
}
return ret;
}),
privateNegate: (d: Uint8Array): Uint8Array => tweakUtils.privateNegate(d),
privateNegate: (d: Uint8Array): Uint8Array => necc.utils.privateNegate(d),
sign: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => {
return necc.sign(h, d, { prehash: false, extraEntropy: e });
return necc.signSync(h, d, { der: false, extraEntropy: e });
},
signDER: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => {
return compactToDER(necc.sign(h, d, { prehash: false, extraEntropy: e }));
return necc.signSync(h, d, { der: true, extraEntropy: e });
},
signSchnorr: (h: Uint8Array, d: Uint8Array, e: Uint8Array = new Uint8Array(32).fill(0x00)): Uint8Array => {
return necc.schnorr.sign(h, d, e);
return necc.schnorr.signSync(h, d, e);
},
verify: (h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean => {
return necc.verify(signature, h, Q, { prehash: false, lowS: strict !== false });
return necc.verify(signature, h, Q, { strict });
},
verifySchnorr: (h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean => {
return necc.schnorr.verify(signature, h, Q);
return necc.schnorr.verifySync(signature, h, Q);
},
};
export default ecc;
// module.exports.ecc = ecc;

View File

@ -8,16 +8,16 @@ import {
Notifications,
} from 'react-native-notifications';
import { checkNotifications, requestNotifications, RESULTS } from 'react-native-permissions';
import type { BoltzReverseSwap } from '@arkade-os/boltz-swap';
import loc from '../loc';
import { arkadePaymentPushUri, groundControlUri } from './constants';
import { groundControlUri } from './constants';
import { fetch } from '../util/fetch';
const PUSH_TOKEN = 'PUSH_TOKEN';
const GROUNDCONTROL_BASE_URI = 'GROUNDCONTROL_BASE_URI';
const NOTIFICATIONS_STORAGE = 'NOTIFICATIONS_STORAGE';
const ANDROID_NOTIFICATION_CHANNEL_ID = 'channel_01';
export const NOTIFICATIONS_NO_AND_DONT_ASK_FLAG = 'NOTIFICATIONS_NO_AND_DONT_ASK_FLAG';
const baseURI = groundControlUri;
let baseURI = groundControlUri;
let notificationSubscriptions: EmitterSubscription[] = [];
let onProcessNotificationsHandler: undefined | (() => void | Promise<void>);
const handledNotificationKeys = new Set<string>();
@ -252,29 +252,6 @@ export const tryToObtainPermissions = async (): Promise<boolean> => {
return false;
}
};
export const enqueueTestPushNotification = async (): Promise<void> => {
const pushToken = await getPushToken();
if (!pushToken?.token || !pushToken?.os) {
throw new Error('No push token available');
}
const response = await fetch(`${baseURI}/enqueue`, {
method: 'POST',
headers: _getHeaders(),
body: JSON.stringify({
type: 5,
token: pushToken.token,
os: pushToken.os,
text: 'Test push notification',
}),
});
if (!response.ok) {
throw new Error(`Enqueue request failed with status ${response.status}: ${response.statusText}`);
}
};
/**
* Submits onchain bitcoin addresses and ln invoice preimage hashes to GroundControl server, so later we could
* be notified if they were paid
@ -350,44 +327,6 @@ export const majorTomToGroundControl = async (addresses: string[], hashes: strin
}
};
/**
* Registers an Ark swap with the bitcoin-payment-push-service so the device is
* pushed when the invoice gets paid. Fire-and-forget: never throws, gated by
* the same opt-out/token rules as majorTomToGroundControl(). The swap's
* preimage is always stripped before leaving the device.
*/
export const registerArkPaymentPush = async (paymentHash: string, label: string, pendingSwap: BoltzReverseSwap): Promise<void> => {
if (!arkadePaymentPushUri) return;
try {
const noAndDontAskFlag = await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG);
if (noAndDontAskFlag === 'true') {
console.warn('User has opted out of notifications.');
return;
}
const pushToken = await getPushToken();
if (!pushToken || !pushToken.token || !pushToken.os) {
return;
}
const response = await fetch(`${arkadePaymentPushUri}/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
topic: paymentHash,
label,
swap: { ...pendingSwap, preimage: '' },
}),
});
if (!response.ok) {
throw new Error(`status ${response.status}`);
}
console.log('[ARK] payment push registration ok');
} catch (e: any) {
console.log('[ARK] payment push registration failed:', e?.message ?? e);
}
};
/**
* Returns a permissions object:
* alert: boolean
@ -590,6 +529,22 @@ const configureNotifications = async (onProcessNotifications?: () => void): Prom
}
};
/**
* Validates whether the provided GroundControl URI is valid by pinging it.
*
* @param uri {string}
* @returns {Promise<boolean>} TRUE if valid, FALSE otherwise
*/
export const isGroundControlUriValid = async (uri: string) => {
try {
const response = await fetch(`${uri}/ping`, { headers: _getHeaders() });
const json = await response.json();
return !!json.description;
} catch (_) {
return false;
}
};
export const isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
export const getPushToken = async (): Promise<TPushToken> => {
@ -721,6 +676,38 @@ export const removeAllDeliveredNotifications = () => {
Notifications.removeAllDeliveredNotifications();
};
export const getDefaultUri = () => {
return groundControlUri;
};
export const saveUri = async (uri: string) => {
try {
baseURI = uri || groundControlUri;
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, baseURI);
} catch (error) {
console.error('Error saving URI:', error);
throw error;
}
};
export const getSavedUri = async () => {
try {
const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI);
if (baseUriStored) {
baseURI = baseUriStored;
}
return baseUriStored;
} catch (e) {
console.error(e);
try {
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri);
} catch (storageError) {
console.error('Failed to reset URI:', storageError);
}
throw e;
}
};
export const isNotificationsEnabled = async () => {
try {
const levels = await getLevels();
@ -770,6 +757,10 @@ export const initializeNotifications = async (onProcessNotifications?: () => voi
return;
}
const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI);
baseURI = baseUriStored || groundControlUri;
console.log('Base URI set to:', baseURI);
setApplicationIconBadgeNumber(0);
// Only check permissions, never request
@ -790,5 +781,7 @@ export const initializeNotifications = async (onProcessNotifications?: () => voi
}
} catch (error) {
console.error('Failed to initialize notifications:', error);
baseURI = groundControlUri;
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri).catch(err => console.error('Failed to reset URI:', err));
}
};

View File

@ -23,7 +23,7 @@ export const startAndDecrypt = async (retry?: boolean, passwordPrompt?: Password
password = await passwordPrompt();
} else {
do {
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, { cancelable: false });
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false);
} while (!password);
}
}

View File

@ -1,41 +0,0 @@
// Display state for the transaction detail screen.
//
// On-chain rows (a real Bitcoin txid is present in `hash`) keep the existing
// confirmations-based logic. Ark/Lightning rows synthesized by
// LightningArkWallet.getTransactions() carry no on-chain `hash` and never a
// `confirmations` field, so their state is derived from row semantics instead.
// The off-chain branch mirrors the off-chain cases of
// components/TransactionListItem.tsx `listTitleKey` so the list row and the detail
// screen always agree. A `boarding-utxo-` row is a refill still awaiting
// settlement and is pending (matches TransactionListItem.isPendingRefill); a
// settled `boarding-` refill is a confirmed receive. Today only `bitcoind_tx` Ark
// rows reach the detail screen (swap rows route to LNDViewInvoice); the invoice
// cases are handled defensively.
export type TxDisplayState = 'pending' | 'sent' | 'received';
export function isOnChainTransaction(tx: any): boolean {
return typeof tx?.hash === 'string' && tx.hash.length > 0;
}
export function resolveTxDisplayState(tx: any): TxDisplayState {
if (isOnChainTransaction(tx)) {
const confs = Number(tx?.confirmations);
const pending = Number.isFinite(confs) ? confs <= 0 : !tx?.confirmations;
if (pending) return 'pending';
return Number(tx?.value) < 0 ? 'sent' : 'received';
}
// A refill awaiting settlement (boarding UTXO not yet swept into a VTXO) is
// pending until it promotes to a settled `boarding-<txid>` refill — mirror
// TransactionListItem.isPendingRefill so the list row and detail screen agree.
if (typeof tx?.txid === 'string' && tx.txid.startsWith('boarding-utxo-')) return 'pending';
// Off-chain Ark/Lightning row — never confirmations-based.
switch (tx?.type) {
case 'paid_invoice':
return 'sent';
case 'user_invoice':
case 'payment_request':
return tx?.ispaid ? 'received' : 'pending';
default: // settled refill (boarding-<txid>), native Ark legs (ark-), any other hash-less row
return Number(tx?.value) < 0 ? 'sent' : 'received';
}
}

View File

@ -147,10 +147,11 @@ export class BlueApp {
console.warn('error reading', key, error.message);
console.warn('fallback to realm');
const realmKeyValue = await this.openRealmKeyValue();
const obj = realmKeyValue.objectForPrimaryKey<{ key: string; value: string }>('KeyValue', key);
const obj = realmKeyValue.objectForPrimaryKey('KeyValue', key); // search for a realm object with a primary key
value = obj?.value;
realmKeyValue.close();
if (value) {
// @ts-ignore value.length
console.warn('successfully recovered', value.length, 'bytes from realm for key', key);
return value;
}
@ -546,11 +547,10 @@ export class BlueApp {
(walletToInflate._txs_by_internal_index[tx.index] as Transaction[]).push(transaction);
}
} else {
// Legacy single-address wallets - store under index 0
walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || {};
walletToInflate._txs_by_external_index[0] = walletToInflate._txs_by_external_index[0] || [];
if (!Array.isArray(walletToInflate._txs_by_external_index)) walletToInflate._txs_by_external_index = [];
walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || [];
const transaction = JSON.parse(tx.tx);
walletToInflate._txs_by_external_index[0].push(transaction);
(walletToInflate._txs_by_external_index as Transaction[]).push(transaction);
}
}
}
@ -559,6 +559,32 @@ export class BlueApp {
const id = wallet.getID();
const walletToSave = ('_hdWalletInstance' in wallet && wallet._hdWalletInstance) || wallet;
if (Array.isArray(walletToSave._txs_by_external_index)) {
// if this var is an array that means its a single-address wallet class, and this var is a flat array
// with transactions
realm.write(() => {
// cleanup all existing transactions for the wallet first
const walletTransactionsToDelete = realm.objects('WalletTransactions').filtered(`walletid = '${id}'`);
realm.delete(walletTransactionsToDelete);
// @ts-ignore walletToSave._txs_by_external_index is array
for (const tx of walletToSave._txs_by_external_index) {
realm.create(
'WalletTransactions',
{
walletid: id,
tx: JSON.stringify(tx),
},
Realm.UpdateMode.Modified,
);
}
});
return;
}
/// ########################################################################################################
if (walletToSave._txs_by_external_index) {
realm.write(() => {
// cleanup all existing transactions for the wallet first
@ -566,14 +592,16 @@ export class BlueApp {
realm.delete(walletTransactionsToDelete);
// insert new ones:
for (const [indexStr, txs] of Object.entries(walletToSave._txs_by_external_index)) {
for (const index of Object.keys(walletToSave._txs_by_external_index)) {
// @ts-ignore index is number
const txs = walletToSave._txs_by_external_index[index];
for (const tx of txs) {
realm.create(
'WalletTransactions',
{
walletid: id,
internal: false,
index: parseInt(indexStr, 10),
index: parseInt(index, 10),
tx: JSON.stringify(tx),
},
Realm.UpdateMode.Modified,
@ -581,14 +609,16 @@ export class BlueApp {
}
}
for (const [indexStr, txs] of Object.entries(walletToSave._txs_by_internal_index)) {
for (const index of Object.keys(walletToSave._txs_by_internal_index)) {
// @ts-ignore index is number
const txs = walletToSave._txs_by_internal_index[index];
for (const tx of txs) {
realm.create(
'WalletTransactions',
{
walletid: id,
internal: true,
index: parseInt(indexStr, 10),
index: parseInt(index, 10),
tx: JSON.stringify(tx),
},
Realm.UpdateMode.Modified,

View File

@ -390,7 +390,7 @@ export class HDSegwitBech32Transaction {
}
}
// Non-null assertions are safe here because the while loop always runs at least once (add starts at 0)
return { tx: tx!, inputs: inputs!, outputs: outputs!, fee: fee! };
// @ts-ignore stfu
return { tx, inputs, outputs, fee };
}
}

View File

@ -2,7 +2,7 @@ import { bech32 } from 'bech32';
import bolt11 from 'bolt11';
import { sha256 } from '@noble/hashes/sha256';
import { hmac } from '@noble/hashes/hmac';
import { cbc } from '@noble/ciphers/aes';
import CryptoJS from 'crypto-js';
import ecc from '../blue_modules/noble_ecc';
import { parse } from 'url'; // eslint-disable-line n/no-deprecated-api
import { fetch } from '../util/fetch';
@ -321,24 +321,13 @@ export default class Lnurl {
}
static decipherAES(ciphertextBase64: string, preimageHex: string, ivBase64: string): string {
// crypto-js's old implementation silently returned '' on malformed
// ciphertext (non-16-aligned bytes, bad PKCS7 padding) and threw on
// malformed UTF-8 plaintext. @noble/ciphers throws on the former. We
// catch every throw and return '' — the call site at
// screen/lnd/lnurlPaySuccess.tsx renders this directly without a
// try/catch, so a misbehaving LNURL server should not crash the screen.
// Note: unlike crypto-js's strict `enc.Utf8` decoder, `uint8ArrayToString`
// is lenient on bad UTF-8 (mojibake instead of throw); this is strictly
// safer than the old behaviour for this user-facing path.
try {
const key = hexToUint8Array(preimageHex);
const iv = base64ToUint8Array(ivBase64);
const ct = base64ToUint8Array(ciphertextBase64);
const pt = cbc(key, iv).decrypt(ct);
return uint8ArrayToString(pt);
} catch (_) {
return '';
}
const iv = CryptoJS.enc.Base64.parse(ivBase64);
const key = CryptoJS.enc.Hex.parse(preimageHex);
return CryptoJS.AES.decrypt(uint8ArrayToHex(base64ToUint8Array(ciphertextBase64)), key, {
iv,
mode: CryptoJS.mode.CBC,
format: CryptoJS.format.Hex,
}).toString(CryptoJS.enc.Utf8);
}
getCommentAllowed(): number | false {

View File

@ -106,31 +106,23 @@ export class MultisigCosigner {
this._valid = false;
}
// is it coldcard / unchained json?
// is it coldcard json?
try {
const json = JSON.parse(data);
// p2wsh_p2sh (Coldcard), p2sh_p2wsh (Unchained)
// same script type with reversed naming
const xpub = json.p2wsh_p2sh || json.p2sh_p2wsh;
const path = (json.p2wsh_p2sh_deriv || json.p2sh_p2wsh_deriv)?.replace(/h/g, "'");
const p2sh_deriv = json.p2sh_deriv?.replace(/h/g, "'");
const p2wsh_deriv = json.p2wsh_deriv?.replace(/h/g, "'");
if (json.p2sh && p2sh_deriv && json.xfp) {
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2sh, p2sh_deriv));
if (json.p2sh && json.p2sh_deriv && json.xfp) {
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2sh, json.p2sh_deriv));
this._valid = true;
this._cosigners.push(cc);
}
if (xpub && path && json.xfp) {
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, xpub, path));
if (json.p2wsh_p2sh && json.p2wsh_p2sh_deriv && json.xfp) {
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2wsh_p2sh, json.p2wsh_p2sh_deriv));
this._valid = true;
this._cosigners.push(cc);
}
if (json.p2wsh && p2wsh_deriv && json.xfp) {
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2wsh, p2wsh_deriv));
if (json.p2wsh && json.p2wsh_deriv && json.xfp) {
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2wsh, json.p2wsh_deriv));
this._valid = true;
this._cosigners.push(cc);
}

View File

@ -11,7 +11,7 @@
* @return {Promise.<Uint8Array>} The random bytes
*/
export async function randomBytes(size: number): Promise<Uint8Array> {
const g = globalThis as any;
const g: any = globalThis as any;
const rnCrypto = g && g.crypto;
if (!rnCrypto || typeof rnCrypto.getRandomValues !== 'function') {
throw new Error('crypto.getRandomValues is not available');

View File

@ -216,35 +216,10 @@ const startImport = (
if (text.startsWith('arkade://')) {
const ark = new LightningArkWallet();
ark.setSecret(text);
// Defer init() to first wallet open when offline — init touches the ASP
// and delegator over the network. We still detect the wallet by prefix
// and persist it with its secret.
// A network or SDK failure during init must not abort the import: the
// wallet type and secret are known, and the SDK runtime can be brought
// up the next time the wallet is opened.
await ark.init();
if (!offline) {
try {
await ark.init();
// Restore any previous Boltz swap activity for this seed exactly
// once, here at import time. We never run this on later wallet
// opens — the app does not sweep all swaps on bootstrap. A failure
// must not block the import: the wallet itself is fine, the
// restored rows are an optional bonus for imported-from-elsewhere
// wallets.
try {
await ark.restoreSwaps();
} catch (e: any) {
console.log('[wallet-import] restoreSwaps failed:', e?.message ?? e);
}
try {
await ark.fetchBalance();
await ark.fetchTransactions();
} catch (e: any) {
console.log('[wallet-import] initial Ark sync failed:', e?.message ?? e);
}
} catch (e: any) {
console.log('[wallet-import] Ark init failed; deferring to next open:', e?.message ?? e);
}
await ark.fetchBalance();
await ark.fetchTransactions();
}
yield { wallet: ark };
}
@ -344,7 +319,6 @@ const startImport = (
}
yield { progress: 'wif' };
const segwitWallet = new SegwitP2SHWallet();
segwitWallet.setSecret(text);
if (segwitWallet.getAddress()) {
@ -408,89 +382,6 @@ const startImport = (
yield { wallet: legacyWallet };
}
yield { progress: 'Private key in hex/base64' };
// check if text is in hex or base64 format
const isHexKey = /^[0-9a-fA-F]{64}$/.test(text);
const isBase64Key = /^[A-Za-z0-9+/=]{43,44}$/.test(text);
let rawKeyBuffer;
let privateKey;
if (isHexKey) {
rawKeyBuffer = Buffer.from(text, 'hex');
} else if (isBase64Key) {
rawKeyBuffer = Buffer.from(text, 'base64');
}
if (rawKeyBuffer && rawKeyBuffer.length === 32) {
let walletFound = false;
// convert the bytes to Wallet import format, 0x80 for mainnet,
// start with uncompressed p2pkh
privateKey = wif.encode(0x80, rawKeyBuffer, false);
yield { progress: 'p2pkh uncompressed' };
const legacyWalletUncompressed = new LegacyWallet('Legacy (P2PKH) - Uncompressed');
legacyWalletUncompressed.setSecret(privateKey);
if (await wasUsed(legacyWalletUncompressed)) {
await fetch(legacyWalletUncompressed, true);
walletFound = true;
yield { wallet: legacyWalletUncompressed };
}
// compressed is true for other wallet types
privateKey = wif.encode(0x80, rawKeyBuffer, true);
yield { progress: 'p2wpkh' };
const segwitBech32Wallet = new SegwitBech32Wallet();
segwitBech32Wallet.setSecret(privateKey);
if (await wasUsed(segwitBech32Wallet)) {
await fetch(segwitBech32Wallet, true);
walletFound = true;
yield { wallet: segwitBech32Wallet };
}
yield { progress: 'p2tr' };
const taprootWallet = new TaprootWallet();
taprootWallet.setSecret(privateKey);
if (await wasUsed(taprootWallet)) {
await fetch(taprootWallet, true);
walletFound = true;
yield { wallet: taprootWallet };
}
yield { progress: 'p2wpkh-p2sh' };
segwitWallet.setSecret(privateKey);
if (await wasUsed(segwitWallet)) {
await fetch(segwitWallet, true);
walletFound = true;
yield { wallet: segwitWallet };
}
yield { progress: 'p2pkh compressed' };
const legacyWalletCompressed = new LegacyWallet('Legacy (P2PKH) - Compressed');
legacyWalletCompressed.setSecret(privateKey);
if (await wasUsed(legacyWalletCompressed)) {
await fetch(legacyWalletCompressed, true);
walletFound = true;
yield { wallet: legacyWalletCompressed };
}
if (!walletFound) {
yield { wallet: segwitBech32Wallet };
yield { wallet: segwitWallet };
yield { wallet: legacyWalletCompressed };
yield { wallet: taprootWallet };
yield { wallet: legacyWalletUncompressed };
}
}
// maybe its a watch-only address?
yield { progress: 'watch only' };
const wo1 = new WatchOnlyWallet();

View File

@ -45,7 +45,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
_balances_by_external_index: Record<number, BalanceByIndex>;
_balances_by_internal_index: Record<number, BalanceByIndex>;
// @ts-ignore
_txs_by_external_index: Record<number, Transaction[]>;
// @ts-ignore
_txs_by_internal_index: Record<number, Transaction[]>;
_utxo: any[];
@ -202,37 +204,70 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
return child.toWIF();
}
_getNodeByIndex(node: 0 | 1, index: number): BIP32Interface {
const cachedNode = node === 0 ? this._node0 : this._node1;
if (cachedNode) {
return cachedNode.derive(index);
_getNodeAddressByIndex(node: number, index: number): string {
index = index * 1; // cast to int
if (node === 0) {
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
}
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub).derive(node);
if (node === 1) {
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
}
if (node === 0 && !this._node0) {
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node0 = hdNode.derive(node);
}
if (node === 1 && !this._node1) {
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node1 = hdNode.derive(node);
}
let address: string;
if (node === 0) {
// @ts-ignore
address = this._hdNodeToAddress(this._node0.derive(index));
} else {
// tbh the only possible else is node === 1
// @ts-ignore
address = this._hdNodeToAddress(this._node1.derive(index));
}
if (node === 0) {
this._node0 = hdNode;
return (this.external_addresses_cache[index] = address);
} else {
this._node1 = hdNode;
// tbh the only possible else option is node === 1
return (this.internal_addresses_cache[index] = address);
}
}
_getNodePubkeyByIndex(node: number, index: number) {
index = index * 1; // cast to int
if (node === 0 && !this._node0) {
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node0 = hdNode.derive(node);
}
return hdNode.derive(index);
}
if (node === 1 && !this._node1) {
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node1 = hdNode.derive(node);
}
_getNodeAddressByIndex(node: 0 | 1, index: number): string {
const cache = node === 0 ? this.external_addresses_cache : this.internal_addresses_cache;
if (node === 0 && this._node0) {
return this._node0.derive(index).publicKey;
}
if (cache[index]) return cache[index]; // cache hit
if (node === 1 && this._node1) {
return this._node1.derive(index).publicKey;
}
const hdNode = this._getNodeByIndex(node, index);
const address = this._hdNodeToAddress(hdNode);
return (cache[index] = address);
}
_getNodePubkeyByIndex(node: 0 | 1, index: number) {
return this._getNodeByIndex(node, index).publicKey;
throw new Error('Internal error: this._node0 or this._node1 is undefined');
}
_getExternalAddressByIndex(index: number): string {
@ -389,95 +424,137 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
// now, we need to put transactions in all relevant `cells` of internal hashmaps:
// this._txs_by_internal_index, this._txs_by_external_index & this._txs_by_payment_code_index
// address -> index lookup maps; the single pass over transactions below uses them
// to find which cells a transaction belongs to
const externalIndexByAddress = new Map<string, number>();
for (let c = 0; c < next_free_address_index + this.gap_limit; c++) {
externalIndexByAddress.set(this._getExternalAddressByIndex(c), c);
for (const tx of Object.values(txdatas)) {
for (const vin of tx.vin) {
if (vin.addresses && vin.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) {
// this TX is related to our address
this._txs_by_external_index[c] = this._txs_by_external_index[c] || [];
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = {
...txRest,
inputs: txVin.slice(0),
outputs: txVout.slice(0),
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
};
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false;
for (let cc = 0; cc < this._txs_by_external_index[c].length; cc++) {
if (this._txs_by_external_index[c][cc].txid === clonedTx.txid) {
replaced = true;
this._txs_by_external_index[c][cc] = clonedTx;
}
}
if (!replaced) this._txs_by_external_index[c].push(clonedTx);
}
}
for (const vout of tx.vout) {
if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) {
// this TX is related to our address
this._txs_by_external_index[c] = this._txs_by_external_index[c] || [];
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = {
...txRest,
inputs: txVin.slice(0),
outputs: txVout.slice(0),
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
};
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false;
for (let cc = 0; cc < this._txs_by_external_index[c].length; cc++) {
if (this._txs_by_external_index[c][cc].txid === clonedTx.txid) {
replaced = true;
this._txs_by_external_index[c][cc] = clonedTx;
}
}
if (!replaced) this._txs_by_external_index[c].push(clonedTx);
}
}
}
}
const internalIndexByAddress = new Map<string, number>();
for (let c = 0; c < next_free_change_address_index + this.gap_limit; c++) {
internalIndexByAddress.set(this._getInternalAddressByIndex(c), c);
for (const tx of Object.values(txdatas)) {
for (const vin of tx.vin) {
if (vin.addresses && vin.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) {
// this TX is related to our address
this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || [];
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = {
...txRest,
inputs: txVin.slice(0),
outputs: txVout.slice(0),
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
};
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false;
for (let cc = 0; cc < this._txs_by_internal_index[c].length; cc++) {
if (this._txs_by_internal_index[c][cc].txid === clonedTx.txid) {
replaced = true;
this._txs_by_internal_index[c][cc] = clonedTx;
}
}
if (!replaced) this._txs_by_internal_index[c].push(clonedTx);
}
}
for (const vout of tx.vout) {
if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) {
// this TX is related to our address
this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || [];
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = {
...txRest,
inputs: txVin.slice(0),
outputs: txVout.slice(0),
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
};
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false;
for (let cc = 0; cc < this._txs_by_internal_index[c].length; cc++) {
if (this._txs_by_internal_index[c][cc].txid === clonedTx.txid) {
replaced = true;
this._txs_by_internal_index[c][cc] = clonedTx;
}
}
if (!replaced) this._txs_by_internal_index[c].push(clonedTx);
}
}
}
}
const paymentCodeIndexByAddress = new Map<string, { pc: string; c: number }>();
for (const pc of this._receive_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) {
paymentCodeIndexByAddress.set(this._getBIP47AddressReceive(pc, c), { pc, c });
}
}
for (const tx of Object.values(txdatas)) {
// since we are iterating PCs who can pay us, we can completely ignore `tx.vin` and only iterate `tx.vout`
for (const vout of tx.vout) {
if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getBIP47AddressReceive(pc, c)) !== -1) {
// this TX is related to our address
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = {
...txRest,
inputs: txVin.slice(0),
outputs: txVout.slice(0),
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
};
// per-cell txid -> position lookup, used to replace-or-push a transaction into a cell in constant time
const cellPositionsByTxid = new Map<Transaction[], Map<string, number>>();
const getCellPositions = (cell: Transaction[]): Map<string, number> => {
let positions = cellPositionsByTxid.get(cell);
if (!positions) {
positions = new Map();
for (let cc = 0; cc < cell.length; cc++) positions.set(cell[cc].txid, cc);
cellPositionsByTxid.set(cell, positions);
}
return positions;
};
for (const tx of Object.values(txdatas)) {
// collecting which of our address `cells` this transaction touches:
const externalCells = new Set<number>();
const internalCells = new Set<number>();
const paymentCodeCells = new Map<string, { pc: string; c: number }>();
const matchAddress = (address: string, isVout: boolean) => {
const externalIndex = externalIndexByAddress.get(address);
if (externalIndex !== undefined) externalCells.add(externalIndex);
const internalIndex = internalIndexByAddress.get(address);
if (internalIndex !== undefined) internalCells.add(internalIndex);
if (isVout) {
// since we are iterating PCs who can pay us, we can completely ignore `tx.vin` and only check `tx.vout`
const paymentCodeIndex = paymentCodeIndexByAddress.get(address);
if (paymentCodeIndex) paymentCodeCells.set(address, paymentCodeIndex);
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false;
for (let cc = 0; cc < this._txs_by_payment_code_index[pc][c].length; cc++) {
if (this._txs_by_payment_code_index[pc][c][cc].txid === clonedTx.txid) {
replaced = true;
this._txs_by_payment_code_index[pc][c][cc] = clonedTx;
}
}
if (!replaced) this._txs_by_payment_code_index[pc][c].push(clonedTx);
}
}
}
};
for (const vin of tx.vin) {
for (const address of vin.addresses ?? []) matchAddress(address, false);
}
for (const vout of tx.vout) {
for (const address of vout.scriptPubKey.addresses ?? []) matchAddress(address, true);
}
if (externalCells.size === 0 && internalCells.size === 0 && paymentCodeCells.size === 0) continue;
// this TX is related to our address(es)
const upsertClone = (cell: Transaction[]) => {
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = {
...txRest,
inputs: txVin.slice(0),
outputs: txVout.slice(0),
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
};
// trying to replace tx if it exists already (because it has lower confirmations, for example)
const positions = getCellPositions(cell);
const existingPosition = positions.get(clonedTx.txid);
if (existingPosition !== undefined) {
cell[existingPosition] = clonedTx;
} else {
positions.set(clonedTx.txid, cell.length);
cell.push(clonedTx);
}
};
for (const c of externalCells) {
this._txs_by_external_index[c] = this._txs_by_external_index[c] || [];
upsertClone(this._txs_by_external_index[c]);
}
for (const c of internalCells) {
this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || [];
upsertClone(this._txs_by_internal_index[c]);
}
for (const { pc, c } of paymentCodeCells.values()) {
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
upsertClone(this._txs_by_payment_code_index[pc][c]);
}
}
@ -524,7 +601,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
const ret: Transaction[] = [];
for (const tx of txs) {
tx.timestamp = tx.blocktime || Math.floor(+new Date() / 1000) - 30; // fallback for unconfirmed
tx.timestamp = tx.blocktime;
if (!tx.blocktime) tx.timestamp = Math.floor(+new Date() / 1000) - 30; // unconfirmed
tx.confirmations = tx.confirmations || 0; // unconfirmed
tx.hash = tx.txid;
tx.value = 0;
@ -575,7 +653,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
let lastHistoriesWithUsedAddresses = null;
for (let c = 0; c < Math.round(index / this.gap_limit); c++) {
const histories = await BlueElectrum.multiGetHistoryByAddress(gerenateChunkAddresses(c));
if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) {
// @ts-ignore
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c;
lastHistoriesWithUsedAddresses = histories;
@ -617,7 +696,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
let lastHistoriesWithUsedAddresses = null;
for (let c = 0; c < Math.round(index / this.gap_limit); c++) {
const histories = await BlueElectrum.multiGetHistoryByAddress(gerenateChunkAddresses(c));
if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) {
// @ts-ignore
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c;
lastHistoriesWithUsedAddresses = histories;
@ -659,7 +739,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
let lastHistoriesWithUsedAddresses = null;
for (let c = 0; c < Math.round(index / this.gap_limit); c++) {
const histories = await BlueElectrum.multiGetHistoryByAddress(generateChunkAddresses(c));
if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) {
// @ts-ignore
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c;
lastHistoriesWithUsedAddresses = histories;

View File

@ -315,7 +315,7 @@ export class AbstractHDWallet extends LegacyWallet {
throw new Error('Not implemented');
}
_getNodePubkeyByIndex(node: 0 | 1, index: number): Uint8Array | undefined {
_getNodePubkeyByIndex(node: number, index: number): Uint8Array | undefined {
throw new Error('Not implemented');
}

View File

@ -21,20 +21,14 @@ bitcoin.initEccLib(ecc);
*/
export class LegacyWallet extends AbstractWallet {
static readonly type = 'legacy';
static readonly defaultTypeReadable = 'Legacy (P2PKH)';
static readonly typeReadable = 'Legacy (P2PKH)';
// @ts-ignore: override
public readonly type = LegacyWallet.type;
// @ts-ignore: override
public readonly typeReadable: string;
public readonly typeReadable = LegacyWallet.typeReadable;
_txs_by_external_index: Record<number, Transaction[]> = {};
_txs_by_internal_index: Record<number, Transaction[]> = {};
constructor(typeReadable?: string) {
super();
this.typeReadable = typeReadable ?? LegacyWallet.defaultTypeReadable;
}
_txs_by_external_index: Transaction[] = [];
_txs_by_internal_index: Transaction[] = [];
/**
* Simple function which says that we havent tried to fetch balance
@ -344,14 +338,14 @@ export class LegacyWallet extends AbstractWallet {
}
}
this._txs_by_external_index = { 0: _txsByExternalIndex };
this._txs_by_external_index = _txsByExternalIndex;
this._lastTxFetch = +new Date();
}
getTransactions(): Transaction[] {
// a hacky code reuse from electrum HD wallet:
this._txs_by_external_index = this._txs_by_external_index || {};
this._txs_by_internal_index = {};
this._txs_by_external_index = this._txs_by_external_index || [];
this._txs_by_internal_index = [];
const { HDSegwitBech32Wallet } = require('./hd-segwit-bech32-wallet') as {
HDSegwitBech32Wallet: typeof HDSegwitBech32WalletT;

File diff suppressed because it is too large Load Diff

View File

@ -104,11 +104,6 @@ export type LightningTransaction = {
timestamp: number; // seconds, not milliseconds
expire_time?: number;
ispaid?: boolean;
// Terminal non-success state (failed/refunded/expired swap). Distinct from
// `ispaid:false`, which on its own only means "not settled yet" and is also
// true for in-flight rows. Consumers that gate on pending vs. dead state
// (e.g. the wallet-card pending pill) must treat `failed` rows as terminal.
failed?: boolean;
walletID?: string;
value?: number;
amt?: number;
@ -128,11 +123,10 @@ export type Transaction = {
locktime: number;
inputs: TransactionInput[];
outputs: TransactionOutput[];
// Confirmation-only fields: absent on mempool (unconfirmed) responses.
blockhash?: string;
confirmations?: number;
time?: number;
blocktime?: number;
blockhash: string;
confirmations: number;
time: number;
blocktime: number;
timestamp: number; // seconds, not milliseconds
value?: number;

View File

@ -197,13 +197,12 @@ export class WatchOnlyWallet extends LegacyWallet {
async fetchUtxo() {
if (this._hdWalletInstance) return this._hdWalletInstance.fetchUtxo();
// Single-address watch-only uses LegacyWallet UTXO + derivation from txs (no HD instance).
return super.fetchUtxo();
throw new Error('Not initialized');
}
getUtxo(...args: Parameters<THDWalletForWatchOnly['getUtxo']>) {
if (this._hdWalletInstance) return this._hdWalletInstance.getUtxo(...args);
return super.getUtxo(...args);
throw new Error('Not initialized');
}
combinePsbt(...args: Parameters<THDWalletForWatchOnly['combinePsbt']>) {

View File

@ -8,7 +8,7 @@ import loc from '../loc';
import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
type AddWalletButtonProps = {
onPress: (event: GestureResponderEvent) => void;
onPress?: (event: GestureResponderEvent) => void;
};
const AddWalletButton: React.FC<AddWalletButtonProps> = ({ onPress }) => {

View File

@ -94,8 +94,6 @@ const styles = StyleSheet.create({
flex: 1,
paddingHorizontal: 8,
minHeight: 33,
fontSize: 15,
lineHeight: 19,
},
});

View File

@ -1,20 +1,22 @@
import Clipboard from '@react-native-clipboard/clipboard';
import BigNumber from 'bignumber.js';
import dayjs from 'dayjs';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
import {
Text,
Image,
Platform,
LayoutAnimation,
NativeSyntheticEvent,
Pressable,
StyleSheet,
Text,
TextInput,
TextInputProps,
TextInputSelectionChangeEvent,
TextInputSelectionChangeEventData,
TouchableOpacity,
View,
} from 'react-native';
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated';
import Badge from './Badge';
import Icon from './Icon';
import BigNumber from 'bignumber.js';
import dayjs from 'dayjs';
import {
CurrencyRate,
@ -26,12 +28,10 @@ import {
updateExchangeRate,
} from '../blue_modules/currency';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
import { BlueText } from '../BlueComponents';
import confirm from '../helpers/confirm';
import loc, { formatBalancePlain, formatBalanceWithoutSuffix, removeTrailingZeros } from '../loc';
import { BitcoinUnit } from '../models/bitcoinUnits';
import Badge from './Badge';
import BlueText from './BlueText';
import Icon from './Icon';
import { useTheme } from './themes';
export const conversionCache: { [key: string]: string } = {};
@ -44,23 +44,6 @@ export const setCachedSatoshis = (amount: string, sats: string): void => {
conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] = sats;
};
const INPUT_HORIZONTAL_PADDING = 6;
const INPUT_VERTICAL_PADDING = 2;
const MAX_INPUT_WIDTH = 320;
const CRYPTO_CONTAINER_OFFSET = -12;
const SWAP_ICON_SIZE = 24;
const CHAR_FADE_IN_DURATION_MS = 240;
const CHAR_FADE_OUT_DURATION_MS = 160;
const CHAR_LAYOUT_DURATION_MS = 180;
const SIZER_LAYOUT_DURATION_MS = 200;
const androidFontPaddingStyle = Platform.OS === 'android' ? { includeFontPadding: false } : null;
const sizerLayoutTransition = LinearTransition.duration(SIZER_LAYOUT_DURATION_MS).easing(Easing.out(Easing.quad));
const charLayoutTransition = LinearTransition.duration(CHAR_LAYOUT_DURATION_MS).easing(Easing.out(Easing.quad));
const charEntering = FadeIn.duration(CHAR_FADE_IN_DURATION_MS);
const charExiting = FadeOut.duration(CHAR_FADE_OUT_DURATION_MS);
type AmountInputProps = Omit<TextInputProps, 'onChangeText' | 'value'> & {
/**
* Whether the input is in a loading state
@ -112,7 +95,6 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
isLoading = false,
maxSendableAmount,
isMaxAmountEstimate,
style: styleOverride,
...otherProps
} = props;
const [isRateBeingUpdatedLocal, setIsRateBeingUpdatedLocal] = useState(false);
@ -129,23 +111,6 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
}
}, [unit]);
const displayAmount = useMemo(() => {
if (amount === BitcoinUnit.MAX) {
return loc.units.MAX;
}
return parseFloat(amount) >= 0 ? String(amount) : undefined;
}, [amount]);
const inputFontSize = useMemo(() => (amount.length > 10 ? 20 : 36), [amount.length]);
const measureAmountText = displayAmount && displayAmount.length > 0 ? displayAmount : '0';
const inputTextAlign = useMemo((): 'left' | 'right' | 'center' => {
if (amount === BitcoinUnit.MAX) return 'center';
return unit === BitcoinUnit.LOCAL_CURRENCY ? 'left' : 'right';
}, [amount, unit]);
const secondaryDisplayCurrency = useMemo(() => {
if (amount === BitcoinUnit.MAX) {
return '';
@ -175,6 +140,7 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
useEffect(() => {
(async () => {
if (await isRateOutdated()) {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
const recent = await mostRecentFetchedRate();
setOutdatedRefreshRate(recent);
}
@ -214,6 +180,7 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
* here we must recalculate old amont value (which was denominated in `previousUnit`) to new denomination `newUnit`
* and fill this value in input box, so user can switch between, for example, 0.001 BTC <=> 100000 sats
*/
const log = `${amount}(${previousUnit}) ->`;
let sats: string = '0';
switch (previousUnit) {
case BitcoinUnit.BTC:
@ -232,6 +199,7 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
}
const newInputValue = formatBalancePlain(+sats, newUnit, false);
console.log(`${log} ${sats}(sats) -> ${newInputValue}(${newUnit})`);
if (newUnit === BitcoinUnit.LOCAL_CURRENCY && previousUnit === BitcoinUnit.SATS) {
// we cache conversion, so when we will need reverse conversion there wont be a rounding error
@ -301,7 +269,7 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
}, [maxSendableAmount]);
const handleSelectionChange = useCallback(
(event: TextInputSelectionChangeEvent) => {
(event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
const { selection } = event.nativeEvent;
if (selection.start !== selection.end || selection.start !== amount.length) {
textInputRef.current?.setNativeProps({ selection: { start: amount.length, end: amount.length } });
@ -310,120 +278,40 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
[amount],
);
const isCryptoUnit = unit !== BitcoinUnit.LOCAL_CURRENCY;
const amountCharacters = useMemo(() => measureAmountText.split(''), [measureAmountText]);
const displayJustifyContent = useMemo((): 'flex-start' | 'flex-end' | 'center' => {
if (inputTextAlign === 'right') return 'flex-end';
if (inputTextAlign === 'left') return 'flex-start';
return 'center';
}, [inputTextAlign]);
const inputTextColor = disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2;
const hiddenInputTextColor = Platform.OS === 'android' ? `${inputTextColor}00` : 'transparent';
const inputTypography = {
fontSize: inputFontSize,
lineHeight: Math.round(inputFontSize * 1.15),
minHeight: Math.round(inputFontSize * 1.15) + INPUT_VERTICAL_PADDING * 2,
textAlign: inputTextAlign,
...(isCryptoUnit && {
paddingLeft: INPUT_HORIZONTAL_PADDING + 4,
}),
};
const stylesHook = {
container: {
marginLeft: unit === BitcoinUnit.LOCAL_CURRENCY ? 0 : CRYPTO_CONTAINER_OFFSET,
},
localCurrency: { color: inputTextColor },
input: {
color: inputTextColor,
...inputTypography,
},
inputDisplay: {
justifyContent: displayJustifyContent,
...(isCryptoUnit && {
paddingLeft: INPUT_HORIZONTAL_PADDING + 4,
}),
},
inputGlyph: {
color: inputTextColor,
fontSize: inputTypography.fontSize,
lineHeight: inputTypography.lineHeight,
},
inputTransparent: {
color: hiddenInputTextColor,
},
cryptoCurrency: { color: inputTextColor },
};
const stylesHook = StyleSheet.create({
center: { padding: amount === BitcoinUnit.MAX ? 0 : 15 },
localCurrency: { color: disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2 },
input: { color: disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2, fontSize: amount.length > 10 ? 20 : 36 },
cryptoCurrency: { color: disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2 },
});
return (
<Pressable accessibilityRole="button" accessibilityLabel={loc._.enter_amount} disabled={disabled} onPress={handleTextInputOnPress}>
<View style={styles.root}>
{!disabled && <View style={styles.sideRail} />}
{!disabled && <View style={[styles.center, stylesHook.center]} />}
<View style={styles.flex}>
<View style={[styles.container, stylesHook.container]}>
<View style={styles.container}>
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
<Text style={[styles.localCurrency, stylesHook.localCurrency]}>{getCurrencySymbol()}</Text>
<Text style={[styles.localCurrency, stylesHook.localCurrency]}>{getCurrencySymbol() + ' '}</Text>
)}
{amount !== BitcoinUnit.MAX ? (
<Animated.View layout={sizerLayoutTransition} style={styles.inputSizer}>
<Text
style={[styles.input, styles.inputMeasure, stylesHook.input, androidFontPaddingStyle]}
numberOfLines={1}
allowFontScaling={false}
accessible={false}
importantForAccessibility="no-hide-descendants"
>
{measureAmountText}
</Text>
<Animated.View layout={charLayoutTransition} style={[styles.inputDisplay, stylesHook.inputDisplay]} pointerEvents="none">
{amountCharacters.map((char, index) => (
<Animated.Text
key={index}
entering={charEntering}
exiting={charExiting}
layout={charLayoutTransition}
allowFontScaling={false}
style={[styles.inputGlyph, stylesHook.inputGlyph, androidFontPaddingStyle]}
>
{char}
</Animated.Text>
))}
</Animated.View>
<TextInput
{...otherProps}
allowFontScaling={false}
underlineColorAndroid="transparent"
onSelectionChange={handleSelectionChange}
testID="BitcoinAmountInput"
keyboardType="numeric"
onChangeText={handleChangeText}
placeholder="0"
maxLength={maxLength}
ref={textInputRef}
editable={!isLoading && !disabled}
value={displayAmount}
placeholderTextColor={inputTextColor}
cursorColor={inputTextColor}
selectionColor={inputTextColor}
style={[
styles.input,
styles.inputOverlay,
stylesHook.input,
stylesHook.inputTransparent,
androidFontPaddingStyle,
styleOverride,
]}
/>
</Animated.View>
<TextInput
onSelectionChange={handleSelectionChange}
testID="BitcoinAmountInput"
keyboardType="numeric"
onChangeText={handleChangeText}
placeholder="0"
maxLength={maxLength}
ref={textInputRef}
editable={!isLoading && !disabled}
value={amount === BitcoinUnit.MAX ? loc.units.MAX : parseFloat(amount) >= 0 ? String(amount) : undefined}
placeholderTextColor={disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2}
style={[styles.input, stylesHook.input]}
{...otherProps}
/>
) : (
<Pressable onPress={resetAmount} style={styles.maxPressable}>
<Text numberOfLines={1} style={[styles.input, styles.maxLabel, stylesHook.input]}>
{BitcoinUnit.MAX}
</Text>
<Text style={[styles.input, stylesHook.input]}>{BitcoinUnit.MAX}</Text>
{maxSendableAmount != null && (
<Text style={[styles.maxEstimate, stylesHook.localCurrency]} onLongPress={copyMaxEstimate}>
{(isMaxAmountEstimate ? '≈ ' : '') +
@ -435,7 +323,7 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
</Pressable>
)}
{unit !== BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
<Text style={[styles.cryptoCurrency, stylesHook.cryptoCurrency]}>{loc.units[unit]}</Text>
<Text style={[styles.cryptoCurrency, stylesHook.cryptoCurrency]}>{' ' + loc.units[unit]}</Text>
)}
</View>
<View style={styles.secondaryRoot}>
@ -444,20 +332,17 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
</Text>
</View>
</View>
{!disabled &&
(amount !== BitcoinUnit.MAX ? (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.change_input_currency}
testID="changeAmountUnitButton"
style={[styles.sideRail, styles.changeAmountUnit]}
onPress={changeAmountUnit}
>
<Image source={require('../img/round-compare-arrows-24-px.png')} />
</TouchableOpacity>
) : (
<View style={styles.sideRail} />
))}
{!disabled && amount !== BitcoinUnit.MAX && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.change_input_currency}
testID="changeAmountUnitButton"
style={styles.changeAmountUnit}
onPress={changeAmountUnit}
>
<Image source={require('../img/round-compare-arrows-24-px.png')} />
</TouchableOpacity>
)}
</View>
{outdatedRefreshRate && (
<View style={styles.outdatedRateContainer}>
@ -470,7 +355,7 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
accessibilityLabel={loc._.refresh}
onPress={updateRate}
disabled={isRateBeingUpdatedLocal}
style={isRateBeingUpdatedLocal ? styles.disabledButton : undefined}
style={isRateBeingUpdatedLocal ? styles.disabledButton : styles.enabledButon}
>
<Icon name="arrows-rotate" type="font-awesome-6" size={16} color={colors.buttonAlternativeTextColor} />
</TouchableOpacity>
@ -485,15 +370,11 @@ const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'space-between',
},
center: {
alignSelf: 'center',
},
flex: {
flex: 1,
overflow: 'visible',
},
sideRail: {
width: SWAP_ICON_SIZE,
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
},
spacing8: {
width: 8,
@ -507,6 +388,9 @@ const styles = StyleSheet.create({
disabledButton: {
opacity: 0.5,
},
enabledButon: {
opacity: 1,
},
outdatedRateContainer: {
flexDirection: 'row',
justifyContent: 'center',
@ -515,55 +399,24 @@ const styles = StyleSheet.create({
},
container: {
flexDirection: 'row',
alignItems: 'center',
alignContent: 'space-between',
justifyContent: 'center',
paddingTop: 16,
paddingBottom: 2,
overflow: 'visible',
},
localCurrency: {
fontSize: 18,
marginRight: 2,
marginHorizontal: 4,
fontWeight: 'bold',
alignSelf: 'center',
justifyContent: 'center',
},
inputSizer: {
maxWidth: MAX_INPUT_WIDTH,
position: 'relative',
overflow: 'visible',
},
input: {
fontWeight: 'bold',
margin: 0,
borderWidth: 0,
paddingHorizontal: INPUT_HORIZONTAL_PADDING,
paddingVertical: INPUT_VERTICAL_PADDING,
},
inputGlyph: {
fontWeight: 'bold',
margin: 0,
padding: 0,
},
inputMeasure: {
opacity: 0,
},
inputDisplay: {
...StyleSheet.absoluteFill,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: INPUT_HORIZONTAL_PADDING,
paddingVertical: INPUT_VERTICAL_PADDING,
zIndex: 1,
},
inputOverlay: {
...StyleSheet.absoluteFill,
zIndex: 2,
},
cryptoCurrency: {
fontSize: 15,
marginLeft: 2,
marginHorizontal: 4,
fontWeight: '600',
alignSelf: 'center',
justifyContent: 'center',
@ -584,12 +437,11 @@ const styles = StyleSheet.create({
},
maxPressable: {
alignItems: 'center',
flexShrink: 0,
},
maxLabel: {
flexShrink: 0,
},
changeAmountUnit: {
alignSelf: 'center',
marginRight: 16,
paddingLeft: 16,
paddingVertical: 16,
},
});

View File

@ -0,0 +1,93 @@
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
import React, { useState } from 'react';
import { Keyboard, Pressable, StyleSheet, Text, View } from 'react-native';
import Icon from './Icon';
import loc from '../loc';
import { useTheme } from './themes';
interface IHash {
[key: string]: string;
}
type ArrowPickerProps = {
onChange: (key: string) => void;
items: IHash;
isItemUnknown: boolean;
};
export const ArrowPicker = (props: ArrowPickerProps) => {
const keys = Object.keys(props.items);
const [keyIndex, setKeyIndex] = useState(0);
const { colors } = useTheme();
const stylesHook = {
text: {
color: colors.foregroundColor,
},
};
return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Pressable
accessibilityRole="button"
accessibilityLabel={loc.send.dynamic_prev}
onPress={() => {
Keyboard.dismiss();
let newIndex = keyIndex;
if (keyIndex <= 0) {
newIndex = keys.length - 1;
} else {
newIndex--;
}
setKeyIndex(newIndex);
props.onChange(keys[newIndex]);
}}
style={({ pressed }) => [
{
backgroundColor: pressed ? 'rgb(210, 230, 255)' : 'white',
},
styles.wrapperCustom,
]}
>
<Icon size={24} name="chevron-back" type="ionicons" />
</Pressable>
<View style={{ width: 200 }}>
<Text style={[styles.text, stylesHook.text]}>{props.isItemUnknown ? loc.send.fee_custom : keys[keyIndex]}</Text>
</View>
<Pressable
accessibilityRole="button"
accessibilityLabel={loc.send.dynamic_next}
onPress={() => {
Keyboard.dismiss();
let newIndex = keyIndex;
if (keyIndex + 1 >= keys.length) {
newIndex = 0;
} else {
newIndex++;
}
setKeyIndex(newIndex);
props.onChange(keys[newIndex]);
}}
style={({ pressed }) => [
{
backgroundColor: pressed ? 'rgb(210, 230, 255)' : 'white',
},
styles.wrapperCustom,
]}
>
<Icon size={24} name="chevron-forward" type="ionicons" />
</Pressable>
</View>
);
};
const styles = StyleSheet.create({
wrapperCustom: {
borderRadius: 8,
padding: 5,
marginLeft: 20,
marginRight: 20,
},
text: { fontWeight: 'bold', fontSize: 12, textAlign: 'center' },
});

View File

@ -4,13 +4,13 @@ import Icon, { type IconProps } from './Icon';
export interface AvatarProps {
rounded?: boolean;
size: number;
containerStyle: StyleProp<ViewStyle>;
size?: number;
containerStyle?: StyleProp<ViewStyle>;
icon?: Pick<IconProps, 'name' | 'type' | 'color' | 'size'>;
onPress?: () => void;
}
const Avatar: React.FC<AvatarProps> = ({ rounded, size, containerStyle, icon, onPress }) => {
const Avatar: React.FC<AvatarProps> = ({ rounded, size = 40, containerStyle, icon, onPress }) => {
const dimensionStyle = { width: size, height: size, borderRadius: rounded ? size / 2 : 0 } as ViewStyle;
const content = (
<View style={[styles.container, dimensionStyle, containerStyle]}>

View File

@ -5,12 +5,11 @@ export interface BadgeProps {
value?: string | number | React.ReactNode;
badgeStyle?: StyleProp<ViewStyle>;
textStyle?: StyleProp<TextStyle>;
testID?: string;
}
const Badge: React.FC<BadgeProps> = ({ value, badgeStyle, textStyle, testID }) => {
const Badge: React.FC<BadgeProps> = ({ value, badgeStyle, textStyle }) => {
return (
<View testID={testID} style={[styles.badge, badgeStyle]}>
<View style={[styles.badge, badgeStyle]}>
{typeof value === 'string' || typeof value === 'number' ? <Text style={[styles.text, textStyle]}>{value}</Text> : value}
</View>
);

View File

@ -1,34 +0,0 @@
import React, { forwardRef } from 'react';
import { Pressable, PressableProps, StyleSheet, Text } from 'react-native';
import { useTheme } from './themes';
interface BlueButtonLinkProps extends PressableProps {
title: string;
}
const BlueButtonLink = forwardRef<React.ElementRef<typeof Pressable>, BlueButtonLinkProps>((props, ref) => {
const { colors } = useTheme();
return (
<Pressable accessibilityRole="button" style={({ pressed }) => [styles.blueButtonLink, pressed && styles.pressed]} {...props} ref={ref}>
<Text style={[styles.blueButtonLinkText, { color: colors.foregroundColor }]}>{props.title}</Text>
</Pressable>
);
});
const styles = StyleSheet.create({
blueButtonLink: {
minWidth: 100,
minHeight: 36,
justifyContent: 'center',
},
blueButtonLinkText: {
textAlign: 'center',
fontSize: 16,
},
pressed: {
opacity: 0.6,
},
});
export default BlueButtonLink;

View File

@ -1,14 +0,0 @@
import React from 'react';
import { StyleSheet, View, ViewProps } from 'react-native';
const BlueCard: React.FC<ViewProps> = props => {
return <View {...props} style={styles.blueCard} />;
};
const styles = StyleSheet.create({
blueCard: {
padding: 20,
},
});
export default BlueCard;

View File

@ -1,21 +0,0 @@
import { useLocale } from '@react-navigation/native';
import React from 'react';
import { StyleSheet, Text, TextProps } from 'react-native';
import { useTheme } from './themes';
const BlueFormLabel: React.FC<TextProps> = props => {
const { colors } = useTheme();
const { direction } = useLocale();
return <Text {...props} style={[styles.blueFormLabel, { color: colors.foregroundColor, writingDirection: direction }]} />;
};
const styles = StyleSheet.create({
blueFormLabel: {
fontWeight: '400',
marginHorizontal: 20,
},
});
export default BlueFormLabel;

View File

@ -1,50 +0,0 @@
import React from 'react';
import { Platform, StyleSheet, TextInput, TextInputProps } from 'react-native';
import { useTheme } from './themes';
const BlueFormMultiInput: React.FC<TextInputProps> = props => {
const { colors } = useTheme();
const { style, editable, ...restProps } = props;
return (
<TextInput
multiline
underlineColorAndroid="transparent"
numberOfLines={4}
editable={editable}
style={[
styles.blueFormMultiInput,
{
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
color: colors.foregroundColor,
},
style,
]}
autoCorrect={false}
autoCapitalize="none"
spellCheck={false}
{...restProps}
selectTextOnFocus={false}
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
/>
);
};
const styles = StyleSheet.create({
blueFormMultiInput: {
paddingHorizontal: 8,
paddingVertical: 16,
flex: 1,
marginTop: 5,
marginHorizontal: 20,
borderWidth: 1,
borderBottomWidth: 0.5,
borderRadius: 4,
textAlignVertical: 'top',
},
});
export default BlueFormMultiInput;

View File

@ -1,62 +0,0 @@
import { useLocale } from '@react-navigation/native';
import React from 'react';
import { StyleSheet, Text, TextProps } from 'react-native';
import { useTheme } from './themes';
interface BlueTextProps extends TextProps {
bold?: boolean;
h1?: boolean;
h2?: boolean;
h3?: boolean;
h4?: boolean;
}
const BlueText: React.FC<BlueTextProps> = ({ bold = false, h1, h2, h3, h4, style: passedStyle, ...props }) => {
const { colors } = useTheme();
const { direction } = useLocale();
let headingStyle = {};
if (h1) {
headingStyle = styles.h1;
} else if (h2) {
headingStyle = styles.h2;
} else if (h3) {
headingStyle = styles.h3;
} else if (h4) {
headingStyle = styles.h4;
}
const hasHeading = h1 || h2 || h3 || h4;
const style = StyleSheet.compose(
{
color: colors.foregroundColor,
writingDirection: direction,
fontWeight: hasHeading ? undefined : bold ? 'bold' : 'normal',
...headingStyle,
},
passedStyle,
);
return <Text style={style} {...props} />;
};
const styles = StyleSheet.create({
h1: {
fontSize: 40,
fontWeight: 'bold',
},
h2: {
fontSize: 34,
fontWeight: 'bold',
},
h3: {
fontSize: 28,
fontWeight: 'bold',
},
h4: {
fontSize: 22,
fontWeight: 'bold',
},
});
export default BlueText;

View File

@ -1,17 +0,0 @@
import React from 'react';
import { StyleSheet, Text, TextProps } from 'react-native';
import { useTheme } from './themes';
const BlueTextCentered: React.FC<TextProps> = props => {
const { colors } = useTheme();
return <Text {...props} style={[styles.blueTextCentered, { color: colors.foregroundColor }]} />;
};
const styles = StyleSheet.create({
blueTextCentered: {
textAlign: 'center',
},
});
export default BlueTextCentered;

View File

@ -220,11 +220,7 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = React.m
useEffect(() => {
if (walletsInitialized) {
if (isElectrumDisabled) {
BlueElectrum.forceDisconnect();
} else {
BlueElectrum.ensureConnected({ showAlertOnFailure: true });
}
isElectrumDisabled ? BlueElectrum.forceDisconnect() : BlueElectrum.connectMain();
}
}, [isElectrumDisabled, walletsInitialized]);

View File

@ -1,14 +1,13 @@
import React, { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { LayoutAnimation } from 'react-native';
import { BlueApp as BlueAppClass, TCounterpartyMetadata, TTXMetadata } from '../../class/blue-app';
import { LegacyWallet } from '../../class/wallets/legacy-wallet';
import { LightningArkWallet } from '../../class/wallets/lightning-ark-wallet';
import { WatchOnlyWallet } from '../../class/wallets/watch-only-wallet';
import type { TWallet } from '../../class/wallets/types';
import presentAlert from '../../components/Alert';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { registerArkBackgroundTask, stopArkBackgroundTask } from '../../blue_modules/arkade-background';
import { startAndDecrypt } from '../../blue_modules/start-and-decrypt';
import { isNotificationsEnabled, majorTomToGroundControl, unsubscribe } from '../../blue_modules/notifications';
import { BitcoinUnit } from '../../models/bitcoinUnits';
@ -176,15 +175,6 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
const deleteWallet = useCallback((wallet: TWallet) => {
BlueApp.deleteWallet(wallet);
setWallets([...BlueApp.getWallets()]);
if (wallet.type === LightningArkWallet.type) {
// Fire-and-forget: cleans up the per-wallet Arkade Realm (close + delete files)
// and the Keychain encryption key. Errors stay scoped to the Ark wallet path
// and never block deletion.
(wallet as LightningArkWallet).onDelete().catch(e => console.warn('[StorageProvider] Ark wallet cleanup failed:', e?.message ?? e));
if (!BlueApp.getWallets().some(w => w.type === LightningArkWallet.type)) {
stopArkBackgroundTask().catch(e => console.warn('[StorageProvider] Ark background task stop failed:', e?.message ?? e));
}
}
}, []);
const handleWalletDeletion = useCallback(
@ -318,11 +308,7 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
if (walletsInitialized) {
txMetadata.current = BlueApp.tx_metadata;
counterpartyMetadata.current = BlueApp.counterparty_metadata;
const loaded = BlueApp.getWallets();
setWallets(loaded);
if (loaded.some(w => w.type === LightningArkWallet.type)) {
registerArkBackgroundTask().catch(e => console.warn('[StorageProvider] Ark background task register failed:', e?.message ?? e));
}
setWallets(BlueApp.getWallets());
}
}, [walletsInitialized]);
@ -346,21 +332,24 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL);
}
console.debug('[refreshAllWalletTransactions] Waiting for connectivity...');
// `ensureConnected()` ping-checks the existing socket and, only if needed,
// tears it down and reconnects. Replaces the old wait+ping+wait pattern
// which surfaced false "network error" alerts after iOS suspend/resume.
const connected = await BlueElectrum.ensureConnected();
if (!connected) {
console.log('[refreshAllWalletTransactions] could not establish Electrum connection, aborting refresh');
return;
await BlueElectrum.waitTillConnected();
if (!(await BlueElectrum.ping())) {
// above `waitTillConnected` is not reliable, as app might have returned from long sleep, so it thinks its
// connected but actually socket is closed. thus, we ping, and if it fails - we wait again (reconnection code
// should pick up)
console.log('[refreshAllWalletTransactions] ping failed, waiting for connection...');
await BlueElectrum.waitTillConnected();
}
console.debug('[refreshAllWalletTransactions] Connected to Electrum');
// Race only the post-connect work. We budget ample time so that a slow
// initial Electrum connection (cold start, slow TLS, flaky network) doesn't
// cause the fetch race to abort prematurely.
const REFRESH_FETCH_PHASE_TIMEOUT_MS = Math.max(120_000, BlueElectrum.ENSURE_CONNECTED_MAX_WALL_MS * 2);
// Race only the post-connect work. `waitTillConnected` can take up to
// WAIT_TILL_CONNECTED_MAX_WALL_MS_NEVER (+ a second wait); starting the timer earlier caused refresh to abort
// while Electrum was still legitimately connecting.
const REFRESH_FETCH_PHASE_TIMEOUT_MS = Math.max(
120_000,
BlueElectrum.WAIT_TILL_CONNECTED_MAX_WALL_MS_NEVER + BlueElectrum.WAIT_TILL_CONNECTED_MAX_WALL_MS_AFTER_FIRST,
);
const timeoutPromise = new Promise<never>(
(_resolve, reject) =>
(refreshTimeout = setTimeout(() => {
@ -430,11 +419,7 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
}
_lastTimeTriedToRefetchWallet[walletID] = Date.now();
const connected = await BlueElectrum.ensureConnected();
if (!connected) {
console.log('[fetchAndSaveWalletTransactions] could not establish Electrum connection, aborting');
return;
}
await BlueElectrum.waitTillConnected();
setWalletTransactionUpdateStatus(walletID);
const balanceStart = Date.now();
@ -468,9 +453,6 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
if (w.getLabel() === emptyWalletLabel) w.setLabel(loc.wallets.import_imported + ' ' + w.typeReadable);
w.setUserHasSavedExport(true);
addWallet(w);
if (w instanceof LightningArkWallet) {
registerArkBackgroundTask().catch(e => console.warn('[StorageProvider] Ark background task register failed:', e?.message ?? e));
}
if (getScanWasBBQR()) {
// to avoid proxying `useBBQR` through a bunch of screens during import procedure, we use a trick:
// on add-wallet screen we reset `lastScanWasBBQR` to false. then potentially user scans QR in BBQR format
@ -509,6 +491,7 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
text: loc.wallets.details_delete,
onPress: () => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
onConfirmed();
},
style: 'destructive',

View File

@ -3,7 +3,7 @@ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo
import { StyleSheet, Text, TextProps, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
import BlueText from './BlueText';
import { BlueText } from '../BlueComponents';
import loc from '../loc';
import { useTheme } from './themes';

View File

@ -1,7 +1,7 @@
import React from 'react';
import { InputAccessoryView, Keyboard, Platform, StyleSheet, View } from 'react-native';
import { useTheme } from './themes';
import BlueButtonLink from './BlueButtonLink';
import { BlueButtonLink } from '../BlueComponents';
import loc from '../loc';
export const DismissKeyboardInputAccessoryViewID = 'DismissKeyboardInputAccessory';

View File

@ -1,6 +1,6 @@
import React from 'react';
import { InputAccessoryView, Keyboard, Platform, StyleSheet, View } from 'react-native';
import BlueButtonLink from './BlueButtonLink';
import { BlueButtonLink } from '../BlueComponents';
import loc from '../loc';
import { useTheme } from './themes';
import Clipboard from '@react-native-clipboard/clipboard';

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { Dimensions, LayoutAnimation, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { encodeUR } from '../blue_modules/ur';
import { BlueCurrentTheme } from '../components/themes';
@ -159,6 +159,7 @@ export class DynamicQRCode extends Component<DynamicQRCodeProps, DynamicQRCodeSt
accessibilityRole="button"
testID="DynamicCode"
onPress={() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState(prevState => ({ hideControls: !prevState.hideControls }));
}}
>

View File

@ -52,14 +52,7 @@ const useFloatButtonAnimation = (initialHeight: number) => {
};
};
const getScaledButtonHeight = (fontScale: number): number => Math.round(LAYOUT.BUTTON_HEIGHT * fontScale);
/** Scroll padding so list content clears float buttons (excludes safe-area inset). Default 70 at fontScale 1. */
const FLOAT_BUTTON_LIST_CLEARANCE = 18;
export const getFloatingButtonReservedHeight = (fontScale = 1): number => getScaledButtonHeight(fontScale) + FLOAT_BUTTON_LIST_CLEARANCE;
const useFloatButtonLayout = (width: number, sizeClass: SizeClass, fontScale: number) => {
const useFloatButtonLayout = (width: number, sizeClass: SizeClass) => {
const lastVerticalDecision = useRef(false);
const shouldUseVerticalLayout = useCallback(
@ -159,19 +152,15 @@ const useFloatButtonLayout = (width: number, sizeClass: SizeClass, fontScale: nu
[width, sizeClass, shouldUseVerticalLayout],
);
const calculateContainerHeight = useCallback(
(childrenCount: number, isVerticalLayout: boolean) => {
const buttonHeight = getScaledButtonHeight(fontScale);
if (!isVerticalLayout) return { height: '8%', minHeight: buttonHeight };
const calculateContainerHeight = useCallback((childrenCount: number, isVerticalLayout: boolean) => {
if (!isVerticalLayout) return { height: '8%', minHeight: LAYOUT.BUTTON_HEIGHT };
const totalButtonsHeight = childrenCount * buttonHeight;
const totalMarginsHeight = (childrenCount - 1) * LAYOUT.BUTTON_MARGIN;
const calculatedHeight = totalButtonsHeight + totalMarginsHeight;
const totalButtonsHeight = childrenCount * LAYOUT.BUTTON_HEIGHT;
const totalMarginsHeight = (childrenCount - 1) * LAYOUT.BUTTON_MARGIN;
const calculatedHeight = totalButtonsHeight + totalMarginsHeight;
return { height: calculatedHeight };
},
[fontScale],
);
return { height: calculatedHeight };
}, []);
const calculateButtonFontSize = useMemo(() => {
const divisor = sizeClass === SizeClass.Large ? 22 : sizeClass === SizeClass.Regular ? 24 : 28;
@ -278,7 +267,6 @@ interface FButtonProps {
isVertical?: boolean;
borderRadius?: number;
fontSize?: number;
buttonHeight?: number;
disabled?: boolean;
testID?: string;
onPress: () => void;
@ -289,14 +277,13 @@ interface ButtonContentProps {
icon: ReactNode;
text: string;
textStyle: StyleProp<TextStyle>;
buttonHeight: number;
}
const getScaledIconSize = (fontSize: number): number => {
return Math.max(Math.round(fontSize * 1.2), 16);
};
const ButtonContent = ({ icon, text, textStyle, buttonHeight }: ButtonContentProps) => {
const ButtonContent = ({ icon, text, textStyle }: ButtonContentProps) => {
const computedStyle = StyleSheet.flatten(textStyle);
const fontSize = computedStyle.fontSize || LAYOUT.MAX_BUTTON_FONT_SIZE;
const iconSize = getScaledIconSize(Number(fontSize));
@ -320,14 +307,9 @@ const ButtonContent = ({ icon, text, textStyle, buttonHeight }: ButtonContentPro
}
return (
<View style={[buttonContentStaticStyles.contentContainer, { minHeight: buttonHeight }]}>
<View style={buttonContentStaticStyles.contentContainer}>
<View style={buttonStyles.iconContainer}>{scaledIcon}</View>
<Text
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.8}
style={[textStyle, buttonStyles.centeredText, { lineHeight: fontSize * 1.2 }]}
>
<Text numberOfLines={1} adjustsFontSizeToFit style={[textStyle, buttonStyles.centeredText, { lineHeight: fontSize * 1.2 }]}>
{text}
</Text>
</View>
@ -343,7 +325,6 @@ export const FButton = ({
isVertical,
borderRadius = LAYOUT.PILL_BORDER_RADIUS,
fontSize = LAYOUT.MAX_BUTTON_FONT_SIZE,
buttonHeight = LAYOUT.BUTTON_HEIGHT,
testID,
...props
}: FButtonProps) => {
@ -366,8 +347,6 @@ export const FButton = ({
return {
root: {
...baseStyles,
height: buttonHeight,
minHeight: buttonHeight,
backgroundColor: colors.buttonBackgroundColor,
},
text: {
@ -381,7 +360,7 @@ export const FButton = ({
marginBottom: buttonContentStaticStyles.marginBottom,
textBase: buttonContentStaticStyles.textBase,
};
}, [colors, fontSize, buttonHeight]);
}, [colors, fontSize]);
const style: Record<string, any> = {};
const additionalStyles = !last ? (isVertical ? customButtonStyles.marginBottom : customButtonStyles.marginRight) : {};
@ -418,7 +397,7 @@ export const FButton = ({
style={[buttonStyles.root, customButtonStyles.root, style, { borderRadius }]}
{...props}
>
<ButtonContent icon={icon} text={text} textStyle={textStyle} buttonHeight={buttonHeight} />
<ButtonContent icon={icon} text={text} textStyle={textStyle} />
</TouchableOpacity>
</Animated.View>
);
@ -426,9 +405,8 @@ export const FButton = ({
export const FContainer = forwardRef<View, FContainerProps>((props, ref) => {
const insets = useSafeAreaInsets();
const { height, width, fontScale } = useWindowDimensions();
const { height, width } = useWindowDimensions();
const { sizeClass } = useSizeClass();
const scaledButtonHeight = getScaledButtonHeight(fontScale);
const childrenCount = React.Children.toArray(props.children).filter(Boolean).length;
@ -441,7 +419,6 @@ export const FContainer = forwardRef<View, FContainerProps>((props, ref) => {
const { calculateButtonWidth, calculateVisualParameters, calculateContainerHeight, buttonFontSize } = useFloatButtonLayout(
width,
sizeClass,
fontScale,
);
// Compute initial geometry up-front so the slide-in animation starts at the final (computed) size,
@ -531,7 +508,7 @@ export const FContainer = forwardRef<View, FContainerProps>((props, ref) => {
useEffect(() => {
debouncedCalculateLayout();
}, [debouncedCalculateLayout, width, height, childrenCount, sizeClass, fontScale]);
}, [debouncedCalculateLayout, width, height, childrenCount, sizeClass]);
const onLayout = (event: { nativeEvent: { layout: { width: number } } }) => {
const { width: currentLayoutWidth } = event.nativeEvent.layout;
@ -568,7 +545,6 @@ export const FContainer = forwardRef<View, FContainerProps>((props, ref) => {
isVertical,
borderRadius: buttonBorderRadius,
fontSize: buttonFontSize,
buttonHeight: scaledButtonHeight,
});
};
@ -585,10 +561,10 @@ export const FContainer = forwardRef<View, FContainerProps>((props, ref) => {
props.inline ? containerStyles.rootInline : containerStyles.rootAbsolute,
bottomInsets,
effectiveNewWidth ? (isVertical ? containerStyles.rootPostVertical : containerStyles.rootPost) : containerStyles.rootPre,
isVertical ? containerHeight : { minHeight: scaledButtonHeight },
isVertical ? containerHeight : null,
{ transform: [{ translateY: slideAnimation }] },
],
[props.inline, bottomInsets, effectiveNewWidth, isVertical, containerHeight, slideAnimation, scaledButtonHeight],
[props.inline, bottomInsets, effectiveNewWidth, isVertical, containerHeight, slideAnimation],
);
return (

View File

@ -4,13 +4,13 @@ import { StyleSheet, Text, TouchableOpacity } from 'react-native';
import { useTheme } from './themes';
interface HeaderRightButtonProps {
disabled: boolean;
disabled?: boolean;
onPress?: () => void;
title: string;
testID?: string;
}
const HeaderRightButton: React.FC<HeaderRightButtonProps> = ({ disabled, onPress, title, testID }) => {
const HeaderRightButton: React.FC<HeaderRightButtonProps> = ({ disabled = true, onPress, title, testID }) => {
const { colors } = useTheme();
const opacity = disabled ? 0.5 : 1;
return (

View File

@ -1,6 +1,6 @@
import React from 'react';
import { InputAccessoryView, Keyboard, Platform, StyleSheet, Text, View } from 'react-native';
import BlueButtonLink from './BlueButtonLink';
import { BlueButtonLink } from '../BlueComponents';
import loc from '../loc';
import { BitcoinUnit } from '../models/bitcoinUnits';
import { useTheme } from './themes';

View File

@ -1,30 +1,24 @@
import React, { useMemo } from 'react';
import { Pressable, StyleProp, StyleSheet, Switch, SwitchProps, Text, TextStyle, useWindowDimensions, View, ViewStyle } from 'react-native';
import { Pressable, StyleProp, StyleSheet, Switch, SwitchProps, Text, TextStyle, View, ViewStyle } from 'react-native';
import { useLocale } from '@react-navigation/native';
import Icon from './Icon';
import { useTheme } from './themes';
/** Base row height for transaction list `getItemLayout` (padding + title + subtitle at fontScale 1). */
export const TX_ROW_BASE_HEIGHT = 64;
interface ListItemProps {
leftAvatar?: React.JSX.Element;
containerStyle?: StyleProp<ViewStyle>;
noFeedback?: boolean;
bottomDivider?: boolean;
testID?: string;
switchTestID?: string;
onPress?: () => void;
disabled?: boolean;
switch?: SwitchProps;
title: string;
titleStyle?: StyleProp<TextStyle>;
subtitle?: string | React.ReactNode;
subtitleNumberOfLines?: number;
rightTitle?: string;
rightTitleStyle?: StyleProp<TextStyle>;
rightTitleSelectable?: boolean;
rightSubtitle?: string | React.ReactNode;
rightSubtitleStyle?: StyleProp<TextStyle>;
chevron?: boolean;
@ -39,17 +33,14 @@ const ListItem: React.FC<ListItemProps> = React.memo(
noFeedback = false,
bottomDivider = true,
testID,
switchTestID,
onPress,
disabled,
switch: switchProps,
title,
titleStyle,
subtitle,
subtitleNumberOfLines,
rightTitle,
rightTitleStyle,
rightTitleSelectable,
rightSubtitle,
rightSubtitleStyle,
chevron,
@ -58,20 +49,12 @@ const ListItem: React.FC<ListItemProps> = React.memo(
}: ListItemProps) => {
const { colors } = useTheme();
const { direction } = useLocale();
const { fontScale } = useWindowDimensions();
const isRtl = direction === 'rtl';
const contentRowStyle = useMemo(
() => ({
paddingVertical: Math.round(12 * fontScale),
}),
[fontScale],
);
const stylesHook = StyleSheet.create({
title: {
color: disabled ? colors.buttonDisabledTextColor : colors.foregroundColor,
fontSize: 16,
fontWeight: '500',
lineHeight: Math.round(22 * fontScale),
writingDirection: direction,
},
rightMemoText: {
@ -83,7 +66,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
color: colors.alternativeTextColor,
fontWeight: '400',
paddingVertical: switchProps ? 8 : 0,
lineHeight: Math.round(20 * fontScale),
lineHeight: 20,
fontSize: 14,
marginTop: 2,
},
@ -100,11 +83,10 @@ const ListItem: React.FC<ListItemProps> = React.memo(
const memoizedSwitchProps = useMemo(() => {
return switchProps ? { ...switchProps } : undefined;
}, [switchProps]);
const resolvedSwitchTestID = switchTestID ?? memoizedSwitchProps?.testID;
const enableFeedback = !noFeedback && !!onPress && !disabled;
const renderContent = () => (
<View style={[styles.contentRow, contentRowStyle]}>
<View style={styles.contentRow}>
{leftAvatar && (
<View style={styles.leftAvatarContainer}>
{leftAvatar}
@ -112,7 +94,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
</View>
)}
<View style={styles.content}>
<Text style={[stylesHook.title, titleStyle]} numberOfLines={0} accessibilityRole="text">
<Text style={stylesHook.title} numberOfLines={0} accessibilityRole="text">
{title}
</Text>
{subtitle ? (
@ -125,14 +107,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
{rightTitle || rightSubtitle ? (
<View style={styles.rightColumn}>
{rightTitle ? (
<Text
style={rightTitleStyle}
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.75}
accessibilityRole="text"
selectable={rightTitleSelectable}
>
<Text style={rightTitleStyle} numberOfLines={1} accessibilityRole="text">
{rightTitle}
</Text>
) : null}
@ -149,14 +124,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
<Icon name={isRtl ? 'angle-left' : 'angle-right'} type="font-awesome" color={colors.alternativeTextColor} size={18} />
) : null}
{switchProps ? (
<Switch
{...memoizedSwitchProps}
testID={resolvedSwitchTestID}
accessibilityLabel={title}
style={styles.margin16}
accessible
accessibilityRole="switch"
/>
<Switch {...memoizedSwitchProps} accessibilityLabel={title} style={styles.margin16} accessible accessibilityRole="switch" />
) : null}
{checkmark ? (
<View style={styles.checkmarkContainer}>
@ -210,20 +178,16 @@ const styles = StyleSheet.create({
},
content: {
flex: 1,
flexShrink: 1,
minWidth: 0,
justifyContent: 'center',
},
leftAvatarContainer: {
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'center',
},
rightColumn: {
marginStart: 8,
flexShrink: 0,
minWidth: 0,
alignItems: 'flex-end',
alignSelf: 'center',
},
rightMemoWrapper: {
flexShrink: 1,

View File

@ -153,7 +153,6 @@ const ManageWalletsListItem: React.FC<ManageWalletsListItemProps> = ({
]}
onPress={onToggle}
accessibilityRole="button"
testID={isHidden ? 'SwipeShowBalance' : 'SwipeHideBalance'}
>
<Text style={[styles.rightActionText, { color: colors.buttonTextColor }]}>
{isHidden ? loc.wallets.swipe_balance_show : loc.wallets.swipe_balance_hide}

View File

@ -14,7 +14,7 @@ type ErrorCorrectionLevel = 'H' | 'Q' | 'M' | 'L';
interface QRCodeProps {
value: string;
size: number;
size?: number;
isLogoRendered?: boolean;
isMenuAvailable?: boolean;
logoSize?: number;
@ -144,7 +144,7 @@ const getCachedPlan = (value: string, ecl: ErrorCorrectionLevel, size: number, i
const QRCode: React.FC<QRCodeProps> = ({
value = '',
size,
size = 300,
isLogoRendered = true,
isMenuAvailable = true,
logoSize = 90,
@ -216,11 +216,24 @@ const QRCode: React.FC<QRCodeProps> = ({
const gradFill = `url(#${GRADIENT_ID})`;
const finderShapes: React.ReactElement[] = [];
const outerR = 2 * cell;
const holeR = 1.25 * cell;
const dotR = 0.9 * cell;
finderOrigins.forEach(([fr, fc], i) => {
const x = (fc + 1) * cell;
const y = (fr + 1) * cell;
finderShapes.push(
<Rect key={`finder-frame-${i}`} testID="qr-finder-frame" x={x} y={y} width={7 * cell} height={7 * cell} fill={gradFill} />,
<Rect
key={`finder-frame-${i}`}
testID="qr-finder-frame"
x={x}
y={y}
width={7 * cell}
height={7 * cell}
rx={outerR}
ry={outerR}
fill={gradFill}
/>,
<Rect
key={`finder-hole-${i}`}
testID="qr-finder-hole"
@ -228,6 +241,8 @@ const QRCode: React.FC<QRCodeProps> = ({
y={y + cell}
width={5 * cell}
height={5 * cell}
rx={holeR}
ry={holeR}
fill={BACKGROUND}
/>,
<Rect
@ -237,6 +252,8 @@ const QRCode: React.FC<QRCodeProps> = ({
y={y + 2 * cell}
width={3 * cell}
height={3 * cell}
rx={dotR}
ry={dotR}
fill={gradFill}
/>,
);
@ -260,7 +277,16 @@ const QRCode: React.FC<QRCodeProps> = ({
{finderShapes}
{isLogoRendered && logoCells > 0 && (
<>
<Rect testID="qr-logo-backdrop" x={backdropX} y={backdropY} width={backdropSize} height={backdropSize} fill={LOGO_BACKGROUND} />
<Rect
testID="qr-logo-backdrop"
x={backdropX}
y={backdropY}
width={backdropSize}
height={backdropSize}
rx={cell * 0.5}
ry={cell * 0.5}
fill={LOGO_BACKGROUND}
/>
<SvgImage
testID="qr-logo-image"
href={require('../img/qr-code.png')}

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { View, Text, TextInput, TouchableOpacity, Keyboard, StyleSheet } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import BlueText from './BlueText';
import { BlueText } from '../BlueComponents';
import loc, { formatStringAddTwoWhiteSpaces } from '../loc';
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from '../models/networkTransactionFees';
import { useTheme } from './themes';
@ -67,25 +67,26 @@ const ReplaceFeeSuggestions: React.FC<ReplaceFeeSuggestionsProps> = ({ onFeeSele
}, []);
const handleFeeSelection = (feeType: NetworkTransactionFeeType) => {
if (feeType === NetworkTransactionFeeType.CUSTOM) {
setSelectedFeeType(feeType);
return;
if (feeType !== NetworkTransactionFeeType.CUSTOM) {
Keyboard.dismiss();
}
Keyboard.dismiss();
if (networkFees) {
let selectedFee: number;
switch (feeType) {
case NetworkTransactionFeeType.FAST:
onFeeSelected(networkFees.fastestFee);
selectedFee = networkFees.fastestFee;
break;
case NetworkTransactionFeeType.MEDIUM:
onFeeSelected(networkFees.mediumFee);
selectedFee = networkFees.mediumFee;
break;
case NetworkTransactionFeeType.SLOW:
onFeeSelected(networkFees.slowFee);
selectedFee = networkFees.slowFee;
break;
case NetworkTransactionFeeType.CUSTOM:
selectedFee = Number(customFeeValue);
break;
}
onFeeSelected(selectedFee);
setSelectedFeeType(feeType);
}
};
@ -93,8 +94,7 @@ const ReplaceFeeSuggestions: React.FC<ReplaceFeeSuggestionsProps> = ({ onFeeSele
const handleCustomFeeChange = (customFee: string) => {
const sanitizedFee = customFee.replace(/[^0-9]/g, '');
setCustomFeeValue(sanitizedFee);
onFeeSelected(Number(sanitizedFee));
setSelectedFeeType(NetworkTransactionFeeType.CUSTOM);
handleFeeSelection(NetworkTransactionFeeType.CUSTOM);
};
return (
@ -156,10 +156,7 @@ const ReplaceFeeSuggestions: React.FC<ReplaceFeeSuggestionsProps> = ({ onFeeSele
ref={customTextInput}
maxLength={9}
style={[styles.customFeeInput, stylesHook.customFeeInput]}
onFocus={() => {
setSelectedFeeType(NetworkTransactionFeeType.CUSTOM);
onFeeSelected(Number(customFeeValue));
}}
onFocus={() => handleCustomFeeChange(customFeeValue)}
placeholder={loc.send.fee_satvbyte}
placeholderTextColor="#81868e"
inputAccessoryViewID={DismissKeyboardInputAccessoryViewID}

View File

@ -7,18 +7,10 @@ import { useTheme } from './themes';
interface SafeAreaScrollViewProps extends ScrollViewProps {
floatingButtonHeight?: number;
headerHeight?: number; // Additional header height to account for (e.g., when headerTransparent is true)
disableDefaultTopPadding?: boolean;
}
const SafeAreaScrollView = forwardRef<ScrollView, SafeAreaScrollViewProps>((props, ref) => {
const {
style,
contentContainerStyle,
floatingButtonHeight = 0,
headerHeight = 0,
disableDefaultTopPadding = false,
...otherProps
} = props;
const { style, contentContainerStyle, floatingButtonHeight = 0, headerHeight = 0, ...otherProps } = props;
const { colors } = useTheme();
const insets = useSafeAreaInsets();
@ -40,10 +32,7 @@ const SafeAreaScrollView = forwardRef<ScrollView, SafeAreaScrollViewProps>((prop
if (headerHeight > 0) {
return headerHeight;
}
if (disableDefaultTopPadding) {
return 0;
}
// Preserve legacy behavior for existing screens
// iOS safe area or no status bar
return insets.top > 0 ? 5 : 0;
})(),
};
@ -59,7 +48,7 @@ const SafeAreaScrollView = forwardRef<ScrollView, SafeAreaScrollViewProps>((prop
// Now compose with contentContainerStyle to ensure passed styles override defaults
return StyleSheet.compose(basePadding, contentContainerStyle);
}, [insets, contentContainerStyle, floatingButtonHeight, headerHeight, disableDefaultTopPadding]);
}, [insets, contentContainerStyle, floatingButtonHeight, headerHeight]);
return (
<ScrollView

View File

@ -10,8 +10,7 @@ type SecondButtonProps = {
backgroundColor?: string;
disabled?: boolean;
icon?: IconButtonProps;
title: string;
textColor?: string;
title?: string;
onPress?: () => void;
loading?: boolean;
testID?: string;
@ -20,7 +19,7 @@ type SecondButtonProps = {
export const SecondButton = forwardRef<React.ElementRef<typeof TouchableOpacity>, SecondButtonProps>((props, ref) => {
const { colors } = useTheme();
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonGrayBackgroundColor;
let fontColor = props.textColor ?? colors.secondButtonTextColor;
let fontColor = colors.secondButtonTextColor;
if (props.disabled === true) {
backgroundColor = colors.buttonDisabledBackgroundColor;
fontColor = colors.buttonDisabledTextColor;

View File

@ -0,0 +1,87 @@
import React, { FC } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useTheme } from './themes';
export const StyledButtonType: Record<string, string> = { default: 'default', destroy: 'destroy', grey: 'grey' };
interface StyledButtonProps {
onPress: () => void;
text: string;
disabled?: boolean;
buttonStyle?: keyof typeof StyledButtonType;
}
const StyledButton: FC<StyledButtonProps> = ({ onPress, text, disabled = false, buttonStyle = StyledButtonType.default }) => {
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
buttonGrey: {
backgroundColor: colors.lightButton,
},
textGray: {
color: colors.buttonTextColor,
},
container: {
opacity: disabled ? 0.5 : 1.0,
},
});
const textStyles = () => {
if (buttonStyle === StyledButtonType.grey) {
return stylesHook.textGray;
} else if (buttonStyle === StyledButtonType.destroy) {
return styles.textDestroy;
} else {
return styles.textDefault;
}
};
const buttonStyles = () => {
if (buttonStyle === StyledButtonType.grey) {
return stylesHook.buttonGrey;
} else if (buttonStyle === StyledButtonType.destroy) {
return styles.buttonDestroy;
} else {
return styles.buttonDefault;
}
};
return (
<TouchableOpacity accessibilityRole="button" onPress={onPress} disabled={disabled} style={stylesHook.container}>
<View style={[styles.buttonContainer, buttonStyles()]}>
<Text style={[styles.text, textStyles()]}>{text}</Text>
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
buttonContainer: {
borderRadius: 9,
minHeight: 49,
paddingHorizontal: 8,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
alignSelf: 'auto',
flexGrow: 1,
marginHorizontal: 4,
},
buttonDefault: {
backgroundColor: '#EBF2FB',
},
buttonDestroy: {
backgroundColor: '#FFF5F5',
},
text: {
fontWeight: '600',
fontSize: 15,
},
textDefault: {
color: '#1961B9',
},
textDestroy: {
color: '#D0021B',
},
});
export default StyledButton;

View File

@ -1,7 +1,8 @@
import React from 'react';
import { View, StyleSheet, ViewStyle } from 'react-native';
import { useTheme } from './themes';
import BlueText from './BlueText';
import { BlueText } from '../BlueComponents';
interface TipBoxProps {
number?: string;
title?: string;

View File

@ -89,23 +89,13 @@ const ToolTipMenu = (props: ToolTipMenuProps) => {
// Android gesture-cancel race documented above.
return (
<View
style={visibleStyle}
testID={testID}
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}
accessibilityRole={accessibilityRole}
accessibilityState={accessibilityState}
>
<ContextMenu
title={title}
previewBackgroundColor="transparent"
onPress={handlePressMenuItem}
actions={items}
dropdownMenuMode={!shouldOpenOnLongPress}
style={styles.menuFlex}
>
{children}
</ContextMenu>
{menu}
</View>
);
}

View File

@ -1,5 +1,5 @@
import React, { useMemo, useCallback } from 'react';
import { TouchableOpacity, Text, StyleSheet, View, useWindowDimensions } from 'react-native';
import { TouchableOpacity, Text, StyleSheet, LayoutAnimation, View } from 'react-native';
import { useStorage } from '../hooks/context/useStorage';
import loc, { formatBalanceWithoutSuffix } from '../loc';
import { BitcoinUnit } from '../models/bitcoinUnits';
@ -22,7 +22,6 @@ const TotalWalletsBalance: React.FC = React.memo(() => {
setTotalBalancePreferredUnitStorage,
} = useSettings();
const { colors } = useTheme();
const { fontScale } = useWindowDimensions();
const totalBalanceFormatted = useMemo(() => {
const totalBalance = wallets.reduce((prev, curr) => {
@ -32,22 +31,6 @@ const TotalWalletsBalance: React.FC = React.memo(() => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallets, totalBalancePreferredUnit, preferredFiatCurrency]);
const scaledStyles = useMemo(
() => ({
container: {
paddingVertical: Math.round(8 * fontScale),
},
label: {
lineHeight: Math.round(18 * fontScale),
marginBottom: Math.round(2 * fontScale),
},
balance: {
lineHeight: Math.round(38 * Math.max(1, fontScale)),
},
}),
[fontScale],
);
const toolTipActions = useMemo(
() => [
{
@ -72,6 +55,7 @@ const TotalWalletsBalance: React.FC = React.memo(() => {
const onPressMenuItem = useCallback(
async (id: string) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
switch (id) {
case CommonToolTipActions.ViewInFiat.id:
await setTotalBalancePreferredUnitStorage(BitcoinUnit.LOCAL_CURRENCY);
@ -96,6 +80,7 @@ const TotalWalletsBalance: React.FC = React.memo(() => {
);
const handleBalanceOnPress = useCallback(async () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
const nextUnit =
totalBalancePreferredUnit === BitcoinUnit.BTC
? BitcoinUnit.SATS
@ -109,20 +94,13 @@ const TotalWalletsBalance: React.FC = React.memo(() => {
return (
<ToolTipMenu actions={toolTipActions} onPressMenuItem={onPressMenuItem} shouldOpenOnLongPress style={styles.menuContainer}>
<View style={[styles.container, scaledStyles.container]}>
<Text style={[styles.label, scaledStyles.label]} numberOfLines={1} adjustsFontSizeToFit minimumFontScale={0.8}>
{loc.wallets.total_balance}
</Text>
<TouchableOpacity onPress={handleBalanceOnPress} style={styles.balanceTouchable}>
<Text
style={[styles.balance, scaledStyles.balance, { color: colors.foregroundColor }]}
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.55}
>
{totalBalanceFormatted}
<View style={styles.container}>
<Text style={styles.label}>{loc.wallets.total_balance}</Text>
<TouchableOpacity onPress={handleBalanceOnPress}>
<Text style={[styles.balance, { color: colors.foregroundColor }]}>
{totalBalanceFormatted}{' '}
{totalBalancePreferredUnit !== BitcoinUnit.LOCAL_CURRENCY && (
<Text style={[styles.currency, { color: colors.foregroundColor }]}>{` ${totalBalancePreferredUnit}`}</Text>
<Text style={[styles.currency, { color: colors.foregroundColor }]}>{totalBalancePreferredUnit}</Text>
)}
</Text>
</TouchableOpacity>
@ -140,11 +118,6 @@ const styles = StyleSheet.create({
alignItems: 'flex-start',
paddingHorizontal: 16,
paddingVertical: 8,
width: '100%',
},
balanceTouchable: {
alignSelf: 'stretch',
width: '100%',
},
label: {
fontSize: 14,
@ -154,7 +127,6 @@ const styles = StyleSheet.create({
balance: {
fontSize: 32,
fontWeight: 'bold',
lineHeight: 38,
},
currency: {
fontSize: 18,

View File

@ -1,9 +1,8 @@
import React, { memo, useCallback, useMemo, useRef } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import Clipboard from '@react-native-clipboard/clipboard';
import { Animated, Easing, Linking, Pressable, Text, TextStyle, ViewStyle, StyleSheet, View, useWindowDimensions } from 'react-native';
import { Animated, Easing, Linking, Pressable, Text, TextStyle, ViewStyle, StyleSheet, View } from 'react-native';
import Lnurl from '../class/lnurl';
import { LightningArkWallet } from '../class/wallets/lightning-ark-wallet';
import { LightningTransaction, Transaction } from '../class/wallets/types';
import TransactionExpiredIcon from '../components/icons/TransactionExpiredIcon';
import TransactionIncomingIcon from '../components/icons/TransactionIncomingIcon';
@ -29,6 +28,9 @@ import { uint8ArrayToHex } from '../blue_modules/uint8array-extras';
import ListItem from './ListItem';
const styles = StyleSheet.create({
dateLine: {
fontSize: 13,
},
fullWidthButton: {
width: '100%',
alignSelf: 'stretch',
@ -103,7 +105,7 @@ const AnimatedPressableRow: React.FC<AnimatedPressableRowProps> = ({ onPress, ch
};
interface TransactionListItemProps {
itemPriceUnit: BitcoinUnit;
itemPriceUnit?: BitcoinUnit;
walletID: string;
item: Transaction & LightningTransaction; // using type intersection to have less issues with ts
searchQuery?: string;
@ -117,7 +119,7 @@ type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList>;
const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
item,
itemPriceUnit,
itemPriceUnit = BitcoinUnit.BTC,
walletID,
searchQuery,
style,
@ -130,7 +132,6 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
const { txMetadata, counterpartyMetadata, wallets } = useStorage();
const { language, selectedBlockExplorer } = useSettings();
const insets = useSafeAreaInsets();
const { fontScale } = useWindowDimensions();
const containerStyle = useMemo(
() => ({
backgroundColor: colors.background,
@ -153,30 +154,7 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
const txMemo = (counterparty ? `[${shortenContactName(counterparty)}] ` : '') + (txMetadata[item.hash]?.memo ?? '');
const noteForCopy = (txMemo || item.memo || '').trim() || undefined;
// For LightningArkWallet rows, prepend a kind tag to the date subtitle. Such a
// wallet transacts entirely via Boltz swaps, so every row is Lightning; the
// only genuinely on-chain activity is onboarding/refill (boarding UTXOs),
// tagged from the synthetic `boarding-…` txid set in
// lightning-ark-wallet.getTransactions(). Other wallet types are unaffected.
const arkRowKind = useMemo<'Lightning' | 'Refill' | undefined>(() => {
const wallet = wallets.find(w => w.getID() === item.walletID);
if (wallet?.type !== LightningArkWallet.type) return undefined;
const txid = (item as { txid?: string }).txid;
if (txid?.startsWith('boarding-')) return 'Refill';
return 'Lightning';
}, [item, wallets]);
// A refill is "Pending" until the SDK settles its boarding UTXO into a VTXO
// (also when it enters the spendable balance). getTransactions() pass 2 tags
// those not-yet-settled rows with a `boarding-utxo-…` id; settled refills use
// `boarding-…` and render as a normal confirmed receive.
const isPendingRefill = useMemo(
() => arkRowKind === 'Refill' && !!(item as { txid?: string }).txid?.startsWith('boarding-utxo-'),
[arkRowKind, item],
);
const listTitleKey = useMemo((): 'pending' | 'sent' | 'received' => {
if (isPendingRefill) return 'pending';
if (item.category === 'receive' && item.confirmations! < 3) return 'pending';
if (item.type === 'bitcoind_tx') return item.value! < 0 ? 'sent' : 'received';
if (item.type === 'paid_invoice') return 'sent';
@ -186,7 +164,7 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
}
if (!item.confirmations) return 'pending';
return item.value! < 0 ? 'sent' : 'received';
}, [isPendingRefill, item.category, item.confirmations, item.type, item.value, item.ispaid]);
}, [item.category, item.confirmations, item.type, item.value, item.ispaid]);
const listTitle = useMemo(() => {
if (listTitleKey === 'pending') return loc.transactions.pending;
@ -197,11 +175,11 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
const isPending = listTitleKey === 'pending';
const dateLine = useMemo(() => {
const formatted = isPending ? transactionTimeToReadable(item.timestamp) : formatTransactionListDate(item.timestamp * 1000);
return arkRowKind ? `${arkRowKind} · ${formatted}` : formatted;
if (isPending) return transactionTimeToReadable(item.timestamp);
return formatTransactionListDate(item.timestamp * 1000);
// language in deps so date format updates when locale changes (formatters use global locale)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPending, item.timestamp, language, arkRowKind]);
}, [isPending, item.timestamp, language]);
const formattedAmount = useMemo(() => {
return formatBalanceWithoutSuffix(item.value, itemPriceUnit, true).toString();
@ -246,7 +224,6 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
color,
fontSize: 14,
fontWeight: '600' as TextStyle['fontWeight'],
lineHeight: Math.round(20 * fontScale),
textAlign: 'right',
paddingRight: insets.right,
paddingLeft: insets.left,
@ -261,18 +238,9 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
item.ispaid,
insets.right,
insets.left,
fontScale,
]);
const determineTransactionTypeAndAvatar = () => {
// A refill awaiting settlement: show it as pending, not as a completed receive.
if (isPendingRefill) {
return {
label: loc.transactions.pending_transaction,
icon: <TransactionPendingIcon />,
};
}
if (item.category === 'receive' && item.confirmations! < 3) {
return {
label: loc.transactions.pending_transaction,
@ -280,14 +248,6 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
};
}
// Recovered Arkade Lightning legs are bitcoind_tx but represent Boltz swaps,
// not on-chain transfers — render them with the off-chain (Lightning) icon.
if (arkRowKind === 'Lightning' && item.type === 'bitcoind_tx') {
return item.value! < 0
? { label: loc.transactions.offchain, icon: <TransactionOffchainIcon /> }
: { label: loc.transactions.incoming_transaction, icon: <TransactionOffchainIncomingIcon /> };
}
if (item.type && item.type === 'bitcoind_tx') {
return {
label: loc.transactions.onchain,
@ -361,11 +321,7 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
pop();
}
navigate('TransactionStatus', { hash: item.hash, walletID, tx: item });
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice' || item.payment_request) {
// A settled Arkade swap is an enriched native Ark leg (type 'bitcoind_tx')
// carrying the swap's invoice payload (payment_request/hash/preimage). Route
// it to the Lightning invoice view by that payload, not by type — otherwise
// it falls through to the on-chain TransactionStatus branch below.
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') {
const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID);
if (lightningWallet.length === 1) {
try {
@ -396,24 +352,15 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
walletID: lightningWallet[0].getID(),
});
}
} else if ((item as { txid?: string }).txid) {
// Hash-less Ark rows carry a synthetic `txid`. Native transfer legs
// (`ark-…`) open the hash-less-tolerant TransactionStatus detail. Refill
// rows (`boarding-…` / `boarding-utxo-…`) have no detail surface and are
// not tappable — matching master, where on-chain top-ups aren't tappable.
const txid = (item as { txid: string }).txid;
if (!txid.startsWith('boarding-')) {
navigate('TransactionStatus', { tx: item, hash: txid, walletID });
}
} else {
console.log('cant handle press');
}
}, [item, renderHighlightedText, navigate, walletID, wallets, customOnPress, disableNavigation]);
const handleOnDetailsPress = useCallback(() => {
if (walletID && item && item.hash) {
navigate('TransactionStatus', { hash: item.hash, walletID, tx: item });
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice' || item.payment_request) {
// Settled Arkade swaps carry invoice data on a 'bitcoind_tx' leg; route by
// payload so they open the Lightning invoice view (see onPress above).
} else {
const lightningWallet = wallets.find(wallet => wallet?.getID() === item.walletID);
if (lightningWallet) {
navigate('LNDViewInvoice', {
@ -421,13 +368,6 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
walletID: lightningWallet.getID(),
});
}
} else if ((item as { txid?: string }).txid) {
// Match the regular tap path for Ark non-swap rows: native transfer legs
// open TransactionStatus; refills (`boarding-…`) are not tappable (master).
const txid = (item as { txid: string }).txid;
if (!txid.startsWith('boarding-')) {
navigate('TransactionStatus', { tx: item, hash: txid, walletID });
}
}
}, [item, navigate, walletID, wallets]);
@ -509,10 +449,7 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
if (renderHighlightedText && searchQuery) {
const highlighted = renderHighlightedText(subtitle, searchQuery);
if (React.isValidElement(highlighted)) {
const highlightedElement = highlighted as React.ReactElement<{
numberOfLines?: number;
style?: TextStyle | TextStyle[];
}>;
const highlightedElement = highlighted as React.ReactElement<{ numberOfLines?: number; style?: TextStyle | TextStyle[] }>;
const existingStyle = highlightedElement.props?.style;
const mergedStyle: TextStyle[] = (
Array.isArray(existingStyle)
@ -549,7 +486,7 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
<ListItem
leftAvatar={avatar}
title={listTitle}
subtitle={dateLine}
subtitle={<Text style={styles.dateLine}>{dateLine}</Text>}
chevron={false}
rightTitle={rowTitle}
rightTitleStyle={rowTitleStyle}

View File

@ -1,8 +1,8 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
import { Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
import LinearGradient from 'react-native-linear-gradient';
import { useTheme } from './themes';
import { LightningArkWallet } from '../class/wallets/lightning-ark-wallet';
import { LightningCustodianWallet } from '../class/wallets/lightning-custodian-wallet';
import { MultisigHDWallet } from '../class/wallets/multisig-hd-wallet';
@ -14,42 +14,36 @@ import { FiatUnit } from '../models/fiatUnit';
import { BlurredBalanceView } from './BlurredBalanceView';
import { useSettings } from '../hooks/context/useSettings';
import ToolTipMenu from './TooltipMenu';
import useAnimateOnChange from '../hooks/useAnimateOnChange';
import { useLocale } from '@react-navigation/native';
import ActionSheet from '../screen/ActionSheet';
const HERO_BASE_BODY_MIN_HEIGHT = 120;
const HERO_MIN_BODY_HEIGHT = Math.round(HERO_BASE_BODY_MIN_HEIGHT * 1.2);
const HERO_BOTTOM_PADDING = 32;
const WALLET_LABEL_TOP_GAP = 32;
interface TransactionsNavigationHeaderProps {
wallet: TWallet;
unit: BitcoinUnit;
headerOverlayHeight: number;
onWalletUnitChange: (unit: BitcoinUnit) => void;
onManageFundsPressed?: (id?: string) => void;
onWalletBalanceVisibilityChange?: (shouldHideBalance: boolean) => void;
onWalletBalanceVisibilityChange?: (isShouldBeVisible: boolean) => void;
unitSwitching?: boolean;
}
const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps> = ({
wallet,
headerOverlayHeight,
onWalletUnitChange,
onManageFundsPressed,
onWalletBalanceVisibilityChange,
unit = BitcoinUnit.BTC,
unitSwitching = false,
}) => {
const { colors } = useTheme();
const { hideBalance } = wallet;
const isLightningWallet = wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type;
const [allowOnchainAddress, setAllowOnchainAddress] = useState(isLightningWallet);
const [allowOnchainAddress, setAllowOnchainAddress] = useState(false);
const { preferredFiatCurrency } = useSettings();
const { direction } = useLocale();
const balanceOpacity = useSharedValue(1);
const balanceTranslateY = useSharedValue(0);
const previousBalance = useRef<string | undefined>(undefined);
const verifyIfWalletAllowsOnchainAddress = useCallback(() => {
if (isLightningWallet) {
if (wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type) {
wallet
.allowOnchainAddress()
.then((value: boolean) => setAllowOnchainAddress(value))
@ -58,11 +52,7 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
setAllowOnchainAddress(false);
});
}
}, [isLightningWallet, wallet]);
useEffect(() => {
setAllowOnchainAddress(isLightningWallet);
}, [isLightningWallet]);
}, [wallet]);
useEffect(() => {
verifyIfWalletAllowsOnchainAddress();
@ -77,14 +67,13 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
const handleBalanceVisibility = useCallback(() => {
onWalletBalanceVisibilityChange?.(!hideBalance);
}, [hideBalance, onWalletBalanceVisibilityChange]);
}, [onWalletBalanceVisibilityChange, hideBalance]);
const changeWalletBalanceUnit = () => {
if (hideBalance) {
return;
}
let newWalletPreferredUnit = wallet.getPreferredBalanceUnit();
console.debug('[UnitSwitch/UI] tap unit change', { walletID: wallet.getID?.(), current: newWalletPreferredUnit });
if (newWalletPreferredUnit === BitcoinUnit.BTC) {
newWalletPreferredUnit = BitcoinUnit.SATS;
} else if (newWalletPreferredUnit === BitcoinUnit.SATS) {
@ -93,6 +82,7 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
newWalletPreferredUnit = BitcoinUnit.BTC;
}
console.debug('[UnitSwitch/UI] next unit resolved', { walletID: wallet.getID?.(), next: newWalletPreferredUnit });
onWalletUnitChange(newWalletPreferredUnit);
};
@ -107,34 +97,29 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
const onPressMenuItem = useCallback(
(id: string) => {
if (id === actionKeys.WalletBalanceVisibility) {
if (id === 'walletBalanceVisibility') {
handleBalanceVisibility();
} else if (id === actionKeys.CopyToClipboard) {
} else if (id === 'copyToClipboard') {
handleCopyPress();
}
},
[handleBalanceVisibility, handleCopyPress],
);
// The Manage Funds menu is presented via a JS ActionSheet rather than the
// native context menu (ToolTipMenu): react-native-context-menu-view is
// Paper-only and, routed through Fabric's legacy interop on the New
// Architecture, its host view gets mispositioned to the header origin —
// overlapping the wallet label. A plain TouchableOpacity + ActionSheet lays
// out correctly (same pattern as the Multisig button below).
const showManageFundsActionSheet = useCallback(() => {
ActionSheet.showActionSheetWithOptions(
const toolTipActions = useMemo(() => {
return [
{
title: loc.lnd.title,
options: [loc._.cancel, loc.lnd.refill, loc.lnd.refill_external],
cancelButtonIndex: 0,
id: actionKeys.Refill,
text: loc.lnd.refill,
icon: actionIcons.Refill,
},
buttonIndex => {
if (buttonIndex === 1) handleManageFundsPressed(actionKeys.Refill);
else if (buttonIndex === 2) handleManageFundsPressed(actionKeys.RefillWithExternalWallet);
{
id: actionKeys.RefillWithExternalWallet,
text: loc.lnd.refill_external,
icon: actionIcons.RefillWithExternalWallet,
},
);
}, [handleManageFundsPressed]);
];
}, []);
const currentBalance = wallet ? wallet.getBalance() : 0;
const formattedBalance = useMemo(() => {
@ -144,160 +129,154 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
}, [unit, currentBalance]);
const balance = !wallet.hideBalance && formattedBalance;
const safeBalance = balance ? String(balance) : undefined;
useEffect(() => {
if (hideBalance) {
previousBalance.current = undefined;
balanceOpacity.value = 1;
balanceTranslateY.value = 0;
return;
}
if (previousBalance.current !== undefined && previousBalance.current !== safeBalance) {
balanceOpacity.value = 0;
balanceTranslateY.value = 6;
balanceOpacity.value = withTiming(1, { duration: 180 });
balanceTranslateY.value = withSpring(0, { damping: 16, stiffness: 220 });
}
previousBalance.current = safeBalance;
}, [safeBalance, hideBalance, balanceOpacity, balanceTranslateY]);
const balanceAnimationKey = useMemo(
() => `${wallet.getID?.() ?? ''}-${unit}-${hideBalance}-${safeBalance ?? ''}`,
[safeBalance, hideBalance, unit, wallet],
);
const balanceAnimatedStyle = useAnimateOnChange(balanceAnimationKey);
const animatedBalanceTextStyle = useAnimatedStyle(() => ({
opacity: balanceOpacity.value,
transform: [{ translateY: balanceTranslateY.value }],
}));
const toolTipWalletBalanceActions = useMemo(() => {
return hideBalance
? [
{
id: actionKeys.WalletBalanceVisibility,
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_show,
icon: actionIcons.Eye,
icon: {
iconValue: 'eye',
},
},
]
: [
{
id: actionKeys.WalletBalanceVisibility,
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_hide,
icon: actionIcons.EyeSlash,
icon: {
iconValue: 'eye.slash',
},
},
{
id: actionKeys.CopyToClipboard,
id: 'copyToClipboard',
text: loc.transactions.details_copy,
icon: actionIcons.Clipboard,
icon: {
iconValue: 'doc.on.doc',
},
},
];
}, [hideBalance]);
useEffect(() => {
console.debug('[UnitSwitch/UI] render state', {
walletID: wallet.getID?.(),
unit,
hideBalance,
preferredFiat: preferredFiatCurrency?.endPointKey,
switching: unitSwitching,
});
}, [wallet, unit, hideBalance, preferredFiatCurrency, unitSwitching]);
return (
<View
style={[
styles.lineaderGradient,
{
paddingTop: headerOverlayHeight,
minHeight: headerOverlayHeight + HERO_MIN_BODY_HEIGHT,
backgroundColor: WalletGradient.headerColorFor(wallet.type),
},
]}
>
<LinearGradient colors={WalletGradient.gradientsFor(wallet.type)} style={StyleSheet.absoluteFill} />
<LinearGradient colors={WalletGradient.gradientsFor(wallet.type)} style={styles.lineaderGradient}>
<View style={styles.contentContainer}>
<Text testID="WalletLabel" numberOfLines={1} style={[styles.walletLabel, { writingDirection: direction }]}>
{wallet.getLabel()}
</Text>
<View style={styles.balanceSection}>
<View style={styles.walletBalanceAndUnitContainer}>
<ToolTipMenu
shouldOpenOnLongPress
isButton
enableAndroidRipple={false}
buttonStyle={styles.walletBalance}
onPressMenuItem={onPressMenuItem}
actions={toolTipWalletBalanceActions}
>
<View style={styles.walletBalance}>
{hideBalance ? (
<BlurredBalanceView />
) : (
<Text
<Animated.View style={[styles.walletBalanceAndUnitContainer, balanceAnimatedStyle]}>
<ToolTipMenu
shouldOpenOnLongPress
isButton
enableAndroidRipple={false}
buttonStyle={styles.walletBalance}
onPressMenuItem={onPressMenuItem}
actions={toolTipWalletBalanceActions}
>
<View style={styles.walletBalance}>
{hideBalance ? (
<BlurredBalanceView />
) : (
<View key={`wallet-balance-textwrap-${wallet.getID?.() ?? ''}-${String(balance)}`}>
<Animated.Text
key={`wallet-balance-text-${wallet.getID?.() ?? ''}-${String(balance)}`} // force recreation on balance change for RTL correctness
testID="WalletBalance"
numberOfLines={1}
minimumFontScale={0.5}
adjustsFontSizeToFit
style={styles.walletBalanceText}
style={[styles.walletBalanceText, animatedBalanceTextStyle]}
>
{balance}
</Text>
)}
</View>
</ToolTipMenu>
{!hideBalance && (
<TouchableOpacity style={styles.walletPreferredUnitView} onPress={changeWalletBalanceUnit} disabled={unitSwitching}>
<Text style={styles.walletPreferredUnitText}>
{unit === BitcoinUnit.LOCAL_CURRENCY ? (preferredFiatCurrency?.endPointKey ?? FiatUnit.USD) : unit}
</Text>
</TouchableOpacity>
)}
</View>
{(wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type) && allowOnchainAddress && (
<TouchableOpacity style={styles.manageFundsButton} accessibilityRole="button" onPress={showManageFundsActionSheet}>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</TouchableOpacity>
)}
</View>
</Animated.Text>
</View>
)}
</View>
</ToolTipMenu>
<TouchableOpacity style={styles.walletPreferredUnitView} onPress={changeWalletBalanceUnit} disabled={unitSwitching}>
<Text style={styles.walletPreferredUnitText}>
{unit === BitcoinUnit.LOCAL_CURRENCY ? (preferredFiatCurrency?.endPointKey ?? FiatUnit.USD) : unit}
</Text>
</TouchableOpacity>
</Animated.View>
{(wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type) && allowOnchainAddress && (
<ToolTipMenu
shouldOpenOnLongPress
isButton
onPressMenuItem={handleManageFundsPressed}
actions={toolTipActions}
buttonStyle={styles.manageFundsButton}
>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</ToolTipMenu>
)}
{wallet.type === MultisigHDWallet.type && (
<TouchableOpacity style={styles.manageFundsButton} accessibilityRole="button" onPress={() => handleManageFundsPressed()}>
<Text style={styles.manageFundsButtonText}>{loc.multisig.manage_keys}</Text>
</TouchableOpacity>
)}
</View>
<View style={styles.bottomBarSpacer}>
<View
style={[
styles.bottomBar,
{
backgroundColor: colors.background,
...Platform.select({
ios: { shadowColor: colors.shadowColor },
android: {},
}),
},
]}
/>
</View>
</View>
</LinearGradient>
);
};
const styles = StyleSheet.create({
lineaderGradient: {
minHeight: 140,
justifyContent: 'flex-start',
position: 'relative',
},
contentContainer: {
flex: 1,
paddingTop: WALLET_LABEL_TOP_GAP,
paddingHorizontal: 16,
paddingBottom: HERO_BOTTOM_PADDING,
},
bottomBarSpacer: {
position: 'relative',
height: 12,
marginBottom: 0,
},
bottomBar: {
position: 'absolute',
left: 0,
right: 0,
bottom: -1,
height: 13,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
...Platform.select({
ios: {
shadowOffset: { width: 0, height: -8 },
shadowOpacity: 0.1,
shadowRadius: 6,
},
android: {
elevation: 0.5,
},
}),
padding: 15,
},
walletLabel: {
backgroundColor: 'transparent',
fontSize: 19,
color: 'rgba(255, 255, 255, 0.7)',
marginBottom: 4,
color: '#fff',
marginBottom: 10,
},
walletBalance: {
flexShrink: 1,
marginRight: 6,
minHeight: 39,
justifyContent: 'center',
},
balanceSection: {
flexDirection: 'column',
alignItems: 'flex-start',
},
manageFundsButton: {
marginTop: 14,
@ -318,13 +297,13 @@ const styles = StyleSheet.create({
walletBalanceAndUnitContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingRight: 10,
paddingRight: 10, // Ensure there's some padding to the right
},
walletBalanceText: {
color: '#fff',
fontWeight: 'bold',
fontSize: 36,
flexShrink: 1,
flexShrink: 1, // Allow the text to shrink if there's not enough space
},
walletPreferredUnitView: {
justifyContent: 'center',

View File

@ -107,7 +107,7 @@ const WalletListItem: React.FC<Props> = ({
)}
{wallet.hideBalance ? (
<View style={styles.hiddenBalance} testID="HiddenBalance">
<View style={styles.hiddenBalance}>
<View style={styles.hiddenBalanceBar} />
</View>
) : (

View File

@ -30,7 +30,6 @@ import WalletGradient from '../class/wallet-gradient';
import { useSizeClass, SizeClass } from '../blue_modules/sizeClass';
import loc, { formatBalance, transactionTimeToReadable } from '../loc';
import { BlurredBalanceView } from './BlurredBalanceView';
import { withAlpha } from './color';
import { useTheme } from './themes';
import { Transaction, TWallet } from '../class/wallets/types';
import { BlueSpacing10 } from './BlueSpacing';
@ -38,30 +37,6 @@ import { useLocale } from '@react-navigation/native';
export const WALLET_CAROUSEL_HEADER_WIDTH = 16;
/** Base card body height at default Dynamic Type — grows with larger Dynamic Type, never shrinks below default. */
export const WALLET_CARD_BASE_MIN_HEIGHT = 164;
/** Top inset above wallet cards in the horizontal home carousel. */
export const WALLET_CAROUSEL_PADDING_TOP = 12;
/** Bottom inset so iOS card shadows (offset 4 + radius 8) are not clipped by the list row. */
export const WALLET_CAROUSEL_PADDING_BOTTOM = 20;
/** Scale layout metrics up for accessibility sizes; keep the design default when fontScale ≤ 1. */
const scaleLayoutUp = (base: number, fontScale: number): number => Math.round(base * Math.max(1, fontScale));
export const getWalletCardMinHeight = (fontScale = 1): number => scaleLayoutUp(WALLET_CARD_BASE_MIN_HEIGHT, fontScale);
export const getWalletCarouselHeight = (fontScale = 1): number =>
scaleLayoutUp(WALLET_CAROUSEL_PADDING_TOP, fontScale) +
getWalletCardMinHeight(fontScale) +
scaleLayoutUp(WALLET_CAROUSEL_PADDING_BOTTOM, fontScale);
/** Default carousel row height at `fontScale` 1 — prefer `getWalletCarouselHeight(fontScale)` when layout depends on Dynamic Type. */
export const WALLET_CAROUSEL_HEIGHT = getWalletCarouselHeight(1);
/** Vertical gap between the wallet title/balance block and the latest-tx footer on carousel cards. */
const WALLET_CARD_SECTION_GAP = 12;
const WALLET_CARD_TEXT_OPACITY = 0.85;
export const getWalletCarouselItemWidth = (screenWidth: number) => Math.round(screenWidth * 0.82 > 375 ? 375 : screenWidth * 0.82);
interface NewWalletPanelProps {
@ -185,28 +160,23 @@ const iStyles = StyleSheet.create({
borderRadius: 12,
minHeight: 164,
overflow: 'hidden',
justifyContent: 'flex-end',
},
gradCompact: {
borderRadius: 10,
minHeight: 132,
overflow: 'hidden',
justifyContent: 'flex-end',
},
gradContent: {
padding: 15,
width: '100%',
},
gradContentCompact: {
padding: 12,
},
balanceContainer: {
minHeight: 40,
justifyContent: 'center',
height: 40,
},
balanceContainerCompact: {
minHeight: 32,
justifyContent: 'center',
height: 32,
},
image: {
width: 99,
@ -219,6 +189,9 @@ const iStyles = StyleSheet.create({
width: 78,
height: 74,
},
br: {
backgroundColor: 'transparent',
},
label: {
backgroundColor: 'transparent',
fontSize: 19,
@ -233,6 +206,7 @@ const iStyles = StyleSheet.create({
},
balanceCompact: {
fontSize: 28,
lineHeight: 34,
},
latestTx: {
backgroundColor: 'transparent',
@ -308,32 +282,11 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
const balanceOpacity = useSharedValue(1);
const balanceTranslateY = useSharedValue(0);
const { colors } = useTheme();
const { width, fontScale } = useWindowDimensions();
const { width } = useWindowDimensions();
const itemWidth = getWalletCarouselItemWidth(width);
const { sizeClass } = useSizeClass();
const isCompact = sizeVariant === 'compact';
const { direction } = useLocale();
const scaledCardStyles = useMemo(
() => ({
grad: { minHeight: getWalletCardMinHeight(fontScale) },
gradContent: { padding: scaleLayoutUp(15, fontScale) },
balanceContainer: { minHeight: scaleLayoutUp(40, fontScale) },
textSpacer: { height: scaleLayoutUp(WALLET_CARD_SECTION_GAP, fontScale) },
label: { lineHeight: scaleLayoutUp(24, fontScale) },
balance: { lineHeight: scaleLayoutUp(38, fontScale) },
balanceCompact: { lineHeight: scaleLayoutUp(30, fontScale) },
latestTx: { lineHeight: scaleLayoutUp(18, fontScale) },
latestTxTime: { lineHeight: scaleLayoutUp(22, fontScale) },
}),
[fontScale],
);
const cardTextStyle = useMemo(
() => ({
color: withAlpha(colors.inverseForegroundColor, WALLET_CARD_TEXT_OPACITY),
writingDirection: direction,
}),
[colors.inverseForegroundColor, direction],
);
const previousBalance = useRef<string | undefined>(undefined);
const balance = !hideBalance && formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true);
const safeBalance = balance || undefined;
@ -430,21 +383,9 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
let latestTransactionText;
// Lightning / Ark wallets do not have on-chain confirmations — settlement is
// signaled by `ispaid`. Bitcoin/on-chain wallets keep the existing
// `confirmations === 0` rule unchanged so their pending-pill semantics
// never depend on a Lightning shape.
// `ispaid === false` alone is not "pending": it is also true for terminal
// failed/refunded swaps, which stay in history. Gate on `!tx.failed` so a
// dead swap doesn't pin the card to "pending" forever.
const isLightningShaped = item.type === LightningCustodianWallet.type || item.type === LightningArkWallet.type;
const hasPendingTx = isLightningShaped
? item.getTransactions().some((tx: any) => tx.ispaid === false && !tx.failed)
: item.getTransactions().some((tx: Transaction) => tx.confirmations === 0);
if (item.getBalance() !== 0 && item.getLatestTransactionTime() === 0) {
latestTransactionText = loc.wallets.pull_to_refresh;
} else if (hasPendingTx) {
} else if (item.getTransactions().find((tx: Transaction) => tx.confirmations === 0)) {
latestTransactionText = loc.transactions.pending;
} else {
latestTransactionText = transactionTimeToReadable(item.getLatestTransactionTime());
@ -478,23 +419,23 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
{ backgroundColor: colors.background, shadowColor: colors.shadowColor },
]}
>
<LinearGradient
colors={WalletGradient.gradientsFor(item.type)}
style={[iStyles.grad, isCompact && iStyles.gradCompact, scaledCardStyles.grad]}
>
<LinearGradient colors={WalletGradient.gradientsFor(item.type)} style={[iStyles.grad, isCompact && iStyles.gradCompact]}>
<ImageBackground source={image} style={[iStyles.image, isCompact && iStyles.imageCompact]} />
<View style={[iStyles.gradContent, isCompact && iStyles.gradContentCompact, !isCompact && scaledCardStyles.gradContent]}>
<View style={[iStyles.gradContent, isCompact && iStyles.gradContentCompact]}>
<Text style={iStyles.br} />
{!isPlaceHolder && (
<>
<Text
numberOfLines={1}
style={[iStyles.label, isCompact && iStyles.labelCompact, scaledCardStyles.label, cardTextStyle]}
style={[
iStyles.label,
isCompact && iStyles.labelCompact,
{ color: colors.inverseForegroundColor, writingDirection: direction },
]}
>
{renderHighlightedText ? renderHighlightedText(walletLabel, searchQuery || '') : walletLabel}
</Text>
<View
style={[iStyles.balanceContainer, isCompact && iStyles.balanceContainerCompact, scaledCardStyles.balanceContainer]}
>
<View style={[iStyles.balanceContainer, isCompact && iStyles.balanceContainerCompact]}>
{hideBalance ? (
<>
<BlueSpacing10 />
@ -504,13 +445,11 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
<Animated.Text
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.55}
key={`${balance}`} // force component recreation on balance change. To fix right-to-left languages, like Farsi
style={[
iStyles.balance,
isCompact && iStyles.balanceCompact,
isCompact ? scaledCardStyles.balanceCompact : scaledCardStyles.balance,
cardTextStyle,
{ color: colors.inverseForegroundColor, writingDirection: direction },
animatedBalanceStyle,
]}
>
@ -518,20 +457,24 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
</Animated.Text>
)}
</View>
<View style={scaledCardStyles.textSpacer} />
<Text style={iStyles.br} />
<Text
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.8}
style={[iStyles.latestTx, isCompact && iStyles.latestTxCompact, scaledCardStyles.latestTx, cardTextStyle]}
style={[
iStyles.latestTx,
isCompact && iStyles.latestTxCompact,
{ color: colors.inverseForegroundColor, writingDirection: direction },
]}
>
{loc.wallets.list_latest_transaction}
</Text>
<Text
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.8}
style={[iStyles.latestTxTime, isCompact && iStyles.latestTxTimeCompact, scaledCardStyles.latestTxTime, cardTextStyle]}
style={[
iStyles.latestTxTime,
isCompact && iStyles.latestTxTimeCompact,
{ color: colors.inverseForegroundColor, writingDirection: direction },
]}
>
{latestTransactionText}
</Text>
@ -560,7 +503,15 @@ interface WalletsCarouselProps extends Partial<FlatListProps<any>> {
animateChanges?: boolean;
}
export type CarouselListRefType = FlatList<TWallet>;
type FlatListRefType = FlatList<any> & {
scrollToEnd(params?: { animated?: boolean | null }): void;
scrollToIndex(params: { animated?: boolean | null; index: number; viewOffset?: number; viewPosition?: number }): void;
scrollToItem(params: { animated?: boolean | null; item: TWallet; viewPosition?: number }): void;
scrollToOffset(params: { animated?: boolean | null; offset: number }): void;
recordInteraction(): void;
flashScrollIndicators(): void;
getNativeScrollRef(): View;
};
const styles = StyleSheet.create({
listHeaderSeparator: {
@ -571,7 +522,7 @@ const styles = StyleSheet.create({
const ListHeaderSeparator = () => <View style={styles.listHeaderSeparator} />;
const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((props, ref) => {
const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props, ref) => {
const {
horizontal = true,
data,
@ -586,7 +537,7 @@ const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((p
animateChanges = false,
} = props;
const { width, fontScale } = useWindowDimensions();
const { width } = useWindowDimensions();
const itemWidth = React.useMemo(() => getWalletCarouselItemWidth(width), [width]);
const snapInterval = React.useMemo(() => itemWidth, [itemWidth]);
const snapOffsets = React.useMemo(() => {
@ -606,7 +557,7 @@ const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((p
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const isInitialMount = useRef(true);
const flatListRef = useRef<FlatList<TWallet>>(null);
const flatListRef = useRef<FlatList<any>>(null);
const walletRefs = useRef<Record<string, React.MutableRefObject<View | null>>>({});
const { sizeClass } = useSizeClass();
@ -695,7 +646,7 @@ const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((p
console.warn('[WalletsCarousel] Error scrolling to wallet:', error);
// Fallback: try scrolling to offset
// Use different measurement based on orientation
const itemSize = horizontal ? itemWidth : WALLET_CAROUSEL_HEIGHT;
const itemSize = horizontal ? itemWidth : 195; // 195 is the approximate height of wallet card
flatListRef.current.scrollToOffset({
offset: itemSize * walletIndex,
animated,
@ -817,7 +768,7 @@ const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((p
const keyExtractor = useCallback((item: TWallet, index: number) => (item?.getID ? item.getID() : index.toString()), []);
const sliderHeight = getWalletCarouselHeight(fontScale);
const sliderHeight = 195;
useEffect(() => {
return () => {
@ -900,8 +851,7 @@ const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((p
const cStyles = StyleSheet.create({
content: {
paddingTop: scaleLayoutUp(WALLET_CAROUSEL_PADDING_TOP, fontScale),
paddingBottom: scaleLayoutUp(WALLET_CAROUSEL_PADDING_BOTTOM, fontScale),
paddingTop: 16,
},
contentLargeScreen: {
paddingHorizontal: sizeClass === SizeClass.Large ? 16 : 12,
@ -932,7 +882,7 @@ const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((p
automaticallyAdjustContentInsets
automaticallyAdjustKeyboardInsets
automaticallyAdjustsScrollIndicatorInsets
style={{ minHeight: sliderHeight }}
style={{ minHeight: sliderHeight + 12 }}
onScrollToIndexFailed={onScrollToIndexFailed}
ListFooterComponent={onNewWalletPress ? <NewWalletPanel onPress={onNewWalletPress} /> : null}
{...props}

View File

@ -66,13 +66,10 @@ const getHandleCloseAction = (
const navigationStyle = (
{
closeButtonPosition,
closeButtonIfFirstInStack,
onCloseButtonPressed,
...opts
}: NativeStackNavigationOptions & {
closeButtonPosition?: CloseButtonPosition;
/** When set, show this close control only if this screen is the first route in the stack (e.g. Coin Control opened from wallet details). */
closeButtonIfFirstInStack?: CloseButtonPosition;
onCloseButtonPressed?: (deps: { navigation: any; route: any }) => void;
},
formatter?: OptionsFormatter,
@ -83,10 +80,7 @@ const navigationStyle = (
const isModal = route.params?.presentation === 'modal' || route.params?.presentation === 'transparentModal';
const isFormSheet = route.params?.presentation === 'formSheet';
const closeButton =
closeButtonIfFirstInStack && isFirstRouteInStack
? closeButtonIfFirstInStack
: getCloseButtonPosition(closeButtonPosition, isFirstRouteInStack, isModal);
const closeButton = getCloseButtonPosition(closeButtonPosition, isFirstRouteInStack, isModal);
const handleClose = getHandleCloseAction(onCloseButtonPressed, navigation, route);
let headerRight;

View File

@ -31,8 +31,6 @@ export { platformColors } from '../themes';
export const isAndroid = Platform.OS === 'android';
const isIOS = Platform.OS === 'ios';
const iosMajorVersion = isIOS ? Number(String(Platform.Version).split('.')[0]) : 0;
export const isIOS26OrHigher = isIOS && Number.isFinite(iosMajorVersion) && iosMajorVersion >= 26;
export const platformSizing = {
horizontalPadding: isIOS ? 16 : 20,
@ -109,15 +107,6 @@ export const getSettingsHeaderOptions = (
const cardColor = colors.lightButton ?? colors.modal ?? colors.elevated ?? defaultBackgroundColor;
const headerBackgroundColor = isIOS ? (dark ? defaultBackgroundColor : cardColor) : defaultBackgroundColor;
if (isIOS26OrHigher) {
return {
title,
headerLargeTitle: true,
headerLargeTitleShadowVisible: true,
headerBackButtonDisplayMode: 'minimal' as const,
};
}
return {
title,
headerLargeTitle: isIOS,
@ -203,7 +192,6 @@ export const SettingsScrollView = forwardRef<ScrollView, SettingsScrollViewProps
ref={ref}
style={[style, { backgroundColor: screenBackgroundColor }]}
headerHeight={resolvedHeaderHeight}
disableDefaultTopPadding={isIOS26OrHigher}
floatingButtonHeight={floatingButtonHeight}
contentContainerStyle={[staticStyles.contentContainer, contentContainerStyle]}
{...rest}

View File

@ -14,8 +14,6 @@ export const BlueDefaultTheme = {
foregroundColor: '#0c2550',
borderTopColor: 'rgba(0, 0, 0, 0.1)',
buttonBackgroundColor: '#ccddf9',
/** Softer fill for native iOS 26+ prominent header bar buttons (derived from `buttonBackgroundColor`). */
headerProminentButtonBackgroundColor: 'rgba(204, 221, 249, 0.9)',
buttonTextColor: '#0c2550',
secondButtonTextColor: '#50555C',
buttonAlternativeTextColor: '#2f5fb3',
@ -103,7 +101,6 @@ export const BlueDarkTheme: Theme = {
foregroundColor: '#ffffff',
buttonDisabledBackgroundColor: '#3A3A3C',
buttonBackgroundColor: '#3A3A3C',
headerProminentButtonBackgroundColor: 'rgba(58, 58, 60, 0.6)',
buttonTextColor: '#ffffff',
lightButton: 'rgba(255,255,255,.1)',
buttonAlternativeTextColor: '#ffffff',

View File

@ -2,18 +2,15 @@ import { Platform } from 'react-native';
import prompt from 'react-native-prompt-android';
import loc from '../loc';
type PromptHelperOptions = {
cancelable?: boolean;
type?: PromptType | PromptTypeIOS | PromptTypeAndroid;
destructive?: boolean; // applies only to the cancelable (two-button) layout
continueButtonText?: string;
defaultValue?: string;
};
export default (title: string, text: string, options: PromptHelperOptions = {}): Promise<string> => {
const { cancelable = true, destructive = false, continueButtonText = loc._.ok, defaultValue } = options;
let { type = 'secure-text' } = options;
export default (
title: string,
text: string,
isCancelable = true,
type: PromptType | PromptTypeIOS | PromptTypeAndroid = 'secure-text',
isOKDestructive = false,
continueButtonText = loc._.ok,
defaultInputValue?: string,
): Promise<string> => {
const keyboardType = type === 'numeric' ? 'numeric' : 'default';
if (Platform.OS === 'ios' && type === 'numeric') {
@ -22,7 +19,7 @@ export default (title: string, text: string, options: PromptHelperOptions = {}):
}
return new Promise((resolve, reject) => {
const buttons: Array<PromptButton> = cancelable
const buttons: Array<PromptButton> = isCancelable
? [
{
text: loc._.cancel,
@ -37,7 +34,7 @@ export default (title: string, text: string, options: PromptHelperOptions = {}):
console.log('OK Pressed');
resolve(password);
},
style: destructive ? 'destructive' : 'default',
style: isOKDestructive ? 'destructive' : 'default',
},
]
: [
@ -50,12 +47,13 @@ export default (title: string, text: string, options: PromptHelperOptions = {}):
},
];
const message = defaultValue !== undefined ? '' : text;
const message = defaultInputValue !== undefined ? '' : text;
prompt(title, message, buttons, {
type,
cancelable,
cancelable: isCancelable,
// @ts-ignore suppressed because its supported only on ios and is absent from type definitions
keyboardType,
...(defaultValue !== undefined && { defaultValue }),
...(defaultInputValue !== undefined && { defaultValue: defaultInputValue }),
});
});
};

View File

@ -1,7 +1,6 @@
import { CommonActions } from '@react-navigation/native';
import { useCallback, useEffect, useRef } from 'react';
import { AppState, AppStateStatus, Linking } from 'react-native';
import { reconcileArkBackgroundTaskResults } from '../blue_modules/arkade-background';
import { getClipboardContent } from '../blue_modules/clipboard';
import { updateExchangeRate } from '../blue_modules/currency';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
@ -14,7 +13,6 @@ import {
setApplicationIconBadgeNumber,
} from '../blue_modules/notifications';
import { LightningCustodianWallet } from '../class/wallets/lightning-custodian-wallet';
import { LightningArkWallet } from '../class/wallets/lightning-ark-wallet';
import DeeplinkSchemaMatch from '../class/deeplink-schema-match';
import loc from '../loc';
import { Chain } from '../models/bitcoinUnits';
@ -88,47 +86,6 @@ const useCompanionListeners = (skipIfNotInitialized = true) => {
const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction);
console.log('processing push notification:', payload);
// Local notification for actionable Ark swaps. Routed by walletID
// rather than address/txid because the payload is locally generated;
// see blue_modules/arkade-notifications.ts.
if (+payload.type === 100) {
const arkWallet = wallets.find(w => w.getID() === payload.walletID);
if (!arkWallet || !(arkWallet instanceof LightningArkWallet)) {
if (wasTapped) {
navigation.navigate('WalletTransactions', {
walletID: payload.walletID,
walletType: arkWallet?.type,
});
return true;
}
continue;
}
// Refresh swap-derived rows directly via the wallet method to
// bypass the 5-second NOP throttle in StorageProvider.fetchAndSaveWalletTransactions:
// reconcileArkBackgroundTaskResults often runs on app resume immediately
// before this handler, which would make a throttled call NOP and
// leave the synthetic row stale.
try {
await arkWallet.fetchTransactions();
await saveToDisk();
} catch (e: any) {
console.warn('[useCompanionListeners] arkWallet.fetchTransactions failed:', e?.message ?? e);
}
if (wasTapped) {
const arkWalletID = arkWallet.getID();
const row = arkWallet.getTransactions().find(tx => tx.txid === `swap-${payload.swapId}`);
if (row) {
navigation.navigate('LNDViewInvoice', { invoice: row, walletID: arkWalletID });
} else {
navigation.navigate('WalletTransactions', { walletID: arkWalletID, walletType: arkWallet.type });
}
return true;
}
continue;
}
let wallet;
switch (+payload.type) {
case 2:
@ -169,51 +126,6 @@ const useCompanionListeners = (skipIfNotInitialized = true) => {
const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction);
console.log('processing push notification:', payload);
if (+payload.type === 100) {
const arkWallet = wallets.find(w => w.getID() === payload.walletID);
if (!arkWallet || !(arkWallet instanceof LightningArkWallet)) {
if (wasTapped) {
navigationRef.dispatch(
CommonActions.navigate({
name: 'WalletTransactions',
params: { walletID: payload.walletID, walletType: arkWallet?.type },
}),
);
return true;
}
continue;
}
try {
await arkWallet.fetchTransactions();
await saveToDisk();
} catch (e: any) {
console.warn('[useCompanionListeners] arkWallet.fetchTransactions failed:', e?.message ?? e);
}
if (wasTapped) {
const arkWalletID = arkWallet.getID();
const row = arkWallet.getTransactions().find(tx => tx.txid === `swap-${payload.swapId}`);
if (row) {
navigationRef.dispatch(
CommonActions.navigate({
name: 'LNDViewInvoice',
params: { invoice: row, walletID: arkWalletID },
}),
);
} else {
navigationRef.dispatch(
CommonActions.navigate({
name: 'WalletTransactions',
params: { walletID: arkWalletID, walletType: arkWallet.type },
}),
);
}
return true;
}
continue;
}
let wallet;
switch (+payload.type) {
case 2:
@ -267,7 +179,7 @@ const useCompanionListeners = (skipIfNotInitialized = true) => {
console.error('Failed to process push notifications:', error);
}
return false;
}, [shouldActivateListeners, wallets, fetchAndSaveWalletTransactions, saveToDisk, navigation, refreshAllWalletTransactions]);
}, [shouldActivateListeners, wallets, fetchAndSaveWalletTransactions, navigation, refreshAllWalletTransactions]);
useEffect(() => {
if (!shouldActivateListeners) return;
@ -302,12 +214,16 @@ const useCompanionListeners = (skipIfNotInitialized = true) => {
throw new Error(loc.send.qr_error_no_qrcode);
}
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
DeeplinkSchemaMatch.navigationRouteFor({ url: qrValue }, (value: [string, any]) => navigationRef.navigate(...value), {
wallets,
addWallet,
saveToDisk,
setSharedCosigner,
});
DeeplinkSchemaMatch.navigationRouteFor(
{ url: qrValue },
(value: [string, any]) => navigationRef.navigate(...value),
{
wallets,
addWallet,
saveToDisk,
setSharedCosigner,
},
);
} else {
DeeplinkSchemaMatch.navigationRouteFor(event, (value: [string, any]) => navigationRef.navigate(...value), {
wallets,
@ -361,12 +277,6 @@ const useCompanionListeners = (skipIfNotInitialized = true) => {
if ((appState.current.match(/inactive|background/) && nextAppState === 'active') || nextAppState === undefined) {
updateExchangeRate();
const processed = await processPushNotifications();
// Reconcile in-process Ark background task results before the
// notification-handled early return: if the background task observed
// status changes while the app was backgrounded, the affected
// wallets need a transactions refresh whether or not a notification
// also fired.
reconcileArkBackgroundTaskResults(fetchAndSaveWalletTransactions);
if (processed) return;
const clipboard = await getClipboardContent();
if (!clipboard) return;
@ -402,7 +312,7 @@ const useCompanionListeners = (skipIfNotInitialized = true) => {
appState.current = nextAppState;
}
},
[processPushNotifications, fetchAndSaveWalletTransactions, showClipboardAlert, wallets, shouldActivateListeners],
[processPushNotifications, showClipboardAlert, wallets, shouldActivateListeners],
);
const addListeners = useCallback(() => {

View File

@ -74,7 +74,7 @@ export const calculateBalanceAndTransactionTime = async (
const balance = await wallet.getBalance();
const transactions: Transaction[] = await wallet.getTransactions();
const confirmedTransactions = transactions.filter(t => (t.confirmations ?? 0) > 0);
const confirmedTransactions = transactions.filter(t => t.confirmations > 0);
const latestTransactionTime =
confirmedTransactions.length > 0
? secondsToMilliseconds(Math.max(...confirmedTransactions.map(t => t.timestamp || t.time || 0)))

View File

@ -4,30 +4,20 @@ import 'react-native-get-random-values';
import './shim.js';
import React, { useEffect } from 'react';
import { AppRegistry, LogBox } from 'react-native';
import BackgroundFetch from 'react-native-background-fetch';
import { AppRegistry, LogBox, Platform, UIManager } from 'react-native';
import App from './App';
import { restoreSavedPreferredFiatCurrencyAndExchangeFromStorage } from './blue_modules/currency';
import { runArkBackgroundTask } from './blue_modules/arkade-background';
// Android headless execution boots a bare JS runtime without the React tree.
// The headless task callback must be registered at module scope before
// AppRegistry.registerComponent so the symbol exists when the OS dispatches a
// terminated-process wake.
BackgroundFetch.registerHeadlessTask(async event => {
if (event.timeout) {
BackgroundFetch.finish(event.taskId);
return;
}
await runArkBackgroundTask(event.taskId);
});
if (!Error.captureStackTrace) {
// captureStackTrace is only available when debugging
Error.captureStackTrace = () => {};
}
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
LogBox.ignoreLogs([
'Require cycle:',
'Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS.',

View File

@ -1356,7 +1356,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703279999;
CURRENT_PROJECT_VERSION = 1703259999;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
@ -1383,7 +1383,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 8.0.1;
MARKETING_VERSION = 8.0.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1418,7 +1418,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703279999;
CURRENT_PROJECT_VERSION = 1703259999;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
@ -1440,7 +1440,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 8.0.1;
MARKETING_VERSION = 8.0.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1476,7 +1476,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703279999;
CURRENT_PROJECT_VERSION = 1703259999;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
@ -1489,7 +1489,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 8.0.1;
MARKETING_VERSION = 8.0.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -1519,7 +1519,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1703279999;
CURRENT_PROJECT_VERSION = 1703259999;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
@ -1532,7 +1532,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 8.0.1;
MARKETING_VERSION = 8.0.0;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
@ -1564,7 +1564,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703279999;
CURRENT_PROJECT_VERSION = 1703259999;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
@ -1584,7 +1584,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 8.0.1;
MARKETING_VERSION = 8.0.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -1625,7 +1625,7 @@
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1703279999;
CURRENT_PROJECT_VERSION = 1703259999;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
@ -1645,7 +1645,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)",
);
MARKETING_VERSION = 8.0.1;
MARKETING_VERSION = 8.0.0;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
@ -1832,7 +1832,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703279999;
CURRENT_PROJECT_VERSION = 1703259999;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
@ -1854,7 +1854,7 @@
"$(inherited)",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 8.0.1;
MARKETING_VERSION = 8.0.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -1890,7 +1890,7 @@
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1703279999;
CURRENT_PROJECT_VERSION = 1703259999;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
@ -1912,7 +1912,7 @@
"$(inherited)",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 8.0.1;
MARKETING_VERSION = 8.0.0;
MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;

View File

@ -5,7 +5,6 @@
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>io.bluewallet.bluewallet.fetchTxsForWallet</string>
<string>com.transistorsoft.fetch</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
@ -245,6 +244,8 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>UIDesignRequiresCompatibility</key>
<true/>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>

View File

@ -1,5 +1,5 @@
PODS:
- BugsnagReactNative (8.9.0):
- BugsnagReactNative (8.8.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -29,7 +29,7 @@ PODS:
- hermes-engine/Pre-built (= 250829098.0.10)
- hermes-engine/Pre-built (250829098.0.10)
- lottie-ios (4.6.0)
- lottie-react-native (7.3.8):
- lottie-react-native (7.3.6):
- hermes-engine
- lottie-ios (= 4.6.0)
- RCTRequired
@ -1529,7 +1529,7 @@ PODS:
- Yoga
- react-native-notifications (5.2.2):
- React-Core
- react-native-safe-area-context (5.8.0):
- react-native-safe-area-context (5.7.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1541,8 +1541,8 @@ PODS:
- React-graphics
- React-ImageManager
- React-jsi
- react-native-safe-area-context/common (= 5.8.0)
- react-native-safe-area-context/fabric (= 5.8.0)
- react-native-safe-area-context/common (= 5.7.0)
- react-native-safe-area-context/fabric (= 5.7.0)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
@ -1553,7 +1553,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-safe-area-context/common (5.8.0):
- react-native-safe-area-context/common (5.7.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1575,7 +1575,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-safe-area-context/fabric (5.8.0):
- react-native-safe-area-context/fabric (5.7.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2018,8 +2018,6 @@ PODS:
- ReactNativeDependencies (0.85.3)
- RealmJS (20.2.0):
- React
- RNBackgroundFetch (4.2.9):
- React-Core
- RNCAsyncStorage (2.2.0):
- hermes-engine
- RCTRequired
@ -2070,7 +2068,7 @@ PODS:
- React-Core
- RNFS (2.20.0):
- React-Core
- RNGestureHandler (2.31.2):
- RNGestureHandler (2.31.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2184,7 +2182,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNReanimated (4.3.1):
- RNReanimated (4.3.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2206,11 +2204,11 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNReanimated/apple (= 4.3.1)
- RNReanimated/common (= 4.3.1)
- RNReanimated/apple (= 4.3.0)
- RNReanimated/common (= 4.3.0)
- RNWorklets
- Yoga
- RNReanimated/apple (4.3.1):
- RNReanimated/apple (4.3.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2234,7 +2232,7 @@ PODS:
- ReactNativeDependencies
- RNWorklets
- Yoga
- RNReanimated/common (4.3.1):
- RNReanimated/common (4.3.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2258,7 +2256,7 @@ PODS:
- ReactNativeDependencies
- RNWorklets
- Yoga
- RNScreens (4.25.2):
- RNScreens (4.24.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2280,9 +2278,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNScreens/common (= 4.25.2)
- RNScreens/common (= 4.24.0)
- Yoga
- RNScreens/common (4.25.2):
- RNScreens/common (4.24.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2327,7 +2325,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNSVG (15.15.5):
- RNSVG (15.15.4):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2348,9 +2346,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNSVG/common (= 15.15.5)
- RNSVG/common (= 15.15.4)
- Yoga
- RNSVG/common (15.15.5):
- RNSVG/common (15.15.4):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2545,7 +2543,6 @@ DEPENDENCIES:
- ReactNativeCameraKit (from `../node_modules/react-native-camera-kit-no-google`)
- ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`)
- RealmJS (from `../node_modules/realm`)
- RNBackgroundFetch (from `../node_modules/react-native-background-fetch`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- RNDefaultPreference (from `../node_modules/react-native-default-preference`)
@ -2765,8 +2762,6 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec"
RealmJS:
:path: "../node_modules/realm"
RNBackgroundFetch:
:path: "../node_modules/react-native-background-fetch"
RNCAsyncStorage:
:path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
@ -2807,13 +2802,13 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
BugsnagReactNative: 73ce58aac04585e7cba3081c0abba06d848d62fc
BugsnagReactNative: bee770e3f497a8571feb1579bdc083a070bee1f3
BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d
hermes-engine: 4ed74710a31e8e31f20356c641eab1d8f7d54595
lottie-ios: 8f959969761e9c45d70353667d00af0e5b9cadb3
lottie-react-native: ee142214581f3bb68fbda7efcf07b835a189eeda
lottie-react-native: 615e5f4651bee144ea991ad8e900630b6b3daf5d
RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12
RCTRequired: 9f3a7e5645d4bc3f551593de7550bb66ab6e42bc
RCTSwiftUI: 239ed2eb9e73de5a6f518810630f0c95e01c8702
@ -2860,7 +2855,7 @@ SPEC CHECKSUMS:
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
react-native-image-picker: 23540feacc79c63c60857f318fdfa8477c26e70a
react-native-notifications: e2d3c022d6077de7e420ba5c01b4bd9464f3941d
react-native-safe-area-context: fb5c8ee9f6dd62ef710611b3d370c501f42a4ac0
react-native-safe-area-context: 6b4966397ada0f7dd481e4486a2ef936834861a1
react-native-secure-key-store: eb45b44bdec3f48e9be5cdfca0f49ddf64892ea6
react-native-tcp-socket: 7c7e53a07f122ecf00fb3626684bc0ca82c4f044
react-native-vector-icons-entypo: f9de1c24005da510dde0de27caf0d2f5471bd433
@ -2905,23 +2900,22 @@ SPEC CHECKSUMS:
ReactNativeCameraKit: 5974256fc608631c1c812710cd98abe95dae0f88
ReactNativeDependencies: 75299c281f422106c723e79dc1f6ce7ef03241be
RealmJS: 1c37c6bdfe060f4caa0f9175aa0eedb962622ee1
RNBackgroundFetch: 64b1215fbb8ec58afba877ca0ce177e009ce12b7
RNCAsyncStorage: 2ad919e88b8bc2cd80e8697ce66d04d006743283
RNCClipboard: 715fa7c6c8366f17d00f05a439ee7488f390fa5f
RNDefaultPreference: 8a089ee8ce829a66c5453e3c5434f0785499d1c3
RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c
RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8
RNGestureHandler: 2ff61eac036eaf89f6818bf4ed9c39771a17d134
RNGestureHandler: 187c5c7936abf427bc4d22d6c3b1ac80ad1f63c0
RNHandoff: bc8af5a86853ff13b033e7ba1114c3c5b38e6385
RNKeychain: 6778b35b5bd067c322f8479526ac09b1d61f31d0
RNLocalize: f370284ea42c48f29f0d8dd3a7bcc28a04f82155
RNPermissions: dfbe915a8ee532bc55018cf5e387407847713b02
RNQuickAction: c2c8f379e614428be0babe4d53a575739667744d
RNReactNativeHapticFeedback: 576e23c1ad2d800ded4502be3f66b767308b63a1
RNReanimated: f735b1747a7a93bda7ca102c6d37a3cf54b6d5e8
RNScreens: 991cc417cd396602a6cf59a42139e5a9d91462a9
RNReanimated: c4e6659e58b793885ae6da476cb514fc913e7b85
RNScreens: 01b065ded2dfe7987bcce770ff3a196be417ff41
RNShare: 2afdc1739d80ac140b2870ae81e8b2098f4599d9
RNSVG: 0e52210d4d43165e7e2cf9c890a9848b27e513ac
RNSVG: 04044c3abcf177fd674a1a3d13097efa1adebcbe
RNWatch: 28fe1f5e0c6410d45fd20925f4796fce05522e3f
RNWorklets: dd3b2cb0750090d78d85cd3b3ec0fdbeab5ce118
Yoga: 77dfa8673de2874e1855002ae59c68b8be9b007b

View File

@ -11,13 +11,6 @@ module.exports = {
'^expo/fetch$': '<rootDir>/util/expo-fetch-nodejs.js',
'^@react-native-vector-icons/(.*)$': '<rootDir>/tests/mocks/vector-icons.js',
'^react-native-svg$': '<rootDir>/tests/mocks/react-native-svg.js',
// Mirror of metro.config.js resolveRequest: descriptors-core uses @noble/hashes v2
// subpaths (e.g. `sha2.js`, `legacy.js`) but does not declare it as a dep, so npm
// resolves up to v1.3.3 (which only exposes the no-extension subpaths via `exports`).
// Redirect any `.js`-suffixed @noble/hashes subpath to the v2 copy nested under
// descriptors-scure. bitcoinjs-lib imports `@noble/hashes/sha256` (no extension)
// so it is unaffected.
'^@noble/hashes/(.+\\.js)$': '<rootDir>/node_modules/@bitcoinerlab/descriptors-scure/node_modules/@noble/hashes/$1',
},
setupFiles: ['./tests/setup.js'],
watchPathIgnorePatterns: ['<rootDir>/node_modules'],

View File

@ -15,8 +15,8 @@
"seed": "عبارة الاسترداد",
"success": "نجاح",
"wallet_key": "مفتاح المحفظة",
"close": "إغلاق",
"change_input_currency": "تغيير عملة الإدخال",
"close": "اغلاق",
"change_input_currency": "تغيير عملة الادخال",
"refresh": "تحديث",
"pick_file": "اختر ملف",
"enter_amount": "أدخل القيمة",
@ -90,8 +90,8 @@
"ask": "هل حفظت العبارة الاحتياطية لمحفظتك؟ ستحتاج إلى هذه العبارة الاحتياطية للوصول إلى أموالك في حالة فقدانك لهذا الجهاز. ودون العبارة الاحتياطية، ستفقد أموالك للأبد.",
"ok": "حسنًا، لقد دوَّنتها!",
"ok_lnd": "حسنًا، لقد حفظتها.",
"text": "يُرجى أخذ لحظة من وقتك لكتابة هذه العبارة التذكيرية على ورقة.\nإنها وسيلتك الاحتياطية لاستعادة المحفظة على جهاز آخر.",
"text_lnd": "يُرجى حفظ هذه النسخة الاحتياطية للمحفظة. لكي تتمكن من استعادة المحفظة في حالة فقدها.",
"text": "يُرجى أخذ لحظة من وقتك لكتابة هذه العبارة التذكيرية على ورقة. إنها وسيلتك الاحتياطية لاستعادة المحفظة على جهاز آخر.",
"text_lnd": "يُرجى حفظ هذه النسخة الاحتياطية للمحفظة. لكي تتتمكن من استعادة المحفظة في حالة فقدها.",
"title": "تم إنشاء محفظتك...",
"ask_no": "لا، لم أقم بذلك.",
"ask_yes": "نعم، لقد قمت بذلك."
@ -127,7 +127,7 @@
"create_fee": "الرسوم",
"create_memo": "مذكرة",
"create_satoshi_per_vbyte": "ساتوشي لكل بايت افتراضي",
"create_this_is_hex": "هذا هو رقم العملية بصيغة ست عشرية (Hex)، موقَّع وجاهز للبث على الشبكة.",
"create_this_is_hex": "هذا هو رقم العملية بصيغة ست عشرية (Hex) ، موقَّع وجاهز للبث على الشبكة.",
"create_to": "إلى",
"create_tx_size": "حجم المعاملة",
"create_verify": "التحقق على coinb.in",
@ -138,21 +138,21 @@
"details_adv_fee_bump": "السماح بزيادة الرسوم",
"details_adv_full": "استخدام الرصيد الكامل",
"details_adv_full_sure": "هل أنت متأكد أنك تريد استخدام الرصيد الكامل لمحفظتك لهذه المعاملة؟",
"details_adv_full_sure_frozen": "هل أنت متأكد أنك ترغب باستخدام الرصيد الكامل لمحفظتك لهذه المعاملة؟ يرجى ملاحظة أنه تم استبعاد العملات المجمدة.",
"details_adv_full_sure_frozen": "هل أنت متأكد انك ترغب بإستخدام الرصيد الكامل لمحفظتك لهذة المعاملة؟ يرجى ملاحظة انه تم استبعاد العملات المجمدة.",
"details_adv_import": "استيراد العملية",
"details_adv_import_qr": "استيراد معاملة (QR)",
"details_amount_field_is_not_valid": "المبلغ غير صالح",
"details_amount_field_is_less_than_minimum_amount_sat": "المبلغ المحدد صغير جدًا. الرجاء إدخال مبلغ أكبر من 500 ساتوشي.",
"details_create": "إنشاء برقية",
"details_error_decode": "يتعذَّر فك ترميز عنوان البتكوين",
"details_error_decode": "يتعذَّر تجزئة وتحليل عنوان البتكوين",
"details_fee_field_is_not_valid": "حقل الرسوم غير صالح",
"details_next": "التالي",
"details_no_signed_tx": "لا يحتوي الملف المحدَّد على معاملة موقَّعة يمكن استيرادها.",
"details_note_placeholder": "ملاحظة شخصية",
"details_scan": "المسح الضوئي",
"details_scan_hint": "انقر نقرًا مزدوجًا لنسخ أو استيراد مستلم",
"details_scan_hint": "انقر نقرًا مزدوجًا لنسخ او استيراد مستلم",
"details_total_exceeds_balance": "مبلغ الإرسال يتجاوز الرصيد المتاح.",
"details_total_exceeds_balance_frozen": "مبلغ الإرسال يتجاوز الرصيد المتاح، يرجى ملاحظة أنه تم استبعاد العملات المجمدة.",
"details_total_exceeds_balance_frozen": "مبلغ الإرسال يتجاوز الرصيد المتاح، يرجى ملاحظة انه تم استبعاد العملات المجمدة.",
"details_unrecognized_file_format": "تنسيق ملف غير معروف",
"details_wallet_before_tx": "يجب عليك إضافة محفظة بتكوين أولًا قبل إنشاء معاملة.",
"dynamic_init": "جارٍ التهيئة...",
@ -219,14 +219,14 @@
"performance_score": "معدل الأداء: {num}",
"run_performance_test": "اختبار الأداء",
"about_selftest": "تشغيل اختبار ذاتي",
"about_selftest_electrum_disabled": "لا يتوفر الاختبار الذاتي مع وضع Electrum غير المتصل بالإنترنت. يرجى تعطيل وضع عدم اتصال الشبكة والمحاولة مرة أخرى.",
"about_selftest_electrum_disabled": "لا يتوفر الاختبار الذاتي مع وضع Electrum الغير متصل بالانترنت. يرجى تعطيل وضع عدم اتصال الشبكة والمحاولة مرة أخرى.",
"about_selftest_ok": "تم اجتياز جميع الاختبارات الداخلية بنجاح. المحفظة تعمل بشكل جيد.",
"about_sm_github": "GitHub",
"about_sm_telegram": "قناة تيليجرام",
"biometrics": "القياسات الحيوية",
"biom_10times": "لقد حاولت إدخال كلمة المرور الخاصة بك 10 مرات. هل ترغب في إعادة تعيين التخزين الخاص بك؟ سيؤدي هذا إلى إزالة جميع المحافظ وفك تشفير التخزين الخاص بك.",
"biom_conf_identity": "الرجاء تأكيد هويتك.",
"biom_remove_decrypt": "ستتم إزالة جميع محافظك وفك تشفير التخزين الخاص بك. هل أنت متأكد أنك تريد المتابعة؟",
"biom_remove_decrypt": "ستتم إزالة جميع محافظك وفك تشفير التخزين الخاص بك. هل انت متأكد انك تريد المتابعة؟",
"currency": "العملة",
"currency_fetch_error": "حدث خطأ أثناء الحصول على سعر العملة المحددة.",
"default_title": "عند التشغيل",
@ -256,7 +256,7 @@
"header": "الإعدادات",
"language": "اللغة",
"last_updated": "آخر تحديث",
"language_isRTL": "يجب إعادة تشغيل BlueWallet حتى تظهر تعديلات تغيير اللغة.",
"language_isRTL": "يجب اعادة تشغيل BlueWallet حتى تظهر تعديلات تغيير اللغة.",
"lightning_saved": "تم حفظ تغييراتك بنجاح",
"lightning_settings": "إعدادات البرق",
"network": "الشبكة",
@ -265,11 +265,11 @@
"not_a_valid_uri": "معرِّف URI غير صالح",
"notifications": "الإشعارات",
"open_link_in_explorer": "فتح الرابط في المتصفح",
"password": "كلمة المرور",
"password": "كلمه المرور",
"plausible_deniability": "الإنكار المقبول",
"privacy": "الخصوصية",
"privacy_read_clipboard": "قراءة الحافظة",
"privacy_system_settings": "إعدادات الجهاز",
"privacy_system_settings": "اعدادات الجهاز",
"privacy_quickactions": "اختصارات المحفظة",
"privacy_clipboard_explanation": "عرض اختصار إذا تم العثور على عنوان أو فاتورة في الحافظة الخاصة بك.",
"privacy_do_not_track": "تعطيل التحليلات",
@ -278,10 +278,10 @@
"selfTest": "اختبار ذاتي",
"save": "حفظ",
"saved": "تم الحفظ",
"total_balance": "الرصيد الإجمالي",
"total_balance": "الرصيد الاجمالي",
"total_balance_explanation": "اعرض الرصيد الإجمالي لجميع محافظك على ويدجت الشاشة الرئيسية الخاصة بك.",
"widgets": "ويدجت",
"tools": "أدوات",
"tools": "ادوات",
"block_explorer_invalid_custom_url": "الرابط المقدم غير صالح. يُرجى إدخال رابط صالح يبدأ بـ http:// أو https://.",
"privacy_temporary_screenshots": "السماح بالتقاط الشاشة",
"privacy_temporary_screenshots_instructions": "سيتم إيقاف حماية التقاط الشاشة مؤقتًا، مما يسمح بأخذ لقطات وتسجيلات الشاشة. ستعاد الحماية تلقائيًا عند إغلاق BlueWallet وإعادة فتحه.",
@ -329,10 +329,11 @@
"permission_denied_message": "لقد رفضت الإذن لإرسال الإشعارات إليك. إذا كنت ترغب في تلقي الإشعارات، يُرجى تفعيلها في إعدادات جهازك."
},
"transactions": {
"cancel_explain": "سنستبدل هذه المعاملة بمعاملة ذات رسوم أعلى وتُدفع لك أنت؛ سيؤدي ذلك إلى إلغاء المعاملة وإعادة المبلغ لمحفظتك. وهذا ما يُسمَّى RBF؛ أي الاستبدال بالرسوم.",
"cancel_explain": "سنستبدل هذه المعاملة بمعاملة ذات رسوم أعلى وتُدفع لك انت؛ سيؤدي ذلك الى الغاء المعاملة واعادة المبلغ لمحفظتك. وهذا ما يُسمَّى RBF؛ أي الاستبدال بالرسوم.",
"cancel_no": "هذه المعاملة غير قابلة للاستبدال",
"cancel_title": "إلغاء هذه المعاملة (RBF)",
"confirmations_lowercase": "{confirmations} تأكيد",
"copy_link": "نسخ الرابط",
"expand_note": "توسيع الملاحظة",
"cpfp_create": "إنشاء",
"cpfp_exp": "سننشئ عملية أخرى تستبدل عمليتك غير المؤكدة. وسيكون إجمالي الرسوم أعلى من رسوم العملية الأصلية؛ حتى يجري تعدينها بشكلٍ أسرع. وهذا ما يُسمَّى CPFP؛ أي التحكم بالعملية الرئيسية بمعاملة فرعية.",
@ -344,6 +345,7 @@
"details_copy_block_explorer_link": "نسخ رابط متصفح الكتل",
"details_copy_note": "نسخ الملاحظة",
"details_copy_txid": "نسخ معرّف المعاملة",
"details_from": "من",
"details_inputs": "المدخلات",
"details_outputs": "المخرجات",
"date": "التاريخ",
@ -358,6 +360,7 @@
"eta_10m": "الوقت المقدر للتأكيد: في حوالي 10 دقائق",
"eta_3h": "الوقت المقدر للتأكيد: في حوالي 3 ساعات",
"eta_1d": "الوقت المقدر للتأكيد: في حوالي يوم واحد",
"view_wallet": "عرض {walletLabel}",
"list_title": "العمليات",
"list_title_received": "التاريخ",
"transaction": "العملية",
@ -377,8 +380,8 @@
"outgoing_transaction": "معاملة صادرة",
"expired_transaction": "معاملة منتهية الصلاحية",
"pending_transaction": "معاملة قيد الانتظار",
"offchain": "خارج السلسلة",
"onchain": "على السلسلة",
"offchain": "أوف-تشين",
"onchain": "أون-تشين",
"list_title_sent": "مرسلة",
"watchOnlyWarningTitle": "تحذير أمني",
"custom_fee_warning_title": "تحذير",
@ -404,7 +407,7 @@
"add_bitcoin": "بتكوين",
"add_bitcoin_explain": "محفظة بتكوين بسيطة وقوية",
"add_create": "إنشاء",
"total_balance": "الرصيد الإجمالي",
"total_balance": "الرصيد الاجمالي",
"add_entropy": "الإنتروبيا (العشوائية)",
"add_entropy_generated": "{gen} بايت من الإنتروبيا (العشوائية) المحققة",
"add_entropy_provide": "توفير الإنتروبيا (العشوائية) باستخدام النرد",
@ -423,12 +426,12 @@
"details_advanced": "الخيارات المتقدمة",
"details_are_you_sure": "هل أنت متأكد؟",
"details_connected_to": "متصلة بـ",
"details_del_wb_q": "هذه المحفظة يوجد بها رصيد. قبل الاستمرار يرجى العلم أنك لن تتمكن من استرداد الأموال بدون عبارة الاسترداد لهذه المحفظة. لتجنب الحذف غير المتعمد، يرجى إدخال رصيد المحفظة البالغ {balance} ساتوشي.",
"details_del_wb_q": "هذه المحفظة يوجد بها رصيد. قبل الاستمرار يرجى العلم أنك لن تتمكن من استرداد الأموال بدون عبارة الاسترداد لهذه المحفظة. لتجنب الحذف الغير متعمد، يرجى إدخال رصيد المحفظة البالغ {balance} ساتوشي.",
"details_delete": "الحذف",
"details_delete_wallet": "حذف المحفظة",
"details_derivation_path": "مسار الاشتقاق (derivation path)",
"details_export_backup": "التصدير/النسخ الاحتياطي",
"details_export_history": "تصدير السجل إلى ملف CSV",
"details_export_history": "تصدير السجل ل ملف CSV",
"details_master_fingerprint": "البصمة الرئيسية",
"details_multisig_type": "متعدد التواقيع",
"details_show_xpub": "إظهار عنوان XPUB للمحفظة",
@ -471,11 +474,12 @@
"list_title": "المحافظ",
"list_tryagain": "إعادة المحاولة",
"no_ln_wallet_error": "قبل دفع البرقية، يجب عليك أولاً إضافة محفظة برق.",
"looks_like_bip38": "يبدو أن هذا مفتاح خاص محمي بكلمة مرور (BIP38).",
"looks_like_bip38": "يبدوا ان هذا مفتاح خاص محمي بكلمة مرور (BIP38).",
"please_continue_scanning": "الرجاء متابعة الفحص.",
"select_no_bitcoin": "لا توجد محافظ بتكوين متاحة حاليًا.",
"select_no_bitcoin_exp": "تحتاج إلى محفظة بتكوين لإعادة تعبئة محافظ البرق. يُرجى إنشاء محفظة أو استيراد واحدة.",
"select_wallet": "اختيار محفظة",
"xpub_copiedToClipboard": "تم النسخ إلى الحافظة.",
"pull_to_refresh": "اسحب للتحديث",
"add_ln_wallet_first": "يجب عليك أولاً إضافة محفظة برق.",
"identity_pubkey": "هوية Pubkey",
@ -520,7 +524,7 @@
"details_delete_anyway": "احذف على أي حال"
},
"total_balance_view": {
"title": "الرصيد الإجمالي",
"title": "الرصيد الاجمالي",
"display_in_bitcoin": "العرض بـ Bitcoin",
"hide": "إخفاء",
"display_in_sats": "العرض بالساتوشي",
@ -530,21 +534,21 @@
"multisig": {
"multisig_vault": "خزنة متعددة التواقيع",
"default_label": "خزنة متعددة التواقيع",
"multisig_vault_explain": "أفضل حماية للمبالغ الكبيرة",
"multisig_vault_explain": "افضل حماية للمبالغ الكبيرة",
"provide_signature": "قدم توقيعًا",
"vault_key": "مفتاح الخزنة {number}",
"required_keys_out_of_total": "المفاتيح المطلوبة من الإجمالي",
"required_keys_out_of_total": "المفاتيح المطلوبة من الاجمالي",
"fee": "الرسوم: {number}",
"fee_btc": "{number} BTC",
"confirm": "تأكيد",
"header": "إرسال",
"view": "عرض",
"manage_keys": "إدارة المفاتيح",
"how_many_signatures_can_bluewallet_make": "كم عدد التوقيعات التي يمكن لـ BlueWallet أن تنشئها",
"how_many_signatures_can_bluewallet_make": "كم عدد التوقيعات التي يمكن لـ BlueWallet ان تنشأها",
"signatures_required_to_spend": "التوقيعات المطلوبة {number}",
"signatures_we_can_make": "يمكن أن تنشئ {number}",
"signatures_we_can_make": "يمكن أن تنشأ {number}",
"scan_or_import_file": "المسح الضوئي أو استيراد ملف",
"export_coordination_setup": "تصدير تنسيق الإعدادات",
"export_coordination_setup": "تصدير تنسيق الاعدادات",
"cosign_this_transaction": "المشاركة في التوقيع على هذه المعاملة؟",
"lets_start": "لنبدأ",
"create": "إنشاء",
@ -555,11 +559,11 @@
"what_is_vault": "الخزنة هي",
"what_is_vault_numberOfWallets": "{m}-من-{n} محفظة متعددة التواقيع",
"what_is_vault_wallet": ".",
"vault_advanced_customize": "إعدادات الخزنة",
"vault_advanced_customize": "اعدادات الخزنة",
"needs": "تحتاج",
"what_is_vault_description_number_of_vault_keys": "{m} من مفاتيح الخزنة",
"what_is_vault_description_to_spend": إرسال أي معاملة ومفتاح ثالث\nيمكنك استخدامه كاحتياطي.",
"what_is_vault_description_to_spend_other": إرسال أي معاملة",
"what_is_vault_description_to_spend": ارسال اي معاملة ومفتاح ثالث يمكنك استخدامه كاحتياطي.",
"what_is_vault_description_to_spend_other": ارسال اي معاملة",
"quorum": "العدد {m} من {n}",
"quorum_header": "العدد",
"of": "من",
@ -569,27 +573,27 @@
"scan_or_open_file": "المسح الضوئي أو استيراد ملف",
"i_have_mnemonics": "لدي عبارة تذكيرية لهذا المفتاح.",
"type_your_mnemonics": "أدخل العبارة التذكيرية لاستيراد مفتاح خزنتك الحالية.",
"wallet_key_created": "تم إنشاء خزنتك. يُرجى أخذ لحظة من وقتك لتدوين عبارة الاسترداد في مكان آمن.",
"wallet_key_created": "تم انشاء خزنتك. يُرجى أخذ لحظة من وقتك لتدوين عبارة الاسترداد في مكان آمن.",
"are_you_sure_seed_will_be_lost": "هل أنت متأكد؟ ستفقد عبارة الاسترداد إذا لم تحتفظ بها في مكان آخر آمن.",
"forget_this_seed": "انسَ هذه العبارة التذكيرية واستخدم XPUB بدلاً من ذلك.",
"forget_this_seed": "انسى هذه العبارة التذكيرية واستخدام XPUB بدلا من ذلك.",
"export_signed_psbt": "تصدير توقيع PSBT",
"input_fp": "أدخل البصمة (fingerprint)",
"input_fp": "أدخل بصمة الإصبع",
"input_fp_explain": "تخطي لاستخدام الرقم الافتراضي (00000000)",
"input_path": "أدخل مسار الاشتقاق (derivation path)",
"input_path": "ادخل مسار الاشتقاق (derivation path)",
"input_path_explain": "تخطي لاستخدام الخيار الافتراضي ({default})",
"ms_help": "مساعدة",
"ms_help_title": "كيف تعمل الخزائن متعددة التواقيع: النصائح والحيل",
"ms_help_title": "كيف تعمل الخزائن معتددة التواقيع : النصائح والحيل",
"ms_help_text": "محفظة بمفاتيح متعددة، لزيادة الأمان أو التملك المشترك",
"ms_help_title1": "ينصح باستخدام أجهزة متعددة.",
"ms_help_1": "ستعمل الخزنة مع تطبيقات BlueWallet الأخرى والمحافظ المتوافقة مع PSBT، مثل Electrum و Specter و Coldcard و Cobo Vault، إلخ.",
"ms_help_title2": "تحرير المفاتيح",
"ms_help_2": "يمكنك إنشاء جميع مفاتيح الخزنة في هذا الجهاز وإزالتها أو تعديلها لاحقًا. إن وجود جميع المفاتيح على نفس الجهاز له نفس درجة الأمان لمحفظة أحادية التوقيع.",
"ms_help_2": "يمكنك إنشاء جميع مفاتيح الخزنة في هذا الجهاز وإزالتها أو تعديلها لاحقًا. إن وجود جميع المفاتيح على نفس الجهاز له نفس درجة الامان لمحفظة أحادية التوقيع.",
"ms_help_title3": "النسخ الاحتياطية للخزنة",
"ms_help_3": "في خيارات المحفظة، ستجد نسخة احتياطية من الخزنة ونسخة احتياطية بصلاحيات \"مراقبة فقط\". هذه النسخة الاحتياطية هي بمثابة الخريطة لمحفظتك. إنها ضرورية لاستعادة الخزنة في حالة فقدان إحدى عبارات الاسترداد الخاصة بك.",
"ms_help_title4": "استيراد الخزائن",
"ms_help_4": "لاستيراد خزنة متعددة التواقيع، استخدم ملف النسخ الاحتياطي وميزة الاستيراد. إذا كان لديك عبارات الاسترداد و XPUBs فقط، فيمكنك استخدام زر الاستيراد الفردي عند إنشاء مفاتيح الخزنة.",
"ms_help_4": "لاستيراد خزنة متعددة التواقيع، استخدم ملف النسخ الاحتياطي وميزة الاستيراد. إذا كان لديك عبارات الاسترداد و XPUBs فقط ، فيمكنك استخدام زر الاستيراد الفردي عند إنشاء مفاتيح الخزنة.",
"ms_help_title5": "الوضع المتقدم",
"ms_help_5": "بشكل افتراضي، ستقوم BlueWallet بإنشاء خزنة متعددة التواقيع لـ 2 من 3. لإنشاء إعدادات مختلفة أو تغيير نوع العنوان، قم بتنشيط الوضع المتقدم في الإعدادات.",
"ms_help_5": "بشكل افتراضي، ستقوم BlueWallet بإنشاء خزنة متعددة التواقيع لـ 2 من 3. لإنشاء اعدادًا مختلفة أو تغيير نوع العنوان، قم بتنشيط الوضع المتقدم في الإعدادات.",
"provide_signature_details": "استخدم جهازك ومحفظتك حيث يوجد المفتاح لتوقيع هذه المعاملة",
"provide_signature_details_bluewallet": "في BlueWallet، انتقل إلى قائمة شاشة الإرسال وحدد ",
"provide_signature_next_steps": "مسح أو استيراد معاملة موقعة",
@ -681,7 +685,7 @@
},
"bip47": {
"payment_code": "كود الدفع",
"purpose": "أكواد المشاركة التي يمكن إعادة استخدامها (BIP47)",
"purpose": "أكواد المشاركة التي يمكن أعادة استخدامها (BIP47)",
"not_found": "لم يتم العثور على كود الدفع",
"contacts": "جهات الاتصال",
"bip47_explain": "رمز قابل لإعادة الاستخدام والمشاركة",
@ -697,7 +701,7 @@
"invalid_pc": "رمز دفع غير صالح",
"notification_tx_unconfirmed": "معاملة الإشعار غير مؤكدة بعد، يُرجى الانتظار",
"failed_create_notif_tx": "فشل إنشاء المعاملة على السلسلة",
"onchain_tx_needed": عاملة على السلسلة مطلوبة",
"onchain_tx_needed": طلوب معاملة على السلسلة",
"notif_tx_sent": "تم إرسال معاملة الإشعار. يُرجى الانتظار حتى يتم تأكيدها",
"notif_tx": "معاملة الإشعار"
}

View File

@ -16,17 +16,17 @@
"yes": "Так",
"no": "Не",
"save": "Захаваць...",
"seed": "Сід-фраза",
"seed": "Семя",
"success": "Посьпех",
"wallet_key": "Ключ ад кашалька",
"close": "Закрыць",
"change_input_currency": "Зьмяніць валюту ўводу",
"refresh": "Абнавіць",
"pick_image": "Выбраць з бібліятэкі",
"pick_image": "Выбраць зь бібліятэкі",
"pick_file": "Выбраць файл",
"enter_amount": "Увесьці суму",
"qr_custom_input_button": "Націсьніце 10 разоў, каб увесьці адвольны ўвод",
"unlock": "Разблякаваць",
"unlock": "Разблакаваць",
"port": "Порт",
"ssl_port": "SSL-порт",
"suggested": "Прапанаваны"
@ -34,7 +34,7 @@
"azteco": {
"codeIs": "Ваш код ваўчара",
"errorBeforeRefeem": "Перад выплатай вы павінны спачатку дадаць кашалёк Bitcoin.",
"errorSomething": "Нешта пайшло не так. Ці гэты ваўчар яшчэ сапраўдны?",
"errorSomething": "Нешта пайшло не так. Сапраўдны ці гэты ваўчар па-ранейшаму?",
"redeem": "Перавесьці на кашалёк",
"redeemButton": "Перавесьці",
"success": "Посьпех",
@ -45,7 +45,7 @@
"save": "Захаваць",
"title": "Энтрапія",
"undo": "Адмяніць",
"amountOfEntropy": "{bits} з {limit} бітаў"
"amountOfEntropy": "{bits} зь {limit} біт"
},
"errors": {
"broadcast": "Збой трансляцыі.",
@ -101,7 +101,7 @@
"details_label": "Апісаньне",
"details_setAmount": "Атрымаць з сумай",
"details_share": "Падзяліцца...",
"address_not_found": "Немагчыма згенэраваць адрас для атрыманьня.",
"address_not_found": "Немагчыма згенераваць адрас для атрыманьня.",
"bip47_explanation": "Плацежныя коды — гэта ўнівэрсальны адрас, які пазьбягае раскрыцьця адрасоў вашага кашалька. Не ўсе сэрвісы будуць іх падтрымліваць.",
"header": "Атрымаць",
"reset": "Скінуць",
@ -153,7 +153,7 @@
"details_create": "Стварыць інвойс",
"details_error_decode": "Немагчыма дэкадаваць адрас Bitcoin",
"details_fee_field_is_not_valid": "Камісія несапраўдная.",
"details_frozen": "{amount} BTC замарожана.",
"details_frozen": "{amount} BTC заморажана.",
"details_next": "Далей",
"details_no_signed_tx": "Выбраны файл ня ўтрымлівае трансакцыі, якую можна імпартаваць.",
"details_note_placeholder": "Нататка сабе",
@ -190,7 +190,7 @@
"open_settings": "Адкрыць налады",
"permission_storage_denied_message": "BlueWallet ня можа захаваць гэты файл. Калі ласка, адкрыйце налады вашай прылады і ўключыце дазвол на доступ да сховішча.",
"permission_storage_title": "Дазвол на доступ да сховішча",
"psbt_clipboard": "Скапіяваць у буфэр абмену",
"psbt_clipboard": "Скапіяваць у буфер абмену",
"psbt_this_is_psbt": "Гэта часткова падпісаная трансакцыя Bitcoin (PSBT). Калі ласка, скончыце яе падпісаньне з дапамогай вашага апаратнага кашалька.",
"psbt_tx_export": "Экспартаваць у файл",
"no_tx_signing_in_progress": "Няма падпісаньня трансакцыі ў працэсе.",
@ -225,8 +225,8 @@
"about_sm_telegram": "Канал у Telegram",
"privacy_temporary_screenshots": "Дазволіць захоп экрану",
"privacy_temporary_screenshots_instructions": "Абарона ад захопу экрану будзе часова адключана, дазваляючы рабіць здымкі экрану і запіс экрану. Абарона аўтаматычна актывуецца зноў, калі вы закрыеце і адкрыеце BlueWallet.",
"biometrics": "Біямэтрыя",
"biometrics_no_longer_available": "Налады вашай прылады зьмяніліся і больш не адпавядаюць выбраным наладам бясьпекі ў праграме. Калі ласка, паўторна ўключыце біямэтрыю альбо код доступу, а затым перазапусьціце праграму, каб прымяніць гэтыя зьмены.",
"biometrics": "Біяметрыя",
"biometrics_no_longer_available": "Налады вашай прылады зьмяніліся і больш не адпавядаюць выбраным наладам бясьпекі ў праграме. Калі ласка, паўторна ўключыце біяметрыю альбо код доступу, а затым перазапусьціце праграму, каб прымяніць гэтыя зьмены.",
"biom_10times": "Вы 10 разоў спрабавалі ўвесьці пароль. Жадаеце скінуць ваша сховішча? Гэта выдаліць усе кашалькі і расшыфруе ваша сховішча.",
"biom_conf_identity": "Калі ласка, пацьвердзіце вашу асобу.",
"biom_remove_decrypt": "Усе вашы кашалькі будуць выдалены, і ваша сховішча будзе расшыфравана. Вы ўпэўненыя, што хочаце працягнуць?",
@ -238,20 +238,20 @@
"donate_description": "Дапамажыце нам захаваць Blue бясплатнай!",
"electrum_connected": "Падключана",
"electrum_connected_not": "Не падключана",
"electrum_error_connect": "Немагчыма падключыцца да ўказанага сэрвэра Electrum",
"electrum_error_connect_tor": "Немагчыма падключыцца да ўказанага сэрвэра Electrum. Калі ласка, пераканайцеся, што праграма Orbot падключана, і паспрабуйце зноў.",
"electrum_error_connect": "Немагчыма падключыцца да ўказанага сервэра Electrum",
"electrum_error_connect_tor": "Немагчыма падключыцца да ўказанага сервэра Electrum. Калі ласка, пераканайцеся, што праграма Orbot падключана, і паспрабуйце зноў.",
"lndhub_uri": "Напр., {example}",
"electrum_host": "Напр., {example}",
"electrum_offline_mode": "Афлайнавы рэжым",
"electrum_offline_description": "Калі ўключана, вашы кашалькі Bitcoin не будуць спрабаваць атрымліваць балансы альбо трансакцыі.",
"electrum_port": "Порт, звычайна {example}",
"use_ssl": "Выкарыстоўваць SSL",
"electrum_settings_server": "Сэрвэр Electrum",
"electrum_settings_server": "Сервэр Electrum",
"electrum_status": "Статус",
"electrum_preferred_server": "Пераважны сэрвэр",
"electrum_preferred_server_description": "Увядзіце сэрвэр, які вы хочаце, каб ваш кашалёк выкарыстоўваў для ўсіх дзеяньняў Bitcoin. Пасьля ўстаноўкі ваш кашалёк будзе выключна выкарыстоўваць гэты сэрвэр для праверкі балансаў, адпраўкі трансакцыяў і атрыманьня сеткавых дадзеных. Пераканайцеся, што вы давяраеце гэтаму сэрвэру, перш чым яго ўстанаўліваць.",
"electrum_preferred_server": "Пераважны сервэр",
"electrum_preferred_server_description": "Увядзіце сервэр, які вы хочаце, каб ваш кашалёк выкарыстоўваў для ўсіх дзеяньняў Bitcoin. Пасьля ўстаноўкі ваш кашалёк будзе выключна выкарыстоўваць гэты сервэр для праверкі балансаў, адпраўкі трансакцыяў і атрыманьня сеткавых дадзеных. Пераканайцеся, што вы давяраеце гэтаму сервэру, перш чым яго ўстанаўліваць.",
"electrum_history": "Гісторыя",
"electrum_reset_to_default": "Гэта дазволіць BlueWallet выпадковым чынам выбраць сэрвэр са сьпісу сэрвэраў.",
"electrum_reset_to_default": "Гэта дазволіць BlueWallet выпадковым чынам выбраць сервэр са сьпісу сервэраў.",
"electrum_reset": "Скінуць да змаўчаньня",
"electrum_reset_to_default_and_clear_history": "Скінуць да змаўчаньня і ачысьціць гісторыю",
"electrum_saved": "Вашы зьмены пасьпяхова захаваны. Перазапуск BlueWallet можа спатрэбіцца для прымяненьня зьменаў.",
@ -273,11 +273,11 @@
"encrypt_use": "Выкарыстоўваць {type}",
"encrypt_use_expl": "{type} будзе выкарыстоўвацца для пацьверджаньня вашай асобы перад правядзеньнем трансакцыі, разблакаваньнем, экспартам альбо выдаленьнем кашалька.",
"set_as_preferred": "Зрабіць пераважным",
"set_as_preferred_electrum": "Усталяваньне {host}:{port} як пераважнага сэрвэра адключыць выпадковае падключэньне да прапанаванага сэрвэра.",
"set_as_preferred_electrum": "Усталяваньне {host}:{port} як пераважнага сервэра адключыць выпадковае падключэньне да прапанаванага сервэра.",
"general": "Агульныя",
"general_continuity": "Continuity",
"general_continuity_e": "Калі ўключана, вы зможаце праглядаць выбраныя кашалькі і трансакцыі, выкарыстоўваючы вашы іншыя прылады Apple, падключаныя да iCloud.",
"groundcontrol_explanation": "GroundControl — гэта бясплатны сэрвэр пуш-апавяшчэньняў з адкрытым зыходным кодам для кашалькоў Bitcoin. Вы можаце ўсталяваць ваш уласны сэрвэр GroundControl і ўвесьці яго URL сюды, каб не залежаць ад інфраструктуры BlueWallet. Пакіньце пустым, каб выкарыстаць стандартны сэрвэр GroundControl.",
"groundcontrol_explanation": "GroundControl — гэта бясплатны сервэр пуш-апавяшчэньняў з адкрытым зыходным кодам для кашалькоў Bitcoin. Вы можаце ўсталяваць ваш уласны сервэр GroundControl і ўвесьці яго URL сюды, каб не залежаць ад інфраструктуры BlueWallet. Пакіньце пустым, каб выкарыстаць стандартны сервэр GroundControl.",
"header": "Налады",
"language": "Мова",
"language_isRTL": "Перазапуск BlueWallet патрабуецца, каб арыентацыя мовы ўступіла ў сілу.",
@ -291,8 +291,8 @@
"lndhub_github": "Рэпазытар на GitHub",
"network": "Сетка",
"network_broadcast": "Транслюваць трансакцыю",
"network_electrum": "Сэрвэр Electrum",
"electrum_suggested_description": "Калі пераважны сэрвэр ня ўстаноўлены, прапанаваны сэрвэр будзе выпадковым чынам выбіраны для выкарыстаньня.",
"network_electrum": "Сервэр Electrum",
"electrum_suggested_description": "Калі пераважны сервэр ня ўстаноўлены, прапанаваны сервэр будзе выпадковым чынам выбіраны для выкарыстаньня.",
"not_a_valid_uri": "Няправільны URI",
"notifications": "Апавяшчэньні",
"open_link_in_explorer": "Адкрыць спасылку ў аглядальніку",
@ -300,22 +300,22 @@
"password_explain": "Увядзіце пароль, які вы будзеце выкарыстоўваць для разблакаваньня вашага сховішча.",
"plausible_deniability": "Праўдападобнае адмаўленьне",
"privacy": "Прыватнасьць",
"privacy_read_clipboard": "Чытаць буфэр абмену",
"privacy_read_clipboard": "Чытаць буфер абмену",
"privacy_system_settings": "Сыстэмныя налады",
"privacy_quickactions": "Цэтлікі кашалька",
"privacy_quickactions_explanation": "Націсьніце і ўтрымлівайце значок BlueWallet, каб хутка прагледзець баланс вашага кашалька.",
"privacy_clipboard_explanation": "Прадастаўляць цэтлікі, калі адрас альбо інвойс знойдзены ў вашым буфэры абмену.",
"privacy_clipboard_explanation": "Прадастаўляць цэтлікі, калі адрас альбо інвойс знойдзены ў вашым буферы абмену.",
"privacy_do_not_track": "Адключыць аналітыку",
"privacy_do_not_track_explanation": "Інфармацыя пра прадукцыйнасьць і надзейнасьць ня будзе адпраўляцца для аналізу.",
"rate": "Курс",
"biometrics_fail": "Калі {type} не ўключана, альбо ня ўдаецца разблакаваць, вы можаце выкарыстаць код доступу прылады як альтэрнатыву.",
"biom_no_passcode": "Ваша прылада ня мае ўключанага коду доступу альбо біямэтрыі. Каб працягнуць, калі ласка, наладзьце код доступу альбо біямэтрыю ў наладах праграмы.",
"push_notifications_explanation": "Уключыўшы апавяшчэньні, токэн вашай прылады будзе адпраўлены на сэрвэр разам з адрасамі кашалькоў і ID трансакцыяў для ўсіх кашалькоў і трансакцыяў, зробленых пасьля ўключэньня апавяшчэньняў. Токэн прылады выкарыстоўваецца для адпраўкі апавяшчэньняў, а інфармацыя пра кашалёк дазваляе нам апавяшчаць вас аб уваходных Bitcoin альбо пацьверджаньнях трансакцыяў.\n\nПерадаецца толькі інфармацыя пасьля таго, як вы ўключыце апавяшчэньні — нічога зь перыяду да гэтага не зьбіраецца.\n\nАдключэньне апавяшчэньняў выдаліць усю гэтую інфармацыю з сэрвэра. Акрамя таго, выдаленьне кашалька з праграмы таксама выдаліць зьвязаную зь ім інфармацыю з сэрвэра.",
"biom_no_passcode": "Ваша прылада ня мае ўключанага коду доступу альбо біяметрыі. Каб працягнуць, калі ласка, наладзьце код доступу альбо біяметрыю ў наладах праграмы.",
"push_notifications_explanation": "Уключыўшы апавяшчэньні, токен вашай прылады будзе адпраўлены на сервэр разам з адрасамі кашалькоў і ID трансакцыяў для ўсіх кашалькоў і трансакцыяў, зробленых пасьля ўключэньня апавяшчэньняў. Токен прылады выкарыстоўваецца для адпраўкі апавяшчэньняў, а інфармацыя пра кашалёк дазваляе нам апавяшчаць вас аб уваходных Bitcoin альбо пацьверджаньнях трансакцыяў.\n\nПерадаецца толькі інфармацыя пасьля таго, як вы ўключыце апавяшчэньні — нічога зь перыяду да гэтага не зьбіраецца.\n\nАдключэньне апавяшчэньняў выдаліць усю гэтую інфармацыю з сервэра. Акрамя таго, выдаленьне кашалька з праграмы таксама выдаліць зьвязаную зь ім інфармацыю з сервэра.",
"selfTest": "Самаправерка",
"save": "Захаваць",
"saved": "Захавана",
"set_electrum_server_as_default": "Усталяваць {server} як стандартны сэрвэр Electrum?",
"set_lndhub_as_default": "Усталяваць {url} як стандартны сэрвэр LNDhub?",
"set_electrum_server_as_default": "Усталяваць {server} як стандартны сервэр Electrum?",
"set_lndhub_as_default": "Усталяваць {url} як стандартны сервэр LNDhub?",
"success_transaction_broadcasted": "Ваша трансакцыя пасьпяхова транслявана!",
"total_balance": "Агульны баланс",
"total_balance_explanation": "Адлюстраваць агульны баланс усіх вашых кашалькоў на віджэтах хатняга экрану.",
@ -409,9 +409,9 @@
"add_entropy_reset_message": "Зьмена тыпу кашалька скіне бягучую энтрапію. Жадаеце працягнуць?",
"add_entropy": "Энтрапія",
"add_entropy_bytes": "{bytes} байтаў энтрапіі",
"add_entropy_generated": "{gen} байтаў згенэраванай энтрапіі",
"add_entropy_generated": "{gen} байтаў згенераванай энтрапіі",
"add_entropy_provide": "Прадаставіць энтрапію праз кідкі касьцяў",
"add_entropy_remain": "{gen} байтаў згенэраванай энтрапіі. Астатнія {rem} байтаў будуць атрыманы ад сыстэмнага генэратара выпадковых лікаў.",
"add_entropy_remain": "{gen} байтаў згенераванай энтрапіі. Астатнія {rem} байтаў будуць атрыманы ад сыстэмнага генератара выпадковых лікаў.",
"add_import_wallet": "Імпартаваць кашалёк",
"add_lightning": "Lightning",
"add_lightning_explain": "Для расходаваньня зь імгненнымі трансакцыямі",
@ -422,18 +422,18 @@
"add_title": "Дадаць кашалёк",
"add_wallet_name": "Назва",
"add_wallet_type": "Тып",
"add_wallet_seed_length": "Даўжыня сід-фразы",
"add_wallet_seed_length": "Даўжыня семя",
"add_wallet_seed_length_12": "12 словаў",
"add_wallet_seed_length_24": "24 словы",
"clipboard_bitcoin": "У вашым буфэры абмену ёсьць адрас Bitcoin. Жадаеце выкарыстаць яго для трансакцыі?",
"clipboard_lightning": "У вашым буфэры абмену ёсьць інвойс Lightning. Жадаеце выкарыстаць яго для трансакцыі?",
"clear_clipboard_on_import": "Ачысьціць буфэр абмену пры імпарце",
"clipboard_bitcoin": "У вашым буферы абмену ёсьць адрас Bitcoin. Жадаеце выкарыстаць яго для трансакцыі?",
"clipboard_lightning": "У вашым буферы абмену ёсьць інвойс Lightning. Жадаеце выкарыстаць яго для трансакцыі?",
"clear_clipboard_on_import": "Ачысьціць буфер абмену пры імпарце",
"details_address": "Адрас",
"details_advanced": "Дадаткова",
"details_are_you_sure": "Вы ўпэўненыя?",
"details_connected_to": "Падключана да",
"details_del_wb_err": "Прадастаўленая сума балансу не супадае з балансам гэтага кашалька. Калі ласка, паспрабуйце зноў.",
"details_del_wb_q": "У гэтага кашалька ёсьць баланс. Перад тым, як працягнуць, зьвярніце ўвагу, што вы ня зможаце аднавіць сродкі без сід-фразы гэтага кашалька. Каб пазьбегнуць выпадковага выдаленьня, увядзіце баланс кашалька — {balance} сатошы.",
"details_del_wb_q": "Гэты кашалёк мае баланс. Перш чым працягваць, калі ласка, заўважце, што вы ня зможаце аднавіць сродкі без фразы семя гэтага кашалька. Каб пазьбегнуць выпадковага выдаленьня, калі ласка, увядзіце баланс вашага кашалька — {balance} satoshis.",
"details_delete": "Выдаліць",
"details_delete_wallet": "Выдаліць кашалёк",
"details_delete_wallet_error_message": "Узьнікла праблема з пацьверджаньнем таго, што гэты кашалёк быў выдалены з апавяшчэньняў — гэта можа быць зьвязана з праблемай сеткі альбо слабым злучэньнем. Калі вы працягнеце, вы можаце працягваць атрымліваць апавяшчэньні аб трансакцыях, зьвязаных з гэтым кашальком, нават пасьля яго выдаленьня.",
@ -461,7 +461,7 @@
"import_passphrase_title": "Кодавая фраза",
"import_passphrase_message": "Увядзіце кодавую фразу, калі вы яе выкарыстоўвалі",
"import_error": "Не атрымалася імпартаваць. Калі ласка, праверце, што прадастаўленыя дадзеныя сапраўдныя.",
"import_explanation": "Калі ласка, увядзіце словы сід-фразы, публічны ключ, WIF або што заўгодна. BlueWallet паспрабуе адгадаць правільны фармат і імпартаваць ваш кашалёк.",
"import_explanation": "Калі ласка, увядзіце словы вашага семя, публічны ключ, WIF альбо што заўгодна, што ў вас ёсьць. BlueWallet зробіць усё магчымае, каб адгадаць правільны фармат і імпартаваць ваш кашалёк.",
"import_imported": "Імпартавана",
"import_scan_qr": "Сканаваць альбо імпартаваць файл",
"import_success": "Ваш кашалёк пасьпяхова імпартаваны.",
@ -576,13 +576,13 @@
"invalid_cosigner_format": "Няправільны сападпісант: Гэта не сападпісант для фармату {format}.",
"create_new_key": "Стварыць новы",
"scan_or_open_file": "Сканаваць альбо адкрыць файл",
"i_have_mnemonics": "У мяне ёсьць сід-фраза для гэтага ключа.",
"type_your_mnemonics": "Уведзіце сід-фразу, каб імпартаваць існуючы ключ сэйфа.",
"i_have_mnemonics": "У мяне ёсьць семя для гэтага ключа.",
"type_your_mnemonics": "Уставіць семя для імпарту вашага існуючага ключа сэйфу.",
"this_is_cosigners_xpub": "Гэта XPUB сападпісанта — гатовы да імпарту ў іншы кашалёк. Ім можна бясьпечна дзяліцца.",
"this_is_cosigners_xpub_airdrop": "Калі вы дзеліцеся праз AirDrop, атрымальнікі павінны знаходзіцца на экране каардынацыі.",
"wallet_key_created": "Ваш ключ сэйфа створаны. Выдзеліце момант, каб надзейна захаваць мнэманічную фразу.",
"are_you_sure_seed_will_be_lost": "Вы ўпэўненыя? Вашая мнэманічная фраза будзе страчаная, калі ў вас няма рэзэрвовай копіі.",
"forget_this_seed": "Забыцца на гэтую сід-фразу і выкарыстоўваць XPUB.",
"wallet_key_created": "Ваш ключ сэйфу створаны. Не забудзьцеся бясьпечна зрабіць рэзервовую копію вашага мнэмонічнага семя.",
"are_you_sure_seed_will_be_lost": "Вы ўпэўненыя? Ваша мнэмонічнае семя будзе страчана, калі ў вас няма рэзервовай копіі.",
"forget_this_seed": "Забыцца на гэтае семя і выкарыстаць XPUB замест яго.",
"view_edit_cosigners": "Прагляд/рэдагаваньне сападпісантаў",
"this_cosigner_is_already_imported": "Гэты сападпісант ужо імпартаваны.",
"export_signed_psbt": "Экспартаваць падпісаны PSBT",
@ -598,8 +598,8 @@
"ms_help_title5": "Пашыраны рэжым",
"ms_help_1": "Сэйф будзе працаваць зь іншымі праграмамі BlueWallet і кашалькамі, сумяшчальнымі з PSBT, такімі як Electrum, Specter, Coldcard, Cobo Vault і г.д.",
"ms_help_2": "Вы можаце стварыць усе ключы сэйфу на гэтай прыладзе і выдаліць альбо адрэдагаваць іх пазьней. Маючы ўсе ключы на адной прыладзе, бясьпека эквівалентна звычайнаму кашальку Bitcoin.",
"ms_help_3": "У наладах кашалька вы знойдзеце рэзэрвовую копію сэйфа і watch-only копію. Гэтая рэзэрвовая копія — як мапа да вашага кашалька. Яна вельмі важная для аднаўленьня кашалька, калі вы згубіце адну з сід-фразаў.",
"ms_help_4": "Каб імпартаваць multisig, выкарыстайце файл рэзэрвовай копіі і функцыю «Імпарт». Калі ў вас ёсьць толькі сід-фразы і XPUB, можаце выкарыстаць асобную кнопку імпарту пры стварэньні ключоў сэйфа.",
"ms_help_3": "У наладах кашалька вы знойдзеце рэзервовую копію сэйфу і рэзервовую копію толькі для прагляду. Гэтая рэзервовая копія — як карта да вашага кашалька. Яна неабходна для аднаўленьня кашалька ў выпадку страты аднаго з вашых семя.",
"ms_help_4": "Каб імпартаваць мультыподпіс, выкарыстайце ваш файл рэзервовай копіі і функцыю Імпарт. Калі ў вас ёсьць толькі семя і XPUB, вы можаце выкарыстаць асобную кнопку Імпарт пры стварэньні ключоў сэйфу.",
"ms_help_5": "Па змаўчаньні BlueWallet створыць сэйф 2-з-3. Каб стварыць іншы кворум альбо зьмяніць тып адрасу, актывуйце Пашыраны рэжым у наладах.",
"input_fp_explain": "Прапусьціце, каб выкарыстаць стандартны (00000000)",
"input_path_explain": "Прапусьціце, каб выкарыстаць стандартны ({default})",
@ -614,9 +614,9 @@
"view_qrcode": "Паглядзець QR-код"
},
"autofill_word": {
"title": "Апошняе слова сід-фразы",
"title": "Апошняе слова семя",
"enter": "Увядзіце вашу частковую мнэмонічную фразу",
"generate_word": "Згенэраваць апошняе слова",
"generate_word": "Згенераваць апошняе слова",
"error": "Увод не зьяўляецца 11- альбо 23-словнай частковай мнэмонічнай фразай. Калі ласка, паспрабуйце зноў."
},
"cc": {

View File

@ -16,7 +16,7 @@
"yes": "Да",
"no": "Не",
"save": "Запази...",
"seed": "Сийд",
"seed": "Сиид",
"success": "Успех",
"wallet_key": "Парола на портфейла",
"close": "Затвори",
@ -32,10 +32,10 @@
"suggested": "Препоръчан"
},
"azteco": {
"codeIs": "Кодът на вашия ваучър е",
"errorBeforeRefeem": "Преди осребряване, трябва да добавиш Биткойн портфейл.",
"errorSomething": "Възникна грешка. Уверете се, че ваучърът е валиден?",
"redeem": "Депозирай в портфейла",
"codeIs": "Цода на вашият ваучър е",
"errorBeforeRefeem": "Преди осребряване, трябва да добавиш Бикойн портфейл.",
"errorSomething": "Възникна грешка. Уверете се, че ваучъра е валиден?",
"redeem": "Депозирай в уолета",
"redeemButton": "Осребри",
"success": "Успех",
"successMessage": "Ваучерът е осребрен успешно! Вашите средства трябва да пристигнат в Bitcoin портфейла ви скоро.",
@ -81,19 +81,19 @@
"plausibledeniability": {
"create_fake_storage": "Създайте Криптирано Хранилище",
"create_password_explanation": "Паролата за 'Фалшивото' хранилище трябва да е различна от паролата за главното хранилище",
"help": "При определени обстоятелства, може да се наложи да предадете Вашата парола. За да предпазите средствата си, Блу Уолет може да създаде допълнително хранилище с различна парола. Ако сте в ситуация където сте принудени да предадете Вашата парола, дайте паролата за фалшивото хранилище. Когато я въведете, Блу Уолет ще отключи 'Фалшивото' хранилище. Портфейлът изглежда легитимен, като в същото време средствата ви ще са в безопасност.",
"help2": "Новото хранилище ще бъде напълно функционално и може да държите минимални средства в него. Така ще изглежда като легитимен портфейл.",
"help": "При определени обстоятелства, може да се наложи да предадете Вашата парола. За да предпазите средствата си, Блу Уолет може да създаде допълнително хранилище с различна парола. Ако сте в ситуация където сте принудени да предадете Вашата парола, дайте паролата за фалшивото хранилище. Когато я въведете, Блу Уолет ще отключи 'Фалшивото' хранилище. Портфеиля изглежда легитимен, като в същото време средствата ви ще са в безопасност.",
"help2": "Новото хранилище ще бъде напълно функционално и може да държите минимални средства в него. Така ще изглежда като легитимен портфеил.",
"password_should_not_match": "Моля, изберете различна парола.",
"title": "Правдоподобно отричане"
"title": "Plausible Deniability"
},
"pleasebackup": {
"ask": "Запазихте ли сийд фразата - 12/24 думи за портфейла? В случай, че изгубите достъп до устройството, сийд фразата е необходима за да възстановите средствата. В случай, че загубите сийд фразата - 12/24 думи, перманентно ще изгубите достъп до средствата.",
"ask": "Запазихте ли паролата - 12/24 думи за портфейла? В случай, че изгубите достъп до устройството, паролата е необходима за да въстановите средствата. В случай, че загубите паролата - 12/24 думи, перманентно ще изгубите достъп до средствата.",
"ask_no": "Не, не съм.",
"ask_yes": "Да, запазих ги.",
"ok": "OK, записах ги.",
"ok_lnd": "Да, запазих сийда.",
"ok_lnd": "Да, запазих паролата.",
"text": "Моля, отделете малко време, за да запишете тази мнемонична фраза на лист хартия.\nТова е вашето резервно копие и можете да го използвате, за да възстановите портфейла.",
"text_lnd": "Моля, запазете сийд фразата. Тя ви позволява да възстановите портфейла и средствата си на друго устройство.",
"text_lnd": "Моля, запазете паролата/ думите. Те ви позволяват да възтановите портфейла и средствата си на друго устройство.",
"title": "Портфейла е създаден."
},
"receive": {
@ -116,7 +116,7 @@
"broadcastButton": "Изпрати",
"broadcastError": "Грешка",
"broadcastNone": "Въведи Transaction Hex",
"broadcastPending": "Чакаща",
"broadcastPending": "Не потвърдена транзакция",
"broadcastSuccess": "Успех",
"confirm_header": "Потвърди",
"confirm_sendNow": "Изпрати сега",
@ -127,7 +127,7 @@
"create_fee": "Такса",
"create_memo": "Бележка",
"create_satoshi_per_vbyte": "Сатоши на vByte",
"create_this_is_hex": "Това е вашата транзакция/и hex-подписана и готова/и за изпращане",
"create_this_is_hex": "Това е вашата трансакция/и hex-подписана и гова/и за изпращане",
"create_to": "До",
"create_tx_size": "Размер на Транзакцията",
"create_verify": "Потвърди в coinb.in",
@ -142,17 +142,17 @@
"please_complete_recipient_details": "Моля, попълнете данните на получател #{number}, преди да добавите нов получател.",
"details_address": "Адрес",
"details_address_field_is_not_valid": "Невалиден адрес",
"details_adv_fee_bump": "Разреши увеличаване на таксата",
"details_adv_fee_bump": "Разреши увеличаване та таксата",
"details_adv_full": "Използвай целия наличен баланс",
"details_adv_full_sure": "Сигурни ли сте, че искате да използвате целия наличен баланс в портфейла?",
"details_adv_full_sure_frozen": "Сигурни ли сте, че искате да използвате целия наличен баланс на портфейла за тази транзакция? Моля, имайте предвид, че замразените монети са изключени.",
"details_adv_import": "Въведете Транзакция",
"details_adv_import_qr": "Импортирай транзакция (QR)",
"details_amount_field_is_not_valid": "Сумата е невалидна",
"details_amount_field_is_less_than_minimum_amount_sat": "Сумата е прекалено малка. Моля, въведете сума по-голяма от 500 сатоши.",
"details_amount_field_is_less_than_minimum_amount_sat": "Сумата е прекалено малка. Моля, въведете сума по-голяма от 500 сатошита.",
"details_create": "Създайте Фактура",
"details_error_decode": "Биткойн адреса не може да бъде разпознат",
"details_fee_field_is_not_valid": "Невалидна такса",
"details_fee_field_is_not_valid": "Не валидна такса",
"details_frozen": "{amount} BTC са замразени.",
"details_next": "Следващ",
"details_no_signed_tx": "Избраният файл не съдържа транзакция, която може да бъде импортирана.",
@ -160,7 +160,7 @@
"details_scan": "Сканирай",
"details_scan_hint": "Докоснете два пъти, за да сканирате или импортирате дестинация",
"details_scan_error": "Грешка при сканиране",
"details_total_exceeds_balance": "Сумата надвишава наличния баланс.",
"details_total_exceeds_balance": "Сумата надвишава наличният баланс.",
"details_total_exceeds_balance_frozen": "Сумата за изпращане надвишава наличния баланс. Моля, имайте предвид, че замразените монети са изключени.",
"details_unrecognized_file_format": "Непознат файл формат",
"details_wallet_before_tx": "Преди създаване на транзакция, трябва да създадете Биткойн портфейл.",
@ -183,7 +183,7 @@
"input_clear": "Изчисти",
"input_done": "Готово",
"input_paste": "Постави",
"input_total": "Общо:",
"input_total": "Тотално:",
"permission_camera_message": "Необходимо е вашето разрешение за достъп до камерата.",
"psbt_sign": "Подпиши транзакция",
"invalid_psbt": "Предоставен е невалиден PSBT.",
@ -191,7 +191,7 @@
"permission_storage_denied_message": "BlueWallet не може да запази този файл. Моля, отворете настройките на устройството си и разрешете достъп до хранилището.",
"permission_storage_title": "Достъп до папките с файлове",
"psbt_clipboard": "Копирай",
"psbt_this_is_psbt": "Това е Частично Подписана Биткойн Транзакция (ЧПБТ - en. PSBT). Моля, подпишете транзакцията на хардуерния си портфейл.",
"psbt_this_is_psbt": "Това е Частично Подписана Биткойн Транзакция (ЧПБТ - en. PSBT). Моля, подпишете транзакцията на хардуер портфейла си.",
"psbt_tx_export": "Запиши като файл",
"no_tx_signing_in_progress": "Няма транзакция в прогрес.",
"outdated_rate": "Курсът е обновен последно: {date}",
@ -211,7 +211,7 @@
"settings": {
"about": "Повече",
"about_awesome": "Създаден с любов",
"about_backup": "Винаги архивирайте ключовете си!",
"about_backup": "Не забравяйте да направите копие от паролата/думите!",
"about_free": "Блу Уолет е безплатен и отворен код проект. Създаден от Биткойн фенове",
"about_license": "MIT Лиценз",
"about_release_notes": "Промени и Подобрения",
@ -221,7 +221,7 @@
"about_selftest": "Направете селф-тест",
"block_explorer_invalid_custom_url": "Предоставеният URL е невалиден. Моля, въведете валиден URL, започващ с http:// или https://.",
"about_selftest_electrum_disabled": "Самотестът не е достъпен в офлайн режим на Electrum. Моля, изключете офлайн режима и опитайте отново.",
"about_selftest_ok": "Тестът премина успешно. Портфейлът работи отлично.",
"about_selftest_ok": "Теста прмина успешно. Портфейла работи отлично.",
"about_sm_github": "GitHub",
"about_sm_telegram": "Telegram",
"privacy_temporary_screenshots": "Разреши заснемане на екрана",
@ -249,13 +249,13 @@
"electrum_port": "Порт, обикновено {example}",
"use_ssl": "Използвай SSL",
"electrum_saved": "Промените бяха запазени успешно. Моля, рестартирайте Блу Уолет за да видите промените.",
"set_electrum_server_as_default": "Задайте {server} като Електрум сървър по подразбиране?",
"set_electrum_server_as_default": "Задайте {server} като Електрум сървър по подразбиране? ",
"set_lndhub_as_default": "Задайте {url} като LNDhub сървър по подразбиране?",
"electrum_settings_server": "Electrum сървър",
"electrum_status": "Статус",
"electrum_preferred_server": "Предпочитан сървър",
"electrum_preferred_server_description": "Въведете сървъра, който искате портфейлът ви да използва за всички Bitcoin дейности. След като бъде зададен, портфейлът ви ще използва изключително този сървър за проверка на баланси, изпращане на транзакции и получаване на мрежови данни. Уверете се, че имате доверие на този сървър, преди да го зададете.",
"electrum_unable_to_connect": "Невъзможно свързване със сървър {server}.",
"electrum_unable_to_connect": "Не възможно свързване със сървър {server}.",
"electrum_history": "История",
"electrum_reset_to_default": "Това ще позволи на BlueWallet да избере произволно сървър от списъка със сървъри.",
"electrum_reset": "Начални настройки",
@ -279,7 +279,7 @@
"encrypt_use_expl": "{type} ще се използва за потвърждаване на самоличността ви преди извършване на транзакция, отключване, експортиране или изтриване на портфейл.",
"biometrics_fail": "Ако {type} не е активирана или не успее да отключи, можете да използвате кода за достъп на устройството като алтернатива.",
"general": "Основно",
"general_continuity": "Непрекъснатост",
"general_continuity": "Continuity",
"general_continuity_e": "Когато е активирано, ще можете да преглеждате избрани портфейли и транзакции с помощта на другите си устройства, свързани с Apple iCloud.",
"groundcontrol_explanation": "GroundControl е безплатен сървър за push известия с отворен код за Bitcoin портфейли. Можете да инсталирате собствен GroundControl сървър и да поставите неговия URL тук, за да не разчитате на инфраструктурата на BlueWallet. Оставете празно, за да използвате сървъра по подразбиране на GroundControl.",
"header": "Настройки",
@ -302,7 +302,7 @@
"open_link_in_explorer": "Отвори връзката в експлорър",
"password": "Парола",
"password_explain": "Въведете паролата, която ще използвате за отключване на хранилището си.",
"plausible_deniability": "Правдоподобно отричане",
"plausible_deniability": "Plausible Deniability",
"privacy": "Поверителност",
"privacy_read_clipboard": "Чети клипборда",
"privacy_system_settings": "Системни настройки",
@ -355,7 +355,7 @@
"details_to": "Изход",
"enable_offline_signing": "Този портфейл не се използва във връзка с офлайн подписване. Желаете ли да го активирате сега?",
"list_conf": "Потв.: {number}",
"pending": "Чакаща",
"pending": "Не потвърдена транзакция",
"pending_with_amount": "В очакване {amt1} ({amt2})",
"received_with_amount": "+{amt1} ({amt2})",
"eta_10m": "ETA: за ~10 минути",
@ -390,7 +390,7 @@
"details_fee_rate": "Ставка на таксата",
"details_size": "Размер",
"details_virtual_size": "Виртуален размер",
"details_tx_hex": "Hex на транзакцията",
"details_tx_hex": "Tx Hex",
"details_inputs_count": "Входове ({count})",
"details_outputs_count": "Изходи ({count})"
},
@ -588,7 +588,7 @@
"ms_help_3": "В опциите на портфейла ще намерите резервното копие на сейфа и резервното копие само за наблюдение. Това резервно копие е като карта към вашия портфейл. То е от съществено значение за възстановяване на портфейла в случай, че загубите един от сийдовете си.",
"ms_help_title4": "Импортиране на сейфове",
"ms_help_4": "За да импортирате мултисиг, използвайте файла с резервно копие и функцията Импортирай. Ако имате само сийдове и xpub-ове, можете да използвате индивидуалния бутон Импортирай при създаване на ключове на сейфа.",
"ms_help_title5": "Разширен режим",
"ms_help_title5": "Развирени Настройки",
"ms_help_5": "По подразбиране BlueWallet ще генерира сейф 2-от-3. За да създадете различен кворум или да промените типа на адреса, активирайте Разширен режим в Настройките."
},
"cc": {
@ -632,7 +632,7 @@
"notifications": {
"would_you_like_to_receive_notifications": "Искате ли да получавате известия, когато получите входящи плащания?",
"notifications_subtitle": "Входящи плащания и потвърждения на транзакции",
"no_and_dont_ask": "Не, и не ме питай отново.",
"no_and_dont_ask": "Не и не ме питай отново.",
"permission_denied_message": "Отказахте разрешение да ви изпращаме известия. Ако искате да получавате известия, моля, активирайте ги в настройките на устройството си."
},
"total_balance_view": {
@ -655,7 +655,7 @@
"title": "Последна дума на сийда",
"enter": "Въведете частичната си мнемонична фраза",
"generate_word": "Генерирай последната дума",
"error": "Входните данни не са частична мнемонична фраза от 11 или 23 думи. Моля, опитайте отново."
"error": "Входните данни не са частичен мнемоник от 11 или 23 думи. Моля, опитайте отново."
},
"units": {
"BTC": "BTC",

View File

@ -27,9 +27,7 @@
"unlock": "گۊشیڌن چفت",
"port": "پورت",
"ssl_port": "پورت SSL",
"suggested": "پؽشنهاڌی",
"copied": "لف گیری وابی!",
"discard_changes_explain": "آلشتکاریا زفت نوابیڌه دارین. اخۊی هونا ن نیڌه گری ۉ ز ای بلگه و در بئری؟"
"suggested": "پؽشنهاڌی"
},
"azteco": {
"codeIs": "کوڌ تخفیف ایسا",
@ -38,8 +36,7 @@
"redeem": "ازاف کردن و کیف پیل",
"redeemButton": "فعال کردن",
"success": "سر ٱنجوم گرهڌ",
"title": "فعال کردن کوڌ تخفیف Azte.co",
"successMessage": "ووچر وا مووفقیت فعال وابی! دارایی ایسا و زۊڌی و کیف پیل بیت کوین ایسا اوݩ."
"title": "فعال کردن کوڌ تخفیف Azte.co"
},
"entropy": {
"save": "زفت کردن",
@ -83,18 +80,14 @@
"create_password_explanation": "رزمی ک سی جاگه زفت کردنی جعلی هڌ، نوا وا رزم جاگه زفت کردنی ٱلسی ی جۊر بۊ.",
"help2": "جاگه زفت کردنی نۊ قلوه و کار ایا وو ایسا ترین یتی ز دارایی خوتۉݩ ن اۊچنا واڌارنین تا ب تؽ بیا ک هونی زس استفاڌه اکۊنین.",
"password_should_not_match": "رزم هونی ب کار اروه. ی رزم دیری ن ب کار بگر.",
"title": "انکار مووجه",
"help": "تو هالاتی، شاید ایسا ن مجبۊر کۊنن رزم خوته فاش کۊنی. سی ایکه کوینا خوته امن داری، BlueWallet اتره ی جاگه زفت کردنی دؽ ریس رزم وا رزم دیر وورکل کونه. د هالت فشار، تری ای رزم ن وا ی نفر ی غیر فاش کۊنی. ٱر منه BlueWallet زیڌه بۊ، جاگه زفت کردنی نۊ «جعلی» گۊشیڌه ابۊ. یۊ سی ی نفر ی غیر، واقعی ب نظر اونه، اما د دل، جاگه زفت کردنی ٱلسی ایسا وا کوینا امن میمونه."
"title": "انکار مووجه"
},
"pleasebackup": {
"ask_no": "ن، مو نڌاروم.",
"ask_yes": "هری، مو داروم.",
"ok": "خا، هو ن هؽل کردوم.",
"ok_lnd": "خا، هو ن زفت کردوم.",
"title": "کیف پیل ایسا وورکل وابی.",
"ask": "عبارت بازیابی کیف پیلته زفت کردیه؟ ای عبارت بازیابی سی دسرسی و دارایی خوت لازمه، ٱر ای دسگاهن گوم کۊنی. بؽ ای عبارت بازیابی، دارایی خوت سی همیشه گوم اونه.",
"text": "ی دیقه ویرگار بنا تا ای عبارت بازیابی ن ری ی بلگه کاغذ بنویسی.\nیۊ نوسخه لادرار خوته هڌ ۉ تری وا یۊ کیف پیلته بازیابی کۊنی.",
"text_lnd": "تی کۊن ای نوسخه لادرار کیف پیل ن زفت کۊنی. یۊ ایلیه ٱر گوم وابی، کیف پیل ته بازیابی کۊنی."
"title": "کیف پیل ایسا وورکل وابی."
},
"receive": {
"details_create": "وورکل",
@ -108,8 +101,7 @@
"maxSatsFull": "بیشترین مقدار {max} ساتۊشی یا {currency} هڌ.",
"minSats": "کمترین مقدار {min} ساتۊشی هڌ.",
"minSatsFull": "کمترین مقدار {min} ساتۊشی یا {currency} هڌ.",
"qrcode_for_the_address": "QR کود سی ای نشۊوی",
"bip47_explanation": "کود پرداخت ی آدرس عمومی هڌ ک بؽ فاش کردن آدرسا کیف پیل ایسا کار اکونه. هومه خدماتا تی پشتؽوانی نکونن."
"qrcode_for_the_address": "QR کود سی ای نشۊوی"
},
"send": {
"provided_address_is_invoice": "منی ای آدرس ی سۊرت هساو لایتنینگ هڌ. سی پرداخت ای سۊرت هساو، و کیف پیل لایتنینگ خوتۉݩ ریوین.",
@ -155,6 +147,7 @@
"details_next": "نیایی",
"details_no_signed_tx": "فایل پسند بیڌه، تراکونشی منس نؽڌ ک ترسته بویم ب من یاریمس.",
"details_note_placeholder": "ویرداشت و خوت",
"counterparty_label_placeholder": "آلشت نوم هومدنگ",
"details_scan": "اسکن",
"details_scan_hint": "سی اسکن یا و من ٱووردن مقسد، دو کرت بزن ریس",
"details_scan_error": "ختا اسکن",
@ -196,17 +189,7 @@
"success_done": "ٱنجوم وابی",
"txSaved": "فایل تراکونش ({filePath}) زفت وابیڌه.",
"file_saved_at_path": "فایل ({filePath}) زفت وابیڌه.",
"problem_with_psbt": "موشکل وا تراکونش ناقس امزا بیڌه PSBT",
"details_add_recc_rem_all_alert_description": "اخۊی پوی گیرنده یل ن پاک کۊنی؟",
"please_complete_recipient_details": "تی کۊن جۊزیات گیرنده #{number} ن پؽش ز ٱووردن گیرنده نۊ کامل کۊنی.",
"details_total_exceeds_balance_frozen": "مقداری ک خۊی فیشنی، بیشتر ز مۉجۊڌیت هڌ. ویرت بۊ ک کوینا مسدۊد، شومارش نوابن.",
"fee_replace_minvb": "نرخ کارمزد کول (ساتۊشی سی هر بایت مجازی) ک اخۊی پرداخت کۊنی، وا بیشتر ز {min} ساتۊشی سی هر بایت مجازی بۊ.",
"permission_storage_denied_message": "BlueWallet نتره ای فایل ن زفت کونه. تی کۊن سامووا دسگا ته ن گۊشی ۉ موجوز دسرسی و جاگه زفت کردنی ن فعال کۊنی.",
"psbt_this_is_psbt": "یۊ ی تراکونش بیت کوین ناقس امزا بیڌه (PSBT) هڌ. تی کۊن وا کیف پیل سخت ٱفزاری خوت امزاسه کامل کۊنی.",
"qr_error_no_qrcode": "ایما نترسیم ی QR کود زبال منه کتاو هووه پسند بیڌه پؽڌا کۊنیم. موتمعن بۊ ک کتاو هووه فقط ی QR کود داره ۉ هیچ دؽوار، چی نوشتار یا دگمه نڌاره.",
"cant_send_to_silentpayment_adress": "ای کیف پیل نتره و آدرسا Silent Payments فیشنه",
"cant_send_to_bip47": "ای کیف پیل نتره و کود پرداخت BIP47 فیشنه",
"cant_find_bip47_notification": "ٱول ای کود پرداخت ن و هومدنگا اضاف کۊنی"
"problem_with_psbt": "موشکل وا تراکونش ناقس امزا بیڌه PSBT"
},
"settings": {
"about": "زبار",
@ -231,7 +214,7 @@
"default_title": "موقه ره وندن",
"electrum_connected": "منپیز",
"electrum_connected_not": "بؽ منپیز",
"electrum_error_connect": "نتره و سرور الکترام داڌه وابیڌه منپیز بۊوه",
"electrum_error_connect": "نتره و سرور الکتروم داڌه وابیڌه منپیز بۊوه",
"lndhub_uri": "سی نمووه، {example}",
"electrum_host": "سی نمووه، {example}",
"electrum_offline_mode": "هالت آفلاین",
@ -259,7 +242,7 @@
"header": "سامووا",
"language": "زووݩ",
"last_updated": "ورۊ رسۊوی دیندایی",
"language_isRTL": ه وندن دۊوارته BlueWallet سی انجوم آلشت کاریا ری زووݩ الن وا انجوم بۊ.",
"language_isRTL": وندن دووارته BlueWallet سی انجوم آلشت کاریا ری زووݩ الن وا انجوم بۊ.",
"license": "موجوز",
"lightning_error_lndhub_uri": "یۊ آر آی LNDhub زبال نؽ",
"lightning_saved": "آلشت کاریا ایسا و خۊوی زفت وابین.",
@ -283,48 +266,12 @@
"saved": "زفت وابی",
"total_balance": "پوی مۉجۊدی",
"widgets": "اوزارکا",
"tools": "اوزارا",
"about_free": "BlueWallet ی پروژه ٱزاد ۉ بازیه ٱفرینش کۊن. ساته وابیڌه وا کاربرا بیت کوین.",
"block_explorer_invalid_custom_url": "نشۊوی داڌه وابیڌه زبال نؽ. تی کۊن ی نشۊوی زبال ک وا http:// یا https:// شۊرۊ بۊ بزن.",
"privacy_temporary_screenshots_instructions": "موسامینیڌن بلگه نمایش موقتاً قیر فعال اونه، تا بلگه نمایش زفت کردن ۉ بلگه نمایش هۉنبلت کردن مومکن بۊ. ٱر BlueWallet ن بستی ۉ ز نۊ گۊشی، موسامینیڌن ب توور خوس کار وا فعال اونه.",
"biometrics_no_longer_available": "سامووا دسگاه ایسا آلشت وابیڌه ۉ ز ای ب دیندا وا سامووا امنیتی پسند بیڌه من برنامه هومخۊوݩ نؽ. تی کۊن بیومتریک یا کد دسترسی ن ز نۊ فعال کۊنی، ۉ بعد برنامه ن ز نۊ شۊرۊ کۊنی تا آلشتا اعمال بۊن.",
"biom_10times": "ایسا 10 کرت کۊشش کردیه رزم ته بزنی. اخۊی جاگه زفت کردنی ته وورنشۊوی کۊنی؟ ای کار، پوی کیف پیلا ن پاک اکونه ۉ جاگه زفت کردنی ته رزم گوشایی اکونه.",
"biom_no_passcode": "دسگا ایسا کد دسترسی یا بیومتریک فعال نڌاره. سی ادامه، تی کۊن کد دسترسی یا بیومتریک ن منه برنامه سامووا ساموو کۊنی.",
"biom_remove_decrypt": "پوی کیف پیلا ایسا پاک اونن ۉ جاگه زفت کردنی ایسا رزم گوشایی اونه. اخۊی ادامه دی؟",
"donate": "اهدا",
"donate_description": "هؽاری کۊن تا Blue ٱزاد بمونه!",
"electrum_error_connect_tor": "نتره و سرور الکترام داڌه وابیڌه منپیز بۊوه. تی کۊن موتمعن بۊ ک برنامه Orbot منپیز هڌ ۉ ز نۊ تفره کو.",
"electrum_saved": "آلشتکاریا ایسا وا مووفقیت زفت وابین. شاید لازم بۊ BlueWallet ن ز نۊ شۊرۊ کۊنی تا آلشتکاریا اعمال بۊن.",
"set_lndhub_as_default": "{url} سی سرور پؽش فرز LNDhub ساموو بۊوه؟",
"electrum_preferred_server_description": "سروری ک اخۊی کیف پیلت سی پوی کارا بیت کوین استفاڌه کونه ن بزن. ٱر ساموو وابی، کیف پیلت فقط ز ای سرور سی واجۊری مۉجۊڌی، فشناڌن تراکونش ۉ گرؽڌن داڌه شبکه استفاڌه اکونه. پؽش ز سامووݩ، موتمعن بۊ ک و ای سرور اعتماد داری.",
"electrum_reset_to_default": "یۊ ایلیه ک BlueWallet ی سرور ن ز فهرست سرورا تسادفی پسند کونه.",
"electrum_reset_to_default_and_clear_history": "ورگندن و پؽش فرز ۉ پاک کردن ویرگار",
"encrypt_decrypt_q": "اخۊی جاگه زفت کردنی ته رزم گوشایی کۊنی؟ ای کار ایلیه کیف پیلا ته بؽ رزم دسرس بۊن.",
"encrypt_enc_and_pass": "موسامینیڌه وا رزم",
"encrypt_storage_explanation_description_line1": "فعال کردن رزم ناهاڌن ری جاگه زفت کردنی، ی لایه موسامینیڌن دؽ و برنامه ایسا اضاف اکونه ۉ شیوه ناهاڌن داڌه ها ری دسگا ته امن اکونه. یۊ کار اکونه ک سختر بۊ تا هرکی بؽ موجوز و داڌه ها ایسا دسرس داشته بۊ.",
"encrypt_storage_explanation_description_line2": "اما، لازمه بدۊنی ک ای رزم ناهاڌن، فقط دسرسی و کیف پیلا ناهاڌه وابیڌه ری کیچین دسگا ته موسامینه. کیف پیلا ٱلسی ن وا رزم یا موسامینیڌن دؽ، موسامینه نکونه.",
"set_as_preferred_electrum": "سامووݩ {host}:{port} و عونوان سرور ترجیهی، منپیز و سرور پؽشنهاڌی تسادفی ن قیر فعال اکونه.",
"encrypted_feature_disabled": "ای ویژگی وا جاگه زفت کردنی موسامینیڌه فعال نتره ب کار رئڌه بۊوه.",
"encrypt_use_expl": "{type} سی تاییڌ هۊویت ایسا پؽش ز انجوم تراکونش، گۊشیڌن چفت، و در کشیڌن، یا پاک کردن کیف پیل ب کار رئڌه ابۊ.",
"biometrics_fail": "ٱر {type} فعال نؽ، یا منه گۊشیڌن چفت ناکام بۊ، تری ز کد دسترسی دسگا ته و عونوان جانشین استفاڌه کۊنی.",
"general_continuity": "تڌاوم",
"general_continuity_e": "ٱر فعال بۊ، تری کیف پیلا ۉ تراکونشا پسند بیڌه ن ز دۊیار دسگاها Apple iCloud منپیز خوت ووینی.",
"groundcontrol_explanation": "GroundControl ی سرور وارسۊوی ٱزاد ۉ بازیه ٱفرینش کۊن سی کیف پیلا بیت کوین هڌ. تری سرور GroundControl خوته نسو کۊنی ۉ نشۊوی ٱنا ای جا بنی تا و زیر ساخت BlueWallet وابسته نووی. سی استفاڌه ز سرور پؽش فرز GroundControl، ای جا ن خالی هؽل کو.",
"lightning_error_lndhub_uri_tor": "یۊ آر آی LNDhub زبال نؽ. تی کۊن موتمعن بۊ ک برنامه Orbot منپیز هڌ ۉ ز نۊ تفره کو.",
"lightning_settings_explain": "سی منپیز و گره LND خوت، تی کۊن LNDhub ن نسو کۊنی ۉ نشۊوی ٱنا ای جا منه سامووا بنی. ویرت بۊ ک فقط کیف پیلایی ک پس ز زفت آلشتکاریا وورکل اونن، و LNDhub داڌه وابیڌه منپیز اونن.",
"lndhub_github": "گنجگه گیت هاب",
"electrum_suggested_description": "ٱر سرور ترجیهی ساموو نوابی، ی سرور پؽشنهاڌی ب توور تسادفی سی استفاڌه پسند اونه.",
"password_explain": "رزمی ک سی گۊشیڌن چفت جاگه زفت کردنی ته ب کار اوری ن بزن.",
"privacy_quickactions_explanation": "ری نشۊن BlueWallet نگاه دار ۉ بفشار تا مۉجۊڌی کیف پیل خوته ب زۊڌی ووینی.",
"privacy_clipboard_explanation": "ٱر آدرس یا سۊرت هساوی منه ویرگه ایسا پؽڌا وابی، ر نهنگا نشۉݩ بڌه.",
"privacy_do_not_track_explanation": "دؽوسمندیا کارکرد ۉ اعتماد ٱفرینی سی تئلیل فیشناڌه نوابن.",
"push_notifications_explanation": "وا فعال کردن وارسۊویا، توکن دسگا ایسا و گرا وا آدرسا کیف پیل ۉ شناسه تراکونشا سی پوی کیف پیلا ۉ تراکونشایی ک پس ز فعال کردن وارسۊویا ٱنجوم اونن، فیشناڌه ابۊ. توکن دسگا سی فشناڌن وارسۊویا ب کار رئڌه ابۊ، ۉ دؽوسمندیا کیف پیل ایلیه ایما ایسا ن ز بیت کوین وۊرۊڌی یا تاییڌ تراکونش ٱگاه کۊنیم.\n\nفقط دؽوسمندی پس ز فعال کردن وارسۊویا ، فیشناڌه اونه—هیچ چی ز پؽش، گرها وابیڌه نؽ.\n\nقیر فعال کردن وارسۊویا، پوی ای دؽوسمندی ن ز سرور پاک اکونه. ای جور، پاک کردن ی کیف پیل ز برنامه، دؽوسمندی پۊشمت وا هون ن نی ز سرور پاک اکونه.",
"success_transaction_broadcasted": "تراکونش ایسا وا مووفقیت تیجنیڌه وابی!",
"total_balance_explanation": "پوی مۉجۊڌی کیف پیلا خوته من اوزارکا بلگه ٱلسی نشۉݩ بڌه."
"tools": "اوزارا"
},
"transactions": {
"cancel_title": "ای تراکونشن لقو کوݩ (RBF)",
"confirmations_lowercase": "{confirmations} تاییڌ",
"copy_link": "لف گیری لینگ",
"expand_note": "نشۉݩ داڌن کامل ویرداشت",
"cpfp_create": "وورکل",
"cpfp_no_bump": "ای تراکونش نتره کارمزدس بیشتر بۊ.",
@ -362,37 +309,7 @@
"pending": "مندیر سی زفت",
"list_title_received": "گرؽڌه وابیڌه",
"transaction": "تراکونش",
"open_url_error": "نا مووفق منه گۊشیڌن لینگ وا گشت گر پؽش فرز. گشت گر پؽش فرز خوته آلشت کوݩ ۉ ز نۊ تفره کو.",
"cancel_explain": "ای تراکونش ن وا تراکونش دیر ک و خوت پرداخت اکونه ۉ کارمزد بیشتر داره، جانشین اکۊنیم. یۊ تراکونش الن ن لقو اکونه. ای کار ن RBF—جانشینی وا کارمزد اگۊن.",
"cancel_no": "ای تراکونش جانشین پزیر نؽ.",
"transaction_loading_error": "موشکلؽ منه بار ونی تراکونش پؽش ٱووڌ. تی کۊن دیرتر ز نۊ تفره کۊنی.",
"transaction_not_available": "تراکونش منه دسرس نؽ",
"cpfp_exp": "ایما تراکونش دؽر وورکل اکۊنیم ک تراکونش تاییڌ نوابیڌه ته خرج اکونه. کارمزد کول ز کارمزد تراکونش ٱلسی بیشتر اونه، سی همیمی زۊڌتر استخراج اونه. ای کار ن CPFP—بچه سی ٱقا پرداخت اکونه اگۊن.",
"details_copy_txid": "لف گیری شناسه تراکونش",
"details_view_in_browser": "ووینسن من گشت گر",
"incoming_transaction": "تراکونش وۊرۊڌی",
"outgoing_transaction": "تراکونش خروجی",
"enable_offline_signing": "ای کیف پیل وا امزای آفلاین ب کار نری ابۊ. اخۊی ایسا هونه فعال کۊنی؟",
"list_conf": "تاییڌ: {number}",
"eta_10m": "زمووݩ تخمینی: حدود 10 دؽقه دی",
"eta_3h": "زمووݩ تخمینی: حدود 3 ساعت دی",
"eta_1d": "زمووݩ تخمینی: حدود 1 رۊز دی",
"list_title_sent": "فیشناڌه وابیڌه",
"rbf_explain": "ای تراکونش ن وا تراکونش دیر ک کارمزدس بیشتره، جانشین اکۊنیم تا زۊڌتر استخراج بۊ. ای کار ن RBF—جانشینی وا کارمزد اگۊن.",
"transactions_count": "شومار تراکونشا",
"txid": "شناسه تراکونش",
"updating": "ورۊ رسۊوی...",
"watchOnlyWarningTitle": "هشڌار امنیتی",
"watchOnlyWarningDescription": "ز کلاش بازا ک گاهاً ز کیف پیلا «فقط‌خواندنی» سی فریو کاربرا استفاڌه اکونن، باپرها بۊ. ای کیف پیلا ایلیه نڌن دارایی ن وار آور یا فیشنی؛ فقط ایلیه مۉجۊڌی ن ووینی.",
"custom_fee_warning_title": "هشڌار",
"custom_fee_warning_description": "کارمزدا کمتر ز 1 ساتۊشی سی هر بایت مجازی زبالن، اما شاید ب ٱر سامووا گره ها، بازنشر نوابن.",
"details_eta_analyzing": "تئلیل...",
"details_sent": "فیشناڌه وابی",
"details_id": "شناسه",
"details_note": "ویرداشت",
"details_add_note": "ٱووردن",
"details_advanced": "پؽشرفته",
"details_fee_rate": "نرخ کارمزد"
"open_url_error": "نا مووفق منه گۊشیڌن لینگ وا گشت گر پؽش فرز. گشت گر پؽش فرز خوته آلشت کوݩ ۉ ز نۊ تفره کو."
},
"wallets": {
"add_bitcoin": "بیت کوین",
@ -410,10 +327,10 @@
"details_multisig_type": "چند امزایی",
"details_show_xpub": "نشووݩ داڌن XPUB کیف پیل",
"details_show_addresses": "نشووݩ داڌن آدرسا",
"details_title": "کیف پیل",
"details_title": "کیف پبل",
"wallets": "کیف پیلا",
"details_type": "نوع",
"details_use_with_hardware_wallet": "و کار گرؽڌن وا کیف پیل سخت ٱفزاری",
"details_use_with_hardware_wallet": "و کار گرؽڌن وابا کیف پیل سخت ٱفزاری",
"details_yes_delete": "هری پاک کوݩ",
"enter_bip38_password": "رزمن سی رزم گوشایی بزنین",
"export_title": "و در کشیڌن کیف پیل",
@ -451,75 +368,14 @@
"details_delete_anyway": "و هر هال پاک بۊ",
"add_lightning": "لایتنینگ",
"swipe_balance_hide": "بؽڌار",
"details_delete": "پاک کردن",
"add_bitcoin_explain": "کیف پیل بیت کوین ساڌه ۉ پۊر هؽز",
"add_entropy_reset_title": "وورنشۊوی آنتروپی",
"add_entropy_reset_message": "آلشت نوع کیف پیل، آنتروپی الن ن وورنشۊوی اکونه. اخۊی ادامه دی؟",
"add_entropy_bytes": "{bytes} بایت آنتروپی",
"add_entropy_generated": "{gen} بایت آنتروپی وورکل وابیڌه",
"add_entropy_provide": "آنتروپی ن ز ریشت کوݩ تاسا فره کۊن",
"add_entropy_remain": "{gen} بایت آنتروپی وورکل وابیڌه. {rem} بایت ماݩ ز وورکل کوݩ شومارا تسادفی سیستم گرها اونن.",
"add_import_wallet": "و من ٱووردن کیف پیل",
"add_lightning_explain": "سی خرج کردن وا تراکونشا فۊری",
"add_lndhub": "منپیز و LNDhub خوت",
"add_lndhub_error": "نشۊوی گره داڌه وابیڌه، ی گره LNDhub زبال نؽ.",
"add_lndhub_placeholder": "نشۊوی گره ایسا",
"add_placeholder": "کیف پیل ٱوولی مو",
"add_title": "ٱووردن کیف پیل",
"add_wallet_name": "نوم",
"add_wallet_seed_length": "هندا سید",
"add_wallet_seed_length_12": "12 وشه",
"add_wallet_seed_length_24": "24 وشه",
"clipboard_bitcoin": "ی آدرس بیت کوین منه ویرگه ایسا هڌ. اخۊی هونه سی ی تراکونش ب کار بگری؟",
"clipboard_lightning": "ی سۊرت هساو لایتنینگ منه ویرگه ایسا هڌ. اخۊی هونه سی ی تراکونش ب کار بگری؟",
"clear_clipboard_on_import": "پاک کردن ویرگه موقع و من ٱووردن",
"details_advanced": "پؽشرفته",
"details_are_you_sure": "اخۊی موتمعنی؟",
"details_connected_to": "منپیز و",
"details_del_wb_err": "مقدار مۉجۊڌی داڌه وابیڌه وا مۉجۊڌی ای کیف پیل هومخۊوݩ نؽ. تی کۊن ز نۊ تفره کۊنی.",
"details_del_wb_q": "ای کیف پیل مۉجۊڌی داره. پؽش ز ادامه، ویرت بۊ ک بؽ عبارت بازیابی ای کیف پیل، نتری دارایی ن بازیابی کۊنی. سی پؽش گری ز پاک کردن نا خۊسته، تی کۊن مۉجۊڌی کیف پیلت {balance} ساتۊشی ن بزن.",
"swipe_balance_show": "نشۉݩ داڌن",
"drag_to_reorder": "سی ترتیو دی، بکش",
"clear_search": "پاک کردن پیتینیڌن",
"import_explanation": "تی کۊن وشه ها سید، کلید عمومی، WIF، یا هر چی ک داری ن بزن. BlueWallet پوی تلاش خوسه اکونه تا فورمت زبال ن خومه بزنه ۉ کیف پیل ته و من بئره.",
"import_success_watchonly": "کیف پیل ایسا وا مووفقیت و من ٱووڌ. هشڌار: یۊ ی کیف پیل فقط‌خواندنی هڌ، نتری زس فیشنی.",
"import_discovery_offline": "BlueWallet هونی منه هالت آفلاینه. د ای هالت، نتره وجۊد کیف پیل ن تاییڌ کونه، سی همیمی وا دس کیف پیل زبال ن پسند کۊنی",
"import_derivation_subtitle": "تور موشتق وابیڌن دلخای خوته بزن، ایما تلاش اکۊنیم کیف پیل ته پؽڌا کۊنیم.",
"import_wrong_path": "تور موشتق وابیڌن زبال نؽ",
"list_create_a_wallet_text": "ٱزاده، تری هر گد ک اخۊی\nوورکل کۊنی.",
"list_empty_txs1": "تراکونشا ایسا ای جا اوینسن.",
"list_empty_txs1_lightning": "کیف پیل لایتنینگ وا تراکونشا رۊزانه ایسا ب کار رئڌه ابۊ. کارمزدا ب توور بؽ انسافی ارزۊن ۉ پؽڌنی زل هڌ.",
"list_empty_txs2_lightning": "\nسی شۊرۊ ب کار گرهڌن، ری دؽوۉداری دارایی بزن ۉ مۉجۊڌی ته پور کۊن.",
"no_ln_wallet_error": "پؽش ز پرداخت ی سۊرت هساو لایتنینگ، ٱول وا ی کیف پیل لایتنینگ ازاف کۊنی.",
"looks_like_bip38": "یۊ ی کلید خصوصی موسامینیڌه وا رزم (BIP38) ب نظر اونه.",
"manage_title": "مدیریت کیف پیلا",
"no_results_found": "هیچ ناتجه ای پؽڌا نوابی.",
"please_continue_scanning": "تی کۊن ب اسکن کردن ادامه دی.",
"select_no_bitcoin": "هونی هیچ کیف پیل بیت کوین منه دسرس نؽ.",
"select_no_bitcoin_exp": "سی پور کردن کیف پیلا لایتنینگ، ی کیف پیل بیت کوین لازمه. تی کۊن یتی ن وورکل یا و من بئر.",
"select_wallet": "پسند کیف پیل",
"pull_to_refresh": "بکش سی وانۊ کردن",
"warning_do_not_disclose": "دؽوسمندی پاوین ن هیچ گد ب اشتراک نگرا",
"scan_import": "ای QR کود ن اسکن کۊنی تا کیف پیل ته و ی برنامه دؽ و من بئری.",
"write_down_header": "ی نوسخه لادرار دسی وورکل کۊنی",
"write_down": "ای وشه ها ن بنویس ۉ امن نگه دار. هونا ن سی بازیابی کیف پیلت دیرتر ب کار بگر.",
"wallet_type_this": "نوع ای کیف پیل {type} هڌ.",
"share_number": "یک رسۊوی {number}",
"copy_ln_url": "ای نشۊوی ن لف گیری ۉ امن نگه دار تا دیرتر کیف پیل ته بازیابی کۊنی.",
"copy_ln_public": "ای دؽوسمندی ن لف گیری ۉ امن نگه دار تا دیرتر کیف پیل ته بازیابی کۊنی.",
"add_ln_wallet_first": "ٱول وا ی کیف پیل لایتنینگ ازاف کۊنی.",
"identity_pubkey": "کلید عمومی هۊویت",
"xpub_title": "xpub کیف پیل",
"manage_wallets_search_placeholder": "پیتینیڌن کیف پیلا، آدرسا، تراکونشا ۉ ویرداشتا",
"details_delete_wallet_error_message": "موشکلؽ منه تاییڌ پاک وابیڌن ای کیف پیل ز وارسۊویا پؽش ٱووڌ — یۊ شاید ز موشکل شبکه یا منپیز زبال نکوݩ بۊ. ٱر ادامه دی، شاید هنی سی تراکونشا پۊشمت وا ای کیف پیل وارسۊوی گری، حتا پس ز پاک وابیڌنس."
"details_delete": "پاک کردن"
},
"total_balance_view": {
"display_in_bitcoin": "نشووݩ داڌن من بیت کوین",
"hide": "بؽڌار",
"display_in_sats": "نشووݩ داڌن و ری ساتۊشی",
"display_in_fiat": "نشووݩ داڌن من {currency}",
"title": "پوی مۉجۊدی",
"explanation": "پوی مۉجۊڌی کیف پیلا خوته من بلگه نمای کلی ووین."
"title": "پوی مۉجۊدی"
},
"multisig": {
"confirm": "تاییڌ",
@ -538,110 +394,26 @@
"quorum_header": "حد نصاب",
"of": "ز",
"wallet_type": "نوع کیف پیل",
"vault_advanced_customize": "سامووا گاوصندوق",
"multisig_vault_explain": "بؽڌرین امنیت سی مقدارا گت",
"provide_signature_details": "ز دسگا ۉ کیف پیل خوت ک کلید ٱنا هڌ سی امزا ای تراکونش استفاڌه کۊنی",
"provide_signature_details_bluewallet": "د BlueWallet، و بلگه فشناڌن ر ۉ ز فهرست، یۊ ن پسند کۊن: ",
"provide_signature_next_steps": "اسکن یا و من ٱووردن تراکونش امزا وابیڌه",
"provide_signature_next_steps_details": "ٱر کیف پیلت تراکونش ن وا مووفقیت امزا کرد، QR کود داڌه وابیڌه ن اسکن کۊن یا فایل هومرا ن و من بئر، ۉ بعد پوی جۊزیات تراکونش ن پؽش ز انتشار وارسی کۊن.",
"required_keys_out_of_total": "کلیدا لازم ز کول",
"view": "ووینسن",
"shared_key_detected": "امزا کوݩ هومبهر یک رسۊوی وابیڌه",
"shared_key_detected_question": "ی امزا کوݩ هومبهر وا ایسا یک رسۊوی وابیڌه، اخۊی هونه و من بئری؟",
"manage_keys": "مدیریت کلیدا",
"how_many_signatures_can_bluewallet_make": "BlueWallet چن گد امزا تره بکونه",
"signatures_required_to_spend": "امزا یل لازم {number}",
"signatures_we_can_make": "تره {number} ن بکونه",
"scan_or_import_file": "اسکن یا و من ٱووردن فایل",
"export_coordination_setup": "و در کردن سامووا هومٱئنگی",
"cosign_this_transaction": "ای تراکونش ن هومرا امزا کۊنی؟",
"lets_start": "بیا شۊرۊ کۊنیم",
"native_segwit_title": "بؽڌرین کاره",
"wrapped_segwit_title": "بؽڌرین هومخۊوݩ کاری",
"legacy_title": "قدیمی",
"what_is_vault": "گاوصندوق ی",
"what_is_vault_numberOfWallets": " چن امزایی {m} ز {n} ",
"what_is_vault_wallet": "کیف پیل هڌ.",
"needs": "هون اخا",
"what_is_vault_description_number_of_vault_keys": " {m} کلید گاوصندوق ",
"what_is_vault_description_to_spend": "سی خرج کردن ۉ یۊ سؽومی ک \nتری سی نوسخه لادرار ب کار بگری.",
"what_is_vault_description_to_spend_other": "سی خرج کردن.",
"invalid_mnemonics": "ای عبارت بازیابی زبال ب نظر نونه.",
"invalid_cosigner": "داڌه امزا کوݩ هومبهر زبال نؽ",
"not_a_multisignature_xpub": "یۊ xpub ز ی کیف پیل چند امزایی نؽ!",
"invalid_cosigner_format": "امزا کوݩ هومبهر زبال نؽ: یۊ امزا کوݩ هومبهر سی فورمت {format} نؽ.",
"create_new_key": "وورکل نۊ",
"scan_or_open_file": "اسکن یا گۊشیڌن فایل",
"i_have_mnemonics": "مو سید سی ای کلید داروم.",
"type_your_mnemonics": "ی سید بزن سی و من ٱووردن کلید گاوصندوق الن.",
"this_is_cosigners_xpub": "یۊ xpub امزا کوݩ هومبهر هڌ—ٱماڌه سی و من ٱووردن من ی کیف پیل دؽ. یک رسۊویس امنه.",
"this_is_cosigners_xpub_airdrop": "ٱر ز AirDrop یک رسۊوی کۊنی، گرنده ها وا منه بلگه هومٱئنگی بۊن.",
"wallet_key_created": "کلید گاوصندوق ایسا وورکل وابی. ی دیقه ویرگار بنا تا سید عبارت بازیابی ته امن نوسخه لادرار بگری.",
"are_you_sure_seed_will_be_lost": "اخۊی موتمعنی؟ ٱر نوسخه لادرار نڌاری، سید عبارت بازیابی ته گوم اونه.",
"forget_this_seed": "ای سید ن ز ویر بئر ۉ ز xpub و جاس استفاڌه کو.",
"view_edit_cosigners": "ووینسن/ویرٱست امزا کوݩ هومبهر",
"this_cosigner_is_already_imported": "ای امزا کوݩ هومبهر ز پؽش و من ٱووڌه.",
"export_signed_psbt": "و در کردن PSBT امزا وابیڌه",
"input_fp": "جا کلک ن بزن",
"input_fp_explain": "سی استفاڌه ز پؽش فرز (00000000) ز ای کار بئر",
"input_path": "ٱووردن تور موشتق وابیڌن",
"input_path_explain": "سی استفاڌه ز پؽش فرز ({default}) ز ای کار بئر",
"ms_help": "هؽاری",
"ms_help_title": "گاوصندوقا چن امزایی چی جور کار اکونن: نکات ۉ ترفندا",
"ms_help_text": "ی کیف پیل وا چن کلید، سی امنیت بیشتر یا دؽوۉداری اشتراکی",
"ms_help_title1": "استفاڌه ز چن دسگا پؽشنهاڌ اونه.",
"ms_help_1": "گاوصندوق وا برنامه ها دؽر BlueWallet ۉ کیف پیلا هومخۊوݩ وا PSBT چی Electrum، Specter، Coldcard، Cobo Vault ۉ غیره کار اکونه.",
"ms_help_title2": "ویرٱست کلیدا",
"ms_help_2": "تری پوی کلیدا گاوصندوق ن ری ای دسگا وورکل کۊنی ۉ دیرتر هونا ن پاک یا ویرٱست کۊنی. ناهاڌن پوی کلیدا ری ی دسگا، هومراز ی کیف پیل بیت کوین مئمۊلی امنه.",
"ms_help_title3": "نوسخه لادرارا گاوصندوق",
"ms_help_3": "منه گیزینه ها کیف پیل، نوسخه لادرار گاوصندوق ۉ نوسخه لادرار فقط‌خواندنی ته پؽڌا اکۊنی. ای نوسخه لادرار چی ی نخشه ز کیف پیلته. هون سی بازیابی کیف پیل ٱر یتی ز سیدا ته گوم کۊنی ضروریه.",
"ms_help_title4": "و من ٱووردن گاوصندوقا",
"ms_help_4": "سی و من ٱووردن چن امزایی، ز فایل نوسخه لادرار خوت ۉ ویژگی و من ٱووردن استفاڌه کۊن. ٱر فقط سیدا ۉ xpub داری، تری ز دگمه و من ٱووردن جودا موقع وورکل کلیدا گاوصندوق استفاڌه کۊنی.",
"ms_help_5": "ب توور پؽش فرز، BlueWallet ی گاوصندوق 2 ز 3 وورکل اکونه. سی وورکل ی حد نصاب دیر یا آلشت نوع آدرس، هالت پؽشرفته ن منه سامووا فعال کۊن."
"vault_advanced_customize": "سامووا گاوصندوق"
},
"cc": {
"sort_status": "وزیت",
"change": "آلشتکاری",
"change": "باقی‌مانده",
"header": "مدیریت UTXO",
"selected_summ": "{value} پسند بیڌه",
"sort_label": "برچسب",
"coins_selected": "کوینا پسند بیڌه ({number})",
"empty": "ای کیف پیل هونی هیچ کوینی نڌاره.",
"freeze": "مسدۊد کردن",
"freezeLabel": "مسدۊد کردن",
"freezeLabel_un": "گۊشیڌن مسدۊدی",
"use_coin": "استفاڌه ز کوین",
"use_coins": "استفاڌه ز کوینا",
"tip": "ای ویژگی ایلیه کوینا ته سی مدیریت بؽڌر کیف پیلت ووینی، برچسب بنی، مسدۊد کۊنی یا پسند کۊنی. تری وا زیڌن ری دایره ها رنگی، چن کوین ن پسند کۊنی.",
"sort_asc": "ز کم و بیشتر",
"sort_desc": "ز بیشتر و کم",
"sort_height": "بلندی",
"sort_value": "ارزش",
"sort_by": "ترتیو ری ٱر"
"sort_label": "برچسب"
},
"addresses": {
"sign_placeholder_address": "آدرس",
"type_receive": "گرؽڌن",
"type_change": "آلشتکاری",
"type_change": "باقی‌مانده",
"addresses_title": "آدرسا",
"transactions": "تراکونشا",
"copy_private_key": "لف گیری کلید خصوصی",
"sensitive_private_key": "هشڌار: کلیدا خصوصی قلوه هساسن. ادامه دی؟",
"sign_title": "امزا/وارسی پیوم",
"sign_help": "ای جا تری ی امزای رزمی ن ری مووا ی آدرس بیت کوین وورکل یا وارسی کۊنی.",
"sign_sign": "امزا",
"sign_verify": "وارسی",
"sign_signature_correct": "وارسی وا مووفقیت ٱنجوم وابی!",
"sign_signature_incorrect": "وارسی ناکام بی!",
"sign_placeholder_message": "پیوم",
"sign_placeholder_signature": "امزا",
"type_used": "ب کار رئڌه وابی"
"transactions": "تراکونشا"
},
"units": {
"BTC": "BTC",
"sat_vbyte": "ساتۊشی سی هر بایت مجازی",
"sats": "ساتۊشی",
"MAX": "بیشترین"
"sats": "ساتۊشی"
},
"bip47": {
"payment_code": "کود پرداخت",
@ -650,55 +422,6 @@
"copy_payment_code": "لف گیری کود پرداخت",
"add_contact": "ٱووردن هومدنگ",
"invalid_pc": "کود پرداخت زبال نؽ",
"not_found": "کود پرداخت نجۊرست",
"bip47_explain": "کود قابل استفاڌه دۊوار ۉ یک رسۊوی",
"purpose": "کود قابل استفاڌه دۊوار ۉ یک رسۊوی (BIP47)",
"pay_this_contact": "پرداخت و ای هومدنگ",
"rename_contact": "آلشت نوم هومدنگ",
"hide_contact": "بؽڌار کردن هومدنگ",
"rename": "آلشت نوم",
"provide_name": "نوم نۊ سی ای هومدنگ بزن",
"provide_payment_code": "کود پرداخت ن بزن",
"notification_tx_unconfirmed": "تراکونش وارسۊوی هنی تاییڌ نوابیڌه، تی کۊن مندیر بۊ",
"failed_create_notif_tx": "وورکل کردن تراکونش آنچین ناکام بی",
"onchain_tx_needed": "تراکونش آنچین لازمه",
"notif_tx_sent": "تراکونش وارسۊوی فیشناڌه وابی. تی کۊن مندیر بۊ تا تاییڌ بۊ",
"notif_tx": "تراکونش وارسۊوی"
},
"notifications": {
"would_you_like_to_receive_notifications": "اخۊی موقع گرؽڌن پرداختا وۊرۊڌی، وارسۊوی گری؟",
"notifications_subtitle": "پرداختا وۊرۊڌی ۉ تاییڌ تراکونشا",
"no_and_dont_ask": "ن، ز نۊ نی پرس.",
"permission_denied_message": "ایسا موجوز فشناڌن وارسۊویا ن قبۊل نکردیه. ٱر اخۊی وارسۊویا گری، تی کۊن هونا ن منه سامووا دسگا ته فعال کۊنی."
},
"is_it_my_address": {
"title": "آدرس مونه؟",
"owns": "{label} {address} ن داره",
"enter_address": "آدرس ن بزن",
"check_address": "وارسی آدرس",
"no_wallet_owns_address": "هیچ یتی ز کیف پیلا منه دسرس، آدرس داڌه وابیڌه ن نڌاره.",
"view_qrcode": "ووینسن QR کود"
},
"autofill_word": {
"title": "وشه ٱخر سید",
"enter": "عبارت بازیابی ناقس ته بزن",
"generate_word": "وورکل وشه ٱخر",
"error": "وۊرۊڌی ی عبارت بازیابی ناقس 11 یا 23 وشه ای نؽ. تی کۊن ز نۊ تفره کۊنی."
},
"lnurl_auth": {
"register_question_part_1": "اخۊی ی هساو ای جا نوم نویسی کۊنی",
"register_question_part_2": "وا استفاڌه ز کیف پیل لایتنینگ خوت؟",
"register_answer": "ایسا وا مووفقیت ی هساو من {hostname} نوم نویسی کردیه!",
"login_question_part_1": "اخۊی ای جا وۊرۊڌ بۊوی",
"login_question_part_2": "وا استفاڌه ز کیف پیل لایتنینگ خوت؟",
"login_answer": "ایسا وا مووفقیت من {hostname} وۊرۊڌ وابیه!",
"link_question_part_1": "اخۊی هساو خوته ای جا پیوست کۊنی",
"link_question_part_2": "و کیف پیل لایتنینگ خوت؟",
"link_answer": "کیف پیل لایتنینگ ایسا وا مووفقیت و هساو ایسا من {hostname} پیوست وابی!",
"auth_question_part_1": "اخۊی ای جا هۊویت ته تاییڌ کۊنی",
"auth_question_part_2": "وا استفاڌه ز کیف پیل لایتنینگ خوت؟",
"auth_answer": "ایسا وا مووفقیت من {hostname} هۊویت ته تاییڌ کردیه!",
"could_not_auth": "ایما نترسیم هۊویت ایسا ن من {hostname} تاییڌ کۊنیم.",
"authenticate": "تاییڌ هۊویت"
"not_found": "کود پرداخت نجۊرست"
}
}

View File

@ -12,8 +12,8 @@
"of": "{number} de {total}",
"ok": "OK",
"enter_url": "Entrar URL",
"storage_is_encrypted": "La informació està xifrada. Es requereix la contrasenya per a desxifrar-la.",
"yes": "Sí",
"storage_is_encrypted": "L'informació està xifrada. Es requereix la contrasenya per a desxifrar-la.",
"yes": "Si",
"no": "No",
"save": "Desar...",
"seed": "Llavor",
@ -34,12 +34,12 @@
"azteco": {
"codeIs": "El codi del teu val és",
"errorBeforeRefeem": "Abans de canviar, primer heu dafegir un moneder de Bitcoin.",
"errorSomething": "Quelcom ha anat malament. Aquest val continua sent vàlid?",
"errorSomething": "Quelcom ha anat malament. Aquest val continua sent vàlid? ",
"redeem": "Canviar al moneder",
"redeemButton": "Canviar",
"success": "Completat",
"successMessage": "Val bescanviat correctament! Els vostres fons haurien d'arribar al vostre moneder de Bitcoin en breu.",
"title": "Canviar cupó d'Azte.co"
"title": "Canviar cupó de Azte.co"
},
"entropy": {
"save": "Desar",
@ -81,8 +81,8 @@
"plausibledeniability": {
"create_fake_storage": "Crear informació xifrada falsa",
"create_password_explanation": "La contrasenya no pot ser la mateixa que la del seu moneder principal.",
"help": "Sota certes circumstàncies, vostè podria ser obligat a revelar la contrasenya del seu moneder. Per a mantenir les seves monedes segures, BlueWallet pot crear un altre moneder xifrat, amb una altra contrasenya. Si es veu obligat, pot revelar la contrasenya per al fals moneder a un tercer de manera que ells creuran que és el seu moneder principal.",
"help2": "El moneder \"fals\" és completament funcional. Pot dipositar una quantitat mínima perquè sigui més creïble.",
"help": "Sota certes circumstàncies, vostè podria ser obligat a revelar la contrasenya del seu moneder. Per a mantenir les seves monedes segures, BlueWallet pot crear un altre moneder xifrat, amb una altra contrasenya. Si es veu obligat, pot revelar la contrasenya per al fals moneder a un tercer de manera que ells creuran que és el seu moneder principal",
"help2": "El moneder \"false\" és completament funcional. Pot dipositar una quantitat mínima perquè sigui més creïble.",
"password_should_not_match": "La contrasenya no pot ser la mateixa que la del seu moneder principal.",
"title": "Negació plausible"
},
@ -127,7 +127,7 @@
"create_fee": "Comissió",
"create_memo": "Comentari",
"create_satoshi_per_vbyte": "Satoshis per vByte",
"create_this_is_hex": "Això és la representació en hexadecimal (hex) de la transacció, signada i llesta per ser enviada a la xarxa. Continuar?",
"create_this_is_hex": "Això és la representació en hexadecimal (hex) de la transacció, firmada i llesta per ser enviada a la xarxa. ¿Continuar?",
"create_to": "A",
"create_tx_size": "Mida de TX",
"create_verify": "Verificar a coinb.in",
@ -141,18 +141,18 @@
"please_complete_recipient_title": "Destinatari incomplet",
"please_complete_recipient_details": "Si us plau, completeu les dades del destinatari núm. {number} abans d'afegir-ne un de nou.",
"details_address": "Adreça",
"details_address_field_is_not_valid": "Adreça no vàlida",
"details_address_field_is_not_valid": "Adreça invalida",
"details_adv_fee_bump": "Permeteu ampliar la comissió",
"details_adv_full": "Utilitzeu tot el saldo",
"details_adv_full_sure": "Esteu segur que voleu utilitzar el saldo complet del moneder per a aquesta transacció?",
"details_adv_full_sure_frozen": "Esteu segur que voleu utilitzar el saldo complet del moneder per a aquesta transacció? Tingueu en compte que les monedes congelades queden excloses.",
"details_adv_import": "Importar transacció",
"details_adv_import_qr": "Importar transacció (QR)",
"details_amount_field_is_not_valid": "Quantitat no vàlida",
"details_amount_field_is_not_valid": "Quantitat invalida",
"details_amount_field_is_less_than_minimum_amount_sat": "La quantitat especificada és massa petita. Introduïu una quantitat superior a 500 sats.",
"details_create": "Crear",
"details_error_decode": "No s'ha pogut descodificar l'adreça de Bitcoin",
"details_fee_field_is_not_valid": "Comissió no vàlida",
"details_fee_field_is_not_valid": "Comissió invalida",
"details_frozen": "{amount} BTC està congelat.",
"details_next": "Següent",
"details_no_signed_tx": "El fitxer seleccionat no conté una transacció que es pugui importar.",
@ -238,8 +238,8 @@
"default_title": "En obrir",
"donate": "Donar",
"donate_description": "Ajudeu-nos a mantenir Blue gratuït!",
"electrum_connected": "Connectat",
"electrum_connected_not": "No connectat",
"electrum_connected": "Conectat",
"electrum_connected_not": "No conectat",
"electrum_error_connect": "No es pot connectar al servidor Electrum proporcionat",
"electrum_error_connect_tor": "No es pot connectar al servidor Electrum proporcionat. Si us plau, assegureu-vos que l'aplicació Orbot està connectada i torneu-ho a provar.",
"lndhub_uri": "P. ex., {example}",
@ -336,6 +336,7 @@
"transaction_not_available": "Transacció no disponible",
"confirmations_lowercase": "{confirmations} confirmacions",
"expand_note": "Ampliar nota",
"copy_link": "Còpia l'enllaç",
"cpfp_create": "Crear",
"cpfp_exp": "Crearem una altra transacció que gasta la vostra transacció sense confirmar. La comissió total serà més alta que la comissió de la transacció original, de manera que s'hauria de minar més ràpid. Aquesta tècnica s'anomena CPFP—Child Pays for Parent.",
"cpfp_no_bump": "A aquesta transacció no se li pot ampliar la comissió.",
@ -346,6 +347,7 @@
"details_copy_block_explorer_link": "Copiar enllaç de l'explorador de blocs",
"details_copy_note": "Copiar nota",
"details_copy_txid": "Copiar ID de la transacció",
"details_from": "De",
"details_inputs": "Entrades",
"details_outputs": "Sortides",
"date": "Data",
@ -367,6 +369,7 @@
"eta_10m": "ETA: En ~10 minuts",
"eta_3h": "ETA: En ~3 hores",
"eta_1d": "ETA: En ~1 dia",
"view_wallet": "Veure {walletLabel}",
"list_title": "transaccions",
"list_title_sent": "Enviat",
"list_title_received": "Rebut",
@ -428,9 +431,9 @@
"clipboard_bitcoin": "Teniu una adreça de Bitcoin al porta-retalls. Voleu utilitzar-la per a una transacció?",
"clipboard_lightning": "Teniu una factura Lightning al porta-retalls. Voleu utilitzar-la per a una transacció?",
"clear_clipboard_on_import": "Esborrar el porta-retalls en importar",
"details_address": "Adreça",
"details_address": "Direcció",
"details_advanced": "Avançat",
"details_are_you_sure": "Estàs segur?",
"details_are_you_sure": "¿Estàs segur?",
"details_connected_to": "Connectat a",
"details_del_wb_err": "La quantitat de saldo proporcionada no coincideix amb el saldo d'aquest moneder. Si us plau, torneu-ho a provar.",
"details_del_wb_q": "Aquest moneder té saldo. Abans de continuar, tingueu en compte que no podreu recuperar els fons sense la frase de recuperació d'aquest moneder. Per evitar l'eliminació accidental, introduïu el saldo del vostre moneder de {balance} satoshis.",
@ -440,9 +443,9 @@
"details_display": "Mostrar a la pantalla d'inici",
"details_export_backup": "Exportar / Guardar",
"details_export_history": "Exportar historial a CSV",
"details_master_fingerprint": "Empremta digital mestra",
"details_master_fingerprint": "Petjada digital mestre",
"details_multisig_type": "multisig",
"details_show_xpub": "Mostrar XPUB del moneder",
"details_show_xpub": "Mostrar wallet XPUB",
"details_show_addresses": "Mostrar adreces",
"details_title": "Detalls del moneder",
"wallets": "moneders",
@ -451,15 +454,15 @@
"drag_to_reorder": "Arrossegueu per reordenar",
"clear_search": "Esborrar cerca",
"details_type": "Tipus",
"details_use_with_hardware_wallet": "Usar amb un moneder de maquinari",
"details_yes_delete": "Sí, eliminar",
"details_use_with_hardware_wallet": "Usar amb un moneder hardware",
"details_yes_delete": "Si, eliminar",
"enter_bip38_password": "Introduïu la contrasenya per desxifrar",
"export_title": "Exportació de moneder",
"import_do_import": "Importar",
"import_passphrase": "Frase de contrasenya",
"import_passphrase_title": "Frase de contrasenya",
"import_passphrase_message": "Introduïu la frase de contrasenya si n'heu utilitzat alguna",
"import_error": "No s'ha pogut importar. És vàlid?",
"import_error": "No s'ha pogut importar. ¿És vàlid?",
"import_explanation": "Si us plau, introduïu les vostres paraules de la llavor, la clau pública, el WIF o qualsevol cosa que tingueu. BlueWallet farà tot el possible per endevinar el format correcte i importar el vostre moneder.",
"import_imported": "Importat",
"import_scan_qr": "o escanejar codi QR?",
@ -517,7 +520,8 @@
"manage_wallets_search_placeholder": "Cercar moneders, adreces, transaccions i comentaris",
"more_info": "Més informació",
"details_delete_wallet_error_message": "Hi ha hagut un problema en confirmar si aquest moneder s'ha eliminat de les notificacions—això podria ser degut a un problema de xarxa o una mala connexió. Si continueu, podríeu seguir rebent notificacions per a transaccions relacionades amb aquest moneder, fins i tot després d'eliminar-lo.",
"details_delete_anyway": "Eliminar igualment"
"details_delete_anyway": "Eliminar igualment",
"xpub_copiedToClipboard": "Copiat al porta-retalls."
},
"total_balance_view": {
"display_in_bitcoin": "Mostrar en Bitcoin",

View File

@ -27,9 +27,9 @@
"enter_amount": "Zadejte částku",
"qr_custom_input_button": "Klepněte 10× k zadání vlastního vstupu",
"unlock": "Odemknout",
"port": "Port",
"ssl_port": "SSL port",
"suggested": "Doporučené",
"port": "Port"
"suggested": "Doporučené"
},
"azteco": {
"codeIs": "Váš kód voucheru je",
@ -74,9 +74,9 @@
"please_pay_between_and": "Zaplaťte prosím mezi {min} a {max}",
"please_pay": "Zaplaťte prosím",
"preimage": "Předobraz",
"sats": "sats.",
"date_time": "Datum a čas",
"wasnt_paid_and_expired": "Tato faktura nebyla zaplacena a její platnost vypršela.",
"sats": "sats."
"wasnt_paid_and_expired": "Tato faktura nebyla zaplacena a její platnost vypršela."
},
"plausibledeniability": {
"create_fake_storage": "Vytvořit zašifrované úložiště",
@ -105,9 +105,9 @@
"header": "Přijmout",
"reset": "Obnovit",
"maxSats": "Maximální množství je {max} sats",
"maxSatsFull": "Maximální částka je {max} sats nebo {currency}",
"maxSatsFull": "Maximální částka je {max} sats nebo {currency} ",
"minSats": "Minimální množství je {min} sats",
"minSatsFull": "Minimální částka je {min} sats nebo {currency}",
"minSatsFull": "Minimální částka je {min} sats nebo {currency} ",
"qrcode_for_the_address": "QR kód pro adresu",
"bip47_explanation": "Platební kódy jsou univerzální adresy, které zabraňují prozrazení adres vaší peněženky. Ne všechny služby je však podporují."
},
@ -157,6 +157,7 @@
"details_next": "Další",
"details_no_signed_tx": "Vybraný soubor neobsahuje transakci, kterou lze importovat.",
"details_note_placeholder": "Poznámka pro sebe",
"counterparty_label_placeholder": "Upravit jméno kontaktu",
"details_scan": "Skenovat",
"details_scan_hint": "Dvojitým klepnutím naskenujete nebo importujete cíl",
"details_scan_error": "Chyba skenování",
@ -205,7 +206,7 @@
"file_saved_at_path": "Soubor ({filePath}) byl uložen.",
"cant_send_to_silentpayment_adress": "Tato peněženka nedokáže odesílat prostředky na SilentPayment adresy",
"cant_send_to_bip47": "Tato peněženka nedokáže odesílat prostředky na platební kódy BIP47",
"cant_find_bip47_notification": "Nejdříve tento platební kód přidejte mezi kontakty",
"cant_find_bip47_notification": "Nejdříve teno platební kód přidejte mezi kontakty",
"problem_with_psbt": "Problém s PSBT (částečně podepsanou bitcoinovou transakcí)"
},
"settings": {
@ -227,7 +228,7 @@
"privacy_temporary_screenshots": "Umožnit zachycení snímku obrazovky",
"privacy_temporary_screenshots_instructions": "Ochrana proti zachycení snímku obrazovky bude dočasně vypnuta, aby bylo možné pořizovat snímky obrazovky a nahrávání obrazovky. Ochrana se znovu automaticky aktivuje, když zavřete a znovu otevřete aplikaci BlueWallet.",
"biometrics": "Biometrie",
"biometrics_no_longer_available": "Nastavení vašeho zařízení se změnila a již neodpovídají zvoleným nastavením zabezpečení v aplikaci. Aktivujte prosím znovu biometrii nebo přístupový kód, a poté aplikaci restartujte, aby se tyto změny projevily.",
"biometrics_no_longer_available": "Nastavení vašeho zařízení se změnily a již neodpovídají zvoleným nastavením zabezpečení v aplikaci. Aktivujte prosím znovu biometrii nebo přístupový kód, a poté aplikaci restartujte, aby se tyto změny projevily.",
"biom_10times": "Pokusili jste se zadat heslo 10×. Chcete obnovit úložiště? Tím odstraníte všechny peněženky a dešifrujete úložiště.",
"biom_conf_identity": "Potvrďte prosím svoji identitu.",
"biom_no_passcode": "Vaše zařízení nemá povolené odemykání pomocí přístupového kódu nebo biometriky. Abyste mohli pokračovat, nastavte prosím přístupový kód nebo biometriku v aplikaci Nastavení.",
@ -335,6 +336,7 @@
"transaction_loading_error": "Během načítání transakce došlo k problému. Zkuste to prosím znovu později.",
"transaction_not_available": "Transakce není dostupná",
"confirmations_lowercase": "{confirmations} potvrzení",
"copy_link": "Zkopírovat odkaz",
"expand_note": "Rozbalit poznámku",
"cpfp_create": "Vytvořit",
"cpfp_exp": "Vytvoříme další transakci, která utratí vaši nepotvrzenou transakci. Celkový poplatek bude vyšší než původní transakční poplatek, takže by měl být těžen rychleji. Tomu se říká CPFP Child Pays for Parent (dítě platí za rodiče).",
@ -346,6 +348,7 @@
"details_copy_block_explorer_link": "Zkopírovat odkaz na průzkumník bloků",
"details_copy_note": "Zkopírovat poznámku",
"details_copy_txid": "Zkopírovat ID transakce",
"details_from": "Vstup",
"details_inputs": "Vstupy",
"details_outputs": "Výstupy",
"date": "Datum",
@ -367,6 +370,7 @@
"eta_10m": "Doručení: Za ~10 minut",
"eta_3h": "Doručení: Za ~3 hodiny",
"eta_1d": "Doručení: Za ~1 den",
"view_wallet": "Zobrazit {walletLabel}",
"list_title": "Transakce",
"list_title_sent": "Odeslané",
"list_title_received": "Přijato",
@ -378,6 +382,8 @@
"status_cancel": "Zrušit transakci",
"transactions_count": "Počet transakcí",
"txid": "ID transakce",
"from": "Od: {counterparty}",
"to": "Pro: {counterparty}",
"updating": "Aktualizování…",
"watchOnlyWarningTitle": "Bezpečnostní upozornění",
"watchOnlyWarningDescription": "Dávejte si pozor na podvodníky, kteří často používají peněženky „pouze pro sledování“ ke klamání uživatelů. Tyto peněženky vám neumožňují kontrolovat nebo odesílat prostředky; umožňují vám pouze zobrazit zůstatek.",
@ -386,10 +392,10 @@
"details_eta_analyzing": "Analyzování…",
"details_sent": "Odesláno",
"details_section": "Podrobnosti",
"details_id": "ID",
"details_explorer": "průzkumník",
"details_network_fee": "Síťový poplatek",
"details_to_address": "Komu",
"details_id": "ID",
"details_note": "Poznámka",
"details_add_note": "přidat",
"details_advanced": "Pokročilé",
@ -502,6 +508,7 @@
"select_no_bitcoin": "V současné době nejsou k dispozici žádné bitcoinové peněženky.",
"select_no_bitcoin_exp": "Bitcoinová peněženka je vyžadována pro doplnění Lightning peněženky. Nějakou prosím vytvořte nebo importujte.",
"select_wallet": "Vyberte peněženku",
"xpub_copiedToClipboard": "Zkopírováno do schránky.",
"pull_to_refresh": "Zatáhněte pro obnovení",
"warning_do_not_disclose": "Níže uvedené info nikdy nesdílejte",
"scan_import": "Chcete-li importovat vaši peněženku do jiné aplikace, naskenujte tento QR kód.",
@ -574,7 +581,7 @@
"invalid_mnemonics": "Zdá se, že tato mnemotechnická fráze není platná.",
"invalid_cosigner": "Neplatná data spolupodepisujícího",
"not_a_multisignature_xpub": "Toto není XPUB z vícepodpisové peněženky!",
"invalid_cosigner_format": "Nesprávný spolupodepisující: toto není spolupodepisující pro {format} formát.",
"invalid_cosigner_format": "Nesprávný spolupodepsaný: toto není spolupodepsaný pro {format} formát.",
"create_new_key": "Vytvořit nový",
"scan_or_open_file": "Naskenujte nebo otevřete soubor",
"i_have_mnemonics": "Mám seed pro tento klíč.",
@ -582,7 +589,7 @@
"this_is_cosigners_xpub": "Toto je XPUB spolupodepisujícího připravený k importu do jiné peněženky. Je bezpečné ho sdílet.",
"this_is_cosigners_xpub_airdrop": "Pokud sdílíte přes AirDrop, příjemci musí být na obrazovce koordinace.",
"wallet_key_created": "Klíč k Úložišti byl vytvořen. Udělejte si chvíli a bezpečně zálohujte svůj mnemotechnický seed.",
"are_you_sure_seed_will_be_lost": "Jste si jisti? Váš mnemotechnický seed bude ztracen, pokud nemáte zálohu.",
"are_you_sure_seed_will_be_lost": "Jste si jisti? Vaš mnemotechnický seed bude ztracen, pokud nemáte zálohu.",
"forget_this_seed": "Zapomenout tento seed a použít namísto něj XPUB.",
"view_edit_cosigners": "Zobrazit/upravit spolupodepisující",
"this_cosigner_is_already_imported": "Tento spolupodepisující je již importován.",
@ -658,7 +665,7 @@
"sign_placeholder_message": "Zpráva",
"sign_placeholder_signature": "Podpis",
"addresses_title": "Adresy",
"type_change": "Drobné",
"type_change": "Change",
"type_receive": "Přijímací",
"type_used": "Použitá",
"transactions": "Transakce"

View File

@ -12,11 +12,11 @@
"of": "{number} o {total}",
"ok": "Iawn",
"enter_url": "Mewnosod URL",
"storage_is_encrypted": "Mae'r storfa wedi'i hamgryptio. Mae angen cyfrinair i'w dadgryptio.",
"storage_is_encrypted": "Mae'r storfa wedi encryptio. Mae angen Cyfrinair i'w ddad-gryptio. ",
"yes": "Ie",
"no": "Na",
"save": "Safio...",
"seed": "Seed",
"seed": "Hadyn",
"success": "Llwyddiant",
"wallet_key": "Allwedd waled",
"close": "Cau",
@ -54,7 +54,7 @@
},
"lnd": {
"errorInvoiceExpired": "Mae'r anfoneb wedi dod i ben.",
"expired": "Wedi dod i ben",
"expired": "Gorffen",
"expiresIn": "Yn dod i ben mewn {time} munud",
"payButton": "Talu",
"payment": "Taliad",
@ -71,15 +71,15 @@
"additional_info": "Gwybodaeth Ychwanegol",
"for": "Ar Gyfer:",
"lightning_invoice": "Anfoneb Mellten",
"please_pay_between_and": "Tala rhwng {min} a {max}",
"please_pay_between_and": "Tâl rhwng {min} a {max} os gweli'n dda",
"please_pay": "Talu",
"preimage": "Cynddelw",
"sats": "sats.",
"date_time": "Dyddiad ac Amser",
"wasnt_paid_and_expired": "Ni chafodd yr anfoneb ei thalu, ac mae wedi gorffen.",
"sats": "sats."
"wasnt_paid_and_expired": "Ni chafodd yr anfoneb ei thalu, ac mae wedi gorffen."
},
"plausibledeniability": {
"create_fake_storage": "Creu storfa wedi'i hamgryptio",
"create_fake_storage": "Creu storfa wedi encryptio",
"create_password_explanation": "Ni ddyliai'r cyfrinair ar gyfer y storfa ffug fod yr un cyfrinair ag ar gyfer y brif storfa",
"help": "O dan rai amgylchiadau, gallai rhywun dy orfodi i ddatgelu cyfrinair. Er mwyn cadw dy gyllid yn ddiogel, gall BlueWallet greu storfa wedi'i hamgryptio arall efo cyfrinair gwahanol. O dan bwysau, gelli ddatgelu'r cyfrinair hwn i drydydd parti. Os caiff ei fewnosod yn BlueWallet, bydd yn datgloi storfa “ffug” newydd. Bydd hyn yn ymddangos yn ddilys i'r trydydd parti, ond bydd dy brif storfa yn parhau'n gyfrinachol gyda dy gyllid yn ddiogel.",
"help2": "Bydd y storfa newydd yn gwbl weithredol, a gelli storio rhywfaint o gyllid yno er mwyn iddi edrych yn fwy credadwy.",
@ -144,7 +144,7 @@
"details_address_field_is_not_valid": "Dydi'r cyfeiriad ddim yn ddilys.",
"details_adv_fee_bump": "Caniatáu Codi'r Ffi",
"details_adv_full": "Defnyddio Balans Llawn",
"details_adv_full_sure": "Wyt ti'n siŵr dy fod eisiau defnyddio balans llawn dy waled ar gyfer y trafodyn hwn?",
"details_adv_full_sure": "Wyt ti'n sicr dy fod eisiau defnyddio balans llawn dy walet ar gyfer y trafodyn hwn?",
"details_adv_full_sure_frozen": "Wyt ti'n siŵr dy fod eisiau defnyddio balans llawn dy waled ar gyfer y trafodyn hwn? Sylwer bod darnau arian wedi'u rhewi yn cael eu heithrio.",
"details_adv_import": "Mewnforio Trafodyn",
"details_adv_import_qr": "Mewnforio Trafodyn (QR)",
@ -169,6 +169,8 @@
"dynamic_prev": "Blaenorol",
"dynamic_start": "Dechrau",
"dynamic_stop": "Stopio",
"fee_10m": "10m",
"fee_1d": "1d",
"fee_3h": "3a",
"fee_custom": "Arbennig",
"insert_custom_fee": "Mewnosod ffi",
@ -182,7 +184,7 @@
"input_done": "Wedi Cwblhau",
"input_paste": "Pastio",
"input_total": "Cyfanswm:",
"permission_camera_message": "Angen dy ganiatâd i ddefnyddio dy gamera.",
"permission_camera_message": "Angen dy ganiatad i ddefnyddio dy gamera.",
"psbt_sign": "Arwyddo trafodyn",
"invalid_psbt": "PSBT annilys.",
"open_settings": "Agor Gosodiadau",
@ -204,9 +206,7 @@
"cant_send_to_silentpayment_adress": "Ni all y waled hon anfon at gyfeiriadau Silent Payments",
"cant_send_to_bip47": "Ni all y waled hon anfon at godau taliad BIP47",
"cant_find_bip47_notification": "Adia'r Cod Taliad hwn at gysylltiadau yn gyntaf",
"problem_with_psbt": "Problem efo'r PSBT",
"fee_10m": "10m",
"fee_1d": "1d"
"problem_with_psbt": "Problem efo'r PSBT"
},
"settings": {
"about": "Amdano",
@ -289,21 +289,21 @@
"license": "Trwydded",
"lightning_error_lndhub_uri": "URI LNDhub annilys",
"lightning_error_lndhub_uri_tor": "URI LNDhub annilys. Sicrha bod yr ap Orbot wedi'i gysylltu a thria eto.",
"lightning_saved": "Mae dy newidiadau wedi cael eu safio'n llwyddiannus.",
"lightning_saved": "Mae dy newidiadau wedi cael eu safio'n llwyddianus.",
"lightning_settings": "Gosodiadau Mellten",
"lightning_settings_explain": "I gysylltu â'th nod LND dy hun, gosoda LNDhub a rho ei URL yma yn y gosodiadau. Sylwer mai dim ond waledi a grëwyd ar ôl safio'r newidiadau fydd yn cysylltu â'r LNDhub penodol.",
"lightning_settings_explain": "I gysylltu â'th nôd LND dy hun, gosoda LNDhub a rho ei URL yma yn y gosodiadau. Sylwer mai dim ond waledi a grëwyd ar ôl safio'r newidiadau fydd yn cysylltu â'r LNDhub penodol.",
"lndhub_github": "Storfa GitHub",
"network": "Rhwydwaith",
"network_broadcast": "Darlledu Trafodyn",
"network_electrum": "Gweinydd Electrum",
"electrum_suggested_description": "Pan na fydd gweinydd dewisol wedi'i osod, dewisir gweinydd awgrymedig ar hap.",
"not_a_valid_uri": "URI annilys",
"notifications": "Hysbysiadau",
"notifications": "Hysbysebion",
"open_link_in_explorer": "Agor dolen yn yr archwiliwr",
"password": "Cyfrinair",
"password_explain": "Mewnosod y cyfrinair y byddi'n ei ddefnyddio i ddatgloi dy storfa.",
"plausible_deniability": "Gwadu credadwy",
"privacy": "Preifatrwydd",
"privacy": "Prefiatrwydd",
"privacy_read_clipboard": "Darllen Clipfwrdd",
"privacy_system_settings": "Gosodiadau System",
"privacy_quickactions": "Llwybrau Byr Waled",
@ -389,6 +389,7 @@
"details_explorer": "archwiliwr",
"details_network_fee": "Ffi Rhwydwaith",
"details_to_address": "At",
"details_id": "ID",
"details_note": "Nodyn",
"details_add_note": "adio",
"details_advanced": "Arbenigol",
@ -398,11 +399,11 @@
"details_tx_hex": "Hex y Trafodyn",
"details_inputs_count": "Mewnbynau ({count})",
"details_outputs_count": "Allbynau ({count})",
"details_id": "ID"
"details_from": "Mewnbwn"
},
"wallets": {
"add_bitcoin": "Bitcoin",
"add_bitcoin_explain": "Waled Bitcoin syml a chryf",
"add_bitcoin_explain": "Waled Bitcoin syml a cryf",
"add_create": "Creu",
"total_balance": "Balans Llawn",
"add_entropy_reset_title": "Ailosod Entropi",
@ -416,13 +417,13 @@
"add_lightning": "Mellten",
"add_lightning_explain": "Er mwyn gwario yn syth",
"add_lndhub": "Cysylltu â'th LNDhub",
"add_lndhub_error": "Mae cyfeiriad y nod a ddarparwyd yn nod LNDhub annilys.",
"add_lndhub_placeholder": "Cyfeiriad dy Nod",
"add_lndhub_error": "Mae cyfeiriad y nôd a ddarparwyd yn nôd LNDhub annilys.",
"add_lndhub_placeholder": "Cyfeiriad dy Nôd",
"add_placeholder": "fy waled gyntaf",
"add_title": "Adio waled",
"add_wallet_name": "Enw",
"add_wallet_type": "Math",
"add_wallet_seed_length": "Hyd y Seed",
"add_wallet_seed_length": "Hyd yr Hadyn",
"add_wallet_seed_length_12": "12 gair",
"add_wallet_seed_length_24": "24 gair",
"clipboard_bitcoin": "Mae gen ti gyfeiriad Bitcoin ar dy glipfwrdd. Wyt ti isio ei ddefnyddio ar gyfer trafodyn?",
@ -430,7 +431,7 @@
"clear_clipboard_on_import": "Clirio'r clipfwrdd ar ôl mewnforio",
"details_address": "Cyfeiriad",
"details_advanced": "Arbenigol",
"details_are_you_sure": "Wyt ti'n siŵr?",
"details_are_you_sure": "Wyt ti'n siwr?",
"details_connected_to": "Wedi cysylltu efo",
"details_del_wb_err": "Nid yw'r swm balans a ddarparwyd yn cyfateb i falans y waled hon. Tria eto.",
"details_del_wb_q": "Mae gan y waled hon falans. Cyn parhau, sylwer na fyddi'n gallu adfer y cyllid heb ymadrodd hadyn y waled hon. Er mwyn osgoi gwaredu damweiniol, mewnosod balans dy waled o {balance} satoshis.",
@ -453,7 +454,7 @@
"details_type": "Math",
"details_use_with_hardware_wallet": "Defnyddio gyda Waled Caledwedd",
"details_yes_delete": "Ia, dileu",
"enter_bip38_password": "Angen cyfrinair i ddadgryptio",
"enter_bip38_password": "Angen cyfrinair i ddad-gryptio",
"export_title": "Waled Allfor",
"import_do_import": "Mewnforio",
"import_passphrase": "Geiriau pas",
@ -463,7 +464,7 @@
"import_explanation": "Mewnosod dy eiriau hadyn, allwedd gyhoeddus, WIF, neu beth bynnag sydd gennyt. Bydd BlueWallet yn gwneud ei orau i ddyfalu'r fformat cywir a mewnforio dy waled.",
"import_imported": "Mewnforwyd",
"import_scan_qr": "Sganio neu fewnforio ffeil",
"import_success": "Mae dy waled wedi cael ei mewnforio'n llwyddiannus.",
"import_success": "Mae dy waled wedi cael ei fewnforio'n llwyddiannus.",
"import_success_watchonly": "Mae dy waled wedi'i mewnforio'n llwyddiannus. RHYBUDD: Waled gwylio'n unig yw hon, NI elli wario ohoni.",
"import_search_accounts": "Chwilio cyfrifon",
"import_title": "Mewnforio",
@ -517,7 +518,8 @@
"manage_wallets_search_placeholder": "Chwilio waledi, cyfeiriadau, trafodion a nodion",
"more_info": "Mwy o Wybodaeth",
"details_delete_wallet_error_message": "Cafwyd problem wrth gadarnhau a oedd y waled hon wedi'i gwaredu o'r hysbysiadau—gallai hyn fod oherwydd problem rhwydwaith neu gysylltiad gwael. Os byddi'n parhau, gallet dal i dderbyn hysbysiadau am drafodion sy'n gysylltiedig â'r waled hon, hyd yn oed ar ôl ei dileu.",
"details_delete_anyway": "Dileu beth bynnag"
"details_delete_anyway": "Dileu beth bynnag",
"xpub_copiedToClipboard": "Wedi gopio i'r clipfwrdd."
},
"total_balance_view": {
"display_in_bitcoin": "Dangos mewn Bitcoin",
@ -635,9 +637,9 @@
"sort_desc": "Disgynnol",
"sort_height": "Uchder",
"sort_value": "Gwerth",
"sort_label": "Label",
"sort_status": "Statws",
"sort_by": "Trefnu yn ôl",
"sort_label": "Label"
"sort_by": "Trefnu yn ôl"
},
"units": {
"BTC": "BTC",

View File

@ -18,7 +18,7 @@
"no": "Nej",
"save": "Gem...",
"seed": "Seed",
"wallet_key": "Wallet-nøgle",
"wallet_key": "Tegnebogsnøgle",
"close": "Luk",
"change_input_currency": "Skift inputvaluta",
"refresh": "Opdater",
@ -33,22 +33,22 @@
},
"azteco": {
"success": "Succes",
"codeIs": "Din voucher-kode er",
"errorBeforeRefeem": "Før du indløser, skal du først tilføje en Bitcoin-wallet.",
"codeIs": "Din voucherkode er",
"errorBeforeRefeem": "Før du indløser, skal du først tilføje en Bitcoin-tegnebog.",
"errorSomething": "Noget gik galt. Er denne voucher stadig gyldig?",
"redeem": "Indløs til wallet",
"redeem": "Indløs til tegnebog",
"redeemButton": "Indløs",
"successMessage": "Voucher indløst! Dine midler bør ankomme i din Bitcoin-wallet inden længe.",
"successMessage": "Voucher indløst! Dine midler bør ankomme i din Bitcoin-tegnebog inden længe.",
"title": "Indløs Azte.co-voucher"
},
"entropy": {
"save": "Gem",
"save": "save",
"title": "Entropi",
"undo": "Fortryd",
"amountOfEntropy": "{bits} af {limit} bit"
},
"errors": {
"broadcast": "Transmission mislykkedes.",
"broadcast": "Transmittering mislykkedes.",
"error": "Fejl",
"network": "Netværksfejl"
},
@ -63,9 +63,9 @@
"payment": "Betaling",
"placeholder": "Faktura eller adresse",
"potentialFee": "Potentielt gebyr: {fee}",
"refill_create": "For at fortsætte skal du oprette en Bitcoin-wallet at genopfylde med.",
"refill_external": "Genopfyld med ekstern wallet",
"sameWalletAsInvoiceError": "Du kan ikke betale en faktura med den samme wallet, som blev brugt til at oprette den."
"refill_create": "For at fortsætte skal du oprette en Bitcoin-tegnebog at genopfylde med.",
"refill_external": "Genopfyld med ekstern tegnebog",
"sameWalletAsInvoiceError": "Du kan ikke betale en faktura med den samme tegnebog, som blev brugt til at oprette den."
},
"lndViewInvoice": {
"additional_info": "Yderligere information",
@ -73,28 +73,28 @@
"lightning_invoice": "Lightning-faktura",
"please_pay_between_and": "Betal venligst mellem {min} og {max}",
"please_pay": "Betal venligst",
"preimage": "Pre-image",
"preimage": "Preimage",
"sats": "sats.",
"date_time": "Dato og tid",
"wasnt_paid_and_expired": "Denne faktura blev ikke betalt og er udløbet."
},
"plausibledeniability": {
"create_fake_storage": "Opret falsk krypteret lager",
"create_fake_storage": "Opret falsk kryopteret lager",
"create_password_explanation": "Adgangskoden til det falske lager må ikke være den samme som den du bruger til det rigtige lager",
"help": "Under visse omstændigheder kan du blive tvunget til at give din adgangskode. For at beskytte dine coins kan du med BlueWallet lave et falsk krypteret lager med en anden kode. I en presset situation kan du give denne adgangskode i stedet. Hvis denne kode indtastes i BlueWallet, vil brugeren se den alternative wallet. Det vil se helt legitimt ud for andre, og dermed beskytte din originale wallet og dine coins.",
"help2": "Det nye lager vil være fuldt funktionsdygtigt, og du kan eventuelt have nogle småbeløb, så det ser troværdigt ud.",
"help": "Under visse omstændighder, kan du blive tvunget til at give din adgangskode. For at beskytte dine coins kan du med Bluewallet lave et falsk krypteret lager, med en anden kode. I en presset situation, kan du give denne adgangskode istedet. Hvis denne kode indtastes i BlueWallet, vil brugeren se den alternative wallet. Det vil se heltlegitimt ud for andre, og dermed beskytte din originale wallet og dine coins.",
"help2": "Det nye lager vil være fuldt funktionsdygtigt, og du kan evt have nogle småbeløb så det ser troværdigt ud.",
"password_should_not_match": "Adgangskoden til det falske lager må ikke være den samme som den du bruger til det rigtige lager",
"title": "Sandsynlig benægtelse"
},
"pleasebackup": {
"ask": "Har du gemt din wallets seed-frase? Denne seed-frase er nødvendig for at få adgang til dine midler, hvis du mister denne enhed. Uden seed-frasen vil dine midler være permanent tabt.",
"ask": "Har du gemt din tegnebogs gendannelsesfrase? Denne gendannelsesfrase er nødvendig for at få adgang til dine midler, hvis du mister denne enhed. Uden gendannelsesfrasen vil dine midler være permanent tabt.",
"ask_no": "Nej, det har jeg ikke.",
"ask_yes": "Ja, det har jeg.",
"ok": "OK, jeg har skrevet det ned.",
"ok_lnd": "OK, jeg har gemt den.",
"text": "Tag dig venligst et øjeblik til at skrive denne mnemonic ned på et stykke papir.\nDet er din backup, og du kan bruge den til at gendanne wallet'en.",
"text_lnd": "Gem venligst denne wallet-backup. Den giver dig mulighed for at gendanne wallet'en i tilfælde af tab.",
"title": "Din wallet er blevet oprettet."
"text": "Tag dig venligst et øjeblik til at skrive denne mnemoniske frase ned på et stykke papir.\nDet er din sikkerhedskopi, og du kan bruge den til at gendanne tegnebogen.",
"text_lnd": "Gem venligst denne tegnebogs-sikkerhedskopi. Den giver dig mulighed for at gendanne tegnebogen i tilfælde af tab.",
"title": "Din tegnebog er blevet oprettet."
},
"receive": {
"details_create": "Opret",
@ -102,14 +102,14 @@
"details_setAmount": "Modtag med beløb",
"header": "Modtag",
"details_share": "Del...",
"address_not_found": "Kunne ikke generere modtageradresse.",
"address_not_found": "Kunne ikke generere modtageadresse.",
"reset": "Nulstil",
"maxSats": "Maksimalt beløb er {max} sats",
"maxSatsFull": "Maksimalt beløb er {max} sats eller {currency}",
"minSats": "Minimumsbeløb er {min} sats",
"minSatsFull": "Minimumsbeløb er {min} sats eller {currency}",
"qrcode_for_the_address": "QR-kode for adressen",
"bip47_explanation": "Payment Codes er en universel adresse, der undgår at afsløre dine wallet-adresser. Ikke alle tjenester understøtter dem."
"bip47_explanation": "Betalingskoder er en universel adresse, der undgår at afsløre dine tegnebogsadresser. Ikke alle tjenester understøtter dem."
},
"send": {
"broadcastButton": "Transmitter",
@ -124,22 +124,21 @@
"create_this_is_hex": "Dette er transaktion hex, klar til at sende ud til netværket.",
"create_to": "Til",
"create_tx_size": "TX størrelse",
"details_address": "Adresse",
"details_address_field_is_not_valid": "Adressen er ikke gyldig.",
"details_amount_field_is_not_valid": "Beløbet er ikke gyldigt.",
"details_address": "adresse",
"details_address_field_is_not_valid": "Adresse felt er ikke gyldigt",
"details_amount_field_is_not_valid": "Beløbsfeltet er ikke gyldigt",
"details_create": "Opret",
"details_fee_field_is_not_valid": "Gebyret er ikke gyldigt.",
"details_fee_field_is_not_valid": "Gebyr feltet er ikke gyldigt",
"details_note_placeholder": "Notat til eget brug",
"details_scan": "Scan",
"details_total_exceeds_balance": "Beløbet du prøver at sende er større end din kontosaldo.",
"input_done": "Udført",
"success_done": "Udført",
"provided_address_is_invoice": "Denne adresse ser ud til at være en Lightning-faktura. Gå venligst til din Lightning-wallet for at betale denne faktura.",
"provided_address_is_invoice": "Denne adresse ser ud til at være en Lightning-faktura. Gå venligst til din Lightning-tegnebog for at betale denne faktura.",
"broadcastError": "Fejl",
"broadcastNone": "Indsæt transaktions-hex",
"broadcastPending": "Afventende",
"create_copy": "Kopier og transmitter senere",
"create_satoshi_per_vbyte": "Satoshi pr. vByte",
"create_satoshi_per_vbyte": "Satoshi per vByte",
"create_verify": "Verificer på coinb.in",
"details_insert_contact": "Indsæt kontakt",
"details_add_rec_add": "Tilføj modtager",
@ -152,8 +151,8 @@
"please_complete_recipient_details": "Udfyld venligst detaljerne for modtager #{number}, før du tilføjer en ny modtager.",
"details_adv_fee_bump": "Tillad gebyrforøgelse",
"details_adv_full": "Brug fuld saldo",
"details_adv_full_sure": "Er du sikker på, at du vil bruge din wallets fulde saldo til denne transaktion?",
"details_adv_full_sure_frozen": "Er du sikker på, at du vil bruge din wallets fulde saldo til denne transaktion? Bemærk, at frosne coins er udelukket.",
"details_adv_full_sure": "Er du sikker på, at du vil bruge din tegnebogs fulde saldo til denne transaktion?",
"details_adv_full_sure_frozen": "Er du sikker på, at du vil bruge din tegnebogs fulde saldo til denne transaktion? Bemærk, at frosne mønter er udelukket.",
"details_adv_import": "Importér transaktion",
"details_adv_import_qr": "Importér transaktion (QR)",
"details_amount_field_is_less_than_minimum_amount_sat": "Det angivne beløb er for lille. Indtast venligst et beløb større end 500 sats.",
@ -161,11 +160,12 @@
"details_frozen": "{amount} BTC er frosset.",
"details_next": "Næste",
"details_no_signed_tx": "Den valgte fil indeholder ikke en transaktion, der kan importeres.",
"details_scan": "Scan",
"details_scan_hint": "Dobbelttryk for at scanne eller importere en destination",
"details_scan_error": "Scanningsfejl",
"details_total_exceeds_balance_frozen": "Sendebeløbet overstiger den tilgængelige saldo. Bemærk, at frosne coins er udelukket.",
"details_total_exceeds_balance_frozen": "Sendebeløbet overstiger den tilgængelige saldo. Bemærk, at frosne mønter er udelukket.",
"details_unrecognized_file_format": "Filformat ikke genkendt",
"details_wallet_before_tx": "Før du opretter en transaktion, skal du først tilføje en Bitcoin-wallet.",
"details_wallet_before_tx": "Før du opretter en transaktion, skal du først tilføje en Bitcoin-tegnebog.",
"dynamic_init": "Initialiserer",
"dynamic_next": "Næste",
"dynamic_prev": "Forrige",
@ -186,39 +186,39 @@
"input_paste": "Indsæt",
"input_total": "I alt:",
"permission_camera_message": "Vi har brug for din tilladelse til at bruge dit kamera.",
"psbt_sign": "Signer en transaktion",
"psbt_sign": "Underskriv en transaktion",
"invalid_psbt": "Ugyldig PSBT angivet.",
"open_settings": "Åbn indstillinger",
"permission_storage_denied_message": "BlueWallet kan ikke gemme denne fil. Åbn venligst dine enhedsindstillinger og aktivér lagertilladelse.",
"permission_storage_title": "Tilladelse til lageradgang",
"psbt_clipboard": "Kopier til udklipsholder",
"psbt_this_is_psbt": "Dette er en Partially Signed Bitcoin Transaction (PSBT). Færdiggør venligst signeringen med din hardware wallet.",
"psbt_this_is_psbt": "Dette er en delvist underskrevet Bitcoin-transaktion (PSBT). Færdiggør venligst underskrivelsen med din hardwaretegnebog.",
"psbt_tx_export": "Eksportér til fil",
"no_tx_signing_in_progress": "Der er ingen transaktionssignering i gang.",
"no_tx_signing_in_progress": "Der er ingen transaktionsunderskrivelse i gang.",
"outdated_rate": "Kurs blev sidst opdateret: {date}",
"psbt_tx_open": "Åbn signeret transaktion",
"psbt_tx_scan": "Scan signeret transaktion",
"psbt_tx_open": "Åbn underskrevet transaktion",
"psbt_tx_scan": "Scan underskrevet transaktion",
"qr_error_no_qrcode": "Vi kunne ikke finde en gyldig QR-kode i det valgte billede. Sørg for, at billedet kun indeholder en QR-kode og intet yderligere indhold såsom tekst eller knapper.",
"reset_amount": "Nulstil beløb",
"reset_amount_confirm": "Vil du nulstille beløbet?",
"txSaved": "Transaktionsfilen ({filePath}) er blevet gemt.",
"file_saved_at_path": "Filen ({filePath}) er blevet gemt.",
"cant_send_to_silentpayment_adress": "Denne wallet kan ikke sende til Silent Payments-adresser",
"cant_send_to_bip47": "Denne wallet kan ikke sende til BIP47 Payment Codes",
"cant_find_bip47_notification": "Tilføj denne Payment Code til kontakter først",
"cant_send_to_silentpayment_adress": "Denne tegnebog kan ikke sende til Silent Payments-adresser",
"cant_send_to_bip47": "Denne tegnebog kan ikke sende til BIP47-betalingskoder",
"cant_find_bip47_notification": "Tilføj denne betalingskode til kontakter først",
"problem_with_psbt": "Problem med PSBT"
},
"settings": {
"about": "Andet",
"currency": "Valuta",
"header": "Indstillinger",
"header": "indstillinger",
"language": "Sprog",
"lightning_settings": "Lightning-indstillinger",
"lightning_settings": "Lightning settings",
"password": "Adgangskode",
"plausible_deniability": "Sandsynlig benægtelse...",
"save": "Gem",
"about_awesome": "Bygget med det fantastiske",
"about_backup": "Tag altid backup af dine nøgler!",
"save": "save",
"about_awesome": "Bygget med den fantastiske",
"about_backup": "Sikkerhedskopier altid dine nøgler!",
"about_free": "BlueWallet er et gratis og open source-projekt. Lavet af Bitcoin-brugere.",
"about_license": "MIT-licens",
"about_release_notes": "Versionsnoter",
@ -228,17 +228,17 @@
"about_selftest": "Kør selvtest",
"block_explorer_invalid_custom_url": "Den angivne URL er ugyldig. Indtast venligst en gyldig URL, der starter med http:// eller https://.",
"about_selftest_electrum_disabled": "Selvtest er ikke tilgængelig med Electrum offline-tilstand. Deaktiver venligst offline-tilstand og prøv igen.",
"about_selftest_ok": "Alle interne tests er bestået. Wallet'en fungerer fint.",
"about_selftest_ok": "Alle interne tests er bestået. Tegnebogen fungerer fint.",
"about_sm_github": "GitHub",
"about_sm_telegram": "Telegram-kanal",
"privacy_temporary_screenshots": "Tillad skærmoptagelse",
"privacy_temporary_screenshots_instructions": "Beskyttelse mod skærmoptagelse vil midlertidigt blive slået fra, hvilket tillader skærmbilleder og skærmoptagelser. Beskyttelsen genaktiveres automatisk, når du lukker og genåbner BlueWallet.",
"biometrics": "Biometri",
"biometrics_no_longer_available": "Dine enhedsindstillinger er ændret og matcher ikke længere de valgte sikkerhedsindstillinger i appen. Genaktivér venligst biometri eller kode, og genstart derefter appen for at anvende disse ændringer.",
"biom_10times": "Du har forsøgt at indtaste din adgangskode 10 gange. Vil du nulstille dit lager? Dette vil fjerne alle wallets og dekryptere dit lager.",
"biom_10times": "Du har forsøgt at indtaste din adgangskode 10 gange. Vil du nulstille dit lager? Dette vil fjerne alle tegnebøger og dekryptere dit lager.",
"biom_conf_identity": "Bekræft venligst din identitet.",
"biom_no_passcode": "Din enhed har ikke en kode eller biometri aktiveret. For at fortsætte skal du konfigurere en kode eller biometri i indstillingsappen.",
"biom_remove_decrypt": "Alle dine wallets vil blive fjernet, og dit lager vil blive dekrypteret. Er du sikker på, at du vil fortsætte?",
"biom_remove_decrypt": "Alle dine tegnebøger vil blive fjernet, og dit lager vil blive dekrypteret. Er du sikker på, at du vil fortsætte?",
"currency_source": "Kursen hentes fra",
"currency_fetch_error": "Der opstod en fejl under hentning af kursen for den valgte valuta.",
"default_title": "Ved opstart",
@ -251,7 +251,7 @@
"lndhub_uri": "F.eks. {example}",
"electrum_host": "F.eks. {example}",
"electrum_offline_mode": "Offline-tilstand",
"electrum_offline_description": "Når aktiveret, vil dine Bitcoin-wallets ikke forsøge at hente saldi eller transaktioner.",
"electrum_offline_description": "Når aktiveret, vil dine Bitcoin-tegnebøger ikke forsøge at hente saldi eller transaktioner.",
"electrum_port": "Port, normalt {example}",
"use_ssl": "Brug SSL",
"electrum_saved": "Dine ændringer er blevet gemt. Genstart af BlueWallet kan være nødvendig, for at ændringerne træder i kraft.",
@ -260,34 +260,34 @@
"electrum_settings_server": "Electrum-server",
"electrum_status": "Status",
"electrum_preferred_server": "Foretrukken server",
"electrum_preferred_server_description": "Indtast den server, du vil have din wallet til at bruge til alle Bitcoin-aktiviteter. Når den er indstillet, vil din wallet udelukkende bruge denne server til at tjekke saldi, sende transaktioner og hente netværksdata. Sørg for, at du har tillid til denne server, før du indstiller den.",
"electrum_preferred_server_description": "Indtast den server, du vil have din tegnebog til at bruge til alle Bitcoin-aktiviteter. Når den er indstillet, vil din tegnebog udelukkende bruge denne server til at tjekke saldi, sende transaktioner og hente netværksdata. Sørg for, at du har tillid til denne server, før du indstiller den.",
"electrum_unable_to_connect": "Kan ikke oprette forbindelse til {server}.",
"electrum_history": "Historik",
"electrum_reset_to_default": "Dette vil lade BlueWallet vælge en server tilfældigt fra serverlisten.",
"electrum_reset": "Nulstil til standard",
"electrum_reset_to_default_and_clear_history": "Nulstil til standard og ryd historik",
"encrypt_decrypt": "Dekrypter lager",
"encrypt_decrypt_q": "Er du sikker på, at du vil dekryptere dit lager? Dette vil tillade adgang til dine wallets uden en adgangskode.",
"encrypt_decrypt_q": "Er du sikker på, at du vil dekryptere dit lager? Dette vil tillade adgang til dine tegnebøger uden en adgangskode.",
"encrypt_enc_and_pass": "Adgangskodebeskyttet",
"encrypt_storage_explanation_headline": "Aktivér lagerkryptering",
"encrypt_storage_explanation_description_line1": "Aktivering af lagerkryptering tilføjer et ekstra lag af beskyttelse til din app ved at sikre den måde, dine data lagres på din enhed. Dette gør det sværere for nogen at få adgang til dine oplysninger uden tilladelse.",
"encrypt_storage_explanation_description_line2": "Det er dog vigtigt at vide, at denne kryptering kun beskytter adgangen til dine wallets, der er gemt i enhedens keychain. Den sætter ikke en adgangskode eller ekstra beskyttelse på selve wallets.",
"encrypt_storage_explanation_description_line2": "Det er dog vigtigt at vide, at denne kryptering kun beskytter adgangen til dine tegnebøger, der er gemt i enhedens nøglering. Den sætter ikke en adgangskode eller ekstra beskyttelse på selve tegnebøgerne.",
"i_understand": "Jeg forstår",
"block_explorer": "Block Explorer",
"block_explorer_preferred": "Brug foretrukken block explorer",
"block_explorer_error_saving_custom": "Fejl ved lagring af foretrukken block explorer",
"block_explorer": "Blokudforsker",
"block_explorer_preferred": "Brug foretrukken blokudforsker",
"block_explorer_error_saving_custom": "Fejl ved lagring af foretrukken blokudforsker",
"encrypt_title": "Sikkerhed",
"encrypt_tstorage": "Lager",
"encrypt_use": "Brug {type}",
"set_as_preferred": "Indstil som foretrukken",
"set_as_preferred_electrum": "Indstilling af {host}:{port} som foretrukken server vil deaktivere tilfældig forbindelse til en foreslået server.",
"encrypted_feature_disabled": "Denne funktion kan ikke bruges med krypteret lager aktiveret.",
"encrypt_use_expl": "{type} vil blive brugt til at bekræfte din identitet, før du foretager en transaktion, låser op, eksporterer eller sletter en wallet.",
"encrypt_use_expl": "{type} vil blive brugt til at bekræfte din identitet, før du foretager en transaktion, låser op, eksporterer eller sletter en tegnebog.",
"biometrics_fail": "Hvis {type} ikke er aktiveret eller ikke kan låse op, kan du bruge din enheds kode som alternativ.",
"general": "Generelt",
"general_continuity": "Kontinuitet",
"general_continuity_e": "Når aktiveret, vil du kunne se udvalgte wallets og transaktioner ved hjælp af dine andre Apple iCloud-tilsluttede enheder.",
"groundcontrol_explanation": "GroundControl er en gratis, open source push notifikationsserver til Bitcoin-wallets. Du kan installere din egen GroundControl-server og indsætte dens URL her for ikke at være afhængig af BlueWallets infrastruktur. Lad være tom for at bruge GroundControls standardserver.",
"general_continuity_e": "Når aktiveret, vil du kunne se udvalgte tegnebøger og transaktioner ved hjælp af dine andre Apple iCloud-tilsluttede enheder.",
"groundcontrol_explanation": "GroundControl er en gratis, open source push notifikationsserver til Bitcoin-tegnebøger. Du kan installere din egen GroundControl-server og indsætte dens URL her for ikke at være afhængig af BlueWallets infrastruktur. Lad være tom for at bruge GroundControls standardserver.",
"last_updated": "Sidst opdateret",
"language_isRTL": "Genstart af BlueWallet er nødvendig, for at sprogretningen træder i kraft.",
"license": "Licens",
@ -302,23 +302,23 @@
"electrum_suggested_description": "Når en foretrukken server ikke er indstillet, vil en foreslået server blive valgt til brug tilfældigt.",
"not_a_valid_uri": "Ugyldig URI",
"notifications": "Notifikationer",
"open_link_in_explorer": "Åbn link i explorer",
"open_link_in_explorer": "Åbn link i udforsker",
"password_explain": "Indtast den adgangskode, du vil bruge til at låse dit lager op.",
"privacy": "Privatliv",
"privacy_read_clipboard": "Læs udklipsholder",
"privacy_system_settings": "Systemindstillinger",
"privacy_quickactions": "Wallet-genveje",
"privacy_quickactions_explanation": "Tryk og hold på BlueWallet-appikonet for hurtigt at se din wallets saldo.",
"privacy_quickactions": "Tegnebogsgenveje",
"privacy_quickactions_explanation": "Tryk og hold på BlueWallet-appikonet for hurtigt at se din tegnebogs saldo.",
"privacy_clipboard_explanation": "Tilbyd genveje, hvis en adresse eller faktura findes i din udklipsholder.",
"privacy_do_not_track": "Deaktiver analyse",
"privacy_do_not_track_explanation": "Oplysninger om ydeevne og pålidelighed vil ikke blive indsendt til analyse.",
"rate": "Kurs",
"push_notifications_explanation": "Ved at aktivere notifikationer vil dit enhedstoken blive sendt til serveren sammen med wallet-adresser og transaktions-id'er for alle wallets og transaktioner foretaget efter aktivering af notifikationer. Enhedstokenet bruges til at sende notifikationer, og wallet-informationen tillader os at give dig besked om indgående Bitcoin eller transaktionsbekræftelser.\n\nKun oplysninger fra efter du aktiverer notifikationer overføres—intet fra før indsamles.\n\nDeaktivering af notifikationer vil fjerne alle disse oplysninger fra serveren. Derudover vil sletning af en wallet fra appen også fjerne dens tilknyttede oplysninger fra serveren.",
"push_notifications_explanation": "Ved at aktivere notifikationer vil dit enhedstoken blive sendt til serveren sammen med tegnebogsadresser og transaktions-id'er for alle tegnebøger og transaktioner foretaget efter aktivering af notifikationer. Enhedstokenet bruges til at sende notifikationer, og tegnebogsinformationen tillader os at give dig besked om indgående Bitcoin eller transaktionsbekræftelser.\n\nKun oplysninger fra efter du aktiverer notifikationer overføres—intet fra før indsamles.\n\nDeaktivering af notifikationer vil fjerne alle disse oplysninger fra serveren. Derudover vil sletning af en tegnebog fra appen også fjerne dens tilknyttede oplysninger fra serveren.",
"selfTest": "Selvtest",
"saved": "Gemt",
"success_transaction_broadcasted": "Din transaktion er blevet transmitteret!",
"total_balance": "Samlet saldo",
"total_balance_explanation": "Vis den samlede saldo for alle dine wallets på din startskærms-widgets.",
"total_balance_explanation": "Vis den samlede saldo for alle dine tegnebøger på din startskærms widgets.",
"widgets": "Widgets",
"tools": "Værktøjer"
},
@ -331,9 +331,10 @@
"transactions": {
"cpfp_create": "Opret",
"details_copy": "Kopier",
"details_from": "Fra",
"details_title": "Transaktion",
"details_to": "Til",
"list_title": "Transaktioner",
"list_title": "transaktioner",
"transaction": "Transaktion",
"cancel_explain": "Vi vil erstatte denne transaktion med en, der betaler dig og har højere gebyrer. Dette annullerer effektivt den aktuelle transaktion. Dette kaldes RBF—Replace by Fee.",
"cancel_no": "Denne transaktion kan ikke erstattes.",
@ -347,7 +348,7 @@
"cpfp_title": "Forøg gebyr (CPFP)",
"details_balance_hide": "Skjul saldo",
"details_balance_show": "Vis saldo",
"details_copy_block_explorer_link": "Kopier block explorer-link",
"details_copy_block_explorer_link": "Kopier blokudforsker-link",
"details_copy_note": "Kopier note",
"details_copy_txid": "Kopier transaktions-ID",
"details_inputs": "Input",
@ -359,10 +360,10 @@
"outgoing_transaction": "Udgående transaktion",
"expired_transaction": "Udløbet transaktion",
"pending_transaction": "Afventende transaktion",
"offchain": "Offchain",
"onchain": "Onchain",
"enable_offline_signing": "Denne wallet bruges ikke sammen med offline-signering. Vil du aktivere det nu?",
"list_conf": "Bekr.: {number}",
"offchain": "Off-chain",
"onchain": "On-chain",
"enable_offline_signing": "Denne tegnebog bruges ikke sammen med offline-underskrivelse. Vil du aktivere det nu?",
"list_conf": "Bekr: {number}",
"pending": "Afventende",
"pending_with_amount": "Afventende {amt1} ({amt2})",
"received_with_amount": "+{amt1} ({amt2})",
@ -380,13 +381,13 @@
"txid": "Transaktions-ID",
"updating": "Opdaterer...",
"watchOnlyWarningTitle": "Sikkerhedsadvarsel",
"watchOnlyWarningDescription": "Vær forsigtig med svindlere, som ofte bruger watch-only wallets til at narre brugere. Disse wallets giver dig ikke mulighed for at kontrollere eller sende midler; de lader dig kun se saldoen.",
"watchOnlyWarningDescription": "Vær forsigtig med svindlere, som ofte bruger \"kun-kigge\"-tegnebøger til at narre brugere. Disse tegnebøger giver dig ikke mulighed for at kontrollere eller sende midler; de lader dig kun se saldoen.",
"custom_fee_warning_title": "Advarsel",
"custom_fee_warning_description": "Gebyrer under 1 sat/vB er gyldige, men videresendes muligvis ikke på grund af nodepolitikker.",
"details_eta_analyzing": "Analyserer...",
"details_sent": "Sendt",
"details_section": "Detaljer",
"details_explorer": "explorer",
"details_explorer": "udforsker",
"details_network_fee": "Netværksgebyr",
"details_to_address": "Til",
"details_id": "ID",
@ -401,38 +402,34 @@
"details_outputs_count": "Output ({count})"
},
"wallets": {
"add_bitcoin_explain": "Enkel og kraftfuld Bitcoin-wallet",
"add_bitcoin_explain": "Simple and powerful Bitcoin wallet",
"add_create": "Opret",
"add_import_wallet": "Importer wallet",
"add_title": "Tilføj wallet",
"add_wallet_name": "Navn",
"add_wallet_type": "Type",
"add_wallet_name": "wallet navn",
"details_address": "Adresse",
"details_are_you_sure": "Er du sikker?",
"details_delete": "Slet",
"details_export_backup": "Eksporter / backup",
"details_show_xpub": "Vis wallet XPUB",
"details_title": "Wallet",
"wallets": "Wallets",
"details_type": "Type",
"details_yes_delete": "Ja, slet",
"export_title": "Wallet-eksport",
"export_title": "wallet eksport",
"import_do_import": "Importer",
"import_error": "Importen lykkedes ikke. Er det en gyldig nøgle?",
"import_imported": "Importeret",
"import_scan_qr": "Scan eller importer en fil",
"import_scan_qr": "eller scan QR kode istedet?",
"import_success": "Succes",
"import_title": "Importer",
"import_title": "importer",
"list_create_a_button": "Tilføj nu",
"list_create_a_wallet": "Tilføj en wallet",
"list_title": "Wallets",
"list_create_a_wallet": "Tilføj en tegnebog",
"list_empty_txs1": "Dine transaktioner vil blive vist her,",
"list_latest_transaction": "seneste transaktion",
"select_wallet": "Vælg wallet",
"xpub_copiedToClipboard": "Kopieret til udklipsholder.",
"add_bitcoin": "Bitcoin",
"total_balance": "Samlet saldo",
"add_entropy_reset_title": "Nulstil entropi",
"add_entropy_reset_message": "Ændring af wallet-typen vil nulstille den aktuelle entropi. Vil du fortsætte?",
"add_entropy_reset_message": "Ændring af tegnebogstypen vil nulstille den aktuelle entropi. Vil du fortsætte?",
"add_entropy": "Entropi",
"add_entropy_bytes": "{bytes} bytes entropi",
"add_entropy_generated": "{gen} bytes genereret entropi",
@ -443,7 +440,8 @@
"add_lndhub": "Forbind til din LNDhub",
"add_lndhub_error": "Den angivne nodeadresse er en ugyldig LNDhub-node.",
"add_lndhub_placeholder": "Din nodeadresse",
"add_placeholder": "min første wallet",
"add_placeholder": "min første tegnebog",
"add_wallet_type": "Type",
"add_wallet_seed_length": "Seed-længde",
"add_wallet_seed_length_12": "12 ord",
"add_wallet_seed_length_24": "24 ord",
@ -452,71 +450,75 @@
"clear_clipboard_on_import": "Ryd udklipsholder ved import",
"details_advanced": "Avanceret",
"details_connected_to": "Forbundet til",
"details_del_wb_err": "Det angivne saldobeløb matcher ikke denne wallets saldo. Prøv venligst igen.",
"details_del_wb_q": "Denne wallet har en saldo. Før du fortsætter, skal du være opmærksom på, at du ikke vil være i stand til at genskabe midlerne uden denne wallets seed-frase. For at undgå utilsigtet fjernelse skal du indtaste din wallets saldo på {balance} satoshis.",
"details_delete_wallet": "Slet wallet",
"details_del_wb_err": "Det angivne saldobeløb matcher ikke denne tegnebogs saldo. Prøv venligst igen.",
"details_del_wb_q": "Denne tegnebog har en saldo. Før du fortsætter, skal du være opmærksom på, at du ikke vil være i stand til at genskabe midlerne uden denne tegnebogs seed-frase. For at undgå utilsigtet fjernelse skal du indtaste din tegnebogs saldo på {balance} satoshis.",
"details_delete_wallet": "Slet tegnebog",
"details_derivation_path": "afledningssti",
"details_display": "Vis på startskærm",
"details_export_history": "Eksportér historik til CSV",
"details_master_fingerprint": "Master fingerprint",
"details_master_fingerprint": "Hovednøgle-fingeraftryk",
"details_multisig_type": "multisig",
"details_show_addresses": "Vis adresser",
"details_title": "Tegnebog",
"wallets": "Tegnebøger",
"swipe_balance_hide": "Skjul",
"swipe_balance_show": "Vis",
"drag_to_reorder": "Træk for at omarrangere",
"clear_search": "Ryd søgning",
"details_use_with_hardware_wallet": "Brug med hardware wallet",
"details_type": "Type",
"details_use_with_hardware_wallet": "Brug med hardwaretegnebog",
"enter_bip38_password": "Indtast adgangskode for at dekryptere",
"import_passphrase": "Adgangsfrase",
"import_passphrase_title": "Adgangsfrase",
"import_passphrase_message": "Indtast adgangsfrase, hvis du har brugt en",
"import_explanation": "Indtast venligst dine seed-ord, offentlige nøgle, WIF eller hvad du har. BlueWallet vil gøre sit bedste for at gætte det korrekte format og importere din wallet.",
"import_success_watchonly": "Din wallet er blevet importeret. ADVARSEL: Dette er en watch-only wallet, du kan IKKE sende fra den.",
"import_explanation": "Indtast venligst dine seed-ord, offentlige nøgle, WIF eller hvad du har. BlueWallet vil gøre sit bedste for at gætte det korrekte format og importere din tegnebog.",
"import_success_watchonly": "Din tegnebog er blevet importeret. ADVARSEL: Dette er en kun-kigge-tegnebog, du kan IKKE sende fra den.",
"import_search_accounts": "Søg konti",
"learn_more": "Lær mere",
"import_discovery_title": "Opdagelse",
"import_discovery_subtitle": "Vælg en opdaget wallet",
"import_discovery_subtitle": "Vælg en opdaget tegnebog",
"import_discovery_derivation": "Brug brugerdefineret afledningssti",
"import_discovery_no_wallets": "Ingen wallets blev fundet.",
"import_discovery_offline": "BlueWallet er i øjeblikket i offline-tilstand. I denne tilstand kan den ikke verificere eksistensen af wallet'en, så du skal vælge den korrekte manuelt",
"import_discovery_no_wallets": "Ingen tegnebøger blev fundet.",
"import_discovery_offline": "BlueWallet er i øjeblikket i offline-tilstand. I denne tilstand kan den ikke verificere eksistensen af tegnebogen, så du skal vælge den korrekte manuelt",
"import_derivation_found": "Fundet",
"import_derivation_found_not": "Ikke fundet",
"import_derivation_loading": "Indlæser...",
"import_derivation_subtitle": "Indtast brugerdefineret afledningssti, og vi vil forsøge at opdage din wallet.",
"import_derivation_subtitle": "Indtast brugerdefineret afledningssti, og vi vil forsøge at opdage din tegnebog.",
"import_derivation_title": "Afledningssti",
"import_derivation_unknown": "Ukendt",
"import_wrong_path": "Forkert afledningssti",
"list_create_a_wallet_text": "Det er gratis, og du kan oprette \nså mange du vil.",
"list_empty_txs1_lightning": "Lightning-wallet bør bruges til dine daglige transaktioner. Gebyrer er uretfærdigt billige, og hastigheden er lynhurtig.",
"list_empty_txs2": "Start med din wallet.",
"list_empty_txs2_lightning": "\nFor at begynde at bruge den skal du trykke på Administrer midler og fylde op på din saldo.",
"list_empty_txs1_lightning": "Lightning-tegnebog bør bruges til dine daglige transaktioner. Gebyrer er uretfærdigt billige, og hastigheden er lynhurtig.",
"list_empty_txs2": "Start med din tegnebog.",
"list_empty_txs2_lightning": "\nFor at begynde at bruge den skal du trykke på Administrer midler og opfylde din saldo.",
"list_long_choose": "Vælg foto",
"paste_from_clipboard": "Indsæt",
"import_file": "Importér fil",
"list_long_scan": "Scan QR-kode",
"list_title": "Tegnebøger",
"list_tryagain": "Prøv igen",
"no_ln_wallet_error": "Før du betaler en Lightning-faktura, skal du først tilføje en Lightning-wallet.",
"no_ln_wallet_error": "Før du betaler en Lightning-faktura, skal du først tilføje en Lightning-tegnebog.",
"looks_like_bip38": "Dette ligner en adgangskodebeskyttet privat nøgle (BIP38).",
"manage_title": "Administrer wallets",
"manage_title": "Administrer tegnebøger",
"no_results_found": "Ingen resultater fundet.",
"please_continue_scanning": "Fortsæt venligst med at scanne.",
"select_no_bitcoin": "Der er i øjeblikket ingen Bitcoin-wallets tilgængelige.",
"select_no_bitcoin_exp": "En Bitcoin-wallet er påkrævet for at genopfylde Lightning-wallets. Opret eller importer venligst en.",
"select_no_bitcoin": "Der er i øjeblikket ingen Bitcoin-tegnebøger tilgængelige.",
"select_no_bitcoin_exp": "En Bitcoin-tegnebog er påkrævet for at genopfylde Lightning-tegnebøger. Opret eller importer venligst en.",
"pull_to_refresh": "Træk for at opdatere",
"warning_do_not_disclose": "Del aldrig oplysningerne nedenfor",
"scan_import": "Scan denne QR-kode for at importere din wallet i en anden applikation.",
"write_down_header": "Opret en manuel backup",
"write_down": "Skriv disse ord ned og opbevar dem sikkert. Brug dem til at gendanne din wallet senere.",
"wallet_type_this": "Denne wallet-type er {type}.",
"scan_import": "Scan denne QR-kode for at importere din tegnebog i en anden applikation.",
"write_down_header": "Opret en manuel sikkerhedskopi",
"write_down": "Skriv disse ord ned og opbevar dem sikkert. Brug dem til at gendanne din tegnebog senere.",
"wallet_type_this": "Denne tegnebogstype er {type}.",
"share_number": "Del {number}",
"copy_ln_url": "Kopier og opbevar denne URL sikkert for at gendanne din wallet senere.",
"copy_ln_public": "Kopier og opbevar disse oplysninger sikkert for at gendanne din wallet senere.",
"add_ln_wallet_first": "Du skal først tilføje en Lightning-wallet.",
"copy_ln_url": "Kopier og opbevar denne URL sikkert for at gendanne din tegnebog senere.",
"copy_ln_public": "Kopier og opbevar disse oplysninger sikkert for at gendanne din tegnebog senere.",
"add_ln_wallet_first": "Du skal først tilføje en Lightning-tegnebog.",
"identity_pubkey": "Identitets-pubkey",
"xpub_title": "Wallet XPUB",
"manage_wallets_search_placeholder": "Søg wallets, adresser, transaktioner og notater",
"xpub_title": "Tegnebogs XPUB",
"manage_wallets_search_placeholder": "Søg tegnebøger, adresser, transaktioner og notater",
"more_info": "Mere info",
"details_delete_wallet_error_message": "Der opstod et problem med at bekræfte, om denne wallet blev fjernet fra notifikationer—dette kan skyldes et netværksproblem eller dårlig forbindelse. Hvis du fortsætter, modtager du muligvis stadig notifikationer for transaktioner relateret til denne wallet, selv efter den er slettet.",
"details_delete_wallet_error_message": "Der opstod et problem med at bekræfte, om denne tegnebog blev fjernet fra notifikationer—dette kan skyldes et netværksproblem eller dårlig forbindelse. Hvis du fortsætter, modtager du muligvis stadig notifikationer for transaktioner relateret til denne tegnebog, selv efter den er slettet.",
"details_delete_anyway": "Slet alligevel"
},
"total_balance_view": {
@ -525,118 +527,118 @@
"display_in_sats": "Vis i sats",
"display_in_fiat": "Vis i {currency}",
"title": "Samlet saldo",
"explanation": "Vis den samlede saldo for alle dine wallets på oversigtsskærmen."
"explanation": "Vis den samlede saldo for alle dine tegnebøger på oversigtsskærmen."
},
"multisig": {
"confirm": "Bekræft",
"create": "Opret",
"header": "Send",
"multisig_vault": "Multisig Vault",
"default_label": "Multisig Vault",
"ms_help_title5": "Avanceret tilstand",
"ms_help_title5": "Enable advanced mode",
"multisig_vault": "Multisig-boks",
"default_label": "Multisig-boks",
"multisig_vault_explain": "Bedste sikkerhed til store beløb",
"provide_signature": "Signer",
"provide_signature_details": "Brug din enhed og wallet, hvor nøglen findes, til at signere denne transaktion",
"provide_signature": "Underskriv",
"provide_signature_details": "Brug din enhed og tegnebog, hvor nøglen findes, til at underskrive denne transaktion",
"provide_signature_details_bluewallet": "I BlueWallet skal du gå til Send-skærmens menu og vælge",
"provide_signature_next_steps": "Scan eller importér signeret transaktion",
"provide_signature_next_steps_details": "Når din wallet har signeret transaktionen, scan den angivne QR-kode eller importer den medfølgende fil, og gennemse derefter alle transaktionsdetaljerne, før du transmitterer den.",
"vault_key": "Vault-nøgle {number}",
"provide_signature_next_steps": "Scan eller importér underskrevet transaktion",
"provide_signature_next_steps_details": "Når din tegnebog har underskrevet transaktionen, skal du scanne den medfølgende QR-kode eller importere den tilhørende fil og derefter gennemgå alle transaktionsdetaljerne, før du transmitterer den.",
"vault_key": "Boksnøgle {number}",
"required_keys_out_of_total": "Påkrævede nøgler ud af det samlede antal",
"fee": "Gebyr: {number}",
"fee_btc": "{number} BTC",
"header": "Send",
"share": "Del...",
"view": "Vis",
"shared_key_detected": "Delt co-signer",
"shared_key_detected_question": "En co-signer er blevet delt med dig, vil du importere den?",
"shared_key_detected": "Delt medunderskriver",
"shared_key_detected_question": "En medunderskriver er blevet delt med dig, vil du importere den?",
"manage_keys": "Administrer nøgler",
"how_many_signatures_can_bluewallet_make": "hvor mange signaturer kan BlueWallet lave",
"signatures_required_to_spend": "Påkrævede signaturer {number}",
"how_many_signatures_can_bluewallet_make": "hvor mange underskrifter kan BlueWallet lave",
"signatures_required_to_spend": "Påkrævede underskrifter {number}",
"signatures_we_can_make": "kan lave {number}",
"scan_or_import_file": "Scan eller importér fil",
"export_coordination_setup": "Eksportér koordineringsopsætning",
"cosign_this_transaction": "Co-sign denne transaktion?",
"cosign_this_transaction": "Medunderskriv denne transaktion?",
"lets_start": "Lad os starte",
"native_segwit_title": "Bedste praksis",
"wrapped_segwit_title": "Bedste kompatibilitet",
"legacy_title": "Legacy",
"co_sign_transaction": "Signer en transaktion",
"what_is_vault": "En Vault er en",
"co_sign_transaction": "Underskriv en transaktion",
"what_is_vault": "En boks er en",
"what_is_vault_numberOfWallets": " {m}-af-{n} multisig ",
"what_is_vault_wallet": "wallet.",
"vault_advanced_customize": "Vault-indstillinger",
"what_is_vault_wallet": "tegnebog.",
"vault_advanced_customize": "Boks-indstillinger",
"needs": "Den kræver",
"what_is_vault_description_number_of_vault_keys": " {m} Vault-nøgler ",
"what_is_vault_description_to_spend": "til at sende og en tredje, som du \nkan bruge som backup.",
"what_is_vault_description_number_of_vault_keys": " {m} boksnøgler ",
"what_is_vault_description_to_spend": "til at sende og en tredje, som du \nkan bruge som sikkerhedskopi.",
"what_is_vault_description_to_spend_other": "til at sende.",
"quorum": "{m} af {n} quorum",
"quorum_header": "Quorum",
"quorum": "{m} af {n} kvorum",
"quorum_header": "Kvorum",
"of": "af",
"wallet_type": "Wallet-type",
"invalid_mnemonics": "Denne mnemonic ser ikke ud til at være gyldig.",
"invalid_cosigner": "Ugyldige co-signer-data",
"not_a_multisignature_xpub": "Dette er ikke en XPUB fra en multisignatur-wallet!",
"invalid_cosigner_format": "Forkert co-signer: Dette er ikke en co-signer til {format}-formatet.",
"wallet_type": "Tegnebogstype",
"invalid_mnemonics": "Denne mnemoniske frase ser ikke ud til at være gyldig.",
"invalid_cosigner": "Ugyldige medunderskriverdata",
"not_a_multisignature_xpub": "Dette er ikke en XPUB fra en multisignatur-tegnebog!",
"invalid_cosigner_format": "Forkert medunderskriver: Dette er ikke en medunderskriver til {format}-formatet.",
"create_new_key": "Opret ny",
"scan_or_open_file": "Scan eller åbn fil",
"i_have_mnemonics": "Jeg har en seed til denne nøgle.",
"type_your_mnemonics": "Indsæt en seed for at importere din eksisterende Vault-nøgle.",
"this_is_cosigners_xpub": "Dette er co-signerens XPUB—klar til at blive importeret til en anden wallet. Det er sikkert at dele den.",
"i_have_mnemonics": "Jeg har et seed til denne nøgle.",
"type_your_mnemonics": "Indsæt et seed for at importere din eksisterende boksnøgle.",
"this_is_cosigners_xpub": "Dette er medunderskriverens XPUB—klar til at blive importeret til en anden tegnebog. Det er sikkert at dele den.",
"this_is_cosigners_xpub_airdrop": "Hvis du deler via AirDrop, skal modtagerne være på koordineringsskærmen.",
"wallet_key_created": "Din Vault-nøgle er oprettet. Tag et øjeblik til sikkert at sikkerhedskopiere dit mnemonic seed.",
"are_you_sure_seed_will_be_lost": "Er du sikker? Dit mnemonic seed vil gå tabt, hvis du ikke har en backup.",
"forget_this_seed": "Glem denne seed og brug XPUB i stedet.",
"view_edit_cosigners": "Vis/rediger co-signers",
"this_cosigner_is_already_imported": "Denne co-signer er allerede importeret.",
"export_signed_psbt": "Eksportér signeret PSBT",
"input_fp": "Indtast fingerprint",
"wallet_key_created": "Din boksnøgle er oprettet. Tag et øjeblik til sikkert at sikkerhedskopiere dit mnemoniske seed.",
"are_you_sure_seed_will_be_lost": "Er du sikker? Dit mnemoniske seed vil gå tabt, hvis du ikke har en sikkerhedskopi.",
"forget_this_seed": "Glem dette seed og brug XPUB i stedet.",
"view_edit_cosigners": "Vis/rediger medunderskrivere",
"this_cosigner_is_already_imported": "Denne medunderskriver er allerede importeret.",
"export_signed_psbt": "Eksportér underskrevet PSBT",
"input_fp": "Indtast fingeraftryk",
"input_fp_explain": "Spring over for at bruge standardværdien (00000000)",
"input_path": "Indsæt afledningssti",
"input_path_explain": "Spring over for at bruge standardværdien ({default})",
"ms_help": "Hjælp",
"ms_help_title": "Sådan fungerer Multisig Vaults: Tips og tricks",
"ms_help_text": "En wallet med flere nøgler, til øget sikkerhed eller delt opbevaring",
"ms_help_title": "Sådan fungerer Multisig-bokse: Tips og tricks",
"ms_help_text": "En tegnebog med flere nøgler, til øget sikkerhed eller delt opbevaring",
"ms_help_title1": "Flere enheder anbefales.",
"ms_help_1": "Vault'en vil fungere med andre BlueWallet-apps og PSBT-kompatible wallets såsom Electrum, Specter, Coldcard, Cobo Vault osv.",
"ms_help_1": "Boksen vil fungere med andre BlueWallet-apps og PSBT-kompatible tegnebøger såsom Electrum, Specter, Coldcard, Cobo Vault osv.",
"ms_help_title2": "Redigering af nøgler",
"ms_help_2": "Du kan oprette alle Vault-nøgler på denne enhed og fjerne eller redigere dem senere. At have alle nøgler på samme enhed har samme sikkerhed som en almindelig Bitcoin-wallet.",
"ms_help_title3": "Vault-backups",
"ms_help_3": "Under wallet-indstillingerne finder du din Vault-backup og watch-only-backup. Denne backup er som et kort til din wallet. Den er afgørende for wallet-gendannelse, hvis du mister et af dine seeds.",
"ms_help_title4": "Import af Vaults",
"ms_help_4": "For at importere en multisig skal du bruge din backup-fil og Import-funktionen. Hvis du kun har seeds og XPUB'er, kan du bruge den individuelle Import-knap, når du opretter Vault-nøgler.",
"ms_help_5": "Som standard vil BlueWallet generere en 2-af-3 Vault. For at oprette et andet quorum eller ændre adressetypen skal du aktivere Avanceret tilstand i indstillingerne."
"ms_help_2": "Du kan oprette alle boksnøgler på denne enhed og fjerne eller redigere dem senere. At have alle nøgler på samme enhed har samme sikkerhed som en almindelig Bitcoin-tegnebog.",
"ms_help_title3": "Boks-sikkerhedskopier",
"ms_help_3": "Under tegnebogsindstillingerne finder du din boks-sikkerhedskopi og kun-kigge-sikkerhedskopi. Denne sikkerhedskopi er som et kort til din tegnebog. Den er afgørende for tegnebogsgendannelse, hvis du mister et af dine seeds.",
"ms_help_title4": "Import af bokse",
"ms_help_4": "For at importere en multisig skal du bruge din sikkerhedskopifil og Import-funktionen. Hvis du kun har seeds og XPUB'er, kan du bruge den individuelle Import-knap, når du opretter boksnøgler.",
"ms_help_5": "Som standard vil BlueWallet generere en 2-af-3-boks. For at oprette et andet kvorum eller ændre adressetypen skal du aktivere Avanceret tilstand i indstillingerne."
},
"is_it_my_address": {
"title": "Er det min adresse?",
"owns": "{label} ejer {address}",
"enter_address": "Indtast adresse",
"check_address": "Tjek adresse",
"no_wallet_owns_address": "Ingen af de tilgængelige wallets ejer den angivne adresse.",
"no_wallet_owns_address": "Ingen af de tilgængelige tegnebøger ejer den angivne adresse.",
"view_qrcode": "Vis QR-kode"
},
"autofill_word": {
"title": "Sidste seed-ord",
"enter": "Indtast din delvise mnemonic",
"enter": "Indtast din delvise mnemoniske frase",
"generate_word": "Generér det sidste ord",
"error": "Inputtet er ikke en delvis mnemonic med 11 eller 23 ord. Prøv venligst igen."
"error": "Inputtet er ikke en delvis mnemonisk frase med 11 eller 23 ord. Prøv venligst igen."
},
"cc": {
"header": "Coin Control",
"sort_label": "Etiket",
"sort_status": "Status",
"change": "Byttepenge",
"coins_selected": "Coins valgt ({number})",
"coins_selected": "Mønter valgt ({number})",
"selected_summ": "{value} valgt",
"empty": "Denne wallet har ingen coins i øjeblikket.",
"empty": "Denne tegnebog har ingen mønter i øjeblikket.",
"freeze": "Frys",
"freezeLabel": "Frys",
"freezeLabel_un": "Frigør",
"use_coin": "Brug coin",
"use_coins": "Brug coins",
"tip": "Denne funktion giver dig mulighed for at se, mærke, fryse eller vælge coins for forbedret wallet-styring. Du kan vælge flere coins ved at trykke på de farvede cirkler.",
"header": "UTXO-håndtering",
"use_coin": "Brug mønt",
"use_coins": "Brug mønter",
"tip": "Denne funktion giver dig mulighed for at se, mærke, fryse eller vælge mønter for forbedret tegnebogsstyring. Du kan vælge flere mønter ved at trykke på de farvede cirkler.",
"sort_asc": "Stigende",
"sort_desc": "Faldende",
"sort_height": "Højde",
"sort_value": "Værdi",
"sort_status": "Status",
"sort_by": "Sortér efter"
},
"units": {
@ -646,14 +648,14 @@
"sats": "sats"
},
"addresses": {
"sign_placeholder_address": "Adresse",
"sign_placeholder_address": "adresse",
"type_receive": "Modtag",
"transactions": "Transaktioner",
"transactions": "transaktioner",
"copy_private_key": "Kopier privat nøgle",
"sensitive_private_key": "Advarsel: private nøgler er ekstremt følsomme. Fortsæt?",
"sign_title": "Signer/verificer besked",
"sign_title": "Underskriv/verificer besked",
"sign_help": "Her kan du oprette eller verificere en kryptografisk signatur baseret på en Bitcoin-adresse.",
"sign_sign": "Signer",
"sign_sign": "Underskriv",
"sign_verify": "Verificer",
"sign_signature_correct": "Verificering lykkedes!",
"sign_signature_incorrect": "Verificering mislykkedes!",
@ -665,40 +667,40 @@
},
"lnurl_auth": {
"register_question_part_1": "Vil du registrere en konto hos",
"register_question_part_2": "ved hjælp af din Lightning-wallet?",
"register_question_part_2": "ved hjælp af din Lightning-tegnebog?",
"register_answer": "Du har registreret en konto hos {hostname}!",
"login_question_part_1": "Vil du logge ind hos",
"login_question_part_2": "ved hjælp af din Lightning-wallet?",
"login_question_part_2": "ved hjælp af din Lightning-tegnebog?",
"login_answer": "Du er logget ind hos {hostname}!",
"link_question_part_1": "Vil du knytte din konto hos",
"link_question_part_2": "til din Lightning-wallet?",
"link_answer": "Din Lightning-wallet blev knyttet til din konto hos {hostname}!",
"link_question_part_2": "til din Lightning-tegnebog?",
"link_answer": "Din Lightning-tegnebog blev knyttet til din konto hos {hostname}!",
"auth_question_part_1": "Vil du blive godkendt hos",
"auth_question_part_2": "ved hjælp af din Lightning-wallet?",
"auth_question_part_2": "ved hjælp af din Lightning-tegnebog?",
"auth_answer": "Du er blevet godkendt hos {hostname}!",
"could_not_auth": "Vi kunne ikke godkende dig hos {hostname}.",
"authenticate": "Godkend"
},
"bip47": {
"payment_code": "Payment Code",
"payment_code": "Betalingskode",
"contacts": "Kontakter",
"bip47_explain": "Genbrugelig og delbar kode",
"bip47_explain_subtitle": "BIP47",
"purpose": "Genbrugelig og delbar kode (BIP47)",
"pay_this_contact": "Betal denne kontakt",
"rename_contact": "Omdøb kontakt",
"copy_payment_code": "Kopier Payment Code",
"copy_payment_code": "Kopier betalingskode",
"hide_contact": "Skjul kontakt",
"rename": "Omdøb",
"provide_name": "Angiv nyt navn til denne kontakt",
"add_contact": "Tilføj kontakt",
"provide_payment_code": "Angiv Payment Code",
"invalid_pc": "Ugyldig Payment Code",
"notification_tx_unconfirmed": "Notification transaction er endnu ikke bekræftet, vent venligst",
"failed_create_notif_tx": "Kunne ikke oprette onchain-transaktion",
"onchain_tx_needed": "Onchain-transaktion er nødvendig",
"notif_tx_sent": "Notification transaction sendt. Vent venligst på, at den bekræftes",
"notif_tx": "Notification transaction",
"not_found": "Payment Code ikke fundet"
"provide_payment_code": "Angiv betalingskode",
"invalid_pc": "Ugyldig betalingskode",
"notification_tx_unconfirmed": "Underretningstransaktion er endnu ikke bekræftet, vent venligst",
"failed_create_notif_tx": "Kunne ikke oprette on-chain-transaktion",
"onchain_tx_needed": "On-chain-transaktion er nødvendig",
"notif_tx_sent": "Underretningstransaktion sendt. Vent venligst på, at den bekræftes",
"notif_tx": "Underretningstransaktion",
"not_found": "Betalingskode ikke fundet"
}
}

View File

@ -4,9 +4,8 @@
"cancel": "Abbrechen",
"continue": "Fortsetzen",
"clipboard": "Zwischenablage",
"copied": "Kopiert!",
"discard_changes": "Änderungen verwerfen?",
"discard_changes_explain": "Die ungespeicherten Änderungen verwerfen und den Bildschirm verlassen?",
"discard_changes_explain": "Die ungespeicherten Änderungen verwerfen und den Bildschrim verlassen?",
"enter_password": "Passwort eingeben",
"never": "nie",
"of": "{number} von {total}",
@ -20,7 +19,7 @@
"success": "Erfolg",
"wallet_key": "Wallet Schlüssel",
"close": "Schließen",
"change_input_currency": "Eingabewährung ändern",
"change_input_currency": "Eingangswährung ändern",
"refresh": "Aktualisieren",
"pick_image": "Aus der Bibliothek wählen",
"pick_file": "Datei auswählen",
@ -28,17 +27,18 @@
"qr_custom_input_button": "10x antippen für individuelle Eingabe",
"unlock": "Entsperren",
"port": "Port",
"ssl_port": "SSL-Port",
"suggested": "Vorgeschlagen"
"ssl_port": "SSL Port",
"suggested": "Vorgeschlagen",
"copied": "Kopiert!"
},
"azteco": {
"codeIs": "Dein Gutscheincode lautet",
"errorBeforeRefeem": "Vor dem Einlösen zuerst eine Bitcoin-Wallet hinzufügen.",
"errorBeforeRefeem": "Vor dem Einlösen zuerst eine Bitcoin Wallet hinzufügen.",
"errorSomething": "Etwas ist schiefgelaufen. Ist der Gutscheincode noch gültig?",
"redeem": "Einlösen in eine Wallet",
"redeemButton": "Einlösen",
"success": "Erfolg",
"successMessage": "Gutschein erfolgreich eingelöst! Deine Gelder sollten in Kürze in deiner Bitcoin-Wallet ankommen.",
"successMessage": "Gutschein erfolgreich eingelöst! Ihre Gelder sollten in Kürze in Ihrer Bitcoin-Wallet ankommen.",
"title": "Azte.co Gutschein einlösen"
},
"entropy": {
@ -61,10 +61,10 @@
"placeholder": "Rechnung oder Adresse",
"potentialFee": "Geschätzte Gebühr: {fee}",
"refill": "Aufladen",
"refill_create": "Bitte eine Bitcoin-Wallet erstellen, um fortzufahren",
"refill_external": "Mit externer Wallet aufladen",
"refill_lnd_balance": "Lade deine Lightning-Wallet auf",
"sameWalletAsInvoiceError": "Die Rechnung lässt sich nicht mit derselben Wallet begleichen, mit der sie erstellt wurde.",
"refill_create": "Bitte eine Bitcoin Wallet erstellen um fortzufahren",
"refill_external": "Mit externem Wallet aufladen",
"refill_lnd_balance": "Lade deine Lightning Wallet auf",
"sameWalletAsInvoiceError": "Die Rechnung lässt sich nicht mit gleichen Wallet begleichen, mit der sie erstellt wurde.",
"title": "Beträge verwalten"
},
"lndViewInvoice": {
@ -73,7 +73,7 @@
"lightning_invoice": "Lightning Rechnung",
"please_pay_between_and": "Zwischen {min} und {max} zahlen",
"please_pay": "Bitte zahle",
"preimage": "Pre-image",
"preimage": "Urbild",
"sats": "sats",
"date_time": "Datum und Zeit",
"wasnt_paid_and_expired": "Diese Rechnung ist unbezahlt abgelaufen."
@ -81,20 +81,20 @@
"plausibledeniability": {
"create_fake_storage": "Verschlüsselten Speicher erstellen",
"create_password_explanation": "Das Passwort für den täuschenden Speicher darf nicht mit dem deines Hauptspeichers übereinstimmen",
"help": "BlueWallet erlaubt die Erstellung eines zweiten verschlüsselten Speichers mit eigenem Passwort. Wird unter Zwang die Preisgabe des Passworts verlangt, kann anstelle des richtigen Passworts dieses genannt werden. BlueWallet öffnet dann die Wallet, welche im zweiten Speicher zur Täuschung angelegt ist, und der Hauptspeicher bleibt geheim und sicher.",
"help2": "Der zweite Speicher ist funktional identisch. Zahle auf die darin angelegte Wallet einen Minimalbetrag ein, um die Täuschung glaubhafter zu machen.",
"help": "BlueWallet erlaubt die Erstellung eines zweiten verschlüsselten Speichers mit eigenem Passwort. Sodass unter Zwang das Passwort preiszugeben, dann dies, anstelle des richtigen Passwortes genannt werden kann. BlueWallet öffnet dann die Wallet, welche im zweiten Speicher zur Täuschung angelegt ist und der Hauptspeicher bleibt geheim und sicher.",
"help2": "Der zweite Speicher ist funktional identisch. Zahle auf die darin angelegten Wallet ein Minimalbetrag ein, um die Täuschung glaubhafter zu machen.",
"password_should_not_match": "Das Passwort für den täuschenden Speicher darf nicht mit dem deines Hauptspeichers übereinstimmen",
"title": "Glaubhafte Täuschung"
},
"pleasebackup": {
"ask": "Ist die Wiederherstellungs-Phrase der Wallet gesichert? Ohne sie lässt sich nie mehr auf die Bitcoin zugreifen und sie wären für immer verloren, sollte das Gerät verloren oder kaputt gehen.",
"ask": "Ist die Wiederherstellungs-Phrase des Wallets gesichert? Ohne Sie lässt sich nie mehr auf die bitcoin zugreifen und sie wären für immer verloren, sollte das Gerät verloren oder kaputt gehen.",
"ask_no": "Nein, habe ich nicht.",
"ask_yes": "Ja, habe ich.",
"ok": "Ok, sie sind notiert.",
"ok_lnd": "Die Sicherung ist erstellt.",
"text": "Unbedingt die Seed-Wörter auf Papier schreiben.\nDiese Wörter sind das Backup zur Wallet-Wiederherstellung.",
"text_lnd": "Zur Wiederherstellung der Wallet im Verlustfall bitte dieses Wallet-Backup sichern.",
"title": "Deine Wallet ist erstellt."
"text": "Unbedingt die mnemonischen Wörter auf Papier schreiben.\nDiese Wörter sind das Backup zur Wallet-Wiederherstellung.",
"text_lnd": "Zur Wiederherstellung des Wallet im Verlustfall bitte dieses Wallet-Backup sichern. ",
"title": "Dein Wallet ist erstellt."
},
"receive": {
"details_create": "Erstelle",
@ -112,7 +112,7 @@
"bip47_explanation": "Zahlungscodes sind eine universelle Adresse. Sie vermeiden die Offenlegung der Wallet-Adressen, werden aber nicht durch alle Dienste unterstützt."
},
"send": {
"provided_address_is_invoice": "Diese Adresse ist für eine Lightning-Rechnung. Um diese Rechnung zu zahlen, ist eine Lightning-Wallet zu verwenden.",
"provided_address_is_invoice": "Diese Adresse ist für eine Lightning-Rechnung. Um diese Rechnung zu zahlen ist ein Lightning Wallet zu verwenden.",
"broadcastButton": "Ins Netzwerk übertragen",
"broadcastError": "Fehler",
"broadcastNone": "Rohtransaktion eingeben",
@ -127,7 +127,7 @@
"create_fee": "Gebühr",
"create_memo": "Notiz",
"create_satoshi_per_vbyte": "Satoshi pro vByte",
"create_this_is_hex": "Dies ist die signierte Transaktion. Hexadezimal dargestellt und bereit zur Übertragung ins Netzwerk.",
"create_this_is_hex": "Dies ist die signierte Transaktion. Hexadezimal dargestellt und bereit zu Übertragung ins Netzwerk.",
"create_to": "An",
"create_tx_size": "Transaktionsgröße",
"create_verify": "Verifiziere auf coinb.in",
@ -157,6 +157,7 @@
"details_next": "Weiter",
"details_no_signed_tx": "Die ausgewählte Datei enthält keine importierbare signierte Transaktion.",
"details_note_placeholder": "Eigene Bezeichnung",
"counterparty_label_placeholder": "Kontaktnamen bearbeiten",
"details_scan": "Scannen",
"details_scan_hint": "Zum Importieren / Scannen zweimal tippen",
"details_scan_error": "Scan-Fehler",
@ -168,7 +169,7 @@
"dynamic_next": "Nächste",
"dynamic_prev": "Vorherige",
"dynamic_start": "Start",
"dynamic_stop": "Stopp",
"dynamic_stop": "Stop",
"fee_10m": "10min",
"fee_1d": "1T",
"fee_3h": "3h",
@ -184,14 +185,14 @@
"input_done": "Fertig",
"input_paste": "Einfügen",
"input_total": "Gesamt:",
"permission_camera_message": "BlueWallet braucht deine Erlaubnis, um die Kamera zu nutzen.",
"permission_camera_message": "BlueWallet braucht Deine Erlaubnis, um die Kamera zu nutzen.",
"psbt_sign": "Transaktion signieren",
"invalid_psbt": "Ungültige PSBT bereitgestellt.",
"open_settings": "Einstellungen öffnen",
"permission_storage_denied_message": "BlueWallet kann die Datei nicht speichern. Dazu in den Systemeinstellungen der App BlueWallet das Recht erteilen, den internen Speicher zu verwenden.",
"permission_storage_title": "Speicherzugriffsrecht",
"psbt_clipboard": "In die Zwischenablage kopieren",
"psbt_this_is_psbt": "Dies ist eine partiell signierte Bitcoin-Transaktion (PSBT). Zum Senden mithilfe der Hardware Wallet final signieren.",
"psbt_this_is_psbt": "Dies ist eine partiell signierte Bitcoin-Transaktion (PSBT). Zum Senden mithilfe der Hardware-Wallet final signieren.",
"psbt_tx_export": "In Datei exportieren",
"no_tx_signing_in_progress": "Keine Transaktionsignierung in Arbeit",
"outdated_rate": "Kurs zuletzt aktualisiert: {date}",
@ -203,14 +204,14 @@
"success_done": "Fertig",
"txSaved": "Die Transaktionsdatei ({filePath}) wurde gespeichert.",
"file_saved_at_path": "Die Datei ({filePath}) wurde gespeichert.",
"cant_send_to_silentpayment_adress": "Diese Wallet kann nicht an Silent-Payment-Adressen senden",
"cant_send_to_bip47": "Diese Wallet kann nicht an BIP47-Zahlungscodes senden",
"cant_send_to_silentpayment_adress": "Diese Wallet kann nicht an Stille Zahlung Adressen senden",
"cant_send_to_bip47": "Dieses Wallet kann nicht an BIP47 Zahlungscodes senden",
"cant_find_bip47_notification": "Diesen Zahlungscode zuerst zu den Kontakten hinzufügen ",
"problem_with_psbt": "PSBT-Problem"
},
"settings": {
"about": "Über",
"about_awesome": "Entwickelt mit dem eindrucksvollen",
"about_awesome": "Entwickelt mt dem eindrucksvollen",
"about_backup": "Stets die Backup-Phrase sichern!",
"about_free": "BlueWallet ist kostenlose Open Source Software. Produziert von Bitcoin-Benutzern.",
"about_license": "MIT-Lizenz",
@ -225,19 +226,17 @@
"about_sm_github": "GitHub",
"about_sm_telegram": "Telegram-Channel",
"privacy_temporary_screenshots": "Bildschirmaufnahme erlauben",
"privacy_temporary_screenshots_instructions": "Der Schutz vor Bildschirmaufnahmen wird vorübergehend deaktiviert, wodurch Screenshots und Bildschirmaufzeichnungen möglich sind. Der Schutz wird automatisch reaktiviert, wenn du BlueWallet schließt und erneut öffnest.",
"privacy_temporary_screenshots_instructions": "Der Schutz vor Bildschirmaufnahmen wird vorübergehend deaktiviert, wodurch Screenshots und Bildschirmaufzeichnungen möglich sind. Der Schutz wird automatisch reaktiviert, wenn Sie BlueWallet schließen und erneut öffnen.",
"biometrics": "Biometrie",
"biometrics_no_longer_available": "Die Geräteeinstellungen stimmen nicht mehr mit den App-Sicherheitseinstellungen überein. Um die Änderungen zu übernehmen, die Biometrie oder den Passcode einrichten, dann die App neu starten.",
"biom_10times": "Es wurde 10 Mal versucht, das Passwort einzugeben. Soll der Speicher zurückgesetzt werden? Dabei werden alle Wallets entfernt und der Speicher entschlüsselt.",
"biometrics_no_longer_available": "Die Geräteeinstellungen stimmen nicht mehr mit den App Sicherheitseinstellungen überein. Um die Änderungen zu übernehmen, die Biometrie oder den Passcode einrichten, dann die App neu starten.",
"biom_10times": "Es wurde 10 Mal versucht, das Passwort einzugeben. Soll der Speicher zurückgesetzt werden? Dabei werden alle Wallets entfernt der Speicher entschlüsselt.",
"biom_conf_identity": "Bitte deine Identität bestätigen.",
"biom_no_passcode": "Um fortzufahren, müssen auf dem Gerät entweder ein Sicherheitscode oder biometrische Daten aktiviert sein. Dies lässt sich in der App „Einstellungen“ vornehmen.",
"biom_remove_decrypt": "Alle Wallets werden entfernt und der Speicher wird entschlüsselt. Wirklich fortfahren?",
"biom_no_passcode": "Um fortzufahren müssen auf dem Gerät entweder ein Sicherheitscode oder biometrische Daten aktiviert sein. Dies lässt sich in der App \"Einstellungen\" vornehmen.",
"biom_remove_decrypt": "Alle Wallet werden entfernt und der Speicher wird entschlüsselt. Wirklich fortfahren?",
"currency": "Währung",
"currency_source": "Der Kurs wird bezogen von",
"currency_fetch_error": "Beim Abrufen des Wechselkurses für die ausgewählte Währung trat ein Fehler auf.",
"default_title": "Beim Start",
"donate": "Spenden",
"donate_description": "Hilf uns, Blue kostenlos zu halten!",
"electrum_connected": "Verbunden",
"electrum_connected_not": "Nicht verbunden",
"electrum_error_connect": "Keine Verbindung zum angegebenen Electrum-Server möglich.",
@ -249,35 +248,33 @@
"electrum_port": "Port, üblich {example}",
"use_ssl": "SSL verwenden",
"electrum_saved": "Deine Änderungen wurden gespeichert. Zur Aktivierung ist ggf. ein Neustart von BlueWallet erforderlich.",
"set_electrum_server_as_default": "{server} als Standard-Electrum-Server festlegen?",
"set_electrum_server_as_default": "{server} als Standard Electrum-Server setzten?",
"set_lndhub_as_default": "{url} als Standard LNDhub-Server festlegen?",
"electrum_settings_server": "Electrum-Server",
"electrum_settings_server": "Electrum Server",
"electrum_status": "Status",
"electrum_preferred_server": "Präferierter Server",
"electrum_preferred_server_description": "Den Server eingeben, der die Wallet für alle Bitcoin-Aktivitäten verwenden soll. Sobald festgelegt, wird die Wallet ausschließlich diesen Server verwenden, um Salden abzufragen, Transaktionen zu senden und Netzwerkdaten abzurufen. Prüfe vorher, dass der Server vertrauenswürdig ist.",
"electrum_preferred_server_description": "Den Server eingeben, der die Wallet für alle Bitcoin-Aktivitäten verwenden soll. Sobald festgelegt, wird die Wallet um Salden abzufragen, Transaktionen zu senden und Netzwerkdaten abzurufen ausschliesslich diesen Server verwenden. Prüfen sie vorher, dass der Server vertrauenswürdig ist.",
"electrum_unable_to_connect": "Verbindung zu {server} kann nicht hergestellt werden.",
"electrum_history": "Historie",
"electrum_reset_to_default": "Dies lässt BlueWallet zufällig einen Server aus der Liste der Server auswählen.",
"electrum_reset": "Zurücksetzen",
"electrum_reset": "Zurücksetzten",
"electrum_reset_to_default_and_clear_history": "Auf die Standardeinstellungen zurücksetzen und den Verlauf löschen.",
"encrypt_decrypt": "Speicher entschlüsseln",
"encrypt_decrypt_q": "Die Speicherverschlüsselung wirklich aufheben? Hiermit werden die Wallets ohne Passwortschutz direkt benutzbar.",
"encrypt_enc_and_pass": "Passwortgeschützt",
"encrypt_decrypt_q": "Die Speicherverschlüsselung wirklich aufheben? Hiermit werden die Wallet ohne Passwortschutz direkt benutzbar. ",
"encrypt_storage_explanation_headline": "Speicherverschlüsselung aktivieren",
"encrypt_storage_explanation_description_line1": "Die Aktivierung der Speicherverschlüsselung fügt deiner App eine zusätzliche Schutzschicht hinzu. Die Art und Weise, wie die Daten auf deinem Gerät gespeichert werden, macht es anderen damit schwieriger, ohne Erlaubnis darauf zuzugreifen.",
"encrypt_storage_explanation_description_line2": "Diese Verschlüsselung betrifft den Zugriff auf die im Gerät gespeicherten Schlüssel, schützt also die Wallets. Die Wallets selbst werden dabei nicht mit einem Passwort oder einem anderen zusätzlichen Schutz versehen.",
"encrypt_storage_explanation_description_line1": "Die Aktivierung der Speicherverschlüsselung fügt Ihrer App ein zusätzlicher Schutz hinzu. Die Art und Weise, wie die Daten auf Ihrem Gerät gespeichert werden, macht es anderen damit schwieriger, ohne Erlaubnis darauf zuzugreifen.",
"encrypt_storage_explanation_description_line2": "Diese Verschlüsselung betrifft den Zugriff auf die im Gerät gespeicherten Schlüssel, schützt also die Wallets. Die Wallet selbst werden dabei nicht mit einem Passwort oder einem anderen zusätzlichen Schutz versehen.",
"i_understand": "Ich habe verstanden",
"block_explorer": "Block Explorer",
"block_explorer_preferred": "Bevorzugten Block Explorer verwenden",
"block_explorer_error_saving_custom": "Fehler beim Speichern des bevorzugten Block Explorers.",
"block_explorer": "Block-Explorer",
"block_explorer_preferred": "Bevorzugten Block-Explorer verwenden",
"block_explorer_error_saving_custom": "Fehler beim Speichern des bevorzugten Block-Explorers.",
"encrypt_title": "Sicherheit",
"encrypt_tstorage": "Speicher",
"encrypt_use": "Benutze {type}",
"set_as_preferred": "Als bevorzugt festlegen",
"set_as_preferred_electrum": "Mit der Wahl von {host}:{port} als präferierten Server wird die zufällige Verbindung zu einem der vorgeschlagenen Servern abgeschaltet.",
"encrypted_feature_disabled": "Diese Funktion kann bei verschlüsseltem Speicher nicht genutzt werden.",
"encrypt_use_expl": "{type} wird zur Identitätsbestätigung verwendet, bevor eine Transaktion gesendet, ein Wallet entsperrt, exportiert oder gelöscht wird.",
"biometrics_fail": "Wenn {type} nicht aktiviert ist oder entsperrt werden kann, alternativ den Gerätepasscode verwenden.",
"biometrics_fail": "Wenn {type} nicht aktiviert ist oder entsperrt werden kann, alternativ Ihren Gerätepasscode verwenden.",
"general": "Allgemein",
"general_continuity": "Kontinuität",
"general_continuity_e": "Wenn aktiviert werden ausgewählte Wallets und deren Transaktionen auf deinen anderen Apple iCloud Geräten angezeigt.",
@ -291,11 +288,10 @@
"lightning_error_lndhub_uri_tor": "Ungültige LNDhub-URI. Orbot-App verbinden und es erneut versuchen.",
"lightning_saved": "Deine Änderungen wurden gespeichert.",
"lightning_settings": "Lightning-Einstellungen",
"lightning_settings_explain": "Um sich mit dem eigenen LND-Knoten zu verbinden, LNDhub installieren und dessen URL hier in den Einstellungen eintragen. Achtung: Nur Wallets, die nach dem Speichern der Änderungen erstellt werden, verbinden sich mit dem angegebenen LNDhub.",
"lndhub_github": "GitHub-Repository",
"lightning_settings_explain": "Um sich mit Ihrem eigenen LND-Knoten zu verbinden, LNDhub installieren und dessen URL hier in den Einstellungen eintragen. Achtung. Nur Geldbörsen, die nach dem Speichern der Änderungen erstellt werden, verbinden sich mit dem angegebenen LNDhub.",
"network": "Netzwerk",
"network_broadcast": "Transaktion publizieren",
"network_electrum": "Electrum-Server",
"network_electrum": "Electrum Server",
"electrum_suggested_description": "Ist kein Bevorzugter festgelegt, wird zufällig einer der vorgeschlagenen Server genutzt.",
"not_a_valid_uri": "Keine gültige URI",
"notifications": "Benachrichtigungen",
@ -312,15 +308,20 @@
"privacy_do_not_track": "Diagnosedaten ausschalten",
"privacy_do_not_track_explanation": "Leistungs- und Zuverlässigkeitsinformationen nicht zur Analyse einreichen.",
"rate": "Kurs",
"push_notifications_explanation": "Durch das Aktivieren von Benachrichtigungen wird das Gerätetoken zusammen mit den Wallet-Adressen inkl. künftigen Transaktions-IDs an den Benachrichtigungsdienst gesendet. Das Gerätetoken erlaubt es, Benachrichtigungen an das Gerät zu adressieren; die Wallet-Informationen ermöglichen, über eingehende Transaktionen und Bestätigungen zu informieren.\n\nNach der Aktivierung werden nur künftige, nicht aber vergangene Transaktions-IDs übertragen.\n\nMit der Deaktivierung werden alle diese Informationen wieder vom Server entfernt. Das Gleiche passiert beim Löschen einer Wallet aus der App.",
"push_notifications_explanation": "Durch das Aktivieren von Benachrichtigungen wird das Gerätetoken zusammen mit den Wallet-Adressen inkl. künftigen Transaktions-IDs, an den Benachrichtigungsdienst gesendet. Das Gerätetoken erlaubt Benachrichtigungen an das Gerät zu adressieren, die Wallet-Informationen ermöglichen, eingehende Transaktionen und Bestätigungen zu notifizieren.\n\nNach der Aktivierung werden nur künftige, nicht aber vergangene Transaktions-IDs übertragen.\n\nMit der Deaktivierung, werden alle diese Informationen wieder vom Server entfernt. Das Gleiche passiert beim löschen der Wallet-App.",
"selfTest": "Selbsttest",
"save": "Speichern",
"saved": "Gespeichert",
"success_transaction_broadcasted": "Erfolg! Deine Transaktion wurde übertragen.",
"success_transaction_broadcasted": "Erfolg! Diene Transaktion wurde übertragen.",
"total_balance": "Gesamtes Guthaben",
"total_balance_explanation": "Zeigt das Gesamtguthaben aller Wallets auf dem Widget deines Startbildschirms an.",
"total_balance_explanation": "Zeigt das Wallet Guthaben auf dem Widget deiner Homepage",
"widgets": "Widgets",
"tools": "Werkzeuge"
"tools": "Werkzeuge",
"donate": "Spenden",
"donate_description": "Hilf uns Blue kostenlos zu halten!",
"encrypt_enc_and_pass": "Passwortgeschützt",
"encrypt_use_expl": "{type} wird zur Identitätsbestätigung verwendet, bevor eine Transaktion gesendet, ein Wallet entsperrt, exportiert oder gelöscht wird.",
"lndhub_github": "GitHub-Repository"
},
"notifications": {
"would_you_like_to_receive_notifications": "Soll bei Zahlungseingängen eine Benachrichtigung zugestellt werden?",
@ -335,6 +336,7 @@
"transaction_loading_error": "Es gab ein Problem beim Laden der Transaktion. Bitte später erneut versuchen.",
"transaction_not_available": "Transaktion ist nicht verfügbar",
"confirmations_lowercase": "{confirmations} Bestätigungen",
"copy_link": "Link kopieren",
"expand_note": "Bezeichnung erweitern",
"cpfp_create": "Erstellen",
"cpfp_exp": "BlueWallet erzeugt eine weitere Transaktion, welche die unbestätigte Transaktion ausgibt. Das höhere Gebührentotal beider Transaktionen führt zu einer schnelleren Verarbeitung (Child Pays for Parent).",
@ -343,9 +345,10 @@
"details_balance_hide": "Guthaben verbergen",
"details_balance_show": "Guthaben zeigen",
"details_copy": "Kopieren",
"details_copy_block_explorer_link": "Block Explorer Link kopieren",
"details_copy_block_explorer_link": "Block-Explorer Link kopieren",
"details_copy_note": "Beschreibung kopieren",
"details_copy_txid": "Transaktions-ID kopieren",
"details_from": "Eingang",
"details_inputs": "Eingänge",
"details_outputs": "Ausgänge",
"date": "Datum",
@ -367,8 +370,8 @@
"eta_10m": "In ca. 10 Min. verbucht.",
"eta_3h": "In ca. 3 Std. verbucht.",
"eta_1d": "In ca. 1 Tag verbucht.",
"view_wallet": "{walletLabel} anzeigen",
"list_title": "Transaktionen",
"list_title_sent": "Gesendet",
"list_title_received": "Empfangen",
"transaction": "Transaktion",
"open_url_error": "Der Standardbrowser kann die URL nicht öffnen. Bitte diesen ggf. ändern, um es erneut zu versuchen.",
@ -378,11 +381,14 @@
"status_cancel": "Transaktion abbrechen",
"transactions_count": "Anzahl Transaktionen",
"txid": "Transaktions-ID",
"from": "Von: {counterparty}",
"to": "Zu: {counterparty}",
"updating": "Aktualisiere....",
"watchOnlyWarningTitle": "Sicherheitswarnung",
"watchOnlyWarningDescription": "Achtung: Betrüger verwenden „Watch-only\"-Wallets, um Nutzern echte Wallets vorzutäuschen. Mit dieser Wallet lassen sich keine Gelder kontrollieren oder senden, sondern nur der Kontostand einsehen.",
"watchOnlyWarningDescription": "Achtung. Betrüger verwenden „Watch-only“-Wallets um Nutzern echte Wallet vorzutäsuchen. Sie können mit diesem Wallet keine Gelder kontrollieren oder senden, sondern nur den Kontostand einsehen.",
"custom_fee_warning_title": "Warnung",
"custom_fee_warning_description": "Gebühren unter 1 sat/vB sind gültig, werden aber ggf. nicht weitergeleitet.",
"custom_fee_warning_description": "Gebühren unter 1 sat/vB sind gültig, aber werden ggf. nicht weitergeleitet.",
"list_title_sent": "Gesendet",
"details_eta_analyzing": "Analysiere...",
"details_sent": "Gesendet",
"details_section": "Details",
@ -405,19 +411,19 @@
"add_bitcoin_explain": "Einfache und leistungsstarke Bitcoin Wallet",
"add_create": "Erstellen",
"total_balance": "Gesamtes Guthaben",
"add_entropy_reset_title": "Entropie zurücksetzen",
"add_entropy_reset_message": "Ein Wechsel des Wallet-Typs wird die Entropie zurücksetzen. Wirklich fortfahren?",
"add_entropy_reset_title": "Entropie zurücksetzten",
"add_entropy_reset_message": "Ein Wechsel des Wallet Typs wird die Entropie zurücksetzten. Wirklich weiterfahren? ",
"add_entropy": "Entropie",
"add_entropy_bytes": "{bytes} Bytes Entropie",
"add_entropy_generated": "{gen} Bytes an generierter Entropie",
"add_entropy_generated": "{gen} Bytes an generierter Entropie ",
"add_entropy_provide": "Entropie selbst erzeugen",
"add_entropy_remain": "{gen} Bytes an generierter Entropie. Die restlichen {rem} Bytes werden vom Zufallsgenerator des Systems ergänzt.",
"add_import_wallet": "Wallet importieren",
"add_lightning": "Lightning",
"add_lightning_explain": "Für Ausgaben mit sofortigen Transaktionen",
"add_lndhub": "Mit deinem LNDhub verbinden",
"add_lndhub": "Ihren LNDhub verbinden",
"add_lndhub_error": "Die Adresse verweist auf einen ungültigen LNDhub-Knoten.",
"add_lndhub_placeholder": "Knoten-Adresse",
"add_lndhub_placeholder": "Bitcoin Knoten-Adresse",
"add_placeholder": "Mein Wallet",
"add_title": "Wallet hinzufügen",
"add_wallet_name": "Wallet Name",
@ -425,46 +431,42 @@
"add_wallet_seed_length": "Seedlänge",
"add_wallet_seed_length_12": "12 Wörter",
"add_wallet_seed_length_24": "24 Wörter",
"clipboard_bitcoin": "In der Zwischenablage ist eine Bitcoin-Adresse. Soll diese für eine Transaktion verwendet werden?",
"clipboard_bitcoin": "In der Zwischenablage ist eine Bitcoin Adresse. Soll diese für eine Transaktion verwendet werden?",
"clipboard_lightning": "In der Zwischenablage ist eine Lightning-Rechnung. Soll diese für eine Transaktion verwendet werden?",
"clear_clipboard_on_import": "Zwischenablage beim Import löschen",
"details_address": "Adresse",
"details_advanced": "Fortgeschritten",
"details_are_you_sure": "Wirklich ok?",
"details_connected_to": "Verbunden mit",
"details_del_wb_err": "Das angegebene Guthaben deckt sich nicht mit dem Guthaben der Wallet. Bitte versuche es erneut.",
"details_del_wb_q": "Dieses Wallet enthält noch Bitcoin. Ohne vorhandenes Backup des Seeds sind diese unwiederbringlich verloren. Um ein versehentliches Löschen zu vermeiden, gib bitte das Wallet-Guthaben von {balance} Satoshis ein.",
"details_del_wb_err": "Das angegebene Guthaben deckt sich nicht mit dem Guthaben des Wallet. Bitte versuche es erneut.",
"details_del_wb_q": "Dieses Wallet enthält noch bitcoin. Ohne vorhandenes Backup der mnemonischen Phrase sind diese unwiederbringlich verloren. Um ein versehentliches Löschen zu vermeiden, gib bitte das Wallet-Guthaben von {balance} Satoshis ein.",
"details_delete": "Löschen",
"details_delete_wallet": "Wallet löschen",
"details_derivation_path": "Ableitungspfad",
"details_display": "Auf der Startseite anzeigen",
"details_export_backup": "Exportieren / Backup",
"details_export_history": "Verlauf als CSV exportieren",
"details_master_fingerprint": "Master-Fingerabdruck",
"details_multisig_type": "Multisig",
"details_master_fingerprint": "Fingerabdruckkennung",
"details_multisig_type": "Mehrfachsignatur",
"details_show_xpub": "Wallet xPub zeigen",
"details_show_addresses": "Adressen anzeigen",
"details_title": "Wallet",
"wallets": "Wallets",
"swipe_balance_hide": "Verbergen",
"swipe_balance_show": "Anzeigen",
"drag_to_reorder": "Ziehen zum Neuanordnen",
"clear_search": "Suche löschen",
"details_type": "Typ",
"details_use_with_hardware_wallet": "Hardware Wallet nutzen",
"details_yes_delete": "Ja, löschen",
"enter_bip38_password": "Passwort zur Entschlüsselung eingeben",
"enter_bip38_password": "Passwort zur Entschlüssellung eingeben",
"export_title": "Wallet exportieren",
"import_do_import": "Importieren",
"import_passphrase": "Passphrase",
"import_passphrase_title": "Passphrase",
"import_passphrase_message": "Wenn genutzt, die Passphrase eingeben",
"import_error": "Fehler beim Import. Ist die Eingabe korrekt?",
"import_explanation": "Gib hier den Seed, den öffentlichen Schlüssel, WIF oder was immer du hast ein. BlueWallet wird bestmöglich das Format interpretieren und die Wallet importieren.",
"import_explanation": "Gib hier die mnemonische Phrase, den privaten Schlüssel, WIF oder was immer du hast ein. BlueWallet wird bestmöglich das Format interpretieren und die Wallet importieren.",
"import_imported": "Importiert",
"import_scan_qr": "QR-Code scannen oder Datei importieren",
"import_success": "Wallet wurde erfolgreich importiert.",
"import_success_watchonly": "Deine Wallet wurde erfolgreich importiert. WARNUNG: Dies ist eine reine Watch-only-Wallet, du kannst von ihr NICHT ausgeben.",
"import_success_watchonly": "Deine Wallet wurde erfolgreich importiert. WARNUNG: Dies ist eine reine Wallet nur zum Anschauen, du kannst NICHT mit ihr ausgeben.",
"import_search_accounts": "Konten suchen",
"import_title": "Importieren",
"learn_more": "Mehr erfahren",
@ -484,7 +486,7 @@
"list_create_a_wallet": "Wallet hinzufügen",
"list_create_a_wallet_text": "Kostenlose Wallets, \nunbegrenzt erstellbar.",
"list_empty_txs1": "Deine Transaktionen erscheinen hier",
"list_empty_txs1_lightning": "Verwende die Lightning-Wallet für deine täglichen Bezahlungen. Lightning-Transaktionen sind konkurrenzlos günstig und verblüffend schnell.",
"list_empty_txs1_lightning": "Verwende das Lightning Wallet für Deine täglichen Bezahlungen. Lightning Transaktionen sind konkurrenzlos günstig und verblüffend schnell.",
"list_empty_txs2": "Beginne mit deinem Wallet.",
"list_empty_txs2_lightning": "\nDrücke zum Starten «Beträge verwalten», um das Wallet aufzuladen.",
"list_latest_transaction": "Letzte Transaktion",
@ -494,30 +496,35 @@
"list_long_scan": "QR Code scannen",
"list_title": "Wallets",
"list_tryagain": "Nochmal versuchen",
"no_ln_wallet_error": "Vor Bezahlung einer Lightning-Rechnung zuerst eine Lightning-Wallet eröffnen.",
"no_ln_wallet_error": "Vor Bezahlung einer Lightning Rechnung zuerst ein Lightning Wallet eröffnen.",
"looks_like_bip38": "Passwortgeschützter Privatschlüssel (BIP38) erkannt.",
"manage_title": "Wallets verwalten",
"no_results_found": "Keine Ergebnisse gefunden.",
"please_continue_scanning": "Bitte Scanvorgang fortsetzen",
"select_no_bitcoin": "Es sind momentan keine Bitcoin-Wallets verfügbar.",
"select_no_bitcoin_exp": "Eine Bitcoin-Wallet ist Voraussetzung dafür, um eine Lightning-Wallet zu befüllen. Bitte erstelle oder importiere eine.",
"please_continue_scanning": "Bitte Scanvorgang fortsetzten",
"select_no_bitcoin": "Es sind momentan keine Bitcoin Wallets verfügbar.",
"select_no_bitcoin_exp": "Eine Bitcoin Wallet ist Voraussetzung dafür, um eine Lightning Wallet zu befüllen. Bitte erstelle oder importiere eines.",
"select_wallet": "Wähle eine Wallet",
"xpub_copiedToClipboard": "In die Zwischenablage kopiert.",
"pull_to_refresh": "Zum Aktualisieren ziehen",
"warning_do_not_disclose": "Niemals die nachfolgenden Informationen teilen",
"scan_import": "Diesen QR-Code zum Import der Wallet in einer anderen App scannen.",
"write_down_header": "Manuelles Backup erstellen",
"write_down": "Diese Worte aufschreiben und sicher verwahren. Sie sind nötig, um die Wallet später wiederherzustellen.",
"write_down": "Diese Worte aufschreiben und sicher verwahren. Sie sind nötig um die Wallet später wiederherzustellen.",
"wallet_type_this": "Wallet vom Typ {type}.",
"share_number": "Teil {number}",
"copy_ln_url": "Diese URL kopieren und sicher speichern. Sie ist nötig, um die Wallet später wiederherzustellen.",
"copy_ln_public": "Diese Information kopieren und sicher speichern. Sie ist nötig, um die Wallet später wiederherzustellen.",
"copy_ln_url": "Diese URL kopieren und sicher speichern. Sie ist um nötig die Wallet später wiederherzustellen.",
"copy_ln_public": "Diese Information kopieren und sicher speichern. Sie ist nötig um die Wallet später wiederherzustellen.",
"add_ln_wallet_first": "Bitte zuerst ein Lightning-Wallet hinzufügen.",
"identity_pubkey": "Pubkey-Identität",
"xpub_title": "Wallet xPub",
"manage_wallets_search_placeholder": "Wallets, Adressen, Transaktionen und Memos suchen",
"more_info": "Mehr Infos",
"details_delete_wallet_error_message": "Problem beim Entfernen der Wallet aus Benachrichtigungen evtl. Netzwerkproblem oder schlechte Verbindung. Bei Fortfahren könnten Transaktions-Benachrichtigungen für diese Wallet weiterhin ankommen, selbst nach deren Löschung.",
"details_delete_anyway": "Trotzdem löschen"
"details_delete_wallet_error_message": "Problem beim Entfernen der Wallet aus Benachrichtigungen evtl. Netzwerkproblem oder schlechte Verbindung. Bei Fortfahren könnten Transaktions-Benachrichtigungen für diese Wallet weiterhin ankommen, selbst nach dessen Löschung.",
"details_delete_anyway": "Trotzdem löschen",
"swipe_balance_hide": "Verbergen",
"swipe_balance_show": "Anzeigen",
"drag_to_reorder": "Ziehen zum Neuanordnen",
"clear_search": "Suche löschen"
},
"total_balance_view": {
"display_in_bitcoin": "In bitcoin anzeigen",
@ -532,20 +539,20 @@
"default_label": "Multisignatur Tresor",
"multisig_vault_explain": "Höchste Sicherheit für große Beträge",
"provide_signature": "Schlüssel eingeben",
"provide_signature_details": "Mit dem Gerät und der Wallet, in der der Schlüssel ist, die Transaktion signieren.",
"provide_signature_details": "Mit Ihrem Gerät und der Wallet, in der der Schlüssel ist, die Transaktion signieren.",
"provide_signature_details_bluewallet": "Zum Menü des Senden-Bildschirms gehen, dort auswählen",
"provide_signature_next_steps": "Signierte Transaktion scannen oder importieren",
"provide_signature_next_steps_details": "Sobald die Wallet die Transaktion signiert hat, den bereitgestellten QR-Code scannen oder die Datei importieren. Alle Transaktionsdetails vor dem Übertragen prüfen.",
"provide_signature_next_steps_details": "Sobald Ihre Wallet die Transaktion signiert hat, den bereitgestellten QR-Code scannen oder die Datei importieren. Alle Transaktionsdetails vor dem Übertragen prüfen.",
"vault_key": "Tresor-Schlüssel: {number}",
"required_keys_out_of_total": "Erforderliche Schlüssel aus dem Total",
"fee": "Gebühr: {number}",
"fee": "Gebhür: {number}",
"fee_btc": "{number} BTC",
"confirm": "Bestätigen",
"header": "Senden",
"share": "Teilen",
"view": "Anzeigen",
"shared_key_detected": "Geteilte Mitsignierer",
"shared_key_detected_question": "Ein Mitsignierer wurde mit dir geteilt. Diesen jetzt importieren?",
"shared_key_detected_question": "Ein Mitsignierer wurde mit Ihnen geteilt. Diesen jetzt importierten?",
"manage_keys": "Schlüssel verwalten",
"how_many_signatures_can_bluewallet_make": "Anzahl Signaturen durch BlueWallet",
"signatures_required_to_spend": "Benötigte Signaturen {number}",
@ -560,7 +567,7 @@
"legacy_title": "Altformat",
"co_sign_transaction": "Transaktion signieren",
"what_is_vault": "Ein Tresor ist ein ",
"what_is_vault_numberOfWallets": "{m}-von-{n} Multisig",
"what_is_vault_numberOfWallets": "{m}-von-{n} Multisignatur",
"what_is_vault_wallet": "wallet",
"vault_advanced_customize": "Tresor Einstellungen",
"needs": "Es braucht",
@ -571,39 +578,39 @@
"quorum_header": "Signaturfähigkeit",
"of": "von",
"wallet_type": "Typ des Wallets",
"invalid_mnemonics": "Ungültiger Seed.",
"invalid_cosigner": "Die Mitsignierer-Daten sind ungültig",
"not_a_multisignature_xpub": "Dies ist kein xPub einer Multisig-Wallet!",
"invalid_mnemonics": "Ungültige mnemonische Phrase.",
"invalid_cosigner": "Die Mitsignierer Daten sind ungültig",
"not_a_multisignature_xpub": "Dies ist keine XPUB eines Multisignatur-Wallet!",
"invalid_cosigner_format": "Falscher Mitsignierer: Dies ist kein Mitsignierer für das Format {format}.",
"create_new_key": "Neuerstellen",
"scan_or_open_file": "Datei scannen oder öffnen",
"i_have_mnemonics": "Seed des Schlüssels importieren",
"type_your_mnemonics": "Seed zum Import deines Tresor-Schlüssels eingeben",
"this_is_cosigners_xpub": "Dies ist der xPub für Mitsignierer zum Import in eine andere Wallet. Er kann sicher mit anderen geteilt werden.",
"type_your_mnemonics": "Seed zum Import deines Tresorschlüssels eingeben",
"this_is_cosigners_xpub": "Dies ist der xPub für Mitsigierer zum Import in ein anderes Wallet. Er kann sicher mit anderen geteilt werden.",
"this_is_cosigners_xpub_airdrop": "Zur AirDrop-Freigabe müssen alle Empfänger auf dem Koordinierungsbildschirm sein.",
"wallet_key_created": "Dein Tresor-Schlüssel wurde erstellt. Nimm dir Zeit, ein sicheres Backup des Seeds herzustellen.",
"are_you_sure_seed_will_be_lost": "Wirklich? Der Seed ist ohne Backup verloren!",
"wallet_key_created": "Dein Tresorschlüssel wurde erstellt. Nimm dir Zeit ein sicheres Backup des mnemonischen Seeds herzustellen. ",
"are_you_sure_seed_will_be_lost": "Wirklich ok? Der mnemonischer Seed ist ohne Backup verloren!",
"forget_this_seed": "Seed vergessen und xPub verwenden.",
"view_edit_cosigners": "Mitsignierer anzeigen/bearbeiten",
"view_edit_cosigners": "Mitsignierer Anzeigen/Bearbeiten",
"this_cosigner_is_already_imported": "Dieser Mitsignierer ist schon vorhanden.",
"export_signed_psbt": "Signierte PSBT exportieren",
"input_fp": "Fingerprint eingeben",
"input_fp": "Fingerabdruckkennung eingeben",
"input_fp_explain": "Überspringen, um den Standard zu verwenden (00000000)",
"input_path": "Ableitungspfad eingeben",
"input_path_explain": "Überspringen, um den Standard zu verwenden ({default})",
"ms_help": "Hilfe",
"ms_help_title": "Tipps und Tricks zur Funktionsweise von Multisig",
"ms_help_text": "Eine Wallet mit mehreren Schlüsseln zur gemeinsamen Verwahrung oder zur Erhöhung der Sicherheit.",
"ms_help_text": "Ein Wallet mit mehreren Schlüsseln zur gemeinsamen Verwahrung oder zur Erhöhung der Sicherheit.",
"ms_help_title1": "Dazu sind mehrere Geräte empfohlen.",
"ms_help_1": "Der Tresor funktioniert mit weiteren BlueWallet-Apps und PSBT-kompatiblen Wallets wie Electrum, Specter, Coldcard, Keystone, etc.",
"ms_help_1": "Der Tresor funktioniert mit weiteren BlueWallet Apps und PSBT kompatiblen Wallets wie Electrum, Specter, Coldcard, Keystone, etc.",
"ms_help_title2": "Schlüssel bearbeiten",
"ms_help_2": "Alle Tresor-Schlüssel lassen sich auf diesem Gerät erstellen und später löschen. Dazu in den Wallet-Einstellungen die Mitsignierer bearbeiten. Sind alle Tresor-Schlüssel auf dem gleichen Gerät, ist die Sicherheit die einer regulären Bitcoin-Wallet.",
"ms_help_2": "Alle Tresor Schlüssel lassen sich auf diesem Geräts erstellen und später löschen. Dazu in den Wallet-Einstellungen die Mitsignierer bearbeiten. Sind alle Tresorschlüssel auf dem gleichen Gerät, ist die Sicherheit, die eines regulären Bitcoin Wallet.",
"ms_help_title3": "Tresor-Sicherungen",
"ms_help_3": "Die Tresor-Backup- und Watch-only-Export-Funktion ist in den Wallet-Optionen. Geht ein Seed verloren, ist das Backup zur Wiederherstellung der Wallet essenziell. Es ist wie eine Karte zu deinem Vermögen.",
"ms_help_3": "Die Tresor Backup und Watch-only Export Funktion ist in den Wallet-Optionen. Geht ein Seed verloren, ist das Backup zur Wiederherstellung des Wallet essenziell. Es ist wie eine Karte zu Deinem Vermögen.",
"ms_help_title4": "Tresor importieren",
"ms_help_4": "Um einen Tresor zu importieren, die Multisig-Backupdatei mittels Import-Funktion laden. Wenn nur Seeds und xPubs vorhanden sind, können diese während der Tresor-Erstellung einzeln hinzugefügt werden.",
"ms_help_4": "Um ein Tresor zu importieren, die Multisignatur Backupdatei mittels Import-Funktion laden. Seeds der erweiterten Schlüssel während der Tresor-Erstellung hinzufügen.",
"ms_help_title5": "Erweiterte Optionen",
"ms_help_5": "Standardmäßig erstellt BlueWallet einen 2-von-3-Tresor. Zum Ändern der Anzahl oder des Adresstyps unter Einstellungen > Allgemein den erweiterten Modus aktivieren."
"ms_help_5": "Der geplante Tresor erfordert 2 von 3 Signaturen. Zum Ändern der Anzahl oder des Adresstyps unter Einstellungen > Allgemein den erweiterten Modus aktivieren."
},
"is_it_my_address": {
"title": "Ist dies meine Adresse?",
@ -614,22 +621,22 @@
"view_qrcode": "QR-Code anzeigen"
},
"autofill_word": {
"enter": "Den unvollständigen Seed eingeben",
"generate_word": "Erzeuge das letzte Wort",
"error": "Die Eingabe ist keine unvollendete 11- oder 23-Wort-Phrase. Bitte erneut versuchen.",
"enter": "Die unvollendete mnemonische Phrase eingeben",
"generate_word": "Erzeuge das letzte Word",
"error": "Die Eingabe ist keine unvollendete 11- oder 23 Wort Phrase. Bitte erneut versuchen.",
"title": "Letztes Seed-Wort"
},
"cc": {
"change": "Ändern",
"coins_selected": "Anz. gewählte Münzen ({number})",
"selected_summ": "{value} ausgewählt",
"empty": "Diese Wallet hat aktuell keine Münzen.",
"empty": "Dieses Wallet hat aktuell keine Münzen.",
"freeze": "einfrieren",
"freezeLabel": "Einfrieren",
"freezeLabel_un": "Auftauen",
"freezeLabel_un": "Entblocken",
"header": "Münzkontrolle",
"use_coin": "Münzen benutzen",
"use_coins": "Münzen benutzen",
"use_coins": "Benutze Münzen",
"tip": "Wallet-Verwaltung zum Anzeigen, Beschriften, Einfrieren oder Auswählen von Münzen. Zur Mehrfachselektion auf die Farbkreise tippen.",
"sort_asc": "Aufsteigend",
"sort_desc": "Absteigend",
@ -647,15 +654,15 @@
},
"addresses": {
"copy_private_key": "Privaten Schlüssel kopieren",
"sensitive_private_key": "Warnung: Private Schlüssel sind hochsensibel. Fortfahren?",
"sign_title": "Nachricht signieren/verifizieren",
"sensitive_private_key": "Warnung: Private Schlüssel sind gefahrvoll. Weiterfahren?",
"sign_title": "Meldung signieren/verifizieren",
"sign_help": "Auf einer Bitcoin-Adresse basierende, kryptografische Signatur erstellen oder verifizieren.",
"sign_sign": "Signieren",
"sign_verify": "Verifizieren",
"sign_signature_correct": "Verifizierung erfolgreich!",
"sign_signature_incorrect": "Verifizierung fehlgeschlagen!",
"sign_placeholder_address": "Adresse",
"sign_placeholder_message": "Nachricht",
"sign_placeholder_message": "Meldung",
"sign_placeholder_signature": "Signatur",
"addresses_title": "Adressen",
"type_change": "Wechsel",
@ -690,13 +697,13 @@
"copy_payment_code": "Zahlungscode kopieren",
"hide_contact": "Kontakt ausblenden",
"rename": "Umbenennen",
"provide_name": "Neuen Kontaktnamen eingeben",
"provide_name": "Neuer Kontaktnahme eingeben",
"add_contact": "Kontakt hinzufügen",
"provide_payment_code": "Zahlungscode eingeben",
"invalid_pc": "Ungültiger Zahlungscode",
"notification_tx_unconfirmed": "Benachrichtigungstransaktion noch unbestätigt. Bitte warten",
"failed_create_notif_tx": "On-Chain-Transaktion konnte nicht erstellt werden",
"onchain_tx_needed": "On-Chain-Transaktion benötigt.",
"failed_create_notif_tx": "On-Chain Transaktion konnte nicht in erstellt werden",
"onchain_tx_needed": "On-Chain Transaktion benötigt.",
"notif_tx_sent": "Benachrichtigungstransaktion ist gesendet. Auf Bestätigung warten.",
"notif_tx": "Benachrichtigungstransaktion",
"not_found": "Zahlungscode nicht gefunden"

View File

@ -70,12 +70,12 @@
"lndViewInvoice": {
"additional_info": "Επιπρόσθετη πληροφορία",
"for": "Για:",
"lightning_invoice": "Τιμολόγιο Lightning",
"lightning_invoice": "Τιμολόγιο Lightning",
"please_pay": "Παρακαλώ πληρώστε",
"sats": "sats.",
"wasnt_paid_and_expired": "Το τιμολόγιο δεν πληρώθηκε και έχει λήξει.",
"please_pay_between_and": "Παρακαλώ πληρώστε μεταξύ {min} και {max}",
"preimage": "Προεικόνα",
"sats": "sats.",
"date_time": "Ημερομηνία και Ώρα"
},
"plausibledeniability": {
@ -122,7 +122,7 @@
"create_broadcast": "Μετάδοση",
"create_copy": "Αντιγραφή και μετάδοση αργότερα",
"create_details": "Λεπτομέρειες",
"create_fee": "Προμήθεια",
"create_fee": "Κόστος",
"create_memo": "Σημείωση",
"create_satoshi_per_vbyte": "Satoshi ανά vByte",
"create_this_is_hex": "Αυτή είναι η υπογεγραμμένη συναλλαγή σε μορφή hex και είναι έτοιμη για αποστολή στο δίκτυο.",
@ -168,11 +168,11 @@
"psbt_sign": "Υπογραφή μιας συναλλαγής",
"open_settings": "Άνοιγμα ρυθμίσεων",
"permission_storage_denied_message": "Το BlueWallet δεν μπορεί να αποθηκεύσει αυτό το αρχείο. Παρακαλώ ανοίξτε τις ρυθμίσεις της συσκευής σας και ενεργοποιήστε την πρόσβαση στον αποθηκευτικό χώρο.",
"permission_storage_title": "Άδεια πρόσβασης στον αποθηκευτικό χώρο",
"permission_storage_title": "Άδεια πρόσβαση στον αποθηκευτικό χώρο",
"psbt_clipboard": "Αντιγραφή στο πρόχειρο",
"psbt_tx_export": "Εξαγωγή σε αρχείο",
"success_done": "Ολοκληρώθηκε",
"problem_with_psbt": "Πρόβλημα με PSBT",
"problem_with_psbt": "Πρόβλημα με PSBT",
"provided_address_is_invoice": "Αυτή η διεύθυνση φαίνεται να είναι για ένα τιμολόγιο Lightning. Παρακαλώ μεταβείτε στο πορτοφόλι Lightning για να πραγματοποιήσετε πληρωμή για αυτό το τιμολόγιο.",
"broadcastNone": "Εισαγωγή hex συναλλαγής",
"details_insert_contact": "Εισαγωγή επαφής",
@ -219,7 +219,7 @@
"about_sm_github": "GitHub",
"about_sm_telegram": "Κανάλι Telegram",
"biometrics": "Βιομετρικά",
"biom_conf_identity": "Παρακαλώ επιβεβαιώστε την ταυτότητά σας.",
"biom_conf_identity": "Παρακαλώ επιβεβαιώστε την ταυτότητα σας.",
"currency": "Νόμισμα",
"default_title": "Στην έναρξη",
"electrum_connected": "Σε σύνδεση",
@ -241,7 +241,7 @@
"language": "Γλώσσα",
"last_updated": "Τελευταία ενημέρωση",
"license": "Άδεια χρήσης",
"lightning_saved": "Οι αλλαγές σας αποθηκεύτηκαν με επιτυχία.",
"lightning_saved": "Η αλλαγές σας αποθηκεύτηκαν με επιτυχία.",
"lightning_settings": "Ρυθμίσεις Lightning",
"network": "Δίκτυο",
"network_broadcast": "Μετάδοση συναλλαγής",
@ -253,7 +253,7 @@
"privacy": "Ιδιωτικότητα",
"privacy_read_clipboard": "Ανάγνωση προχείρου",
"privacy_system_settings": "Ρυθμίσεις συστήματος",
"privacy_do_not_track": "Απενεργοποίηση ανάλυσης",
"privacy_do_not_track": "Απενεργοποίηση ανάλυσης ",
"selfTest": "Διαγνωστικός έλεγχος",
"save": "Σώσε",
"saved": "Αποθηκεύτηκε",
@ -308,7 +308,7 @@
"lightning_error_lndhub_uri": "Μη έγκυρο LNDhub URI",
"lightning_error_lndhub_uri_tor": "Μη έγκυρο LNDhub URI. Παρακαλώ βεβαιωθείτε ότι η εφαρμογή Orbot είναι συνδεδεμένη και δοκιμάστε ξανά.",
"lightning_settings_explain": "Για να συνδεθείτε στον δικό σας LND κόμβο, παρακαλώ εγκαταστήστε το LNDhub και βάλτε τη διεύθυνση URL του εδώ στις ρυθμίσεις. Σημειώστε ότι μόνο τα πορτοφόλια που δημιουργούνται μετά την αποθήκευση των αλλαγών θα συνδέονται στο καθορισμένο LNDhub.",
"lndhub_github": "Αποθετήριο GitHub",
"lndhub_github": "GitHub Repository",
"electrum_suggested_description": "Όταν δεν έχει οριστεί προτιμώμενος διακομιστής, ένας προτεινόμενος διακομιστής θα επιλέγεται για χρήση τυχαία.",
"open_link_in_explorer": "Άνοιγμα συνδέσμου στον εξερευνητή",
"password_explain": "Εισάγετε τον κωδικό που θα χρησιμοποιείτε για να ξεκλειδώνετε τον αποθηκευτικό σας χώρο.",
@ -331,18 +331,21 @@
"transactions": {
"cancel_no": "Αυτή η συναλλαγή δεν μπορεί να αντικατασταθεί.",
"confirmations_lowercase": "{confirmations} επιβεβαιώσεις",
"copy_link": "Αντιγραφή συνδέσμου",
"expand_note": "Επέκταση σημείωσης",
"cpfp_create": "Δημιουργία",
"details_balance_hide": "Απόκρυψη υπολοίπου",
"details_balance_show": "Εμφάνιση υπολοίπου",
"details_copy": "Αντέγραψε",
"details_copy_note": "Αντιγραφή σημείωσης",
"details_from": "Εισερχόμενες διευθύνσεις",
"date": "Ημερομηνία",
"details_received": "Ελήφθη",
"details_title": "Συναλλαγή",
"details_to": "Εξερχόμενες διευθύνσεις",
"pending": "Σε επεξεργασία",
"received_with_amount": "+{amt1} ({amt2})",
"view_wallet": "Προβολή {walletLabel}",
"list_title": "Συναλλαγές",
"list_title_received": "Ελήφθη",
"transaction": "Συναλλαγή",
@ -408,7 +411,7 @@
"add_entropy": "Εντροπία",
"add_import_wallet": "Εισαγωγή πορτοφολιού",
"add_lightning": "Lightning",
"add_lndhub_placeholder": "Η διεύθυνση του κόμβου σας",
"add_lndhub_placeholder": "Η διεύθυνση του κόμβου σας",
"add_placeholder": "το πρώτο μου πορτοφόλι",
"add_title": "Προσθήκη πορτοφολιού",
"add_wallet_name": "Όνομα",
@ -452,6 +455,7 @@
"list_title": "Πορτοφόλια",
"list_tryagain": "Προσπαθήστε ξανά",
"select_wallet": "Επιλογή πορτοφολιού",
"xpub_copiedToClipboard": "Αντιγράφηκε στο πρόχειρο",
"xpub_title": "XPUB του πορτοφολιού",
"add_entropy_reset_title": "Επαναφορά εντροπίας",
"add_entropy_reset_message": "Η αλλαγή του τύπου πορτοφολιού θα επαναφέρει την τρέχουσα εντροπία. Θέλετε να προχωρήσετε;",
@ -540,7 +544,7 @@
"native_segwit_title": "Καλές πρακτικές",
"co_sign_transaction": "Υπογραφή μιας συναλλαγής",
"what_is_vault_wallet": "πορτοφόλι.",
"wallet_type": "Τύπος πορτοφολιού",
"wallet_type": "Τύπος πορτοφολιου",
"create_new_key": "Δημιουργία νέου",
"scan_or_open_file": "Σάρωση ή άνοιγμα αρχείου",
"ms_help": "Βοήθεια",

View File

@ -56,7 +56,6 @@
"errorInvoiceExpired": "Invoice expired.",
"expired": "Expired",
"expiresIn": "Expires in {time} minutes",
"network_fee": "Network fee: {fee}",
"payButton": "Pay",
"payment": "Payment",
"placeholder": "Invoice or address",
@ -77,13 +76,7 @@
"preimage": "Pre-image",
"sats": "sats.",
"date_time": "Date and Time",
"wasnt_paid_and_expired": "This invoice was not paid and has expired.",
"receiving_payment": "Receiving payment…",
"refund_funds": "Refund funds",
"refund_deferred": "Funds aren't refundable yet. Try again after the swap timelock expires.",
"notification_action_title": "Action needed",
"notification_claim_body": "{walletLabel}: tap to claim your incoming Lightning payment.",
"notification_refund_body": "{walletLabel}: tap to refund your stuck Lightning payment."
"wasnt_paid_and_expired": "This invoice was not paid and has expired."
},
"plausibledeniability": {
"create_fake_storage": "Create Encrypted Storage",
@ -288,6 +281,7 @@
"general": "General",
"general_continuity": "Continuity",
"general_continuity_e": "When enabled, you will be able to view selected wallets, and transactions, using your other Apple iCloud connected devices.",
"groundcontrol_explanation": "GroundControl is a free, open-source push notifications server for Bitcoin wallets. You can install your own GroundControl server and put its URL here to not rely on BlueWallets infrastructure. Leave blank to use GroundControls default server.",
"header": "Settings",
"language": "Language",
"last_updated": "Last Updated",
@ -303,6 +297,7 @@
"network_broadcast": "Broadcast Transaction",
"network_electrum": "Electrum Server",
"electrum_suggested_description": "When a preferred server is not set, a suggested server will be selected for use at random.",
"not_a_valid_uri": "Invalid URI",
"notifications": "Notifications",
"open_link_in_explorer": "Open link in explorer",
"password": "Password",
@ -320,6 +315,7 @@
"push_notifications_explanation": "By enabling notifications, your device token will be sent to the server, along with wallet addresses and transaction IDs for all wallets and transactions made after enabling notifications. The device token is used to send notifications, and the wallet information allows us to notify you about incoming Bitcoin or transaction confirmations.\n\nOnly information from after you enable notifications is transmitted—nothing from before is collected.\n\nDisabling notifications will remove all of this information from the server. Additionally, deleting a wallet from the app will also remove its associated information from the server.",
"selfTest": "Self-Test",
"save": "Save",
"saved": "Saved",
"success_transaction_broadcasted": "Your transaction has been successfully broadcasted!",
"total_balance": "Total Balance",
"total_balance_explanation": "Display the total balance of all your wallets on your home screen widgets.",
@ -380,6 +376,7 @@
"rbf_title": "Speed Up (RBF)",
"status_bump": "Speed Up",
"status_cancel": "Cancel",
"transactions_count": "Transactions Count",
"txid": "Transaction ID",
"updating": "Updating...",
"watchOnlyWarningTitle": "Security warning",
@ -441,18 +438,13 @@
"details_delete_wallet": "Delete Wallet",
"details_derivation_path": "derivation path",
"details_display": "Display in Home Screen",
"details_edit": "edit",
"details_export_backup": "Export/Backup",
"details_export_history": "Export History to CSV",
"details_master_fingerprint": "Master Fingerprint",
"details_multisig_type": "multisig",
"details_options": "Options",
"details_show_xpub": "Show Wallet XPUB",
"details_show_addresses": "Show addresses",
"details_stats_coins": "Coins",
"details_title": "Wallet",
"restore_swap_activity": "Restore swap activity",
"restore_swap_activity_done": "Swap activity restored.",
"wallets": "Wallets",
"swipe_balance_hide": "Hide",
"swipe_balance_show": "Show",

View File

@ -2,10 +2,10 @@
"_": {
"bad_password": "Contraseña incorrecta. Por favor, inténtalo de nuevo.",
"cancel": "Cancelar",
"continue": "Continuar",
"continue": "Continua",
"clipboard": "Portapapeles",
"copied": "¡Copiado!",
"discard_changes": "¿Descartar cambios?",
"discard_changes": "¿Descartar cambios? ",
"discard_changes_explain": "Tienes cambios sin guardar. ¿Seguro que quieres descartarlos y salir de la pantalla?",
"enter_password": "Introduce la contraseña",
"enter_url": "Introduce la URL",
@ -43,14 +43,14 @@
},
"entropy": {
"save": "Guardar",
"title": "Entropía",
"title": "Entropía ",
"undo": "Deshacer",
"amountOfEntropy": "{bits} de {limit} bits"
},
"errors": {
"broadcast": "Emisión fallida",
"network": "Error de red",
"error": "Error"
"error": "Error",
"network": "Error de red"
},
"lnd": {
"errorInvoiceExpired": "Factura caducada.",
@ -70,19 +70,19 @@
"lndViewInvoice": {
"additional_info": "Información adicional",
"for": "Para:",
"lightning_invoice": "Factura Lightning",
"lightning_invoice": "Factura Lighting",
"please_pay_between_and": "Paga entre {min} y {max}",
"please_pay": "Por favor, paga",
"please_pay": "Por favor, pague",
"preimage": "Preimagen",
"sats": "sats.",
"date_time": "Fecha y hora",
"wasnt_paid_and_expired": "Esta factura no fue pagada y ha expirado.",
"sats": "sats"
"wasnt_paid_and_expired": "Esta factura no fue pagada y ha expirado."
},
"plausibledeniability": {
"create_fake_storage": "Crear almacenamiento cifrado",
"create_password_explanation": "La contraseña para el almacenamiento falso no puede ser igual que la del almacenamiento principal.",
"help": "Bajo ciertas circunstancias, podrías verte forzado a revelar tu contraseña. Para proteger tus fondos, BlueWallet puede crear otro almacenamiento cifrado con una contraseña diferente. Proporciona esta otra contraseña a quien te esté obligando a hacerlo y BlueWallet mostrará un almacenamiento \"falso\" que parecerá legítimo. Así mantendrás a buen recaudo el almacenamiento con tus fondos.",
"help2": "El nuevo almacén será completamente funcional, y puedes almacenar cantidades mínimas para que sea más creíble.",
"help2": "El nuevo almacen sera completamente funcional, y puedes almacenar cantidades minimas para que sea mas creible.",
"password_should_not_match": "Esta contraseña ya está en uso. Por favor, introduce una diferente.",
"title": "Negación plausible"
},
@ -92,7 +92,7 @@
"ask_yes": "Sí, la he guardado.",
"ok": "OK, ya la he anotado.",
"ok_lnd": "OK, lo he guardado.",
"text": "Por favor, apunta esta frase mnemotécnica en un papel.\nSerá tu copia de seguridad y te permitirá restaurar la cartera en otro dispositivo.",
"text": "Por favor, apunta esta frase mnemotécnica en un papel. Será tu copia de seguridad y te permitirá restaurar la cartera en otro dispositivo.",
"text_lnd": "Por favor guarda la copia de seguridad de esta cartera. Te permitirá restaurarla en caso de pérdida.",
"title": "Tu cartera ha sido creada"
},
@ -122,37 +122,37 @@
"confirm_sendNow": "Enviar ahora",
"create_amount": "Cantidad",
"create_broadcast": "Emitir",
"create_copy": "Copiar y emitir más tarde",
"create_copy": "Escanear código QR",
"create_details": "Detalles",
"create_fee": "Comisión",
"create_memo": "Comentario",
"create_satoshi_per_vbyte": "Satoshis por vByte",
"create_this_is_hex": "Esta es tu transacción en formato hexadecimal—firmada y lista para ser emitida a través de la red.",
"create_this_is_hex": "Esta es tu transacción en formato hexadecimal—firmada y lista para ser emitido a través de la red.",
"create_to": "Dirección de destino",
"create_tx_size": "Tamaño de la transacción",
"create_verify": "Verificar en coinb.in",
"details_add_rec_add": "Añadir destinatario",
"details_add_rec_rem": "Borrar destinatario",
"details_address": "Dirección",
"details_address_field_is_not_valid": "La dirección no es válida.",
"details_address_field_is_not_valid": "La dirección no es válida",
"details_adv_fee_bump": "Permitir aumentar la comisión",
"details_adv_full": "Usar todo el balance",
"details_adv_full_sure": "¿Estás seguro de querer utilizar todo el saldo de tu cartera en esta transacción?",
"details_adv_full_sure_frozen": "¿Estás seguro de que deseas utilizar todo el balance de tu cartera para esta transacción? Ten en cuenta que las monedas congeladas están excluidas.",
"details_adv_full_sure_frozen": "¿Estás seguro de que deseas utilizar todo el balance de tu wallet para esta transacción?",
"details_adv_import": "Importar transacción",
"details_adv_import_qr": "Importar transacción (QR)",
"details_amount_field_is_not_valid": "La cantidad no es válida.",
"details_amount_field_is_less_than_minimum_amount_sat": "La cantidad especificada es muy pequeña. Por favor, introduce una cantidad mayor a 500 sats.",
"details_amount_field_is_not_valid": "La cantidad no es válida",
"details_amount_field_is_less_than_minimum_amount_sat": "La cantidad especificada es muy pequeña. Por favor, introduzca una cantidad mayor a 500 sats. ",
"details_create": "Crear factura",
"details_error_decode": "No se ha podido decodificar la dirección de Bitcoin",
"details_fee_field_is_not_valid": "La comisión introducida no es válida.",
"details_fee_field_is_not_valid": "La comisión introducida no es válida",
"details_next": "Siguiente",
"details_no_signed_tx": "El archivo seleccionado no contiene una transacción que se pueda importar.",
"details_note_placeholder": "Nota personal",
"details_scan": "Escanear",
"details_scan_hint": "Toca dos veces para escanear o importar un destino",
"details_total_exceeds_balance": "El monto excede el balance disponible.",
"details_total_exceeds_balance_frozen": "La cantidad a enviar excede el balance disponible. Ten en cuenta que las monedas congeladas están excluidas.",
"details_total_exceeds_balance_frozen": "La cantidad a enviar excede el balance disponible. Tenga en cuenta que las monedas congeladas están excluidas",
"details_unrecognized_file_format": "Formato no reconocido",
"details_wallet_before_tx": "Antes de crear una transacción debes añadir una cartera de Bitcoin.",
"dynamic_init": "Iniciando",
@ -183,14 +183,14 @@
"psbt_this_is_psbt": "Esta transacción está parcialmente firmada (PSBT). Por favor, termina de firmarla con tu cartera de hardware.",
"psbt_tx_export": "Exportar a archivo",
"no_tx_signing_in_progress": "No hay ninguna transacción siendo firmada en estos momentos.",
"outdated_rate": "Fecha de la última actualización del tipo de cambio: {date}",
"outdated_rate": "Fecha de la última actualización de la tarifa de cambio: {date}",
"psbt_tx_open": "Abrir transacción firmada",
"psbt_tx_scan": "Escanear transacción firmada",
"reset_amount": "Cantidad predeterminada",
"reset_amount_confirm": "¿Quieres volver a la cantidad predeterminada?",
"success_done": "Completado",
"details_insert_contact": "Insertar contacto",
"details_add_recc_rem_all_alert_description": "¿Seguro que quieres eliminar a todos los destinatarios?",
"details_add_recc_rem_all_alert_description": "¿Seguro que quieres eliminar todos los destinatarios?",
"details_add_rec_rem_all": "Eliminar todos los destinatarios",
"details_recipients_title": "Destinatarios",
"details_recipient_title": "Destinatario #{number} de #{total}",
@ -210,15 +210,15 @@
},
"settings": {
"about": "Sobre nosotros",
"about_awesome": "Construido con el increíble",
"about_awesome": "Built with the awesome",
"about_backup": "¡Guarda siempre una copia de seguridad de tus llaves!",
"about_free": "BlueWallet es un proyecto de código abierto y gratuito. Elaborado por usuarios de Bitcoin.",
"about_license": "Licencia MIT",
"about_release_notes": "Notas de la versión",
"about_review": "Escribe una reseña",
"about_selftest": "Iniciar test local",
"about_selftest_electrum_disabled": "La autocomprobación no está disponible en el modo sin conexión de Electrum. Desactiva el modo sin conexión y vuelve a intentarlo.",
"about_selftest_ok": "Todas las pruebas internas han concluido satisfactoriamente. La cartera funciona bien.",
"about_selftest_electrum_disabled": "La autocomprobación no está disponible en el modo sin conexión de Electrum. Desactiva el modo sin conexión y vuelve a intentarlo. ",
"about_selftest_ok": "Todas las pruebas internas han concluido satisfactoriamente. La billetera funciona bien. ",
"about_sm_github": "GitHub",
"about_sm_telegram": "Canal de Telegram",
"biometrics": "Biometría",
@ -233,14 +233,14 @@
"lndhub_uri": "Ej.: {example}",
"electrum_host": "Ej.: {example}",
"electrum_offline_mode": "Modo offline",
"electrum_offline_description": "Al habilitarlo, tus carteras de Bitcoin no intentarán actualizar balances ni transacciones.",
"electrum_offline_description": "Al habilitarlo, tus carteras de Bitcoin no intentaran actualizar balances ni transacciones.",
"electrum_port": "Puerto, generalmente {example}",
"use_ssl": "Utiliza SSL",
"electrum_saved": "Los cambios se han guardado. Puede que se requiera reiniciar la aplicación para que tomen efecto.",
"set_electrum_server_as_default": "¿Establecer {server} como servidor Electrum por defecto?",
"electrum_settings_server": "Servidor Electrum",
"electrum_status": "Estado",
"electrum_unable_to_connect": "Imposible conectar a {server}.",
"electrum_unable_to_connect": "Imposible conectar a {server}. ",
"electrum_reset": "Restablecer valores predeterminados",
"encrypt_decrypt": "Desencriptar almacenamiento",
"encrypt_decrypt_q": "¿Seguro que quieres desencriptar tu almacenamiento? Al hacerlo, se podrá acceder a tus carteras sin contraseña.",
@ -249,7 +249,7 @@
"encrypt_use": "Usar {type}",
"general": "General",
"general_continuity": "Continuidad",
"general_continuity_e": "Al activarlo, podrás ver las transacciones y carteras seleccionadas usando cualquiera de tus dispositivos Apple conectados a iCloud.",
"general_continuity_e": "Al activarlo, podrá ver las transacciones y carteras seleccionadas usando cualquiera de sus dispositivos Apple conectados a iCloud.",
"groundcontrol_explanation": "GroundControl es un servidor gratuito y de código abierto de notificaciones push para carteras Bitcoin. Puedes instalar tu propio servidor de GroundControl y poner su URL aquí para no depender del de BlueWallet. Déjalo en blanco para usar el predeterminado.",
"header": "Ajustes",
"language": "Idioma",
@ -270,10 +270,10 @@
"privacy_system_settings": "Configuración del sistema",
"privacy_quickactions": "Atajos para tus carteras",
"privacy_clipboard_explanation": "Muestra atajos si encuentra direcciones o facturas en tu portapapeles.",
"privacy_do_not_track": "Deshabilitar analíticas",
"privacy_do_not_track": "Desabilitar Analytics",
"privacy_do_not_track_explanation": "Los datos sobre funcionamiento y fiabilidad no serán enviados para ser analizados.",
"rate": "Tasa",
"selfTest": "Autodiagnóstico",
"selfTest": "Self-Test",
"save": "Guardar",
"saved": "Guardado",
"total_balance": "Balance total",
@ -323,7 +323,7 @@
"success_transaction_broadcasted": "¡Tu transacción se ha emitido correctamente!"
},
"notifications": {
"would_you_like_to_receive_notifications": "¿Quieres recibir notificaciones cuando detectemos transferencias entrantes?",
"would_you_like_to_receive_notifications": "¿Quires recibir notificaciones cuando detectemos transferencias entrantes?",
"notifications_subtitle": "Pagos entrantes y confirmaciones de transacciones",
"no_and_dont_ask": "No, y no me lo vuelvas a preguntar.",
"permission_denied_message": "Has denegado el permiso para enviarte notificaciones. Si quieres recibir notificaciones, por favor habilítalas en los ajustes de tu dispositivo."
@ -333,6 +333,7 @@
"cancel_no": "Esta transacción no se puede reemplazar.",
"cancel_title": "Cancelar esta transacción (RBF)",
"confirmations_lowercase": "{confirmations} confirmaciones",
"copy_link": "Copiar enlace",
"expand_note": "Expandir Nota",
"cpfp_create": "Crear",
"cpfp_exp": "Crearemos una nueva transacción que gastará tu transacción aún no confirmada. La comisión total será mayor que la comisión original, lo cual debería hacer que sea minada en menos tiempo. Esto se denomina CPFP—Child Pays For Parent.",
@ -344,31 +345,33 @@
"details_copy_block_explorer_link": "Copiar el enlace del explorador de bloques",
"details_copy_note": "Copiar nota",
"details_copy_txid": "Copiar el ID de la transacción",
"details_inputs": "Entradas",
"details_outputs": "Salidas",
"details_from": "Origen",
"details_inputs": "Inputs",
"details_outputs": "Outputs",
"date": "Fecha",
"details_received": "Recibido",
"details_title": "Transacción",
"details_title": "Transaccion",
"details_to": "Destino",
"enable_offline_signing": "Esta cartera no se está usando en conjunción con una firma offline. ¿Quieres activarlo ahora?",
"enable_offline_signing": "Esta billetera no se está usando en conjunción con una firma offline. ¿Quieres activarlo ahora? ",
"list_conf": "Conf: {number}",
"pending": "En espera",
"pending_with_amount": "En espera {amt1} ({amt2})",
"received_with_amount": "+{amt1} ({amt2})",
"eta_10m": "Tiempo estimado: En ~10 minutos",
"eta_3h": "Tiempo estimado: En ~3 horas",
"eta_1d": "Tiempo estimado: En ~1 día",
"list_conf": "Conf: {number}",
"view_wallet": "Ver {walletLabel}",
"list_title": "Transacciones",
"list_title_received": "Recibido",
"transaction": "Transacción",
"transaction": "Transaccion",
"open_url_error": "No se puede abrir el enlace con el navegador predeterminado. Cambia tu navegador predeterminado y vuelve a intentarlo.",
"rbf_explain": "Reemplazaremos esta transacción con una tarifa más alta para que se ejecute más rápido. Esto se llama RBF—Reemplazo por comisión.",
"rbf_title": "Incrementar comisión (RBF)",
"status_bump": "Aumentar comisión",
"status_bump": "Aumentar comisón",
"status_cancel": "Cancelar transacción",
"transactions_count": "Número de transacciones",
"txid": "ID de transacción",
"updating": "Actualizando...",
"txid": "ID de transacción ",
"updating": "Actualizando... ",
"transaction_loading_error": "Ha habido un problema al cargar la transacción. Por favor, inténtalo de nuevo más tarde.",
"transaction_not_available": "Transacción no disponible",
"details_view_in_browser": "Ver en el navegador",
@ -389,6 +392,7 @@
"details_explorer": "explorador",
"details_network_fee": "Comisión de red",
"details_to_address": "Para",
"details_id": "ID",
"details_note": "Nota",
"details_add_note": "añadir",
"details_advanced": "Avanzado",
@ -397,18 +401,17 @@
"details_virtual_size": "Tamaño virtual",
"details_tx_hex": "Hex de la tx",
"details_inputs_count": "Entradas ({count})",
"details_outputs_count": "Salidas ({count})",
"details_id": "ID"
"details_outputs_count": "Salidas ({count})"
},
"wallets": {
"add_bitcoin": "Bitcoin",
"add_bitcoin_explain": "Una cartera de Bitcoin útil y fácil de usar",
"add_bitcoin_explain": "Una cartera de Bitcoin útil y facil de usar",
"add_create": "Crear",
"total_balance": "Balance total",
"add_entropy": "Entropía",
"add_entropy": "Entropía ",
"add_entropy_generated": "{gen} bytes de entropía generada",
"add_entropy_provide": "Entropía lanzando dados",
"add_entropy_remain": "{gen} bytes de entropía generada. Los {rem} bytes restantes serán obtenidos del generador de números aleatorios.",
"add_entropy_remain": "{gen} bytes of entropía generada. Los {rem} bytes restantes serán obtenidos del generador de números aleatorios.",
"add_import_wallet": "Importar cartera",
"add_lightning": "Lightning",
"add_lightning_explain": "Para pagos con transferencias instantáneas",
@ -437,15 +440,15 @@
"wallets": "Carteras",
"details_type": "Tipo",
"details_use_with_hardware_wallet": "Usar con cartera de hardware",
"details_yes_delete": "Sí, eliminar",
"enter_bip38_password": "Introduce la contraseña para descifrar",
"details_yes_delete": "Si, eliminar",
"enter_bip38_password": "Introduce el password para descifrar",
"export_title": "Exportación de cartera",
"import_do_import": "Importar",
"import_passphrase": "Frase de contraseña",
"import_passphrase_title": "Frase de contraseña",
"import_passphrase": "Passphrase",
"import_passphrase_title": "Passphrase",
"import_passphrase_message": "Introduce la passphrase si has usado una",
"import_error": "Error al importar. Por favor, asegúrate de que los datos introducidos son correctos.",
"import_explanation": "Introduce las palabras de tu semilla, llave pública, WIF, o cualquier otra cosa que tengas. BlueWallet hará lo posible para descifrar el formato correcto e importar tu cartera.",
"import_explanation": "Introduce las palabras de tu semilla, llave pública, WIF, o cualquier otra cosa que tengas. BlueWallet hará lo posible para descifrar el formato correcto e importar tu cartera. ",
"import_imported": "Importado",
"import_scan_qr": "Escanear o importar un archivo",
"import_success": "Tu cartera ha sido importada.",
@ -469,15 +472,16 @@
"list_long_scan": "Escanear código QR",
"list_title": "Carteras",
"list_tryagain": "Inténtalo otra vez",
"no_ln_wallet_error": "Antes de pagar una factura Lightning, primero debes agregar una cartera Lightning.",
"no_ln_wallet_error": "Antes de pagar una factura Lightning, primero debe agregar una cartera Lightning.",
"looks_like_bip38": "Parece que esto es una llave privada protegida con contraseña (BIP38).",
"please_continue_scanning": "Por favor, continúa escaneando.",
"select_no_bitcoin": "No hay carteras de Bitcoin disponibles.",
"select_no_bitcoin_exp": "Una cartera de Bitcoin es necesaria para recargar una cartera Lightning. Por favor, crea o importa una.",
"select_no_bitcoin_exp": "Una cartera de Bitcoin es necesaria para recargar una cartera Lightning. Por favor, cree o importe una.",
"select_wallet": "Selecciona una cartera",
"xpub_copiedToClipboard": "Copiado a portapapeles.",
"pull_to_refresh": "Desliza el dedo de arriba a abajo para actualizar",
"add_ln_wallet_first": "Primero tienes que agregar una cartera Lightning.",
"identity_pubkey": "Clave pública de identidad",
"identity_pubkey": "Identity Pubkey",
"xpub_title": "XPUB de la cartera",
"add_entropy_reset_title": "Restablecer entropía",
"add_entropy_reset_message": "Cambiar el tipo de cartera restablecerá la entropía actual. ¿Quieres continuar?",
@ -544,8 +548,8 @@
"signatures_required_to_spend": "{number} firmas requeridas",
"signatures_we_can_make": "puede hacer {number}",
"scan_or_import_file": "Escanear o importar archivo",
"export_coordination_setup": "Exportar coordinación",
"cosign_this_transaction": "¿Co-firmar esta transacción?",
"export_coordination_setup": "exportar coordinacion",
"cosign_this_transaction": "Co-firmar esta transacción?",
"lets_start": "Comencemos",
"create": "Crear",
"native_segwit_title": "La mejor opción para la mayoría de usuarios",
@ -580,12 +584,12 @@
"ms_help": "Ayuda",
"ms_help_title": "Cómo funcionan las Vaults multifirma: Consejos y trucos",
"ms_help_text": "Una cartera con múltiples claves, para aumentar exponencialmente la seguridad o para custodia compartida.",
"ms_help_title1": "Se recomienda usar múltiples dispositivos.",
"ms_help_title1": "Se recomienda usar multiples dispositivos.",
"ms_help_1": "La Vault funcionará con otras aplicaciones de BlueWallet y carteras compatibles con PSBT como Electrum, Spectre, Coldcard, Cobo Vault, etc.",
"ms_help_title2": "Editar claves",
"ms_help_2": "Puedes crear todas las claves de la Vault en este dispositivo y eliminarlas o editarlas más tarde. Tener todas las claves en el mismo dispositivo tiene la misma seguridad que una cartera de Bitcoin convencional.",
"ms_help_title3": "Copias de seguridad de la Vault",
"ms_help_3": "En las opciones de la cartera, encontrarás la copia de seguridad de tu Vault y de tu cartera de solo-ver. Esta copia de seguridad es como un mapa de tu cartera. Es esencial para recuperar tu cartera si pierdes una de tus semillas.",
"ms_help_3": "En las opciones de la cartera, encontrarás la copia de seguridad de tu Vault y de tu cartera de solo-ver. Esta copia de seguridad es como un mapa de tu cartera. Es esencial para recuperar tu cartera si pierdes una de tus semillas. ",
"ms_help_title4": "Importando Vaults",
"ms_help_4": "Para importar una Vault multifirma, selecciona la opción de importar y usa la copia de seguridad. Si solo tienes claves extendidas y semillas, puedes usar los campos de importación individuales durante el proceso de crear una Vault.",
"ms_help_title5": "Opciones avanzadas",
@ -659,16 +663,16 @@
},
"lnurl_auth": {
"register_question_part_1": "¿Te gustaría registrar una cuenta en",
"register_question_part_2": "usando tu cartera Lightning?",
"register_question_part_2": "usando tu billetera Lightning?",
"register_answer": "¡Has registrado con éxito una cuenta en {hostname}!",
"login_question_part_1": "¿Te gustaría iniciar sesión en",
"login_question_part_2": "usando tu cartera Lightning?",
"login_question_part_2": "usando tu billetera Lightning?",
"login_answer": "¡Has iniciado sesión correctamente en {hostname}!",
"link_question_part_1": "¿Te gustaría vincular tu cuenta en",
"link_question_part_2": "a tu cartera Lightning?",
"link_answer": "¡Tu cartera Lightning se vinculó con éxito a tu cuenta en {hostname}!",
"auth_question_part_1": "¿Te gustaría ser autenticado en",
"auth_question_part_2": "usando tu cartera Lightning?",
"auth_question_part_2": "usando tu billetera Lightning?",
"auth_answer": "¡Te has autenticado con éxito en {hostname}!",
"could_not_auth": "No pudimos autenticarte en {hostname}.",
"authenticate": "Autenticar"

View File

@ -2,19 +2,19 @@
"_": {
"bad_password": "Contraseña incorrecta. Intenta nuevamente.",
"cancel": "Cancelar",
"continue": "Continuar",
"continue": "Continúa",
"clipboard": "Portapapeles",
"copied": "¡Copiado!",
"discard_changes": "¿Descartar cambios?",
"discard_changes_explain": "Tienes cambios sin guardar. ¿Estás seguro de que quieres descartarlos y salir de la pantalla?",
"enter_password": "Ingresar contraseña",
"no": "No",
"never": "Nunca",
"of": "{number} de {total}",
"ok": "OK",
"enter_url": "Introducir URL",
"storage_is_encrypted": "Tu almacenamiento está encriptado. Se requiere contraseña para descifrarlo.",
"yes": "Sí",
"no": "No",
"save": "Guardar",
"seed": "Semilla",
"success": "Éxito",
@ -43,7 +43,7 @@
},
"entropy": {
"save": "Guardar",
"title": "Entropía",
"title": "Entropía ",
"undo": "Deshacer",
"amountOfEntropy": "{bits} de {limit} bits"
},
@ -59,7 +59,7 @@
"payButton": "Pagar",
"payment": "Pago",
"placeholder": "Factura o dirección",
"potentialFee": "Comisión potencial: {fee}",
"potentialFee": "Tasas potenciales: {fee}",
"refill": "Recarga",
"refill_create": "Para continuar, crea una billetera Bitcoin para recargar.",
"refill_external": "Recarga con billetera externa",
@ -72,8 +72,8 @@
"for": "Para:",
"lightning_invoice": "Factura Lightning",
"please_pay_between_and": "Paga entre {min} y {max}",
"please_pay": "Por favor paga",
"preimage": "Preimagen",
"please_pay": "Pagar por favor",
"preimage": "Imagen previa",
"sats": "sats.",
"date_time": "Fecha y hora",
"wasnt_paid_and_expired": "Esta factura no se pagó y ha caducado."
@ -82,7 +82,7 @@
"create_fake_storage": "Crear almacenamiento encriptado",
"create_password_explanation": "La contraseña del almacenamiento falso no debe coincidir con la contraseña de tu almacenamiento principal.",
"help": "Bajo ciertas circunstancias, podrías verte obligado a revelar una contraseña. Para mantener tus monedas seguras, BlueWallet puede crear otro almacenamiento encriptado, con una contraseña diferente. Bajo presión puedes revelar esta contraseña a un tercero. Si se ingresa en BlueWallet, desbloqueará un nuevo almacenamiento \"falso\". Esto parecerá legítimo para un tercero, pero en secreto mantendrá tu almacenamiento principal con monedas seguras.",
"help2": "El nuevo almacén será completamente funcional, y puedes almacenar cantidades mínimas para que sea más creíble.",
"help2": "El nuevo almacén será completamente funcional, y puedes almacenar cantidades mínimas para que sea mas creíble.",
"password_should_not_match": "La contraseña está actualmente en uso. Intenta con una contraseña diferente.",
"title": "Negación plausible"
},
@ -113,7 +113,7 @@
},
"send": {
"provided_address_is_invoice": "Esta dirección parece ser para una factura Lightning. Por favor, ve a tu billetera Lightning para realizar el pago de esta factura.",
"broadcastButton": "Transmitir",
"broadcastButton": "Transmitiendo",
"broadcastError": "Error",
"broadcastNone": "Introducir hex de transacción",
"broadcastPending": "Pendiente",
@ -124,7 +124,7 @@
"create_broadcast": "Transmitir",
"create_copy": "Copiar y transmitir más tarde",
"create_details": "Detalles",
"create_fee": "Comisión",
"create_fee": "Tasa",
"create_memo": "Nota",
"create_satoshi_per_vbyte": "Satoshi por vByte",
"create_this_is_hex": "Este es el hex de tu transacción, firmado y listo para ser transmitido a la red.",
@ -152,11 +152,12 @@
"details_amount_field_is_less_than_minimum_amount_sat": "La cantidad especificada es demasiado pequeña. Introduce una cantidad superior a 500 sats.",
"details_create": "Crear Factura",
"details_error_decode": "No se puede decodificar la dirección de Bitcoin",
"details_fee_field_is_not_valid": "La comisión no es válida.",
"details_fee_field_is_not_valid": "La tasa no es válida.",
"details_frozen": "{amount} BTC está congelado.",
"details_next": "Siguiente",
"details_no_signed_tx": "El archivo seleccionado no contiene una transacción que se pueda importar.",
"details_note_placeholder": "Nota personal",
"counterparty_label_placeholder": "Editar nombre de contacto",
"details_scan": "Escanear",
"details_scan_hint": "Toca dos veces para escanear o importar un destino",
"details_scan_error": "Error de escaneo",
@ -173,10 +174,10 @@
"fee_1d": "1d",
"fee_3h": "3h",
"fee_custom": "Personalizado",
"insert_custom_fee": "Insertar comisión",
"insert_custom_fee": "Insertar tasa",
"fee_fast": "Rápido",
"fee_medium": "Medio",
"fee_replace_minvb": "La comisión total (satoshi por vByte) que deseas pagar debe ser superior a {min} sat/vByte.",
"fee_replace_minvb": "La tasa de tarifa total (satoshi por vByte) que deseas pagar debe ser superior a {min} sat/vByte.",
"fee_satvbyte": "en sat/vByte",
"fee_slow": "Lento",
"header": "Enviar",
@ -191,10 +192,10 @@
"permission_storage_denied_message": "BlueWallet no puede guardar este archivo. Por favor, abre la configuración de tu dispositivo y activa el permiso de almacenamiento.",
"permission_storage_title": "Permiso de acceso de almacenamiento",
"psbt_clipboard": "Copiar al portapapeles",
"psbt_this_is_psbt": "Esta es una Transacción Bitcoin Parcialmente Firmada (PSBT). Para finalizar, por favor fírmala con tu billetera de hardware.",
"psbt_this_is_psbt": "Esta es una Transacción Bitcoin Parcialmente Firmada (PSBT). Para finalizar por favor fírmala con tu hardware wallet.",
"psbt_tx_export": "Exportar a archivo",
"no_tx_signing_in_progress": "No hay ninguna transacción en curso.",
"outdated_rate": "La cotización se actualizó por última vez: {date}",
"outdated_rate": "La tarifa se actualizó por última vez: {date}",
"psbt_tx_open": "Abrir transacción firmada",
"psbt_tx_scan": "Escanear transacción firmada",
"qr_error_no_qrcode": "No pudimos encontrar un código QR válido en la imagen seleccionada. Asegúrate de que la imagen contenga solo un código QR y ningún contenido adicional, como texto o botones.",
@ -205,7 +206,7 @@
"file_saved_at_path": "El archivo ({filePath}) se ha guardado.",
"cant_send_to_silentpayment_adress": "Esta billetera no puede enviar a direcciones de SilentPayment",
"cant_send_to_bip47": "Esta billetera no puede enviar códigos de pago BIP47",
"cant_find_bip47_notification": "Agrega este código de pago primero a tus contactos",
"cant_find_bip47_notification": "Agrega este código de pago primero a tus contactos ",
"problem_with_psbt": "Problema con PSBT"
},
"settings": {
@ -220,23 +221,22 @@
"run_performance_test": "Prueba de rendimiento",
"about_selftest": "Ejecutar auto-prueba",
"block_explorer_invalid_custom_url": "La URL proporcionada no es válida. Ingresa una URL válida que comience con http:// o https://.",
"about_selftest_electrum_disabled": "La autocomprobación no está disponible con el modo sin conexión de Electrum. Desactiva el modo sin conexión y vuelve a intentarlo.",
"about_selftest_electrum_disabled": "La autocomprobación no está disponible con el modo sin conexión de Electrum. Desactiva el modo sin conexión y vuelve a intentarlo. ",
"about_selftest_ok": "Todas las pruebas internas han pasado satisfactoriamente. La billetera funciona bien.",
"about_sm_github": "GitHub",
"about_sm_telegram": "Chat de Telegram",
"about_sm_telegram": "Chat de Telegram ",
"privacy_temporary_screenshots": "Permitir captura de pantalla",
"privacy_temporary_screenshots_instructions": "La protección contra capturas de pantalla se desactivará temporalmente, lo que permitirá realizar capturas y grabaciones de pantalla. La protección se reactivará automáticamente cuando cierres y vuelvas a abrir BlueWallet.",
"privacy_temporary_screenshots_instructions": "La protección contra capturas de pantalla se desactivará temporalmente, lo que permitirá realizar capturas y grabaciones de pantalla. La protección se reactivará automáticamente cuando cierre y vuelva a abrir BlueWallet.",
"biometrics": "Biometría",
"biometrics_no_longer_available": "La configuración de tu dispositivo cambió y ya no coincide con la configuración de seguridad seleccionada en la aplicación. Vuelve a habilitar los datos biométricos o el código de acceso, luego reinicia la aplicación para aplicar estos cambios.",
"biom_10times": "Has intentado ingresar tu contraseña 10 veces. ¿Te gustaría restablecer tu almacenamiento? Esto eliminará todas las billeteras y descifrará tu almacenamiento.",
"biom_conf_identity": "Por favor confirma tu identidad.",
"biom_no_passcode": "Tu dispositivo no tiene un código de acceso ni datos biométricos habilitados. Para continuar, configura un código de acceso o datos biométricos en la aplicación Configuración.",
"biom_no_passcode": "tu dispositivo no tiene un código de acceso ni datos biométricos habilitados. Para continuar, configura un código de acceso o datos biométricos en la aplicación Configuración.",
"biom_remove_decrypt": "Se eliminarán todas tus billeteras y se descifrará tu almacenamiento. ¿Estás seguro que deseas continuar?",
"currency": "Divisa",
"currency_source": "La cotización se obtiene de",
"currency_source": "La tarifa se obtiene de",
"currency_fetch_error": "Se produjo un error al obtener el tipo de cambio de la divisa seleccionada.",
"default_title": "Al inicio",
"general": "General",
"donate": "Donar",
"donate_description": "¡Ayúdanos a mantener Blue libre!",
"electrum_connected": "Conectado",
@ -250,7 +250,7 @@
"electrum_port": "Puerto, generalmente {example}",
"use_ssl": "Utiliza SSL",
"electrum_saved": "Tus cambios se han guardado correctamente. Es necesario reiniciar para que los cambios surtan efecto.",
"set_electrum_server_as_default": "¿Establecer {server} como el servidor Electrum predeterminado?",
"set_electrum_server_as_default": "Establecer {server} como el servidor Electrum predeterminado?",
"set_lndhub_as_default": "¿Establecer {url} como servidor LNDhub predeterminado?",
"electrum_settings_server": "Servidor Electrum",
"electrum_status": "Estado",
@ -279,8 +279,9 @@
"encrypted_feature_disabled": "Esta función no se puede utilizar con el almacenamiento cifrado habilitado.",
"encrypt_use_expl": "{type} se utilizará para confirmar tu identidad antes de realizar una transacción, desbloquear, exportar o eliminar una billetera.",
"biometrics_fail": "Si {type} no está activado o no se desbloquea, puedes utilizar el código de acceso de tu dispositivo como alternativa.",
"general": "General",
"general_continuity": "Continuidad",
"general_continuity_e": "Cuando esté habilitado, podrás ver billeteras seleccionadas y transacciones utilizando tus otros dispositivos conectados a Apple iCloud.",
"general_continuity_e": "Cuando esté habilitado, podrás ver carteras seleccionadas y transacciones utilizando tus otros dispositivos conectados a Apple iCloud.",
"groundcontrol_explanation": "GroundControl es un servidor de notificaciones push de código abierto gratuito para billeteras Bitcoin. Puedes instalar tu propio servidor GroundControl y poner tu URL aquí para no depender de la infraestructura de BlueWallet. Déjalo en blanco para usar el predeterminado.",
"header": "Ajustes",
"language": "Idioma",
@ -292,7 +293,7 @@
"lightning_saved": "Tus cambios han sido guardados correctamente.",
"lightning_settings": "Configuración de Lightning",
"lightning_settings_explain": "Para conectarte a tu propio nodo LND, instala LNDhub y coloca su URL aquí en la configuración. Ten en cuenta que solo las billeteras creadas después de guardar los cambios se conectarán al LNDhub especificado.",
"lndhub_github": "Repositorio de GitHub",
"lndhub_github": "GitHub Repository",
"network": "Red",
"network_broadcast": "Publicar transacción",
"network_electrum": "Servidor Electrum",
@ -307,7 +308,7 @@
"privacy_read_clipboard": "Leer portapapeles",
"privacy_system_settings": "Configuración de sistema",
"privacy_quickactions": "Atajos de la Billetera",
"privacy_quickactions_explanation": "Mantén pulsado el icono de la aplicación BlueWallet para ver rápidamente el saldo de tu billetera.",
"privacy_quickactions_explanation": "Mantén pulsado el icono de la aplicación BlueWallet para ver rápidamente el saldo de tu cartera.",
"privacy_clipboard_explanation": "Proporciona atajos si encuentras una dirección o factura en tu portapapeles.",
"privacy_do_not_track": "Desactivar análisis",
"privacy_do_not_track_explanation": "La información de rendimiento y confiabilidad no se enviará para su análisis.",
@ -319,8 +320,8 @@
"success_transaction_broadcasted": "¡Tu transacción ha sido transmitida exitosamente!",
"total_balance": "Balance Total",
"total_balance_explanation": "Muestra el saldo total de todas tus billeteras en los widgets de tu pantalla de inicio.",
"tools": "Herramientas",
"widgets": "Widgets"
"widgets": "Widgets",
"tools": "Herramientas"
},
"notifications": {
"would_you_like_to_receive_notifications": "¿Te gustaría recibir notificaciones cuando recibas pagos entrantes?",
@ -331,10 +332,11 @@
"transactions": {
"cancel_explain": "Reemplazaremos esta transacción con una que te pague y tenga tarifas más altas. Esto cancela efectivamente la transacción actual. Esto se llama RBF (Replace by Fee).",
"cancel_no": "Esta transacción no es reemplazable.",
"cancel_title": "Cancelar esta transacción (RBF)",
"cancel_title": "Cancelar ésta transacción (RBF)",
"transaction_loading_error": "Se ha producido un problema al cargar la transacción. Vuelve a intentarlo más tarde.",
"transaction_not_available": "Transacción no disponible",
"confirmations_lowercase": "{confirmations} confirmaciones",
"copy_link": "Copiar enlace",
"expand_note": "Expandir Nota",
"cpfp_create": "Crear",
"cpfp_exp": "Crearemos otra transacción que gaste tu transacción no confirmada. La tarifa total será más alta que la tarifa de la transacción original, por lo que debería extraerse más rápido. Esto se llama CPFP (Child Pays for Parent).",
@ -346,7 +348,7 @@
"details_copy_block_explorer_link": "Copiar el enlace del explorador de bloques",
"details_copy_note": "Copiar nota",
"details_copy_txid": "Copiar ID de transacción",
"details_id": "ID",
"details_from": "Entrada",
"details_inputs": "Entradas",
"details_outputs": "Salidas",
"date": "Fecha",
@ -361,13 +363,14 @@
"onchain": "En cadena",
"details_to": "Salida",
"enable_offline_signing": "Esta billetera no se usa junto con una firma fuera de línea. ¿Deseas habilitarlo ahora?",
"list_conf": "Conf: {number}",
"pending": "Pendiente",
"pending_with_amount": "Pendiente {amt1} ({amt2})",
"received_with_amount": "+{amt1} ({amt2})",
"eta_10m": "TEA: en ~ 10 minutos",
"eta_3h": "TEA: en ~ 3 horas",
"eta_1d": "TEA: en ~ 1 día",
"list_conf": "Conf: {number}",
"view_wallet": "Ver {walletLabel}",
"list_title": "Transacciones",
"list_title_sent": "Enviado",
"list_title_received": "Recibido",
@ -379,6 +382,8 @@
"status_cancel": "Cancelar Transacción",
"transactions_count": "Número de Transacciones",
"txid": "ID de Transacción",
"from": "De: {counterparty}",
"to": "A: {counterparty}",
"updating": "Actualizando...",
"watchOnlyWarningTitle": "Advertencia de seguridad",
"watchOnlyWarningDescription": "Ten cuidado con los estafadores que suelen utilizar billeteras de \"sólo ver\" para engañar a los usuarios. Estas billeteras no permiten controlar ni enviar fondos; solo permiten ver el saldo.",
@ -390,6 +395,7 @@
"details_explorer": "explorador",
"details_network_fee": "Comisión de red",
"details_to_address": "A",
"details_id": "ID",
"details_note": "Nota",
"details_add_note": "agregar",
"details_advanced": "Avanzado",
@ -407,7 +413,7 @@
"total_balance": "Balance Total",
"add_entropy_reset_title": "Restablecer la entropía",
"add_entropy_reset_message": "Al cambiar el tipo de billetera se restablecerá la entropía actual. ¿Quieres continuar?",
"add_entropy": "Entropía",
"add_entropy": "Entropía ",
"add_entropy_bytes": "{bytes} bytes de entropía",
"add_entropy_generated": "{gen} bytes de entropía generada",
"add_entropy_provide": "Entropía mediante el lanzamiento de dados",
@ -425,8 +431,8 @@
"add_wallet_seed_length": "Longitud de la semilla",
"add_wallet_seed_length_12": "12 palabras",
"add_wallet_seed_length_24": "24 palabras",
"clipboard_bitcoin": "Tienes una dirección de Bitcoin en tu portapapeles. ¿Te gustaría usarla para una transacción?",
"clipboard_lightning": "Tienes una factura Lightning en tu portapapeles. ¿Te gustaría usarla para una transacción?",
"clipboard_bitcoin": "Tienes una dirección de Bitcoin en tu portapapeles. ¿te gustaría usarlo para una transacción?",
"clipboard_lightning": "Tienes una factura Lightning en tu portapapeles. ¿Te gustaría usarlo para una transacción?",
"clear_clipboard_on_import": "Limpiar el portapapeles al importar",
"details_address": "Dirección",
"details_advanced": "Avanzado",
@ -452,7 +458,7 @@
"clear_search": "Limpiar búsqueda",
"details_type": "Tipo",
"details_use_with_hardware_wallet": "Usar con Billetera de Hardware",
"details_yes_delete": "Sí, eliminar",
"details_yes_delete": "Si, eliminar",
"enter_bip38_password": "Ingresa la contraseña para descifrar",
"export_title": "Exportación de Billetera",
"import_do_import": "Importar",
@ -464,7 +470,7 @@
"import_imported": "Importado",
"import_scan_qr": "Escanear o importar un archivo",
"import_success": "Tu billetera se ha importado correctamente.",
"import_success_watchonly": "Tu billetera se ha importado correctamente. ADVERTENCIA: Esta es una billetera de \"solo ver\", NO puedes gastar desde ella.",
"import_success_watchonly": "Tu billetera se ha importado correctamente. ADVERTENCIA: Esta es una billetera de \"solo ver\", NO puedes gastar desde él.",
"import_search_accounts": "Buscar cuentas",
"import_title": "Importar",
"learn_more": "Saber más",
@ -476,7 +482,7 @@
"import_derivation_found": "Encontrado",
"import_derivation_found_not": "No se ha encontrado",
"import_derivation_loading": "Cargando...",
"import_derivation_subtitle": "Introduce la ruta de derivación personalizada e intentaremos descubrir tu billetera.",
"import_derivation_subtitle": "Introduce la ruta de derivación personalizada e intentaremos descubrir tu cartera.",
"import_derivation_title": "Ruta de derivación",
"import_derivation_unknown": "Desconocido",
"import_wrong_path": "Ruta de derivación incorrecta",
@ -502,6 +508,7 @@
"select_no_bitcoin": "Actualmente no hay billeteras Bitcoin disponibles.",
"select_no_bitcoin_exp": "Se requiere una billetera Bitcoin para recargar las billeteras Lightning. Por favor, crea o importa una.",
"select_wallet": "Selecciona Billetera",
"xpub_copiedToClipboard": "Copiado al portapapeles.",
"pull_to_refresh": "Tira para actualizar",
"warning_do_not_disclose": "Nunca compartas la siguiente información",
"scan_import": "Escanea este código QR para importar tu billetera en otra aplicación.",
@ -548,7 +555,7 @@
"shared_key_detected_question": "Se compartió un cofirmante contigo, ¿quieres importarlo?",
"manage_keys": "Administrar Claves",
"how_many_signatures_can_bluewallet_make": "cuántas firmas puede hacer BlueWallet",
"signatures_required_to_spend": "Se requieren {number} firmas",
"signatures_required_to_spend": "Se requieren firmas {number}",
"signatures_we_can_make": "puede hacer {number}",
"scan_or_import_file": "Escanear o importar archivo",
"export_coordination_setup": "Exportar Configuración de Coordinación",
@ -567,8 +574,8 @@
"what_is_vault_description_number_of_vault_keys": "{m} claves de la bóveda",
"what_is_vault_description_to_spend": " gastar y un tercero que puede\nutilizar como respaldo.",
"what_is_vault_description_to_spend_other": "gastar.",
"quorum": "Quórum de {m} de {n}",
"quorum_header": "Quórum",
"quorum": "{m} of {n} quorum",
"quorum_header": "Quorum",
"of": "de",
"wallet_type": "Tipo de Billetera",
"invalid_mnemonics": "Esta frase mnemotécnica no parece válida.",
@ -582,7 +589,7 @@
"this_is_cosigners_xpub": "Este es el XPUB del cofirmante, listo para importarse a otra billetera. Es seguro compartirlo.",
"this_is_cosigners_xpub_airdrop": "Si compartes vía AirDrop los receptores tienen que estar en la pantalla de coordinación.",
"wallet_key_created": "Se creó tu clave de Bóveda. Tómate un momento para hacer una copia de seguridad segura de tu semilla mnemotécnica.",
"are_you_sure_seed_will_be_lost": "¿Estás seguro? Tu semilla mnemotécnica se perderá si no tienes una copia de seguridad.",
"are_you_sure_seed_will_be_lost": "¿Estás segur@? Tu semilla mnemotécnica se perderá si no tienes una copia de seguridad.",
"forget_this_seed": "Olvídate de esta semilla y usa XPUB en su lugar.",
"view_edit_cosigners": "Ver/editar cofirmantes",
"this_cosigner_is_already_imported": "Este cofirmante ya está importado.",
@ -599,7 +606,7 @@
"ms_help_title2": "Editar Claves",
"ms_help_2": "Puedes crear todas las claves de la Bóveda en este dispositivo y eliminarlas o editarlas después. Tener todas las claves en el mismo dispositivo tiene la seguridad equivalente a la de un monedero de Bitcoin normal.",
"ms_help_title3": "Copias de seguridad de la Bóveda",
"ms_help_3": "En las opciones de la billetera, encontrarás la copia de seguridad de la bóveda y la copia de seguridad de la billetera de \"solo ver\". Esta copia de seguridad es como un mapa de tu billetera. Es esencial para la recuperación de la billetera en caso de que pierdas una de tus semillas.",
"ms_help_3": "En las opciones de la billetera, encontrarás la copia de seguridad de la bóveda y la copia de seguridad de la billetera de \"solo ver\". Esta copia de seguridad es como un mapa de tu billetera. Es esencial para la recuperación de la billetera en caso de que pierdas una de tus semillas. ",
"ms_help_title4": "Importando Bóvedas",
"ms_help_4": "Para importar una multifirma, usa tu archivo de respaldo y la función Importar. Si solo tienes semillas y XPUB, puedes utilizar el botón de importación individual al crear claves de Bóveda.",
"ms_help_title5": "Modo Avanzado",
@ -610,7 +617,7 @@
"owns": "{label} posee {address}",
"enter_address": "Ingresar dirección",
"check_address": "Verificar dirección",
"no_wallet_owns_address": "Ninguna de las billeteras disponibles posee la dirección proporcionada.",
"no_wallet_owns_address": "Ninguna de las carteras disponibles posee la dirección proporcionada.",
"view_qrcode": "Ver código QR"
},
"autofill_word": {
@ -630,7 +637,7 @@
"header": "Control de Monedas",
"use_coin": "Usar Moneda",
"use_coins": "Usar Monedas",
"tip": "Esta función te permite ver, etiquetar, congelar o seleccionar monedas para una mejor gestión de la billetera. Puedes seleccionar varias monedas tocando los círculos de colores.",
"tip": "Esta función te permite ver, etiquetar, congelar o seleccionar monedas para una mejor gestión de la cartera. Puedes seleccionar varias monedas tocando los círculos de colores.",
"sort_asc": "Ascendente",
"sort_desc": "Descendente",
"sort_height": "Altura",

View File

@ -35,7 +35,7 @@
"codeIs": "Sinu vautšeri kood on",
"errorBeforeRefeem": "Enne lunastamist on vajalik lisada uus Bitcoini rahakott.",
"errorSomething": "Midagi läks valesti. Kas see vautšer on endiselt kehtiv?",
"redeem": "Lunasta rahakotti",
"redeem": "Lunasta rahakotti.",
"redeemButton": "Lunasta",
"success": "Toiming õnnestus",
"successMessage": "Vautšer edukalt lunastatud! Vahendid peaksid varsti sinu Bitcoini rahakotti saabuma.",
@ -48,7 +48,7 @@
"amountOfEntropy": "{bits} {limit} bitist"
},
"errors": {
"broadcast": "Leviedastus nurjus.",
"broadcast": "Ülekanne nurjus.",
"error": "Viga",
"network": "Võrgu viga"
},
@ -104,10 +104,10 @@
"address_not_found": "Vastuvõtuaadressi loomine ebaõnnestus.",
"header": "Võta vastu",
"reset": "Lähtesta",
"maxSats": "Maksimaalne summa on {max} sats",
"maxSatsFull": "Maksimaalne summa on {max} sats või {currency}",
"minSats": "Minimaalne summa on {min} sats",
"minSatsFull": "Minimaalne summa on {min} sats või {currency}",
"maxSats": "Maksimaalne summa on {max} satid",
"maxSatsFull": "Maksimaalne summa on {max} satid või {currency}",
"minSats": "Minimaalne summa on {min} satid",
"minSatsFull": "Minimaalne summa on {min} satid või {currency}",
"qrcode_for_the_address": "Aadressi QR-kood",
"bip47_explanation": "Maksekoodid on universaalne aadress, mis ei avalda sinu rahakoti aadresse. Mitte kõik teenused ei toeta neid."
},
@ -149,7 +149,7 @@
"details_adv_import": "Impordi tehing",
"details_adv_import_qr": "Impordi tehing (QR)",
"details_amount_field_is_not_valid": "Summa ei ole kehtiv.",
"details_amount_field_is_less_than_minimum_amount_sat": "Määratud summa on liiga väike. Palun sisesta summa, mis on suurem kui 500 sats.",
"details_amount_field_is_less_than_minimum_amount_sat": "Määratud summa on liiga väike. Palun sisesta summa, mis on suurem kui 500 satid.",
"details_create": "Loo arve",
"details_error_decode": "Bitcoini aadressi dekodeerimine ebaõnnestus",
"details_fee_field_is_not_valid": "Tasu ei ole kehtiv.",
@ -169,7 +169,7 @@
"dynamic_prev": "Eelmine",
"dynamic_start": "Alusta",
"dynamic_stop": "Peata",
"fee_10m": "10min",
"fee_10m": "10m",
"fee_1d": "1p",
"fee_3h": "3t",
"fee_custom": "Kohandatud",
@ -346,7 +346,6 @@
"details_copy_block_explorer_link": "Kopeeri plokiuurija link",
"details_copy_note": "Kopeeri märkus",
"details_copy_txid": "Kopeeri tehingu ID",
"details_id": "ID",
"details_inputs": "Sisendid",
"details_outputs": "Väljundid",
"date": "Kuupäev",
@ -390,6 +389,7 @@
"details_explorer": "uurija",
"details_network_fee": "Võrgutasu",
"details_to_address": "Kellele",
"details_id": "ID",
"details_note": "Märkus",
"details_add_note": "lisa",
"details_advanced": "Täpsem",
@ -675,7 +675,7 @@
"link_answer": "Sinu Lightningi rahakott on edukalt seotud sinu kontoga saidil {hostname}!",
"auth_question_part_1": "Kas soovid autentida saidil",
"auth_question_part_2": "kasutades oma Lightningi rahakotti?",
"auth_answer": "Oled edukalt autenditud saidil {hostname}!",
"auth_answer": "Oled edukalt autentitud saidil {hostname}!",
"could_not_auth": "Me ei suutnud sind autentida saidil {hostname}.",
"authenticate": "Autendi"
},

View File

@ -10,7 +10,7 @@
"enter_password": "گذرواژه را وارد کنید",
"never": "هرگز",
"of": "{number} از {total}",
"ok": "تأیید",
"ok": "بله",
"enter_url": "آدرس را وارد کنید",
"storage_is_encrypted": "فضای ذخیره‌سازی شما رمزگذاری شده است. برای رمزگشایی آن به گذرواژه نیاز است.",
"yes": "بله",
@ -69,7 +69,7 @@
},
"lndViewInvoice": {
"additional_info": "اطلاعات بیشتر",
"for": "برای:",
"for": "برای: ",
"lightning_invoice": "صورت‌حساب لایتنینگ",
"please_pay_between_and": "لطفاً بین {min} و {max} بپردازید",
"please_pay": "لطفاً",
@ -92,7 +92,7 @@
"ask_yes": "بله، ذخیره کرده‌ام.",
"ok": "خب، آن را نوشتم.",
"ok_lnd": "خب، آن را ذخیره کردم.",
"text": "لطفاً درنگ کرده و این عبارت یادیار (mnemonic phrase) را روی یک تکه کاغذ یادداشت کنید.\nاین کلمه‌ها نسخهٔ پشتیبان شما هستند، و می‌توانید از آن‌ها برای بازیابی کیف پول در دستگاه دیگری استفاده کنید.",
"text": "لطفاً درنگ کرده و این عبارت یادیار (mnemonic phrase) را روی یک تکه کاغذ یادداشت کنید. این کلمه‌ها نسخهٔ پشتیبان شما هستند، و می‌توانید از آن‌ها برای بازیابی کیف پول در دستگاه دیگری استفاده کنید.",
"text_lnd": "لطفاً این نسخهٔ پشتیبان کیف پول را ذخیره کنید. با داشتن آن قادر خواهید بود درصورت ازدست‌رفتن کیف پول آن را بازیابی کنید.",
"title": "کیف پول شما ایجاد شد."
},
@ -219,7 +219,7 @@
"performance_score": "امتیاز کارکرد: {num}",
"run_performance_test": "بررسی کارکرد",
"about_selftest": "اجرای خودآزمایی",
"block_explorer_invalid_custom_url": "آدرس ارائه‌شده نامعتبر است. لطفاً آدرسی معتبر را وارد کنید که با http:// یا https:// آغاز شود.",
"block_explorer_invalid_custom_url": "آدرس ارائه‌شده نامعتبر است. لطفاً آدرسی معتبر را وارد کنید که با //:http یا //:https آغاز شود.",
"about_selftest_electrum_disabled": "خودآزمایی در حالت آفلاین در دسترس نیست. لطفاً حالت آفلاین را غیرفعال کرده و مجدد تلاش کنید.",
"about_selftest_ok": "تمام بررسی‌های داخلی با موفقیت انجام شدند. کیف پول به‌درستی کار می‌کند.",
"about_sm_github": "گیت‌هاب",
@ -240,7 +240,7 @@
"donate_description": "به ما کمک کنید تا Blue را رایگان نگه داریم!",
"electrum_connected": "متصل",
"electrum_connected_not": "عدم اتصال",
"electrum_error_connect": "اتصال به سرور Electrum ارائه‌شده ممکن نیست",
"electrum_error_connect": "اتصال به سرور الکترام ارائه‌شده ممکن نیست",
"electrum_error_connect_tor": "اتصال به سرور الکترام ارائه‌شده ممکن نیست. لطفاً مطمئن شوید که برنامهٔ Orbot متصل است و دوباره تلاش کنید.",
"lndhub_uri": "به‌عنوان مثال، {example}",
"electrum_host": "به‌عنوان مثال، {example}",
@ -292,7 +292,7 @@
"lightning_saved": "تغییرات شما با موفقیت ذخیره شدند.",
"lightning_settings": "تنظیمات لایتنینگ",
"lightning_settings_explain": "برای اتصال به گرهٔ LND خود، لطفاً LNDhub را نصب کرده و آدرس آن را اینجا در تنظیمات قرار دهید. لطفاً توجه داشته باشید که فقط کیف پول‌های ساخته‌شده پس از ذخیرهٔ تغییرات به LNDhub تعیین‌شده متصل خواهند شد.",
"lndhub_github": "مخزن GitHub",
"lndhub_github": "مخزن گیت‌هاب",
"network": "شبکه",
"network_broadcast": "انتشار تراکنش",
"network_electrum": "سرور الکترام",
@ -335,6 +335,7 @@
"transaction_loading_error": "در بارگذاری تراکنش مشکلی پیش آمد. لطفاً بعداً دوباره تلاش کنید.",
"transaction_not_available": "تراکنش در دسترس نیست",
"confirmations_lowercase": "{confirmations} تأیید",
"copy_link": "کپی لینک",
"expand_note": "نمایش کامل یادداشت",
"cpfp_create": "ایجاد",
"cpfp_exp": "ما تراکنش دیگری را ایجاد خواهیم کرد که تراکنش تأییدنشدهٔ شما را خرج می‌کند. کارمزد کل بیشتر از کارمزد تراکنش اولیه خواهد بود، بنابراین سریع‌تر تأیید می‌شود. این کار Child Pays for Parent (به‌اختصار CPFP) نام دارد—فرزند به‌جای والدین می‌پردازد.",
@ -346,6 +347,7 @@
"details_copy_block_explorer_link": "کپی لینک مرورگر بلاک",
"details_copy_note": "کپی یادداشت",
"details_copy_txid": "کپی شناسهٔ تراکنش",
"details_from": "ورودی",
"details_inputs": "ورودی‌ها",
"details_outputs": "خروجی‌ها",
"date": "تاریخ",
@ -356,8 +358,8 @@
"outgoing_transaction": "تراکنش خروجی",
"expired_transaction": "تراکنش منقضی‌شده",
"pending_transaction": "تراکنش در انتظار ثبت",
"offchain": "خارج از زنجیره",
"onchain": "روی زنجیره",
"offchain": "آف‌چین",
"onchain": "آن‌چین",
"details_to": "خروجی",
"enable_offline_signing": "این کیف پول در کنار امضای آفلاین استفاده نمی‌شود. آیا مایل به فعال‌کردن این امکان هستید؟",
"list_conf": "تأییدها: {number}",
@ -367,6 +369,7 @@
"eta_10m": "زمان تقریبی رسیدن: حدود ده دقیقه",
"eta_3h": "زمان تقریبی رسیدن: حدود سه ساعت",
"eta_1d": "زمان تقریبی رسیدن: حدود یک روز",
"view_wallet": "مشاهدهٔ {walletLabel}",
"list_title": "تراکنش‌ها",
"list_title_sent": "ارسال‌شده",
"list_title_received": "دریافت‌شده",
@ -502,6 +505,7 @@
"select_no_bitcoin": "هیچ کیف پول بیت‌کوینی درحال‌حاضر دردسترس نیست.",
"select_no_bitcoin_exp": "یک کیف پول بیت‌کوین برای پرکردن کیف پول‌های لایتنینگ نیاز است. لطفاً یکی بسازید یا وارد کنید.",
"select_wallet": "انتخاب کیف پول",
"xpub_copiedToClipboard": "در کلیپ‌بورد کپی شد.",
"pull_to_refresh": "برای به‌روزسانی به پایین بکشید",
"warning_do_not_disclose": "هرگز اطلاعات زیر را به اشتراک نگذارید",
"scan_import": "این کد QR را اسکن کنید تا کیف پول خود را در برنامهٔ دیگری وارد کنید.",
@ -684,10 +688,10 @@
"contacts": "مخاطبان",
"bip47_explain": "کد بازکاربردپذیر و قابل هم‌رسانی",
"bip47_explain_subtitle": "BIP47",
"purpose": "کد بازکاربردپذیر و قابل همرسانی (BIP47)",
"purpose": "کد بازکاربردپذیر و قابل همرسانی (بیپ47)",
"pay_this_contact": "پرداخت به این مخاطب",
"rename_contact": "ویرایش نام مخاطب",
"copy_payment_code": "کپی کد پرداخت",
"copy_payment_code": "رونویسی کد پرداخت",
"hide_contact": "پنهان‌کردن مخاطب",
"rename": "ویرایش نام",
"provide_name": "نام جدیدی برای این مخاطب ارائه دهید",

Some files were not shown because too many files have changed in this diff Show More