Compare commits
135 Commits
vocabulary
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32d3f77f4f | ||
|
|
f26ff9189c | ||
|
|
1fa290652c | ||
|
|
099f6f46a6 | ||
|
|
01a11bc8dd | ||
|
|
6639891c24 | ||
|
|
4029d294f8 | ||
|
|
276a9ea8f8 | ||
|
|
d415f1a0b8 | ||
|
|
6124cf1c04 | ||
|
|
b922346bb6 | ||
|
|
64f1bd78db | ||
|
|
1412a302a1 | ||
|
|
6785427fe8 | ||
|
|
1f0ce7c813 | ||
|
|
f5379795de | ||
|
|
c76db2f84a | ||
|
|
cb6e3ae69b | ||
|
|
7bc2c0e797 | ||
|
|
5a7c514548 | ||
|
|
81cf0011b3 | ||
|
|
94062ffc9f | ||
|
|
0449a25c6a | ||
|
|
ff98ca0c1c | ||
|
|
c9dcbf40e7 | ||
|
|
c8a7887808 | ||
|
|
e4504b2355 | ||
|
|
13cedbe49e | ||
|
|
abb80665af | ||
|
|
681cbcf2dc | ||
|
|
78c9d49359 | ||
|
|
da606dbff0 | ||
|
|
8fda883933 | ||
|
|
c4abe9562e | ||
|
|
5d8e605fe7 | ||
|
|
387f8dffd6 | ||
|
|
b34f52fdce | ||
|
|
a3c12a27eb | ||
|
|
a04cad686f | ||
|
|
61877ed0db | ||
|
|
859877979e | ||
|
|
71c76bd8c8 | ||
|
|
dd52747191 | ||
|
|
fa9434b0de | ||
|
|
770a7a4075 | ||
|
|
f6f0238e0a | ||
|
|
236791f32e | ||
|
|
0181f0a849 | ||
|
|
f334b985e8 | ||
|
|
bfeda40284 | ||
|
|
a4e224ec96 | ||
|
|
d259e68a85 | ||
|
|
926a863d27 | ||
|
|
f0336e1789 | ||
|
|
2c8cfd3690 | ||
|
|
e37c4a693c | ||
|
|
2a3de6f473 | ||
|
|
bbd6101ddb | ||
|
|
687e007c56 | ||
|
|
0641bf70cd | ||
|
|
cbe6ebb423 | ||
|
|
e19ce1136f | ||
|
|
1a88c085ec | ||
|
|
d7261d4d2a | ||
|
|
4aa07ed904 | ||
|
|
b315d587ff | ||
|
|
d367a2f383 | ||
|
|
e4c8a3057d | ||
|
|
1109a836e9 | ||
|
|
8e62aee2fc | ||
|
|
220cd7e61d | ||
|
|
d043b86310 | ||
|
|
8310c5f9fa | ||
|
|
8599607574 | ||
|
|
1a0ac1188e | ||
|
|
66ca6a9fca | ||
|
|
90fdee2a8f | ||
|
|
d14556ff80 | ||
|
|
047b675faf | ||
|
|
7085dd4a81 | ||
|
|
8c58df270a | ||
|
|
3952ec7e12 | ||
|
|
26b6419507 | ||
|
|
964ceecd6a | ||
|
|
df782c5f7a | ||
|
|
f7c72e13d4 | ||
|
|
9e66ed003a | ||
|
|
08c4c02491 | ||
|
|
3c199ab9f2 | ||
|
|
0881efbd40 | ||
|
|
a5a7d34478 | ||
|
|
c8344e6037 | ||
|
|
54c5f34e11 | ||
|
|
431a8006ea | ||
|
|
092c437557 | ||
|
|
f65ccb5427 | ||
|
|
aa5a6ba7f0 | ||
|
|
9e907566f0 | ||
|
|
265cebef62 | ||
|
|
c1c13e9e58 | ||
|
|
87b2bb2156 | ||
|
|
7a5589eb00 | ||
|
|
87cf8a5600 | ||
|
|
1da0414474 | ||
|
|
151dbbbc67 | ||
|
|
bcc393dca8 | ||
|
|
1766cadcdf | ||
|
|
4524882015 | ||
|
|
4429f127c6 | ||
|
|
44b5640ff3 | ||
|
|
8d8b2fb9cf | ||
|
|
714984cefe | ||
|
|
42b1154543 | ||
|
|
492a617d42 | ||
|
|
ebf8e245ec | ||
|
|
14b3695a36 | ||
|
|
f3294d1fc6 | ||
|
|
ae9a5605f3 | ||
|
|
0749457c04 | ||
|
|
4fe998aad8 | ||
|
|
5f865777c9 | ||
|
|
66736f9c91 | ||
|
|
fef40b4990 | ||
|
|
8b16c53e5c | ||
|
|
32f810cb46 | ||
|
|
fb8fd51d86 | ||
|
|
71dd93ede2 | ||
|
|
1728f33f0a | ||
|
|
295a32caef | ||
|
|
8195855f05 | ||
|
|
d09bd69b96 | ||
|
|
ccb1dcaef7 | ||
|
|
3a48182105 | ||
|
|
edff2468d4 | ||
|
|
620553d0a2 |
@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
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.8
|
||||
ruby-version: 3.4.9
|
||||
|
||||
- name: System Debug Information
|
||||
run: |
|
||||
@ -490,12 +490,12 @@ jobs:
|
||||
BRANCH_NAME: ${{ needs.build.outputs.branch_name }}
|
||||
steps:
|
||||
- name: Checkout Project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Set Up Ruby
|
||||
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
||||
with:
|
||||
ruby-version: 3.4.8
|
||||
ruby-version: 3.4.9
|
||||
|
||||
- name: Install Dependencies with Bundler
|
||||
run: |
|
||||
|
||||
4
.github/workflows/build-mac-catalyst.yml
vendored
4
.github/workflows/build-mac-catalyst.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Checkout project
|
||||
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
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.8
|
||||
ruby-version: 3.4.9
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install Node modules
|
||||
|
||||
8
.github/workflows/build-release-apk.yml
vendored
8
.github/workflows/build-release-apk.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
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.8
|
||||
ruby-version: 3.4.9
|
||||
bundler-cache: true
|
||||
|
||||
- name: Generate Build Number based on timestamp
|
||||
@ -135,12 +135,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
||||
with:
|
||||
ruby-version: 3.4.8
|
||||
ruby-version: 3.4.9
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install dependencies with Bundler
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -34,7 +34,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -53,6 +53,7 @@ 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 }}
|
||||
@ -64,7 +65,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -83,6 +84,7 @@ 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 }}
|
||||
|
||||
4
.github/workflows/e2e-android.yml
vendored
4
.github/workflows/e2e-android.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Free disk space (Ubuntu)
|
||||
run: |
|
||||
@ -86,7 +86,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Free disk space (Ubuntu)
|
||||
run: |
|
||||
|
||||
16
.github/workflows/e2e-ios.yml
vendored
16
.github/workflows/e2e-ios.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- 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.8"
|
||||
ruby-version: "3.4.9"
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install Node dependencies
|
||||
@ -168,7 +168,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
@ -194,9 +194,6 @@ 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: |
|
||||
@ -210,6 +207,13 @@ 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: |
|
||||
|
||||
@ -1 +1 @@
|
||||
3.4.8
|
||||
3.4.9
|
||||
|
||||
@ -1,199 +0,0 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
@ -60,6 +60,8 @@ 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:**
|
||||
|
||||
6
Gemfile
6
Gemfile
@ -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.8"
|
||||
gem "fastlane", "~> 2.232.0"
|
||||
ruby "3.4.9"
|
||||
gem "fastlane", "~> 2.234.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.4'
|
||||
gem 'concurrent-ruby', '< 1.3.8'
|
||||
|
||||
# Ruby 3.4.0 removed these from the standard library
|
||||
gem 'bigdecimal'
|
||||
|
||||
97
Gemfile.lock
97
Gemfile.lock
@ -15,7 +15,7 @@ GEM
|
||||
minitest (>= 5.1, < 6)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.9)
|
||||
addressable (2.9.0)
|
||||
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.1227.0)
|
||||
aws-sdk-core (3.244.0)
|
||||
aws-partitions (1.1252.0)
|
||||
aws-sdk-core (3.247.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.123.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sdk-kms (1.127.0)
|
||||
aws-sdk-core (~> 3, >= 3.247.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.217.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sdk-s3 (1.223.0)
|
||||
aws-sdk-core (~> 3, >= 3.247.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.2.0)
|
||||
base64 (0.3.0)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (4.0.1)
|
||||
bigdecimal (4.1.2)
|
||||
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.3)
|
||||
concurrent-ruby (1.3.7)
|
||||
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.232.2)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
abbrev (~> 0.1.2)
|
||||
fastlane (2.234.0)
|
||||
CFPropertyList (>= 2.3, < 5.0.0)
|
||||
abbrev (~> 0.1)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.197)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
base64 (~> 0.2.0)
|
||||
base64 (~> 0.2)
|
||||
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.0.0)
|
||||
fastlane-sirp (>= 1.1.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.0)
|
||||
mutex_m (~> 0.3)
|
||||
naturally (~> 2.2)
|
||||
nkf (~> 0.2.0)
|
||||
nkf (~> 0.2)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
ostruct (>= 0.1.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
@ -188,8 +188,7 @@ GEM
|
||||
git
|
||||
xml-simple
|
||||
fastlane-plugin-bugsnag_sourcemaps_upload (0.2.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
fastlane-sirp (1.1.0)
|
||||
ffi (1.17.3)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
@ -199,7 +198,7 @@ GEM
|
||||
addressable (~> 2.8)
|
||||
process_executer (~> 4.0)
|
||||
rchardet (~> 1.9)
|
||||
google-apis-androidpublisher_v3 (0.97.0)
|
||||
google-apis-androidpublisher_v3 (0.100.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-core (0.18.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
@ -209,19 +208,19 @@ GEM
|
||||
mutex_m
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
google-apis-iamcredentials_v1 (0.26.0)
|
||||
google-apis-iamcredentials_v1 (0.27.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.61.0)
|
||||
google-apis-storage_v1 (0.62.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.5.0)
|
||||
google-cloud-storage (1.58.0)
|
||||
google-cloud-errors (1.6.0)
|
||||
google-cloud-storage (1.60.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-core (>= 0.18, < 2)
|
||||
@ -246,7 +245,7 @@ GEM
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.19.2)
|
||||
json (2.19.5)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
@ -258,7 +257,7 @@ GEM
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.27.0)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.19.1)
|
||||
multi_json (1.21.1)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.3.0)
|
||||
@ -273,7 +272,7 @@ GEM
|
||||
process_executer (4.0.2)
|
||||
track_open_instances (~> 0.1)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.3.1)
|
||||
rake (13.4.2)
|
||||
rchardet (1.10.0)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
@ -300,7 +299,6 @@ 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)
|
||||
@ -339,8 +337,8 @@ DEPENDENCIES
|
||||
benchmark
|
||||
bigdecimal
|
||||
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
|
||||
concurrent-ruby (< 1.3.4)
|
||||
fastlane (~> 2.232.0)
|
||||
concurrent-ruby (< 1.3.8)
|
||||
fastlane (~> 2.234.0)
|
||||
fastlane-plugin-browserstack
|
||||
fastlane-plugin-bugsnag
|
||||
fastlane-plugin-bugsnag_sourcemaps_upload
|
||||
@ -353,20 +351,20 @@ CHECKSUMS
|
||||
CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261
|
||||
abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242
|
||||
activesupport (7.2.3.1) sha256=11ebed516a43a0bb47346227a35ebae4d9427465a7c9eb197a03d5c8d283cb34
|
||||
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
|
||||
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
|
||||
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.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-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-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
|
||||
babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99
|
||||
base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507
|
||||
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
||||
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
|
||||
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
||||
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
|
||||
claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e
|
||||
cocoapods (1.15.2) sha256=f0f5153de8d028d133b96f423e04f37fb97a1da0d11dda581a9f46c0cba4090a
|
||||
cocoapods-core (1.15.2) sha256=322650d97fe1ad4c0831a09669764b888bd91c6d79d0f6bb07281a17667a2136
|
||||
@ -379,7 +377,7 @@ CHECKSUMS
|
||||
colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c
|
||||
colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a
|
||||
commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9
|
||||
concurrent-ruby (1.3.3) sha256=4f9cd28965c4dcf83ffd3ea7304f9323277be8525819cb18a3b61edcb56a7c6a
|
||||
concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
|
||||
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
||||
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
|
||||
declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9
|
||||
@ -405,25 +403,25 @@ CHECKSUMS
|
||||
faraday-retry (1.0.4) sha256=dc659233777fabf96c69c2ffe56c0a5d2c102af90321a42cc6c90157bcd716aa
|
||||
faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9
|
||||
fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20
|
||||
fastlane (2.232.2) sha256=978689f60f0fc3d54699de86ef12be4eda9f5b52217c1798965257c390d2b112
|
||||
fastlane (2.234.0) sha256=b74835681ad9a8e9c0931a5727dad1bab433895ac534c864a1ed5749625d26e9
|
||||
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.0.0) sha256=66478f25bcd039ec02ccf65625373fca29646fa73d655eb533c915f106c5e641
|
||||
fastlane-sirp (1.1.0) sha256=10bc94f9682efd8e1badfb31452a76dd8981f1f3a33717c765fde6d75b54d847
|
||||
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.97.0) sha256=0f3859844872ec09b64dde3bff6dee84458eb61d664337402adcbb4ac912322a
|
||||
google-apis-androidpublisher_v3 (0.100.0) sha256=7a82935bee985190e8fe23bf5e53df3a27d65dd084114bb71b846b617de16489
|
||||
google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee
|
||||
google-apis-iamcredentials_v1 (0.26.0) sha256=3ff70a10a1d6cddf2554e95b7c5df2c26afdeaeb64100048a355194da19e48a3
|
||||
google-apis-iamcredentials_v1 (0.27.0) sha256=9289f29968610754ef11d98b9ec627f0153f3e2616fef839aef096de529f6d1e
|
||||
google-apis-playcustomapp_v1 (0.17.0) sha256=d5bc90b705f3f862bab4998086449b0abe704ee1685a84821daa90ca7fa95a78
|
||||
google-apis-storage_v1 (0.61.0) sha256=b330e599b58e6a01533c189525398d6dbdbaf101ffb0c60145940b57e1c982e8
|
||||
google-apis-storage_v1 (0.62.0) sha256=f62467c36df53287fb0252ebb4da85f9e25d7b4c5809d045c2aab1fc307760c1
|
||||
google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf
|
||||
google-cloud-env (2.1.1) sha256=cf4bb8c7d517ee1ea692baedf06e0b56ce68007549d8d5a66481aa9f97f46999
|
||||
google-cloud-errors (1.5.0) sha256=b56be28b8c10628125214dde571b925cfcebdbc58619e598250c37a2114f7b4b
|
||||
google-cloud-storage (1.58.0) sha256=1bedc07a9c75af169e1ede1dd306b9f941f9ffa9e7095d0364c0803c468fdffd
|
||||
google-cloud-errors (1.6.0) sha256=1da8476dd706ad04b9d32e3c4b90d07d3463b37d6407cb56d41342ea7647d0a1
|
||||
google-cloud-storage (1.60.0) sha256=b21b752d37945d678a4533be5ef4303f15d33a964d8bc709c7c41c3600f650db
|
||||
googleauth (1.11.2) sha256=7e6bacaeed7aea3dd66dcea985266839816af6633e9f5983c3c2e0e40a44731e
|
||||
highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479
|
||||
http-accept (1.7.0) sha256=c626860682bfbb3b46462f8c39cd470fd7b0584f61b3cc9df5b2e9eb9972a126
|
||||
@ -431,7 +429,7 @@ CHECKSUMS
|
||||
httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8
|
||||
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
|
||||
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
|
||||
json (2.19.2) sha256=e7e1bd318b2c37c4ceee2444841c86539bc462e81f40d134cf97826cb14e83cf
|
||||
json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59
|
||||
jwt (2.10.2) sha256=31e1ee46f7359883d5e622446969fe9c118c3da87a0b1dca765ce269c3a0c4f4
|
||||
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
||||
mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56
|
||||
@ -440,7 +438,7 @@ CHECKSUMS
|
||||
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
||||
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
|
||||
molinillo (0.8.0) sha256=efbff2716324e2a30bccd3eba1ff3a735f4d5d53ffddbc6a2f32c0ca9433045d
|
||||
multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7
|
||||
multi_json (1.21.1) sha256=e6126a31808e3b4d19f483c775ceac34df190dffa62adfb63a165ee14ba68080
|
||||
multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
|
||||
mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
|
||||
nanaimo (0.3.0) sha256=aaaedc60497070b864a7e220f7c4b4cad3a0daddda2c30055ba8dae306342376
|
||||
@ -454,7 +452,7 @@ CHECKSUMS
|
||||
plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42
|
||||
process_executer (4.0.2) sha256=c73eb646d450044241c973a8360f6326e33ec5ad933f7acf503f6f3579873a71
|
||||
public_suffix (4.0.7) sha256=8be161e2421f8d45b0098c042c06486789731ea93dc3a896d30554ee38b573b8
|
||||
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
||||
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
||||
rchardet (1.10.0) sha256=d5ea2ed61a720a220f1914778208e718a0c7ed2a484b6d357ba695aa7001390f
|
||||
representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace
|
||||
rest-client (2.1.0) sha256=35a6400bdb14fae28596618e312776c158f7ebbb0ccad752ff4fa142bf2747e3
|
||||
@ -468,7 +466,6 @@ 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
|
||||
@ -487,7 +484,7 @@ CHECKSUMS
|
||||
xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.4.8
|
||||
ruby 3.4.9
|
||||
|
||||
BUNDLED WITH
|
||||
4.0.7
|
||||
|
||||
@ -116,6 +116,10 @@ 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
|
||||
|
||||
@ -87,7 +87,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "8.0.0"
|
||||
versionName "8.0.1"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
// Keep compatibility across react-native-capture-protection flavor changes.
|
||||
|
||||
@ -14,6 +14,8 @@ 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
|
||||
|
||||
@ -97,6 +99,13 @@ 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()
|
||||
|
||||
@ -57,6 +57,13 @@ 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
|
||||
@ -85,6 +92,17 @@ 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 ->
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
module.exports = {
|
||||
presets: ['module:@react-native/babel-preset'],
|
||||
// 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' }]],
|
||||
plugins: ['react-native-worklets/plugin'],
|
||||
};
|
||||
|
||||
@ -30,7 +30,7 @@ type Utxo = {
|
||||
wif?: string;
|
||||
};
|
||||
|
||||
type ElectrumTransaction = {
|
||||
export type ElectrumTransaction = {
|
||||
txid: string;
|
||||
hash: string;
|
||||
version: number;
|
||||
@ -58,13 +58,14 @@ type ElectrumTransaction = {
|
||||
addresses: string[];
|
||||
};
|
||||
}[];
|
||||
blockhash: string;
|
||||
confirmations: number;
|
||||
time: number;
|
||||
blocktime: number;
|
||||
// Confirmation-only fields: absent on mempool (unconfirmed) responses.
|
||||
blockhash?: string;
|
||||
confirmations?: number;
|
||||
time?: number;
|
||||
blocktime?: number;
|
||||
};
|
||||
|
||||
type ElectrumTransactionWithHex = ElectrumTransaction & {
|
||||
export type ElectrumTransactionWithHex = ElectrumTransaction & {
|
||||
hex: string;
|
||||
};
|
||||
|
||||
@ -100,24 +101,84 @@ 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 };
|
||||
|
||||
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;
|
||||
// --- 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.
|
||||
|
||||
/** 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;
|
||||
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;
|
||||
const txhashHeightCache: Record<string, number> = {};
|
||||
let _realm: Realm | undefined;
|
||||
|
||||
@ -163,10 +224,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('Getting preferred server:', { host, tcpPort, sslPort });
|
||||
console.log('[electrum] Getting preferred server:', { host, tcpPort, sslPort });
|
||||
|
||||
if (!host) {
|
||||
console.warn('Preferred server host is undefined');
|
||||
console.warn('[electrum] Preferred server host is undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -176,7 +237,7 @@ export const getPreferredServer = async (): Promise<ElectrumServerItem | undefin
|
||||
ssl: sslPort ? Number(sslPort) : undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in getPreferredServer:', error);
|
||||
console.error('[electrum] Error in getPreferredServer:', error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
@ -184,12 +245,12 @@ export const getPreferredServer = async (): Promise<ElectrumServerItem | undefin
|
||||
export const removePreferredServer = async () => {
|
||||
try {
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
console.log('Removing preferred server');
|
||||
console.log('[electrum] Removing preferred server');
|
||||
await DefaultPreference.clear(ELECTRUM_HOST);
|
||||
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
|
||||
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
|
||||
} catch (error) {
|
||||
console.error('Error in removePreferredServer:', error);
|
||||
console.error('[electrum] Error in removePreferredServer:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -198,14 +259,14 @@ export async function isDisabled(): Promise<boolean> {
|
||||
try {
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
const savedValue = await DefaultPreference.get(ELECTRUM_CONNECTION_DISABLED);
|
||||
console.log('Getting Electrum connection disabled state:', savedValue);
|
||||
console.log('[electrum] Getting Electrum connection disabled state:', savedValue);
|
||||
if (savedValue === null) {
|
||||
result = false;
|
||||
} else {
|
||||
result = savedValue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting Electrum connection disabled state:', error);
|
||||
console.error('[electrum] Error getting Electrum connection disabled state:', error);
|
||||
result = false;
|
||||
}
|
||||
return !!result;
|
||||
@ -213,8 +274,23 @@ export async function isDisabled(): Promise<boolean> {
|
||||
|
||||
export async function setDisabled(disabled = true) {
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
console.log('Setting Electrum connection disabled state to:', disabled);
|
||||
return DefaultPreference.set(ELECTRUM_CONNECTION_DISABLED, disabled ? '1' : '');
|
||||
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;
|
||||
}
|
||||
|
||||
function getCurrentPeer() {
|
||||
@ -238,7 +314,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('Getting saved peer:', { host, tcpPort, sslPort });
|
||||
console.log('[electrum] Getting saved peer:', { host, tcpPort, sslPort });
|
||||
|
||||
if (!host) {
|
||||
return null;
|
||||
@ -254,53 +330,98 @@ async function getSavedPeer(): Promise<Peer | null> {
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error in getSavedPeer:', error);
|
||||
console.error('[electrum] Error in getSavedPeer:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function connectMain(): Promise<void> {
|
||||
if (await isDisabled()) {
|
||||
console.log('Electrum connection disabled by user. Skipping connectMain call');
|
||||
return;
|
||||
}
|
||||
/** Resolve to the peer this attempt should target (preferred saved peer, or rotate hardcoded list). */
|
||||
async function pickPeer(): Promise<Peer> {
|
||||
let usingPeer = getNextPeer();
|
||||
const savedPeer = await getSavedPeer();
|
||||
if (savedPeer && savedPeer.host && (savedPeer.tcp || savedPeer.ssl)) {
|
||||
usingPeer = savedPeer;
|
||||
}
|
||||
return usingPeer;
|
||||
}
|
||||
|
||||
console.log('Using peer:', JSON.stringify(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;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('begin connection:', JSON.stringify(usingPeer));
|
||||
mainClient = new ElectrumClient(net, tls, usingPeer.ssl || usingPeer.tcp, usingPeer.host, usingPeer.ssl ? 'tls' : 'tcp');
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
// 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');
|
||||
};
|
||||
const ver = await mainClient.initElectrum({ client: 'bluewallet', version: '1.4' });
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
if (ver && ver[0]) {
|
||||
console.log('connected to ', ver);
|
||||
console.log('[electrum] 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':
|
||||
@ -309,8 +430,6 @@ export async function connectMain(): Promise<void> {
|
||||
}
|
||||
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')) {
|
||||
@ -319,36 +438,154 @@ export async function connectMain(): Promise<void> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const header = await mainClient.blockchainHeaders_subscribe();
|
||||
const header = await client.blockchainHeaders_subscribe();
|
||||
if (header && header.height) {
|
||||
latestBlock = {
|
||||
height: header.height,
|
||||
time: Math.floor(+new Date() / 1000),
|
||||
};
|
||||
}
|
||||
// AsyncStorage.setItem(storageKey, JSON.stringify(peers)); TODO: refactor
|
||||
return { ok: true, peer: usingPeer };
|
||||
}
|
||||
return { ok: false, peer: usingPeer };
|
||||
} catch (e) {
|
||||
mainConnected = false;
|
||||
console.log('bad connection:', JSON.stringify(usingPeer), e);
|
||||
mainClient?.close();
|
||||
mainClient = undefined;
|
||||
console.log('[electrum] bad connection:', JSON.stringify(usingPeer), e);
|
||||
if (mainClient) {
|
||||
try {
|
||||
mainClient.close();
|
||||
} catch {}
|
||||
mainClient = undefined;
|
||||
}
|
||||
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 (!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();
|
||||
}
|
||||
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> {
|
||||
@ -370,7 +607,7 @@ export async function presentResetToDefaultsAlert(): Promise<boolean> {
|
||||
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
|
||||
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
|
||||
} catch (e) {
|
||||
console.log(e); // Must be running on Android
|
||||
console.log('[electrum]', e); // Must be running on Android
|
||||
}
|
||||
resolve(true);
|
||||
},
|
||||
@ -389,7 +626,7 @@ export async function presentResetToDefaultsAlert(): Promise<boolean> {
|
||||
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
|
||||
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
|
||||
} catch (e) {
|
||||
console.log(e); // Must be running on Android
|
||||
console.log('[electrum]', e); // Must be running on Android
|
||||
}
|
||||
resolve(true);
|
||||
},
|
||||
@ -415,7 +652,7 @@ export async function presentResetToDefaultsAlert(): Promise<boolean> {
|
||||
async function presentNetworkErrorAlert(usingPeer?: Peer, allowRepeat = false) {
|
||||
if (await isDisabled()) {
|
||||
console.log(
|
||||
'Electrum connection disabled by user. Perhaps we are attempting to show this network error alert after the user disabled connections.',
|
||||
'[electrum] Electrum connection disabled by user. Perhaps we are attempting to show this network error alert after the user disabled connections.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -431,10 +668,10 @@ async function presentNetworkErrorAlert(usingPeer?: Peer, allowRepeat = false) {
|
||||
{
|
||||
text: loc.wallets.list_tryagain,
|
||||
onPress: () => {
|
||||
connectionAttempt = 0;
|
||||
mainClient?.close();
|
||||
mainClient = undefined;
|
||||
setTimeout(connectMain, 500);
|
||||
forceDisconnect();
|
||||
setTimeout(() => {
|
||||
ensureConnected({ showAlertOnFailure: true }).catch(() => {});
|
||||
}, 500);
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
@ -443,10 +680,10 @@ async function presentNetworkErrorAlert(usingPeer?: Peer, allowRepeat = false) {
|
||||
onPress: () => {
|
||||
presentResetToDefaultsAlert().then(result => {
|
||||
if (result) {
|
||||
connectionAttempt = 0;
|
||||
mainClient?.close();
|
||||
mainClient = undefined;
|
||||
setTimeout(connectMain, 500);
|
||||
forceDisconnect();
|
||||
setTimeout(() => {
|
||||
ensureConnected({ showAlertOnFailure: true }).catch(() => {});
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -455,9 +692,7 @@ async function presentNetworkErrorAlert(usingPeer?: Peer, allowRepeat = false) {
|
||||
{
|
||||
text: loc._.cancel,
|
||||
onPress: () => {
|
||||
connectionAttempt = 0;
|
||||
mainClient?.close();
|
||||
mainClient = undefined;
|
||||
forceDisconnect();
|
||||
},
|
||||
style: 'cancel',
|
||||
},
|
||||
@ -518,18 +753,27 @@ export const getBalanceByAddress = async function (address: string): Promise<{ c
|
||||
balance.addr = address;
|
||||
return balance;
|
||||
} catch (error) {
|
||||
console.error('Error in getBalanceByAddress:', error);
|
||||
console.error('[electrum] Error in getBalanceByAddress:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getConfig = async function () {
|
||||
if (!mainClient) throw new Error('Electrum client is not connected');
|
||||
if (!mainClient) {
|
||||
return {
|
||||
host: undefined,
|
||||
port: undefined,
|
||||
serverName: false as typeof serverName,
|
||||
connected: connState === 'connected' ? 1 : 0,
|
||||
};
|
||||
}
|
||||
return {
|
||||
host: mainClient.host,
|
||||
port: mainClient.port,
|
||||
serverName,
|
||||
connected: mainClient.timeLastCall !== 0 && mainClient.status,
|
||||
// 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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -558,14 +802,24 @@ export const getMempoolTransactionsByAddress = async function (address: string):
|
||||
return mainClient.blockchainScripthash_getMempool(uint8ArrayToHex(reversedHash));
|
||||
};
|
||||
|
||||
export const ping = async function () {
|
||||
try {
|
||||
await mainClient.server_ping();
|
||||
return true;
|
||||
} catch (_) {}
|
||||
|
||||
mainConnected = false;
|
||||
return false;
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
// exported only to be used in unit tests
|
||||
@ -778,7 +1032,7 @@ export const multiGetBalanceByAddress = async (addresses: string[], batchsize: n
|
||||
}
|
||||
|
||||
for (const bal of balances) {
|
||||
if (bal.error) console.warn('multiGetBalanceByAddress():', bal.error);
|
||||
if (bal.error) console.warn('[electrum] multiGetBalanceByAddress():', bal.error);
|
||||
ret.balance += +bal.result.confirmed;
|
||||
ret.unconfirmed_balance += +bal.result.unconfirmed;
|
||||
ret.addresses[scripthash2addr[bal.param]] = bal.result;
|
||||
@ -872,7 +1126,7 @@ export const multiGetHistoryByAddress = async function (
|
||||
}
|
||||
|
||||
for (const history of results) {
|
||||
if (history.error) console.warn('multiGetHistoryByAddress():', history.error);
|
||||
if (history.error) console.warn('[electrum] 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
|
||||
@ -913,7 +1167,7 @@ export async function multiGetTransactionByTxid<T extends boolean>(
|
||||
try {
|
||||
ret[txid] = JSON.parse(jsonString.cache_value as string);
|
||||
} catch (error) {
|
||||
console.log(error, 'cache failed to parse', jsonString.cache_value);
|
||||
console.log('[electrum]', error, 'cache failed to parse', jsonString.cache_value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -963,7 +1217,7 @@ export async function multiGetTransactionByTxid<T extends boolean>(
|
||||
tx = txhexToElectrumTransaction(tx);
|
||||
results.push({ result: tx, param: txid });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.log('[electrum]', err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -979,7 +1233,7 @@ export async function multiGetTransactionByTxid<T extends boolean>(
|
||||
}
|
||||
results.push({ result: tx, param: txid });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.log('[electrum]', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1034,40 +1288,12 @@ export async function multiGetTransactionByTxid<T extends boolean>(
|
||||
}
|
||||
});
|
||||
} catch (writeError) {
|
||||
console.error('Failed to write transaction cache:', writeError);
|
||||
console.error('[electrum] 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) {
|
||||
@ -1219,10 +1445,11 @@ 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'), 5000);
|
||||
timeoutId = setTimeout(() => resolve('timeout'), timeoutMs);
|
||||
}),
|
||||
client.connect(),
|
||||
]);
|
||||
@ -1240,8 +1467,19 @@ 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 => {
|
||||
mainClient?.close();
|
||||
disconnectGeneration += 1;
|
||||
if (mainClient) {
|
||||
try {
|
||||
mainClient.close();
|
||||
} catch {}
|
||||
mainClient = undefined;
|
||||
}
|
||||
setConnectionState('disconnected');
|
||||
};
|
||||
|
||||
export const setBatchingDisabled = () => {
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
197
blue_modules/arkade-adapters/realm/realmInstance.ts
Normal file
197
blue_modules/arkade-adapters/realm/realmInstance.ts
Normal file
@ -0,0 +1,197 @@
|
||||
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,
|
||||
};
|
||||
423
blue_modules/arkade-background.ts
Normal file
423
blue_modules/arkade-background.ts
Normal file
@ -0,0 +1,423 @@
|
||||
// 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;
|
||||
},
|
||||
};
|
||||
163
blue_modules/arkade-notifications.ts
Normal file
163
blue_modules/arkade-notifications.ts
Normal file
@ -0,0 +1,163 @@
|
||||
// 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;
|
||||
},
|
||||
};
|
||||
@ -2,4 +2,7 @@
|
||||
* Let's keep config vars, constants and definitions here
|
||||
*/
|
||||
|
||||
export const groundControlUri: string = 'https://groundcontrol-bluewallet.herokuapp.com';
|
||||
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';
|
||||
|
||||
@ -1,23 +1,98 @@
|
||||
import AES from 'crypto-js/aes';
|
||||
import Utf8 from 'crypto-js/enc-utf8';
|
||||
import { cbc } from '@noble/ciphers/aes';
|
||||
import { md5 } from '@noble/hashes/legacy';
|
||||
import { randomBytes } from '@noble/hashes/utils';
|
||||
|
||||
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 ciphertext = AES.encrypt(data, password);
|
||||
return ciphertext.toString();
|
||||
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]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,44 +26,93 @@ export interface TinySecp256k1InterfaceExtended {
|
||||
signDER(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
// @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
|
||||
>;
|
||||
|
||||
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;
|
||||
// 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;
|
||||
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 { 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);
|
||||
},
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -71,7 +120,8 @@ function throwToNull<Type>(fn: () => Type): Type | null {
|
||||
function isPoint(p: Uint8Array, xOnly: boolean): boolean {
|
||||
if ((p.length === 32) !== xOnly) return false;
|
||||
try {
|
||||
return !!necc.Point.fromHex(p);
|
||||
pointFromBytes(p);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
@ -79,23 +129,12 @@ function isPoint(p: Uint8Array, xOnly: boolean): boolean {
|
||||
|
||||
const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256k1InterfaceBIP32 = {
|
||||
isPoint: (p: Uint8Array): boolean => isPoint(p, false),
|
||||
isPrivate: (d: Uint8Array): boolean => {
|
||||
/* if (
|
||||
[
|
||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141',
|
||||
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142',
|
||||
].includes(d.toString('hex'))
|
||||
) {
|
||||
return false;
|
||||
} */
|
||||
return necc.utils.isValidPrivateKey(d);
|
||||
},
|
||||
isPrivate: (d: Uint8Array): boolean => necc.utils.isValidSecretKey(d),
|
||||
isXOnlyPoint: (p: Uint8Array): boolean => isPoint(p, true),
|
||||
|
||||
xOnlyPointAddTweak: (p: Uint8Array, tweak: Uint8Array): { parity: 0 | 1; xOnlyPubkey: Uint8Array } | null =>
|
||||
throwToNull(() => {
|
||||
const P = necc.utils.pointAddScalar(p, tweak, true);
|
||||
const P = tweakUtils.pointAddScalar(p, tweak, true);
|
||||
const parity = P[0] % 2 === 1 ? 1 : 0;
|
||||
return { parity, xOnlyPubkey: P.slice(1) };
|
||||
}),
|
||||
@ -104,60 +143,56 @@ const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256
|
||||
throwToNull(() => necc.getPublicKey(sk, defaultTrue(compressed))),
|
||||
|
||||
pointCompress: (p: Uint8Array, compressed?: boolean): Uint8Array => {
|
||||
return necc.Point.fromHex(p).toRawBytes(defaultTrue(compressed));
|
||||
return pointFromBytes(p).toBytes(defaultTrue(compressed));
|
||||
},
|
||||
|
||||
pointMultiply: (a: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null =>
|
||||
throwToNull(() => necc.utils.pointMultiply(a, tweak, defaultTrue(compressed))),
|
||||
throwToNull(() => tweakUtils.pointMultiply(a, tweak, defaultTrue(compressed))),
|
||||
|
||||
pointAdd: (a: Uint8Array, b: Uint8Array, compressed?: boolean): Uint8Array | null =>
|
||||
throwToNull(() => {
|
||||
const A = necc.Point.fromHex(a);
|
||||
const B = necc.Point.fromHex(b);
|
||||
return A.add(B).toRawBytes(defaultTrue(compressed));
|
||||
const A = pointFromBytes(a);
|
||||
const B = pointFromBytes(b);
|
||||
return A.add(B).toBytes(defaultTrue(compressed));
|
||||
}),
|
||||
|
||||
pointAddScalar: (p: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null =>
|
||||
throwToNull(() => necc.utils.pointAddScalar(p, tweak, defaultTrue(compressed))),
|
||||
throwToNull(() => tweakUtils.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 = necc.utils.privateAdd(d, tweak);
|
||||
// console.log(ret);
|
||||
const ret = tweakUtils.privateAdd(d, tweak);
|
||||
if (ret.join('') === '00000000000000000000000000000000') {
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
}),
|
||||
|
||||
privateNegate: (d: Uint8Array): Uint8Array => necc.utils.privateNegate(d),
|
||||
privateNegate: (d: Uint8Array): Uint8Array => tweakUtils.privateNegate(d),
|
||||
|
||||
sign: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => {
|
||||
return necc.signSync(h, d, { der: false, extraEntropy: e });
|
||||
return necc.sign(h, d, { prehash: false, extraEntropy: e });
|
||||
},
|
||||
|
||||
signDER: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => {
|
||||
return necc.signSync(h, d, { der: true, extraEntropy: e });
|
||||
return compactToDER(necc.sign(h, d, { prehash: false, extraEntropy: e }));
|
||||
},
|
||||
|
||||
signSchnorr: (h: Uint8Array, d: Uint8Array, e: Uint8Array = new Uint8Array(32).fill(0x00)): Uint8Array => {
|
||||
return necc.schnorr.signSync(h, d, e);
|
||||
return necc.schnorr.sign(h, d, e);
|
||||
},
|
||||
|
||||
verify: (h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean => {
|
||||
return necc.verify(signature, h, Q, { strict });
|
||||
return necc.verify(signature, h, Q, { prehash: false, lowS: strict !== false });
|
||||
},
|
||||
|
||||
verifySchnorr: (h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean => {
|
||||
return necc.schnorr.verifySync(signature, h, Q);
|
||||
return necc.schnorr.verify(signature, h, Q);
|
||||
},
|
||||
};
|
||||
|
||||
export default ecc;
|
||||
|
||||
// module.exports.ecc = ecc;
|
||||
|
||||
@ -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 { groundControlUri } from './constants';
|
||||
import { arkadePaymentPushUri, 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';
|
||||
let baseURI = groundControlUri;
|
||||
const baseURI = groundControlUri;
|
||||
let notificationSubscriptions: EmitterSubscription[] = [];
|
||||
let onProcessNotificationsHandler: undefined | (() => void | Promise<void>);
|
||||
const handledNotificationKeys = new Set<string>();
|
||||
@ -252,6 +252,29 @@ 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
|
||||
@ -327,6 +350,44 @@ 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
|
||||
@ -529,22 +590,6 @@ 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> => {
|
||||
@ -676,38 +721,6 @@ 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();
|
||||
@ -757,10 +770,6 @@ 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
|
||||
@ -781,7 +790,5 @@ 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));
|
||||
}
|
||||
};
|
||||
|
||||
@ -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, false);
|
||||
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, { cancelable: false });
|
||||
} while (!password);
|
||||
}
|
||||
}
|
||||
|
||||
41
blue_modules/transactionDisplayState.ts
Normal file
41
blue_modules/transactionDisplayState.ts
Normal file
@ -0,0 +1,41 @@
|
||||
// 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';
|
||||
}
|
||||
}
|
||||
@ -147,11 +147,10 @@ export class BlueApp {
|
||||
console.warn('error reading', key, error.message);
|
||||
console.warn('fallback to realm');
|
||||
const realmKeyValue = await this.openRealmKeyValue();
|
||||
const obj = realmKeyValue.objectForPrimaryKey('KeyValue', key); // search for a realm object with a primary key
|
||||
const obj = realmKeyValue.objectForPrimaryKey<{ key: string; value: string }>('KeyValue', 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;
|
||||
}
|
||||
@ -547,10 +546,11 @@ export class BlueApp {
|
||||
(walletToInflate._txs_by_internal_index[tx.index] as Transaction[]).push(transaction);
|
||||
}
|
||||
} else {
|
||||
if (!Array.isArray(walletToInflate._txs_by_external_index)) walletToInflate._txs_by_external_index = [];
|
||||
walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || [];
|
||||
// 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] || [];
|
||||
const transaction = JSON.parse(tx.tx);
|
||||
(walletToInflate._txs_by_external_index as Transaction[]).push(transaction);
|
||||
walletToInflate._txs_by_external_index[0].push(transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -559,32 +559,6 @@ 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
|
||||
@ -592,16 +566,14 @@ export class BlueApp {
|
||||
realm.delete(walletTransactionsToDelete);
|
||||
|
||||
// insert new ones:
|
||||
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 [indexStr, txs] of Object.entries(walletToSave._txs_by_external_index)) {
|
||||
for (const tx of txs) {
|
||||
realm.create(
|
||||
'WalletTransactions',
|
||||
{
|
||||
walletid: id,
|
||||
internal: false,
|
||||
index: parseInt(index, 10),
|
||||
index: parseInt(indexStr, 10),
|
||||
tx: JSON.stringify(tx),
|
||||
},
|
||||
Realm.UpdateMode.Modified,
|
||||
@ -609,16 +581,14 @@ export class BlueApp {
|
||||
}
|
||||
}
|
||||
|
||||
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 [indexStr, txs] of Object.entries(walletToSave._txs_by_internal_index)) {
|
||||
for (const tx of txs) {
|
||||
realm.create(
|
||||
'WalletTransactions',
|
||||
{
|
||||
walletid: id,
|
||||
internal: true,
|
||||
index: parseInt(index, 10),
|
||||
index: parseInt(indexStr, 10),
|
||||
tx: JSON.stringify(tx),
|
||||
},
|
||||
Realm.UpdateMode.Modified,
|
||||
|
||||
@ -390,7 +390,7 @@ export class HDSegwitBech32Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore stfu
|
||||
return { tx, inputs, outputs, fee };
|
||||
// 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! };
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 CryptoJS from 'crypto-js';
|
||||
import { cbc } from '@noble/ciphers/aes';
|
||||
import ecc from '../blue_modules/noble_ecc';
|
||||
import { parse } from 'url'; // eslint-disable-line n/no-deprecated-api
|
||||
import { fetch } from '../util/fetch';
|
||||
@ -321,13 +321,24 @@ export default class Lnurl {
|
||||
}
|
||||
|
||||
static decipherAES(ciphertextBase64: string, preimageHex: string, ivBase64: string): string {
|
||||
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);
|
||||
// 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 '';
|
||||
}
|
||||
}
|
||||
|
||||
getCommentAllowed(): number | false {
|
||||
|
||||
@ -106,23 +106,31 @@ export class MultisigCosigner {
|
||||
this._valid = false;
|
||||
}
|
||||
|
||||
// is it coldcard json?
|
||||
// is it coldcard / unchained json?
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
if (json.p2sh && json.p2sh_deriv && json.xfp) {
|
||||
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2sh, json.p2sh_deriv));
|
||||
|
||||
// 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));
|
||||
this._valid = true;
|
||||
this._cosigners.push(cc);
|
||||
}
|
||||
|
||||
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));
|
||||
if (xpub && path && json.xfp) {
|
||||
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, xpub, path));
|
||||
this._valid = true;
|
||||
this._cosigners.push(cc);
|
||||
}
|
||||
|
||||
if (json.p2wsh && json.p2wsh_deriv && json.xfp) {
|
||||
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2wsh, json.p2wsh_deriv));
|
||||
if (json.p2wsh && p2wsh_deriv && json.xfp) {
|
||||
const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2wsh, p2wsh_deriv));
|
||||
this._valid = true;
|
||||
this._cosigners.push(cc);
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
* @return {Promise.<Uint8Array>} The random bytes
|
||||
*/
|
||||
export async function randomBytes(size: number): Promise<Uint8Array> {
|
||||
const g: any = globalThis as any;
|
||||
const g = globalThis as any;
|
||||
const rnCrypto = g && g.crypto;
|
||||
if (!rnCrypto || typeof rnCrypto.getRandomValues !== 'function') {
|
||||
throw new Error('crypto.getRandomValues is not available');
|
||||
|
||||
@ -216,10 +216,35 @@ const startImport = (
|
||||
if (text.startsWith('arkade://')) {
|
||||
const ark = new LightningArkWallet();
|
||||
ark.setSecret(text);
|
||||
await ark.init();
|
||||
// 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.
|
||||
if (!offline) {
|
||||
await ark.fetchBalance();
|
||||
await ark.fetchTransactions();
|
||||
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);
|
||||
}
|
||||
}
|
||||
yield { wallet: ark };
|
||||
}
|
||||
@ -319,6 +344,7 @@ const startImport = (
|
||||
}
|
||||
|
||||
yield { progress: 'wif' };
|
||||
|
||||
const segwitWallet = new SegwitP2SHWallet();
|
||||
segwitWallet.setSecret(text);
|
||||
if (segwitWallet.getAddress()) {
|
||||
@ -382,6 +408,89 @@ 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();
|
||||
|
||||
@ -45,9 +45,7 @@ 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[];
|
||||
@ -204,70 +202,37 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
return child.toWIF();
|
||||
}
|
||||
|
||||
_getNodeAddressByIndex(node: number, index: number): string {
|
||||
index = index * 1; // cast to int
|
||||
_getNodeByIndex(node: 0 | 1, index: number): BIP32Interface {
|
||||
const cachedNode = node === 0 ? this._node0 : this._node1;
|
||||
if (cachedNode) {
|
||||
return cachedNode.derive(index);
|
||||
}
|
||||
|
||||
const xpub = this._zpubToXpub(this.getXpub());
|
||||
const hdNode = bip32.fromBase58(xpub).derive(node);
|
||||
|
||||
if (node === 0) {
|
||||
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
|
||||
}
|
||||
|
||||
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));
|
||||
this._node0 = hdNode;
|
||||
} else {
|
||||
// tbh the only possible else is node === 1
|
||||
// @ts-ignore
|
||||
address = this._hdNodeToAddress(this._node1.derive(index));
|
||||
this._node1 = hdNode;
|
||||
}
|
||||
|
||||
if (node === 0) {
|
||||
return (this.external_addresses_cache[index] = address);
|
||||
} else {
|
||||
// tbh the only possible else option is node === 1
|
||||
return (this.internal_addresses_cache[index] = address);
|
||||
}
|
||||
return hdNode.derive(index);
|
||||
}
|
||||
|
||||
_getNodePubkeyByIndex(node: number, index: number) {
|
||||
index = index * 1; // cast to int
|
||||
_getNodeAddressByIndex(node: 0 | 1, index: number): string {
|
||||
const cache = node === 0 ? this.external_addresses_cache : this.internal_addresses_cache;
|
||||
|
||||
if (node === 0 && !this._node0) {
|
||||
const xpub = this._zpubToXpub(this.getXpub());
|
||||
const hdNode = bip32.fromBase58(xpub);
|
||||
this._node0 = hdNode.derive(node);
|
||||
}
|
||||
if (cache[index]) return cache[index]; // cache hit
|
||||
|
||||
if (node === 1 && !this._node1) {
|
||||
const xpub = this._zpubToXpub(this.getXpub());
|
||||
const hdNode = bip32.fromBase58(xpub);
|
||||
this._node1 = hdNode.derive(node);
|
||||
}
|
||||
const hdNode = this._getNodeByIndex(node, index);
|
||||
const address = this._hdNodeToAddress(hdNode);
|
||||
|
||||
if (node === 0 && this._node0) {
|
||||
return this._node0.derive(index).publicKey;
|
||||
}
|
||||
return (cache[index] = address);
|
||||
}
|
||||
|
||||
if (node === 1 && this._node1) {
|
||||
return this._node1.derive(index).publicKey;
|
||||
}
|
||||
|
||||
throw new Error('Internal error: this._node0 or this._node1 is undefined');
|
||||
_getNodePubkeyByIndex(node: 0 | 1, index: number) {
|
||||
return this._getNodeByIndex(node, index).publicKey;
|
||||
}
|
||||
|
||||
_getExternalAddressByIndex(index: number): string {
|
||||
@ -424,137 +389,95 @@ 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++) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
externalIndexByAddress.set(this._getExternalAddressByIndex(c), c);
|
||||
}
|
||||
|
||||
const internalIndexByAddress = new Map<string, number>();
|
||||
for (let c = 0; c < next_free_change_address_index + this.gap_limit; 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
internalIndexByAddress.set(this._getInternalAddressByIndex(c), c);
|
||||
}
|
||||
|
||||
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++) {
|
||||
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 */,
|
||||
};
|
||||
paymentCodeIndexByAddress.set(this._getBIP47AddressReceive(pc, c), { pc, c });
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -601,8 +524,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
|
||||
const ret: Transaction[] = [];
|
||||
for (const tx of txs) {
|
||||
tx.timestamp = tx.blocktime;
|
||||
if (!tx.blocktime) tx.timestamp = Math.floor(+new Date() / 1000) - 30; // unconfirmed
|
||||
tx.timestamp = tx.blocktime || Math.floor(+new Date() / 1000) - 30; // fallback for unconfirmed
|
||||
tx.confirmations = tx.confirmations || 0; // unconfirmed
|
||||
tx.hash = tx.txid;
|
||||
tx.value = 0;
|
||||
@ -653,8 +575,7 @@ 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));
|
||||
// @ts-ignore
|
||||
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
|
||||
if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) {
|
||||
// in this particular chunk we have used addresses
|
||||
lastChunkWithUsedAddressesNum = c;
|
||||
lastHistoriesWithUsedAddresses = histories;
|
||||
@ -696,8 +617,7 @@ 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));
|
||||
// @ts-ignore
|
||||
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
|
||||
if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) {
|
||||
// in this particular chunk we have used addresses
|
||||
lastChunkWithUsedAddressesNum = c;
|
||||
lastHistoriesWithUsedAddresses = histories;
|
||||
@ -739,8 +659,7 @@ 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));
|
||||
// @ts-ignore
|
||||
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
|
||||
if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) {
|
||||
// in this particular chunk we have used addresses
|
||||
lastChunkWithUsedAddressesNum = c;
|
||||
lastHistoriesWithUsedAddresses = histories;
|
||||
|
||||
@ -315,7 +315,7 @@ export class AbstractHDWallet extends LegacyWallet {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
_getNodePubkeyByIndex(node: number, index: number): Uint8Array | undefined {
|
||||
_getNodePubkeyByIndex(node: 0 | 1, index: number): Uint8Array | undefined {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
|
||||
@ -21,14 +21,20 @@ bitcoin.initEccLib(ecc);
|
||||
*/
|
||||
export class LegacyWallet extends AbstractWallet {
|
||||
static readonly type = 'legacy';
|
||||
static readonly typeReadable = 'Legacy (P2PKH)';
|
||||
static readonly defaultTypeReadable = 'Legacy (P2PKH)';
|
||||
// @ts-ignore: override
|
||||
public readonly type = LegacyWallet.type;
|
||||
// @ts-ignore: override
|
||||
public readonly typeReadable = LegacyWallet.typeReadable;
|
||||
public readonly typeReadable: string;
|
||||
|
||||
_txs_by_external_index: Transaction[] = [];
|
||||
_txs_by_internal_index: Transaction[] = [];
|
||||
_txs_by_external_index: Record<number, Transaction[]> = {};
|
||||
_txs_by_internal_index: Record<number, Transaction[]> = {};
|
||||
|
||||
constructor(typeReadable?: string) {
|
||||
super();
|
||||
|
||||
this.typeReadable = typeReadable ?? LegacyWallet.defaultTypeReadable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple function which says that we havent tried to fetch balance
|
||||
@ -338,14 +344,14 @@ export class LegacyWallet extends AbstractWallet {
|
||||
}
|
||||
}
|
||||
|
||||
this._txs_by_external_index = _txsByExternalIndex;
|
||||
this._txs_by_external_index = { 0: _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
@ -104,6 +104,11 @@ 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;
|
||||
@ -123,10 +128,11 @@ export type Transaction = {
|
||||
locktime: number;
|
||||
inputs: TransactionInput[];
|
||||
outputs: TransactionOutput[];
|
||||
blockhash: string;
|
||||
confirmations: number;
|
||||
time: number;
|
||||
blocktime: number;
|
||||
// Confirmation-only fields: absent on mempool (unconfirmed) responses.
|
||||
blockhash?: string;
|
||||
confirmations?: number;
|
||||
time?: number;
|
||||
blocktime?: number;
|
||||
timestamp: number; // seconds, not milliseconds
|
||||
value?: number;
|
||||
|
||||
|
||||
@ -197,12 +197,13 @@ export class WatchOnlyWallet extends LegacyWallet {
|
||||
|
||||
async fetchUtxo() {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.fetchUtxo();
|
||||
throw new Error('Not initialized');
|
||||
// Single-address watch-only uses LegacyWallet UTXO + derivation from txs (no HD instance).
|
||||
return super.fetchUtxo();
|
||||
}
|
||||
|
||||
getUtxo(...args: Parameters<THDWalletForWatchOnly['getUtxo']>) {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.getUtxo(...args);
|
||||
throw new Error('Not initialized');
|
||||
return super.getUtxo(...args);
|
||||
}
|
||||
|
||||
combinePsbt(...args: Parameters<THDWalletForWatchOnly['combinePsbt']>) {
|
||||
|
||||
@ -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 }) => {
|
||||
|
||||
@ -94,6 +94,8 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
paddingHorizontal: 8,
|
||||
minHeight: 33,
|
||||
fontSize: 15,
|
||||
lineHeight: 19,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -1,22 +1,20 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
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 {
|
||||
Text,
|
||||
Image,
|
||||
LayoutAnimation,
|
||||
NativeSyntheticEvent,
|
||||
Platform,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TextInputProps,
|
||||
TextInputSelectionChangeEventData,
|
||||
TextInputSelectionChangeEvent,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Badge from './Badge';
|
||||
import Icon from './Icon';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import dayjs from 'dayjs';
|
||||
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated';
|
||||
|
||||
import {
|
||||
CurrencyRate,
|
||||
@ -28,10 +26,12 @@ 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,6 +44,23 @@ 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
|
||||
@ -95,6 +112,7 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
|
||||
isLoading = false,
|
||||
maxSendableAmount,
|
||||
isMaxAmountEstimate,
|
||||
style: styleOverride,
|
||||
...otherProps
|
||||
} = props;
|
||||
const [isRateBeingUpdatedLocal, setIsRateBeingUpdatedLocal] = useState(false);
|
||||
@ -111,6 +129,23 @@ 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 '';
|
||||
@ -140,7 +175,6 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (await isRateOutdated()) {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
const recent = await mostRecentFetchedRate();
|
||||
setOutdatedRefreshRate(recent);
|
||||
}
|
||||
@ -180,7 +214,6 @@ 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:
|
||||
@ -199,7 +232,6 @@ 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
|
||||
@ -269,7 +301,7 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
|
||||
}, [maxSendableAmount]);
|
||||
|
||||
const handleSelectionChange = useCallback(
|
||||
(event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
|
||||
(event: TextInputSelectionChangeEvent) => {
|
||||
const { selection } = event.nativeEvent;
|
||||
if (selection.start !== selection.end || selection.start !== amount.length) {
|
||||
textInputRef.current?.setNativeProps({ selection: { start: amount.length, end: amount.length } });
|
||||
@ -278,40 +310,120 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
|
||||
[amount],
|
||||
);
|
||||
|
||||
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 },
|
||||
});
|
||||
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 },
|
||||
};
|
||||
|
||||
return (
|
||||
<Pressable accessibilityRole="button" accessibilityLabel={loc._.enter_amount} disabled={disabled} onPress={handleTextInputOnPress}>
|
||||
<View style={styles.root}>
|
||||
{!disabled && <View style={[styles.center, stylesHook.center]} />}
|
||||
{!disabled && <View style={styles.sideRail} />}
|
||||
<View style={styles.flex}>
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.container, stylesHook.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 ? (
|
||||
<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}
|
||||
/>
|
||||
<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>
|
||||
) : (
|
||||
<Pressable onPress={resetAmount} style={styles.maxPressable}>
|
||||
<Text style={[styles.input, stylesHook.input]}>{BitcoinUnit.MAX}</Text>
|
||||
<Text numberOfLines={1} style={[styles.input, styles.maxLabel, stylesHook.input]}>
|
||||
{BitcoinUnit.MAX}
|
||||
</Text>
|
||||
{maxSendableAmount != null && (
|
||||
<Text style={[styles.maxEstimate, stylesHook.localCurrency]} onLongPress={copyMaxEstimate}>
|
||||
{(isMaxAmountEstimate ? '≈ ' : '') +
|
||||
@ -323,7 +435,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}>
|
||||
@ -332,17 +444,20 @@ 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.changeAmountUnit}
|
||||
onPress={changeAmountUnit}
|
||||
>
|
||||
<Image source={require('../img/round-compare-arrows-24-px.png')} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{!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} />
|
||||
))}
|
||||
</View>
|
||||
{outdatedRefreshRate && (
|
||||
<View style={styles.outdatedRateContainer}>
|
||||
@ -355,7 +470,7 @@ export const AmountInput: React.FC<AmountInputProps> = props => {
|
||||
accessibilityLabel={loc._.refresh}
|
||||
onPress={updateRate}
|
||||
disabled={isRateBeingUpdatedLocal}
|
||||
style={isRateBeingUpdatedLocal ? styles.disabledButton : styles.enabledButon}
|
||||
style={isRateBeingUpdatedLocal ? styles.disabledButton : undefined}
|
||||
>
|
||||
<Icon name="arrows-rotate" type="font-awesome-6" size={16} color={colors.buttonAlternativeTextColor} />
|
||||
</TouchableOpacity>
|
||||
@ -370,11 +485,15 @@ 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,
|
||||
@ -388,9 +507,6 @@ const styles = StyleSheet.create({
|
||||
disabledButton: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
enabledButon: {
|
||||
opacity: 1,
|
||||
},
|
||||
outdatedRateContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
@ -399,24 +515,55 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
alignContent: 'space-between',
|
||||
justifyContent: 'center',
|
||||
paddingTop: 16,
|
||||
paddingBottom: 2,
|
||||
overflow: 'visible',
|
||||
},
|
||||
localCurrency: {
|
||||
fontSize: 18,
|
||||
marginHorizontal: 4,
|
||||
marginRight: 2,
|
||||
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,
|
||||
marginHorizontal: 4,
|
||||
marginLeft: 2,
|
||||
fontWeight: '600',
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
@ -437,11 +584,12 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
maxPressable: {
|
||||
alignItems: 'center',
|
||||
flexShrink: 0,
|
||||
},
|
||||
maxLabel: {
|
||||
flexShrink: 0,
|
||||
},
|
||||
changeAmountUnit: {
|
||||
alignSelf: 'center',
|
||||
marginRight: 16,
|
||||
paddingLeft: 16,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,93 +0,0 @@
|
||||
/* 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' },
|
||||
});
|
||||
@ -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 = 40, containerStyle, icon, onPress }) => {
|
||||
const Avatar: React.FC<AvatarProps> = ({ rounded, size, containerStyle, icon, onPress }) => {
|
||||
const dimensionStyle = { width: size, height: size, borderRadius: rounded ? size / 2 : 0 } as ViewStyle;
|
||||
const content = (
|
||||
<View style={[styles.container, dimensionStyle, containerStyle]}>
|
||||
|
||||
@ -5,11 +5,12 @@ export interface BadgeProps {
|
||||
value?: string | number | React.ReactNode;
|
||||
badgeStyle?: StyleProp<ViewStyle>;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const Badge: React.FC<BadgeProps> = ({ value, badgeStyle, textStyle }) => {
|
||||
const Badge: React.FC<BadgeProps> = ({ value, badgeStyle, textStyle, testID }) => {
|
||||
return (
|
||||
<View style={[styles.badge, badgeStyle]}>
|
||||
<View testID={testID} style={[styles.badge, badgeStyle]}>
|
||||
{typeof value === 'string' || typeof value === 'number' ? <Text style={[styles.text, textStyle]}>{value}</Text> : value}
|
||||
</View>
|
||||
);
|
||||
|
||||
34
components/BlueButtonLink.tsx
Normal file
34
components/BlueButtonLink.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
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;
|
||||
14
components/BlueCard.tsx
Normal file
14
components/BlueCard.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
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;
|
||||
21
components/BlueFormLabel.tsx
Normal file
21
components/BlueFormLabel.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
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;
|
||||
50
components/BlueFormMultiInput.tsx
Normal file
50
components/BlueFormMultiInput.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
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;
|
||||
62
components/BlueText.tsx
Normal file
62
components/BlueText.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
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;
|
||||
17
components/BlueTextCentered.tsx
Normal file
17
components/BlueTextCentered.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
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;
|
||||
@ -220,7 +220,11 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = React.m
|
||||
|
||||
useEffect(() => {
|
||||
if (walletsInitialized) {
|
||||
isElectrumDisabled ? BlueElectrum.forceDisconnect() : BlueElectrum.connectMain();
|
||||
if (isElectrumDisabled) {
|
||||
BlueElectrum.forceDisconnect();
|
||||
} else {
|
||||
BlueElectrum.ensureConnected({ showAlertOnFailure: true });
|
||||
}
|
||||
}
|
||||
}, [isElectrumDisabled, walletsInitialized]);
|
||||
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
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';
|
||||
@ -175,6 +176,15 @@ 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(
|
||||
@ -308,7 +318,11 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
if (walletsInitialized) {
|
||||
txMetadata.current = BlueApp.tx_metadata;
|
||||
counterpartyMetadata.current = BlueApp.counterparty_metadata;
|
||||
setWallets(BlueApp.getWallets());
|
||||
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));
|
||||
}
|
||||
}
|
||||
}, [walletsInitialized]);
|
||||
|
||||
@ -332,24 +346,21 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL);
|
||||
}
|
||||
console.debug('[refreshAllWalletTransactions] Waiting for connectivity...');
|
||||
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();
|
||||
// `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;
|
||||
}
|
||||
|
||||
console.debug('[refreshAllWalletTransactions] Connected to Electrum');
|
||||
|
||||
// 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,
|
||||
);
|
||||
// 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);
|
||||
const timeoutPromise = new Promise<never>(
|
||||
(_resolve, reject) =>
|
||||
(refreshTimeout = setTimeout(() => {
|
||||
@ -419,7 +430,11 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
}
|
||||
_lastTimeTriedToRefetchWallet[walletID] = Date.now();
|
||||
|
||||
await BlueElectrum.waitTillConnected();
|
||||
const connected = await BlueElectrum.ensureConnected();
|
||||
if (!connected) {
|
||||
console.log('[fetchAndSaveWalletTransactions] could not establish Electrum connection, aborting');
|
||||
return;
|
||||
}
|
||||
setWalletTransactionUpdateStatus(walletID);
|
||||
|
||||
const balanceStart = Date.now();
|
||||
@ -453,6 +468,9 @@ 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
|
||||
@ -491,7 +509,6 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
text: loc.wallets.details_delete,
|
||||
onPress: () => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
onConfirmed();
|
||||
},
|
||||
style: 'destructive',
|
||||
|
||||
@ -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 '../BlueComponents';
|
||||
import BlueText from './BlueText';
|
||||
import loc from '../loc';
|
||||
import { useTheme } from './themes';
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { InputAccessoryView, Keyboard, Platform, StyleSheet, View } from 'react-native';
|
||||
import { useTheme } from './themes';
|
||||
import { BlueButtonLink } from '../BlueComponents';
|
||||
import BlueButtonLink from './BlueButtonLink';
|
||||
import loc from '../loc';
|
||||
|
||||
export const DismissKeyboardInputAccessoryViewID = 'DismissKeyboardInputAccessory';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { InputAccessoryView, Keyboard, Platform, StyleSheet, View } from 'react-native';
|
||||
import { BlueButtonLink } from '../BlueComponents';
|
||||
import BlueButtonLink from './BlueButtonLink';
|
||||
import loc from '../loc';
|
||||
import { useTheme } from './themes';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Dimensions, LayoutAnimation, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { encodeUR } from '../blue_modules/ur';
|
||||
import { BlueCurrentTheme } from '../components/themes';
|
||||
@ -159,7 +159,6 @@ export class DynamicQRCode extends Component<DynamicQRCodeProps, DynamicQRCodeSt
|
||||
accessibilityRole="button"
|
||||
testID="DynamicCode"
|
||||
onPress={() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
this.setState(prevState => ({ hideControls: !prevState.hideControls }));
|
||||
}}
|
||||
>
|
||||
|
||||
@ -52,7 +52,14 @@ const useFloatButtonAnimation = (initialHeight: number) => {
|
||||
};
|
||||
};
|
||||
|
||||
const useFloatButtonLayout = (width: number, sizeClass: SizeClass) => {
|
||||
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 lastVerticalDecision = useRef(false);
|
||||
|
||||
const shouldUseVerticalLayout = useCallback(
|
||||
@ -152,15 +159,19 @@ const useFloatButtonLayout = (width: number, sizeClass: SizeClass) => {
|
||||
[width, sizeClass, shouldUseVerticalLayout],
|
||||
);
|
||||
|
||||
const calculateContainerHeight = useCallback((childrenCount: number, isVerticalLayout: boolean) => {
|
||||
if (!isVerticalLayout) return { height: '8%', minHeight: LAYOUT.BUTTON_HEIGHT };
|
||||
const calculateContainerHeight = useCallback(
|
||||
(childrenCount: number, isVerticalLayout: boolean) => {
|
||||
const buttonHeight = getScaledButtonHeight(fontScale);
|
||||
if (!isVerticalLayout) return { height: '8%', minHeight: buttonHeight };
|
||||
|
||||
const totalButtonsHeight = childrenCount * LAYOUT.BUTTON_HEIGHT;
|
||||
const totalMarginsHeight = (childrenCount - 1) * LAYOUT.BUTTON_MARGIN;
|
||||
const calculatedHeight = totalButtonsHeight + totalMarginsHeight;
|
||||
const totalButtonsHeight = childrenCount * buttonHeight;
|
||||
const totalMarginsHeight = (childrenCount - 1) * LAYOUT.BUTTON_MARGIN;
|
||||
const calculatedHeight = totalButtonsHeight + totalMarginsHeight;
|
||||
|
||||
return { height: calculatedHeight };
|
||||
}, []);
|
||||
return { height: calculatedHeight };
|
||||
},
|
||||
[fontScale],
|
||||
);
|
||||
|
||||
const calculateButtonFontSize = useMemo(() => {
|
||||
const divisor = sizeClass === SizeClass.Large ? 22 : sizeClass === SizeClass.Regular ? 24 : 28;
|
||||
@ -267,6 +278,7 @@ interface FButtonProps {
|
||||
isVertical?: boolean;
|
||||
borderRadius?: number;
|
||||
fontSize?: number;
|
||||
buttonHeight?: number;
|
||||
disabled?: boolean;
|
||||
testID?: string;
|
||||
onPress: () => void;
|
||||
@ -277,13 +289,14 @@ 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 }: ButtonContentProps) => {
|
||||
const ButtonContent = ({ icon, text, textStyle, buttonHeight }: ButtonContentProps) => {
|
||||
const computedStyle = StyleSheet.flatten(textStyle);
|
||||
const fontSize = computedStyle.fontSize || LAYOUT.MAX_BUTTON_FONT_SIZE;
|
||||
const iconSize = getScaledIconSize(Number(fontSize));
|
||||
@ -307,9 +320,14 @@ const ButtonContent = ({ icon, text, textStyle }: ButtonContentProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={buttonContentStaticStyles.contentContainer}>
|
||||
<View style={[buttonContentStaticStyles.contentContainer, { minHeight: buttonHeight }]}>
|
||||
<View style={buttonStyles.iconContainer}>{scaledIcon}</View>
|
||||
<Text numberOfLines={1} adjustsFontSizeToFit style={[textStyle, buttonStyles.centeredText, { lineHeight: fontSize * 1.2 }]}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit
|
||||
minimumFontScale={0.8}
|
||||
style={[textStyle, buttonStyles.centeredText, { lineHeight: fontSize * 1.2 }]}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
@ -325,6 +343,7 @@ export const FButton = ({
|
||||
isVertical,
|
||||
borderRadius = LAYOUT.PILL_BORDER_RADIUS,
|
||||
fontSize = LAYOUT.MAX_BUTTON_FONT_SIZE,
|
||||
buttonHeight = LAYOUT.BUTTON_HEIGHT,
|
||||
testID,
|
||||
...props
|
||||
}: FButtonProps) => {
|
||||
@ -347,6 +366,8 @@ export const FButton = ({
|
||||
return {
|
||||
root: {
|
||||
...baseStyles,
|
||||
height: buttonHeight,
|
||||
minHeight: buttonHeight,
|
||||
backgroundColor: colors.buttonBackgroundColor,
|
||||
},
|
||||
text: {
|
||||
@ -360,7 +381,7 @@ export const FButton = ({
|
||||
marginBottom: buttonContentStaticStyles.marginBottom,
|
||||
textBase: buttonContentStaticStyles.textBase,
|
||||
};
|
||||
}, [colors, fontSize]);
|
||||
}, [colors, fontSize, buttonHeight]);
|
||||
|
||||
const style: Record<string, any> = {};
|
||||
const additionalStyles = !last ? (isVertical ? customButtonStyles.marginBottom : customButtonStyles.marginRight) : {};
|
||||
@ -397,7 +418,7 @@ export const FButton = ({
|
||||
style={[buttonStyles.root, customButtonStyles.root, style, { borderRadius }]}
|
||||
{...props}
|
||||
>
|
||||
<ButtonContent icon={icon} text={text} textStyle={textStyle} />
|
||||
<ButtonContent icon={icon} text={text} textStyle={textStyle} buttonHeight={buttonHeight} />
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
);
|
||||
@ -405,8 +426,9 @@ export const FButton = ({
|
||||
|
||||
export const FContainer = forwardRef<View, FContainerProps>((props, ref) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const { height, width } = useWindowDimensions();
|
||||
const { height, width, fontScale } = useWindowDimensions();
|
||||
const { sizeClass } = useSizeClass();
|
||||
const scaledButtonHeight = getScaledButtonHeight(fontScale);
|
||||
|
||||
const childrenCount = React.Children.toArray(props.children).filter(Boolean).length;
|
||||
|
||||
@ -419,6 +441,7 @@ 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,
|
||||
@ -508,7 +531,7 @@ export const FContainer = forwardRef<View, FContainerProps>((props, ref) => {
|
||||
|
||||
useEffect(() => {
|
||||
debouncedCalculateLayout();
|
||||
}, [debouncedCalculateLayout, width, height, childrenCount, sizeClass]);
|
||||
}, [debouncedCalculateLayout, width, height, childrenCount, sizeClass, fontScale]);
|
||||
|
||||
const onLayout = (event: { nativeEvent: { layout: { width: number } } }) => {
|
||||
const { width: currentLayoutWidth } = event.nativeEvent.layout;
|
||||
@ -545,6 +568,7 @@ export const FContainer = forwardRef<View, FContainerProps>((props, ref) => {
|
||||
isVertical,
|
||||
borderRadius: buttonBorderRadius,
|
||||
fontSize: buttonFontSize,
|
||||
buttonHeight: scaledButtonHeight,
|
||||
});
|
||||
};
|
||||
|
||||
@ -561,10 +585,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 : null,
|
||||
isVertical ? containerHeight : { minHeight: scaledButtonHeight },
|
||||
{ transform: [{ translateY: slideAnimation }] },
|
||||
],
|
||||
[props.inline, bottomInsets, effectiveNewWidth, isVertical, containerHeight, slideAnimation],
|
||||
[props.inline, bottomInsets, effectiveNewWidth, isVertical, containerHeight, slideAnimation, scaledButtonHeight],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -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 = true, onPress, title, testID }) => {
|
||||
const HeaderRightButton: React.FC<HeaderRightButtonProps> = ({ disabled, onPress, title, testID }) => {
|
||||
const { colors } = useTheme();
|
||||
const opacity = disabled ? 0.5 : 1;
|
||||
return (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { InputAccessoryView, Keyboard, Platform, StyleSheet, Text, View } from 'react-native';
|
||||
import { BlueButtonLink } from '../BlueComponents';
|
||||
import BlueButtonLink from './BlueButtonLink';
|
||||
import loc from '../loc';
|
||||
import { BitcoinUnit } from '../models/bitcoinUnits';
|
||||
import { useTheme } from './themes';
|
||||
|
||||
@ -1,24 +1,30 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Pressable, StyleProp, StyleSheet, Switch, SwitchProps, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { Pressable, StyleProp, StyleSheet, Switch, SwitchProps, Text, TextStyle, useWindowDimensions, 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;
|
||||
@ -33,14 +39,17 @@ 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,
|
||||
@ -49,12 +58,20 @@ 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: {
|
||||
@ -66,7 +83,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
color: colors.alternativeTextColor,
|
||||
fontWeight: '400',
|
||||
paddingVertical: switchProps ? 8 : 0,
|
||||
lineHeight: 20,
|
||||
lineHeight: Math.round(20 * fontScale),
|
||||
fontSize: 14,
|
||||
marginTop: 2,
|
||||
},
|
||||
@ -83,10 +100,11 @@ 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}>
|
||||
<View style={[styles.contentRow, contentRowStyle]}>
|
||||
{leftAvatar && (
|
||||
<View style={styles.leftAvatarContainer}>
|
||||
{leftAvatar}
|
||||
@ -94,7 +112,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.content}>
|
||||
<Text style={stylesHook.title} numberOfLines={0} accessibilityRole="text">
|
||||
<Text style={[stylesHook.title, titleStyle]} numberOfLines={0} accessibilityRole="text">
|
||||
{title}
|
||||
</Text>
|
||||
{subtitle ? (
|
||||
@ -107,7 +125,14 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
{rightTitle || rightSubtitle ? (
|
||||
<View style={styles.rightColumn}>
|
||||
{rightTitle ? (
|
||||
<Text style={rightTitleStyle} numberOfLines={1} accessibilityRole="text">
|
||||
<Text
|
||||
style={rightTitleStyle}
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit
|
||||
minimumFontScale={0.75}
|
||||
accessibilityRole="text"
|
||||
selectable={rightTitleSelectable}
|
||||
>
|
||||
{rightTitle}
|
||||
</Text>
|
||||
) : null}
|
||||
@ -124,7 +149,14 @@ 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} accessibilityLabel={title} style={styles.margin16} accessible accessibilityRole="switch" />
|
||||
<Switch
|
||||
{...memoizedSwitchProps}
|
||||
testID={resolvedSwitchTestID}
|
||||
accessibilityLabel={title}
|
||||
style={styles.margin16}
|
||||
accessible
|
||||
accessibilityRole="switch"
|
||||
/>
|
||||
) : null}
|
||||
{checkmark ? (
|
||||
<View style={styles.checkmarkContainer}>
|
||||
@ -178,16 +210,20 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
leftAvatarContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
rightColumn: {
|
||||
marginStart: 8,
|
||||
minWidth: 0,
|
||||
flexShrink: 0,
|
||||
alignItems: 'flex-end',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
rightMemoWrapper: {
|
||||
flexShrink: 1,
|
||||
|
||||
@ -153,6 +153,7 @@ 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}
|
||||
|
||||
@ -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 = 300,
|
||||
size,
|
||||
isLogoRendered = true,
|
||||
isMenuAvailable = true,
|
||||
logoSize = 90,
|
||||
@ -216,24 +216,11 @@ 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}
|
||||
rx={outerR}
|
||||
ry={outerR}
|
||||
fill={gradFill}
|
||||
/>,
|
||||
<Rect key={`finder-frame-${i}`} testID="qr-finder-frame" x={x} y={y} width={7 * cell} height={7 * cell} fill={gradFill} />,
|
||||
<Rect
|
||||
key={`finder-hole-${i}`}
|
||||
testID="qr-finder-hole"
|
||||
@ -241,8 +228,6 @@ const QRCode: React.FC<QRCodeProps> = ({
|
||||
y={y + cell}
|
||||
width={5 * cell}
|
||||
height={5 * cell}
|
||||
rx={holeR}
|
||||
ry={holeR}
|
||||
fill={BACKGROUND}
|
||||
/>,
|
||||
<Rect
|
||||
@ -252,8 +237,6 @@ const QRCode: React.FC<QRCodeProps> = ({
|
||||
y={y + 2 * cell}
|
||||
width={3 * cell}
|
||||
height={3 * cell}
|
||||
rx={dotR}
|
||||
ry={dotR}
|
||||
fill={gradFill}
|
||||
/>,
|
||||
);
|
||||
@ -277,16 +260,7 @@ const QRCode: React.FC<QRCodeProps> = ({
|
||||
{finderShapes}
|
||||
{isLogoRendered && logoCells > 0 && (
|
||||
<>
|
||||
<Rect
|
||||
testID="qr-logo-backdrop"
|
||||
x={backdropX}
|
||||
y={backdropY}
|
||||
width={backdropSize}
|
||||
height={backdropSize}
|
||||
rx={cell * 0.5}
|
||||
ry={cell * 0.5}
|
||||
fill={LOGO_BACKGROUND}
|
||||
/>
|
||||
<Rect testID="qr-logo-backdrop" x={backdropX} y={backdropY} width={backdropSize} height={backdropSize} fill={LOGO_BACKGROUND} />
|
||||
<SvgImage
|
||||
testID="qr-logo-image"
|
||||
href={require('../img/qr-code.png')}
|
||||
|
||||
@ -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 '../BlueComponents';
|
||||
import BlueText from './BlueText';
|
||||
import loc, { formatStringAddTwoWhiteSpaces } from '../loc';
|
||||
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from '../models/networkTransactionFees';
|
||||
import { useTheme } from './themes';
|
||||
@ -67,26 +67,25 @@ const ReplaceFeeSuggestions: React.FC<ReplaceFeeSuggestionsProps> = ({ onFeeSele
|
||||
}, []);
|
||||
|
||||
const handleFeeSelection = (feeType: NetworkTransactionFeeType) => {
|
||||
if (feeType !== NetworkTransactionFeeType.CUSTOM) {
|
||||
Keyboard.dismiss();
|
||||
if (feeType === NetworkTransactionFeeType.CUSTOM) {
|
||||
setSelectedFeeType(feeType);
|
||||
return;
|
||||
}
|
||||
|
||||
Keyboard.dismiss();
|
||||
if (networkFees) {
|
||||
let selectedFee: number;
|
||||
switch (feeType) {
|
||||
case NetworkTransactionFeeType.FAST:
|
||||
selectedFee = networkFees.fastestFee;
|
||||
onFeeSelected(networkFees.fastestFee);
|
||||
break;
|
||||
case NetworkTransactionFeeType.MEDIUM:
|
||||
selectedFee = networkFees.mediumFee;
|
||||
onFeeSelected(networkFees.mediumFee);
|
||||
break;
|
||||
case NetworkTransactionFeeType.SLOW:
|
||||
selectedFee = networkFees.slowFee;
|
||||
break;
|
||||
case NetworkTransactionFeeType.CUSTOM:
|
||||
selectedFee = Number(customFeeValue);
|
||||
onFeeSelected(networkFees.slowFee);
|
||||
break;
|
||||
}
|
||||
onFeeSelected(selectedFee);
|
||||
|
||||
setSelectedFeeType(feeType);
|
||||
}
|
||||
};
|
||||
@ -94,7 +93,8 @@ const ReplaceFeeSuggestions: React.FC<ReplaceFeeSuggestionsProps> = ({ onFeeSele
|
||||
const handleCustomFeeChange = (customFee: string) => {
|
||||
const sanitizedFee = customFee.replace(/[^0-9]/g, '');
|
||||
setCustomFeeValue(sanitizedFee);
|
||||
handleFeeSelection(NetworkTransactionFeeType.CUSTOM);
|
||||
onFeeSelected(Number(sanitizedFee));
|
||||
setSelectedFeeType(NetworkTransactionFeeType.CUSTOM);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -156,7 +156,10 @@ const ReplaceFeeSuggestions: React.FC<ReplaceFeeSuggestionsProps> = ({ onFeeSele
|
||||
ref={customTextInput}
|
||||
maxLength={9}
|
||||
style={[styles.customFeeInput, stylesHook.customFeeInput]}
|
||||
onFocus={() => handleCustomFeeChange(customFeeValue)}
|
||||
onFocus={() => {
|
||||
setSelectedFeeType(NetworkTransactionFeeType.CUSTOM);
|
||||
onFeeSelected(Number(customFeeValue));
|
||||
}}
|
||||
placeholder={loc.send.fee_satvbyte}
|
||||
placeholderTextColor="#81868e"
|
||||
inputAccessoryViewID={DismissKeyboardInputAccessoryViewID}
|
||||
|
||||
@ -7,10 +7,18 @@ 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, ...otherProps } = props;
|
||||
const {
|
||||
style,
|
||||
contentContainerStyle,
|
||||
floatingButtonHeight = 0,
|
||||
headerHeight = 0,
|
||||
disableDefaultTopPadding = false,
|
||||
...otherProps
|
||||
} = props;
|
||||
const { colors } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
@ -32,7 +40,10 @@ const SafeAreaScrollView = forwardRef<ScrollView, SafeAreaScrollViewProps>((prop
|
||||
if (headerHeight > 0) {
|
||||
return headerHeight;
|
||||
}
|
||||
// iOS safe area or no status bar
|
||||
if (disableDefaultTopPadding) {
|
||||
return 0;
|
||||
}
|
||||
// Preserve legacy behavior for existing screens
|
||||
return insets.top > 0 ? 5 : 0;
|
||||
})(),
|
||||
};
|
||||
@ -48,7 +59,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]);
|
||||
}, [insets, contentContainerStyle, floatingButtonHeight, headerHeight, disableDefaultTopPadding]);
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
|
||||
@ -10,7 +10,8 @@ type SecondButtonProps = {
|
||||
backgroundColor?: string;
|
||||
disabled?: boolean;
|
||||
icon?: IconButtonProps;
|
||||
title?: string;
|
||||
title: string;
|
||||
textColor?: string;
|
||||
onPress?: () => void;
|
||||
loading?: boolean;
|
||||
testID?: string;
|
||||
@ -19,7 +20,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 = colors.secondButtonTextColor;
|
||||
let fontColor = props.textColor ?? colors.secondButtonTextColor;
|
||||
if (props.disabled === true) {
|
||||
backgroundColor = colors.buttonDisabledBackgroundColor;
|
||||
fontColor = colors.buttonDisabledTextColor;
|
||||
|
||||
@ -1,87 +0,0 @@
|
||||
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;
|
||||
@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet, ViewStyle } from 'react-native';
|
||||
import { useTheme } from './themes';
|
||||
import { BlueText } from '../BlueComponents';
|
||||
|
||||
import BlueText from './BlueText';
|
||||
interface TipBoxProps {
|
||||
number?: string;
|
||||
title?: string;
|
||||
|
||||
@ -89,13 +89,23 @@ const ToolTipMenu = (props: ToolTipMenuProps) => {
|
||||
// Android gesture-cancel race documented above.
|
||||
return (
|
||||
<View
|
||||
style={visibleStyle}
|
||||
testID={testID}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityHint={accessibilityHint}
|
||||
accessibilityRole={accessibilityRole}
|
||||
accessibilityState={accessibilityState}
|
||||
>
|
||||
{menu}
|
||||
<ContextMenu
|
||||
title={title}
|
||||
previewBackgroundColor="transparent"
|
||||
onPress={handlePressMenuItem}
|
||||
actions={items}
|
||||
dropdownMenuMode={!shouldOpenOnLongPress}
|
||||
style={styles.menuFlex}
|
||||
>
|
||||
{children}
|
||||
</ContextMenu>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { TouchableOpacity, Text, StyleSheet, LayoutAnimation, View } from 'react-native';
|
||||
import { TouchableOpacity, Text, StyleSheet, View, useWindowDimensions } from 'react-native';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
import loc, { formatBalanceWithoutSuffix } from '../loc';
|
||||
import { BitcoinUnit } from '../models/bitcoinUnits';
|
||||
@ -22,6 +22,7 @@ const TotalWalletsBalance: React.FC = React.memo(() => {
|
||||
setTotalBalancePreferredUnitStorage,
|
||||
} = useSettings();
|
||||
const { colors } = useTheme();
|
||||
const { fontScale } = useWindowDimensions();
|
||||
|
||||
const totalBalanceFormatted = useMemo(() => {
|
||||
const totalBalance = wallets.reduce((prev, curr) => {
|
||||
@ -31,6 +32,22 @@ 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(
|
||||
() => [
|
||||
{
|
||||
@ -55,7 +72,6 @@ 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);
|
||||
@ -80,7 +96,6 @@ const TotalWalletsBalance: React.FC = React.memo(() => {
|
||||
);
|
||||
|
||||
const handleBalanceOnPress = useCallback(async () => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
const nextUnit =
|
||||
totalBalancePreferredUnit === BitcoinUnit.BTC
|
||||
? BitcoinUnit.SATS
|
||||
@ -94,13 +109,20 @@ const TotalWalletsBalance: React.FC = React.memo(() => {
|
||||
|
||||
return (
|
||||
<ToolTipMenu actions={toolTipActions} onPressMenuItem={onPressMenuItem} shouldOpenOnLongPress style={styles.menuContainer}>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.label}>{loc.wallets.total_balance}</Text>
|
||||
<TouchableOpacity onPress={handleBalanceOnPress}>
|
||||
<Text style={[styles.balance, { color: colors.foregroundColor }]}>
|
||||
{totalBalanceFormatted}{' '}
|
||||
<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}
|
||||
{totalBalancePreferredUnit !== BitcoinUnit.LOCAL_CURRENCY && (
|
||||
<Text style={[styles.currency, { color: colors.foregroundColor }]}>{totalBalancePreferredUnit}</Text>
|
||||
<Text style={[styles.currency, { color: colors.foregroundColor }]}>{` ${totalBalancePreferredUnit}`}</Text>
|
||||
)}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
@ -118,6 +140,11 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'flex-start',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
width: '100%',
|
||||
},
|
||||
balanceTouchable: {
|
||||
alignSelf: 'stretch',
|
||||
width: '100%',
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
@ -127,6 +154,7 @@ const styles = StyleSheet.create({
|
||||
balance: {
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
lineHeight: 38,
|
||||
},
|
||||
currency: {
|
||||
fontSize: 18,
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
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 } from 'react-native';
|
||||
import { Animated, Easing, Linking, Pressable, Text, TextStyle, ViewStyle, StyleSheet, View, useWindowDimensions } 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';
|
||||
@ -28,9 +29,6 @@ import { uint8ArrayToHex } from '../blue_modules/uint8array-extras';
|
||||
import ListItem from './ListItem';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
dateLine: {
|
||||
fontSize: 13,
|
||||
},
|
||||
fullWidthButton: {
|
||||
width: '100%',
|
||||
alignSelf: 'stretch',
|
||||
@ -105,7 +103,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;
|
||||
@ -119,7 +117,7 @@ type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList>;
|
||||
|
||||
const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
|
||||
item,
|
||||
itemPriceUnit = BitcoinUnit.BTC,
|
||||
itemPriceUnit,
|
||||
walletID,
|
||||
searchQuery,
|
||||
style,
|
||||
@ -132,6 +130,7 @@ 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,
|
||||
@ -154,7 +153,30 @@ 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';
|
||||
@ -164,7 +186,7 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
|
||||
}
|
||||
if (!item.confirmations) return 'pending';
|
||||
return item.value! < 0 ? 'sent' : 'received';
|
||||
}, [item.category, item.confirmations, item.type, item.value, item.ispaid]);
|
||||
}, [isPendingRefill, item.category, item.confirmations, item.type, item.value, item.ispaid]);
|
||||
|
||||
const listTitle = useMemo(() => {
|
||||
if (listTitleKey === 'pending') return loc.transactions.pending;
|
||||
@ -175,11 +197,11 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
|
||||
const isPending = listTitleKey === 'pending';
|
||||
|
||||
const dateLine = useMemo(() => {
|
||||
if (isPending) return transactionTimeToReadable(item.timestamp);
|
||||
return formatTransactionListDate(item.timestamp * 1000);
|
||||
const formatted = isPending ? transactionTimeToReadable(item.timestamp) : formatTransactionListDate(item.timestamp * 1000);
|
||||
return arkRowKind ? `${arkRowKind} · ${formatted}` : formatted;
|
||||
// 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]);
|
||||
}, [isPending, item.timestamp, language, arkRowKind]);
|
||||
|
||||
const formattedAmount = useMemo(() => {
|
||||
return formatBalanceWithoutSuffix(item.value, itemPriceUnit, true).toString();
|
||||
@ -224,6 +246,7 @@ 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,
|
||||
@ -238,9 +261,18 @@ 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,
|
||||
@ -248,6 +280,14 @@ 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,
|
||||
@ -321,7 +361,11 @@ 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') {
|
||||
} 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.
|
||||
const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID);
|
||||
if (lightningWallet.length === 1) {
|
||||
try {
|
||||
@ -352,15 +396,24 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
|
||||
walletID: lightningWallet[0].getID(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log('cant handle press');
|
||||
} 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 });
|
||||
}
|
||||
}
|
||||
}, [item, renderHighlightedText, navigate, walletID, wallets, customOnPress, disableNavigation]);
|
||||
|
||||
const handleOnDetailsPress = useCallback(() => {
|
||||
if (walletID && item && item.hash) {
|
||||
navigate('TransactionStatus', { hash: item.hash, walletID, tx: item });
|
||||
} else {
|
||||
} 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).
|
||||
const lightningWallet = wallets.find(wallet => wallet?.getID() === item.walletID);
|
||||
if (lightningWallet) {
|
||||
navigate('LNDViewInvoice', {
|
||||
@ -368,6 +421,13 @@ 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]);
|
||||
|
||||
@ -449,7 +509,10 @@ 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)
|
||||
@ -486,7 +549,7 @@ const TransactionListItemComponent: React.FC<TransactionListItemProps> = ({
|
||||
<ListItem
|
||||
leftAvatar={avatar}
|
||||
title={listTitle}
|
||||
subtitle={<Text style={styles.dateLine}>{dateLine}</Text>}
|
||||
subtitle={dateLine}
|
||||
chevron={false}
|
||||
rightTitle={rowTitle}
|
||||
rightTitleStyle={rowTitleStyle}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
|
||||
import { Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
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,36 +14,42 @@ 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?: (isShouldBeVisible: boolean) => void;
|
||||
onWalletBalanceVisibilityChange?: (shouldHideBalance: 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 [allowOnchainAddress, setAllowOnchainAddress] = useState(false);
|
||||
const isLightningWallet = wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type;
|
||||
const [allowOnchainAddress, setAllowOnchainAddress] = useState(isLightningWallet);
|
||||
const { preferredFiatCurrency } = useSettings();
|
||||
const { direction } = useLocale();
|
||||
const balanceOpacity = useSharedValue(1);
|
||||
const balanceTranslateY = useSharedValue(0);
|
||||
const previousBalance = useRef<string | undefined>(undefined);
|
||||
|
||||
const verifyIfWalletAllowsOnchainAddress = useCallback(() => {
|
||||
if (wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type) {
|
||||
if (isLightningWallet) {
|
||||
wallet
|
||||
.allowOnchainAddress()
|
||||
.then((value: boolean) => setAllowOnchainAddress(value))
|
||||
@ -52,7 +58,11 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
|
||||
setAllowOnchainAddress(false);
|
||||
});
|
||||
}
|
||||
}, [wallet]);
|
||||
}, [isLightningWallet, wallet]);
|
||||
|
||||
useEffect(() => {
|
||||
setAllowOnchainAddress(isLightningWallet);
|
||||
}, [isLightningWallet]);
|
||||
|
||||
useEffect(() => {
|
||||
verifyIfWalletAllowsOnchainAddress();
|
||||
@ -67,13 +77,14 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
|
||||
|
||||
const handleBalanceVisibility = useCallback(() => {
|
||||
onWalletBalanceVisibilityChange?.(!hideBalance);
|
||||
}, [onWalletBalanceVisibilityChange, hideBalance]);
|
||||
}, [hideBalance, onWalletBalanceVisibilityChange]);
|
||||
|
||||
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) {
|
||||
@ -82,7 +93,6 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
|
||||
newWalletPreferredUnit = BitcoinUnit.BTC;
|
||||
}
|
||||
|
||||
console.debug('[UnitSwitch/UI] next unit resolved', { walletID: wallet.getID?.(), next: newWalletPreferredUnit });
|
||||
onWalletUnitChange(newWalletPreferredUnit);
|
||||
};
|
||||
|
||||
@ -97,29 +107,34 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
|
||||
|
||||
const onPressMenuItem = useCallback(
|
||||
(id: string) => {
|
||||
if (id === 'walletBalanceVisibility') {
|
||||
if (id === actionKeys.WalletBalanceVisibility) {
|
||||
handleBalanceVisibility();
|
||||
} else if (id === 'copyToClipboard') {
|
||||
} else if (id === actionKeys.CopyToClipboard) {
|
||||
handleCopyPress();
|
||||
}
|
||||
},
|
||||
[handleBalanceVisibility, handleCopyPress],
|
||||
);
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
return [
|
||||
// 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(
|
||||
{
|
||||
id: actionKeys.Refill,
|
||||
text: loc.lnd.refill,
|
||||
icon: actionIcons.Refill,
|
||||
title: loc.lnd.title,
|
||||
options: [loc._.cancel, loc.lnd.refill, loc.lnd.refill_external],
|
||||
cancelButtonIndex: 0,
|
||||
},
|
||||
{
|
||||
id: actionKeys.RefillWithExternalWallet,
|
||||
text: loc.lnd.refill_external,
|
||||
icon: actionIcons.RefillWithExternalWallet,
|
||||
buttonIndex => {
|
||||
if (buttonIndex === 1) handleManageFundsPressed(actionKeys.Refill);
|
||||
else if (buttonIndex === 2) handleManageFundsPressed(actionKeys.RefillWithExternalWallet);
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
);
|
||||
}, [handleManageFundsPressed]);
|
||||
|
||||
const currentBalance = wallet ? wallet.getBalance() : 0;
|
||||
const formattedBalance = useMemo(() => {
|
||||
@ -129,154 +144,160 @@ 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: 'walletBalanceVisibility',
|
||||
id: actionKeys.WalletBalanceVisibility,
|
||||
text: loc.transactions.details_balance_show,
|
||||
icon: {
|
||||
iconValue: 'eye',
|
||||
},
|
||||
icon: actionIcons.Eye,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: 'walletBalanceVisibility',
|
||||
id: actionKeys.WalletBalanceVisibility,
|
||||
text: loc.transactions.details_balance_hide,
|
||||
icon: {
|
||||
iconValue: 'eye.slash',
|
||||
},
|
||||
icon: actionIcons.EyeSlash,
|
||||
},
|
||||
{
|
||||
id: 'copyToClipboard',
|
||||
id: actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: {
|
||||
iconValue: 'doc.on.doc',
|
||||
},
|
||||
icon: actionIcons.Clipboard,
|
||||
},
|
||||
];
|
||||
}, [hideBalance]);
|
||||
|
||||
useEffect(() => {
|
||||
console.debug('[UnitSwitch/UI] render state', {
|
||||
walletID: wallet.getID?.(),
|
||||
unit,
|
||||
hideBalance,
|
||||
preferredFiat: preferredFiatCurrency?.endPointKey,
|
||||
switching: unitSwitching,
|
||||
});
|
||||
}, [wallet, unit, hideBalance, preferredFiatCurrency, unitSwitching]);
|
||||
|
||||
return (
|
||||
<LinearGradient colors={WalletGradient.gradientsFor(wallet.type)} style={styles.lineaderGradient}>
|
||||
<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} />
|
||||
<View style={styles.contentContainer}>
|
||||
<Text testID="WalletLabel" numberOfLines={1} style={[styles.walletLabel, { writingDirection: direction }]}>
|
||||
{wallet.getLabel()}
|
||||
</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
|
||||
<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
|
||||
testID="WalletBalance"
|
||||
numberOfLines={1}
|
||||
minimumFontScale={0.5}
|
||||
adjustsFontSizeToFit
|
||||
style={[styles.walletBalanceText, animatedBalanceTextStyle]}
|
||||
style={styles.walletBalanceText}
|
||||
>
|
||||
{balance}
|
||||
</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>
|
||||
)}
|
||||
</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>
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<TouchableOpacity style={styles.manageFundsButton} accessibilityRole="button" onPress={() => handleManageFundsPressed()}>
|
||||
<Text style={styles.manageFundsButtonText}>{loc.multisig.manage_keys}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</LinearGradient>
|
||||
<View style={styles.bottomBarSpacer}>
|
||||
<View
|
||||
style={[
|
||||
styles.bottomBar,
|
||||
{
|
||||
backgroundColor: colors.background,
|
||||
...Platform.select({
|
||||
ios: { shadowColor: colors.shadowColor },
|
||||
android: {},
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
lineaderGradient: {
|
||||
minHeight: 140,
|
||||
justifyContent: 'flex-start',
|
||||
position: 'relative',
|
||||
},
|
||||
contentContainer: {
|
||||
padding: 15,
|
||||
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,
|
||||
},
|
||||
}),
|
||||
},
|
||||
walletLabel: {
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: 19,
|
||||
color: '#fff',
|
||||
marginBottom: 10,
|
||||
color: 'rgba(255, 255, 255, 0.7)',
|
||||
marginBottom: 4,
|
||||
},
|
||||
walletBalance: {
|
||||
flexShrink: 1,
|
||||
marginRight: 6,
|
||||
minHeight: 39,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
balanceSection: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
manageFundsButton: {
|
||||
marginTop: 14,
|
||||
@ -297,13 +318,13 @@ const styles = StyleSheet.create({
|
||||
walletBalanceAndUnitContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingRight: 10, // Ensure there's some padding to the right
|
||||
paddingRight: 10,
|
||||
},
|
||||
walletBalanceText: {
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 36,
|
||||
flexShrink: 1, // Allow the text to shrink if there's not enough space
|
||||
flexShrink: 1,
|
||||
},
|
||||
walletPreferredUnitView: {
|
||||
justifyContent: 'center',
|
||||
|
||||
@ -107,7 +107,7 @@ const WalletListItem: React.FC<Props> = ({
|
||||
)}
|
||||
|
||||
{wallet.hideBalance ? (
|
||||
<View style={styles.hiddenBalance}>
|
||||
<View style={styles.hiddenBalance} testID="HiddenBalance">
|
||||
<View style={styles.hiddenBalanceBar} />
|
||||
</View>
|
||||
) : (
|
||||
|
||||
@ -30,6 +30,7 @@ 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';
|
||||
@ -37,6 +38,30 @@ 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 {
|
||||
@ -160,23 +185,28 @@ 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: {
|
||||
height: 40,
|
||||
minHeight: 40,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
balanceContainerCompact: {
|
||||
height: 32,
|
||||
minHeight: 32,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
image: {
|
||||
width: 99,
|
||||
@ -189,9 +219,6 @@ const iStyles = StyleSheet.create({
|
||||
width: 78,
|
||||
height: 74,
|
||||
},
|
||||
br: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
label: {
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: 19,
|
||||
@ -206,7 +233,6 @@ const iStyles = StyleSheet.create({
|
||||
},
|
||||
balanceCompact: {
|
||||
fontSize: 28,
|
||||
lineHeight: 34,
|
||||
},
|
||||
latestTx: {
|
||||
backgroundColor: 'transparent',
|
||||
@ -282,11 +308,32 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
|
||||
const balanceOpacity = useSharedValue(1);
|
||||
const balanceTranslateY = useSharedValue(0);
|
||||
const { colors } = useTheme();
|
||||
const { width } = useWindowDimensions();
|
||||
const { width, fontScale } = 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;
|
||||
@ -383,9 +430,21 @@ 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 (item.getTransactions().find((tx: Transaction) => tx.confirmations === 0)) {
|
||||
} else if (hasPendingTx) {
|
||||
latestTransactionText = loc.transactions.pending;
|
||||
} else {
|
||||
latestTransactionText = transactionTimeToReadable(item.getLatestTransactionTime());
|
||||
@ -419,23 +478,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]}>
|
||||
<LinearGradient
|
||||
colors={WalletGradient.gradientsFor(item.type)}
|
||||
style={[iStyles.grad, isCompact && iStyles.gradCompact, scaledCardStyles.grad]}
|
||||
>
|
||||
<ImageBackground source={image} style={[iStyles.image, isCompact && iStyles.imageCompact]} />
|
||||
<View style={[iStyles.gradContent, isCompact && iStyles.gradContentCompact]}>
|
||||
<Text style={iStyles.br} />
|
||||
<View style={[iStyles.gradContent, isCompact && iStyles.gradContentCompact, !isCompact && scaledCardStyles.gradContent]}>
|
||||
{!isPlaceHolder && (
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
iStyles.label,
|
||||
isCompact && iStyles.labelCompact,
|
||||
{ color: colors.inverseForegroundColor, writingDirection: direction },
|
||||
]}
|
||||
style={[iStyles.label, isCompact && iStyles.labelCompact, scaledCardStyles.label, cardTextStyle]}
|
||||
>
|
||||
{renderHighlightedText ? renderHighlightedText(walletLabel, searchQuery || '') : walletLabel}
|
||||
</Text>
|
||||
<View style={[iStyles.balanceContainer, isCompact && iStyles.balanceContainerCompact]}>
|
||||
<View
|
||||
style={[iStyles.balanceContainer, isCompact && iStyles.balanceContainerCompact, scaledCardStyles.balanceContainer]}
|
||||
>
|
||||
{hideBalance ? (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
@ -445,11 +504,13 @@ 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,
|
||||
{ color: colors.inverseForegroundColor, writingDirection: direction },
|
||||
isCompact ? scaledCardStyles.balanceCompact : scaledCardStyles.balance,
|
||||
cardTextStyle,
|
||||
animatedBalanceStyle,
|
||||
]}
|
||||
>
|
||||
@ -457,24 +518,20 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
|
||||
</Animated.Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={iStyles.br} />
|
||||
<View style={scaledCardStyles.textSpacer} />
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
iStyles.latestTx,
|
||||
isCompact && iStyles.latestTxCompact,
|
||||
{ color: colors.inverseForegroundColor, writingDirection: direction },
|
||||
]}
|
||||
adjustsFontSizeToFit
|
||||
minimumFontScale={0.8}
|
||||
style={[iStyles.latestTx, isCompact && iStyles.latestTxCompact, scaledCardStyles.latestTx, cardTextStyle]}
|
||||
>
|
||||
{loc.wallets.list_latest_transaction}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
iStyles.latestTxTime,
|
||||
isCompact && iStyles.latestTxTimeCompact,
|
||||
{ color: colors.inverseForegroundColor, writingDirection: direction },
|
||||
]}
|
||||
adjustsFontSizeToFit
|
||||
minimumFontScale={0.8}
|
||||
style={[iStyles.latestTxTime, isCompact && iStyles.latestTxTimeCompact, scaledCardStyles.latestTxTime, cardTextStyle]}
|
||||
>
|
||||
{latestTransactionText}
|
||||
</Text>
|
||||
@ -503,15 +560,7 @@ interface WalletsCarouselProps extends Partial<FlatListProps<any>> {
|
||||
animateChanges?: boolean;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
export type CarouselListRefType = FlatList<TWallet>;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listHeaderSeparator: {
|
||||
@ -522,7 +571,7 @@ const styles = StyleSheet.create({
|
||||
|
||||
const ListHeaderSeparator = () => <View style={styles.listHeaderSeparator} />;
|
||||
|
||||
const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props, ref) => {
|
||||
const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((props, ref) => {
|
||||
const {
|
||||
horizontal = true,
|
||||
data,
|
||||
@ -537,7 +586,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
||||
animateChanges = false,
|
||||
} = props;
|
||||
|
||||
const { width } = useWindowDimensions();
|
||||
const { width, fontScale } = useWindowDimensions();
|
||||
const itemWidth = React.useMemo(() => getWalletCarouselItemWidth(width), [width]);
|
||||
const snapInterval = React.useMemo(() => itemWidth, [itemWidth]);
|
||||
const snapOffsets = React.useMemo(() => {
|
||||
@ -557,7 +606,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
||||
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
const flatListRef = useRef<FlatList<any>>(null);
|
||||
const flatListRef = useRef<FlatList<TWallet>>(null);
|
||||
const walletRefs = useRef<Record<string, React.MutableRefObject<View | null>>>({});
|
||||
|
||||
const { sizeClass } = useSizeClass();
|
||||
@ -646,7 +695,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
||||
console.warn('[WalletsCarousel] Error scrolling to wallet:', error);
|
||||
// Fallback: try scrolling to offset
|
||||
// Use different measurement based on orientation
|
||||
const itemSize = horizontal ? itemWidth : 195; // 195 is the approximate height of wallet card
|
||||
const itemSize = horizontal ? itemWidth : WALLET_CAROUSEL_HEIGHT;
|
||||
flatListRef.current.scrollToOffset({
|
||||
offset: itemSize * walletIndex,
|
||||
animated,
|
||||
@ -768,7 +817,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
||||
|
||||
const keyExtractor = useCallback((item: TWallet, index: number) => (item?.getID ? item.getID() : index.toString()), []);
|
||||
|
||||
const sliderHeight = 195;
|
||||
const sliderHeight = getWalletCarouselHeight(fontScale);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@ -851,7 +900,8 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
||||
|
||||
const cStyles = StyleSheet.create({
|
||||
content: {
|
||||
paddingTop: 16,
|
||||
paddingTop: scaleLayoutUp(WALLET_CAROUSEL_PADDING_TOP, fontScale),
|
||||
paddingBottom: scaleLayoutUp(WALLET_CAROUSEL_PADDING_BOTTOM, fontScale),
|
||||
},
|
||||
contentLargeScreen: {
|
||||
paddingHorizontal: sizeClass === SizeClass.Large ? 16 : 12,
|
||||
@ -882,7 +932,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
||||
automaticallyAdjustContentInsets
|
||||
automaticallyAdjustKeyboardInsets
|
||||
automaticallyAdjustsScrollIndicatorInsets
|
||||
style={{ minHeight: sliderHeight + 12 }}
|
||||
style={{ minHeight: sliderHeight }}
|
||||
onScrollToIndexFailed={onScrollToIndexFailed}
|
||||
ListFooterComponent={onNewWalletPress ? <NewWalletPanel onPress={onNewWalletPress} /> : null}
|
||||
{...props}
|
||||
|
||||
@ -66,10 +66,13 @@ 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,
|
||||
@ -80,7 +83,10 @@ const navigationStyle = (
|
||||
const isModal = route.params?.presentation === 'modal' || route.params?.presentation === 'transparentModal';
|
||||
const isFormSheet = route.params?.presentation === 'formSheet';
|
||||
|
||||
const closeButton = getCloseButtonPosition(closeButtonPosition, isFirstRouteInStack, isModal);
|
||||
const closeButton =
|
||||
closeButtonIfFirstInStack && isFirstRouteInStack
|
||||
? closeButtonIfFirstInStack
|
||||
: getCloseButtonPosition(closeButtonPosition, isFirstRouteInStack, isModal);
|
||||
const handleClose = getHandleCloseAction(onCloseButtonPressed, navigation, route);
|
||||
|
||||
let headerRight;
|
||||
|
||||
@ -31,6 +31,8 @@ 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,
|
||||
@ -107,6 +109,15 @@ 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,
|
||||
@ -192,6 +203,7 @@ export const SettingsScrollView = forwardRef<ScrollView, SettingsScrollViewProps
|
||||
ref={ref}
|
||||
style={[style, { backgroundColor: screenBackgroundColor }]}
|
||||
headerHeight={resolvedHeaderHeight}
|
||||
disableDefaultTopPadding={isIOS26OrHigher}
|
||||
floatingButtonHeight={floatingButtonHeight}
|
||||
contentContainerStyle={[staticStyles.contentContainer, contentContainerStyle]}
|
||||
{...rest}
|
||||
|
||||
@ -14,6 +14,8 @@ 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',
|
||||
@ -101,6 +103,7 @@ 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',
|
||||
|
||||
@ -2,15 +2,18 @@ import { Platform } from 'react-native';
|
||||
import prompt from 'react-native-prompt-android';
|
||||
import loc from '../loc';
|
||||
|
||||
export default (
|
||||
title: string,
|
||||
text: string,
|
||||
isCancelable = true,
|
||||
type: PromptType | PromptTypeIOS | PromptTypeAndroid = 'secure-text',
|
||||
isOKDestructive = false,
|
||||
continueButtonText = loc._.ok,
|
||||
defaultInputValue?: string,
|
||||
): Promise<string> => {
|
||||
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;
|
||||
|
||||
const keyboardType = type === 'numeric' ? 'numeric' : 'default';
|
||||
|
||||
if (Platform.OS === 'ios' && type === 'numeric') {
|
||||
@ -19,7 +22,7 @@ export default (
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const buttons: Array<PromptButton> = isCancelable
|
||||
const buttons: Array<PromptButton> = cancelable
|
||||
? [
|
||||
{
|
||||
text: loc._.cancel,
|
||||
@ -34,7 +37,7 @@ export default (
|
||||
console.log('OK Pressed');
|
||||
resolve(password);
|
||||
},
|
||||
style: isOKDestructive ? 'destructive' : 'default',
|
||||
style: destructive ? 'destructive' : 'default',
|
||||
},
|
||||
]
|
||||
: [
|
||||
@ -47,13 +50,12 @@ export default (
|
||||
},
|
||||
];
|
||||
|
||||
const message = defaultInputValue !== undefined ? '' : text;
|
||||
const message = defaultValue !== undefined ? '' : text;
|
||||
prompt(title, message, buttons, {
|
||||
type,
|
||||
cancelable: isCancelable,
|
||||
// @ts-ignore suppressed because its supported only on ios and is absent from type definitions
|
||||
cancelable,
|
||||
keyboardType,
|
||||
...(defaultInputValue !== undefined && { defaultValue: defaultInputValue }),
|
||||
...(defaultValue !== undefined && { defaultValue }),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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';
|
||||
@ -13,6 +14,7 @@ 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';
|
||||
@ -86,6 +88,47 @@ 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:
|
||||
@ -126,6 +169,51 @@ 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:
|
||||
@ -179,7 +267,7 @@ const useCompanionListeners = (skipIfNotInitialized = true) => {
|
||||
console.error('Failed to process push notifications:', error);
|
||||
}
|
||||
return false;
|
||||
}, [shouldActivateListeners, wallets, fetchAndSaveWalletTransactions, navigation, refreshAllWalletTransactions]);
|
||||
}, [shouldActivateListeners, wallets, fetchAndSaveWalletTransactions, saveToDisk, navigation, refreshAllWalletTransactions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldActivateListeners) return;
|
||||
@ -214,16 +302,12 @@ 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,
|
||||
@ -277,6 +361,12 @@ 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;
|
||||
@ -312,7 +402,7 @@ const useCompanionListeners = (skipIfNotInitialized = true) => {
|
||||
appState.current = nextAppState;
|
||||
}
|
||||
},
|
||||
[processPushNotifications, showClipboardAlert, wallets, shouldActivateListeners],
|
||||
[processPushNotifications, fetchAndSaveWalletTransactions, showClipboardAlert, wallets, shouldActivateListeners],
|
||||
);
|
||||
|
||||
const addListeners = useCallback(() => {
|
||||
|
||||
@ -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);
|
||||
const confirmedTransactions = transactions.filter(t => (t.confirmations ?? 0) > 0);
|
||||
const latestTransactionTime =
|
||||
confirmedTransactions.length > 0
|
||||
? secondsToMilliseconds(Math.max(...confirmedTransactions.map(t => t.timestamp || t.time || 0)))
|
||||
|
||||
20
index.js
20
index.js
@ -4,20 +4,30 @@ import 'react-native-get-random-values';
|
||||
import './shim.js';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { AppRegistry, LogBox, Platform, UIManager } from 'react-native';
|
||||
import { AppRegistry, LogBox } from 'react-native';
|
||||
import BackgroundFetch from 'react-native-background-fetch';
|
||||
|
||||
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.',
|
||||
|
||||
@ -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 = 1703259999;
|
||||
CURRENT_PROJECT_VERSION = 1703279999;
|
||||
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.0;
|
||||
MARKETING_VERSION = 8.0.1;
|
||||
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 = 1703259999;
|
||||
CURRENT_PROJECT_VERSION = 1703279999;
|
||||
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.0;
|
||||
MARKETING_VERSION = 8.0.1;
|
||||
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 = 1703259999;
|
||||
CURRENT_PROJECT_VERSION = 1703279999;
|
||||
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.0;
|
||||
MARKETING_VERSION = 8.0.1;
|
||||
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 = 1703259999;
|
||||
CURRENT_PROJECT_VERSION = 1703279999;
|
||||
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.0;
|
||||
MARKETING_VERSION = 8.0.1;
|
||||
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 = 1703259999;
|
||||
CURRENT_PROJECT_VERSION = 1703279999;
|
||||
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.0;
|
||||
MARKETING_VERSION = 8.0.1;
|
||||
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 = 1703259999;
|
||||
CURRENT_PROJECT_VERSION = 1703279999;
|
||||
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.0;
|
||||
MARKETING_VERSION = 8.0.1;
|
||||
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 = 1703259999;
|
||||
CURRENT_PROJECT_VERSION = 1703279999;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
@ -1854,7 +1854,7 @@
|
||||
"$(inherited)",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.4;
|
||||
MARKETING_VERSION = 8.0.0;
|
||||
MARKETING_VERSION = 8.0.1;
|
||||
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 = 1703259999;
|
||||
CURRENT_PROJECT_VERSION = 1703279999;
|
||||
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.0;
|
||||
MARKETING_VERSION = 8.0.1;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>io.bluewallet.bluewallet.fetchTxsForWallet</string>
|
||||
<string>com.transistorsoft.fetch</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
@ -244,8 +245,6 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true/>
|
||||
<key>UIDesignRequiresCompatibility</key>
|
||||
<true/>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
PODS:
|
||||
- BugsnagReactNative (8.8.1):
|
||||
- BugsnagReactNative (8.9.0):
|
||||
- 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.6):
|
||||
- lottie-react-native (7.3.8):
|
||||
- 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.7.0):
|
||||
- react-native-safe-area-context (5.8.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -1541,8 +1541,8 @@ PODS:
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- react-native-safe-area-context/common (= 5.7.0)
|
||||
- react-native-safe-area-context/fabric (= 5.7.0)
|
||||
- react-native-safe-area-context/common (= 5.8.0)
|
||||
- react-native-safe-area-context/fabric (= 5.8.0)
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
@ -1553,7 +1553,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-safe-area-context/common (5.7.0):
|
||||
- react-native-safe-area-context/common (5.8.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -1575,7 +1575,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-safe-area-context/fabric (5.7.0):
|
||||
- react-native-safe-area-context/fabric (5.8.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -2018,6 +2018,8 @@ PODS:
|
||||
- ReactNativeDependencies (0.85.3)
|
||||
- RealmJS (20.2.0):
|
||||
- React
|
||||
- RNBackgroundFetch (4.2.9):
|
||||
- React-Core
|
||||
- RNCAsyncStorage (2.2.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
@ -2068,7 +2070,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNFS (2.20.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.31.1):
|
||||
- RNGestureHandler (2.31.2):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -2182,7 +2184,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNReanimated (4.3.0):
|
||||
- RNReanimated (4.3.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -2204,11 +2206,11 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNReanimated/apple (= 4.3.0)
|
||||
- RNReanimated/common (= 4.3.0)
|
||||
- RNReanimated/apple (= 4.3.1)
|
||||
- RNReanimated/common (= 4.3.1)
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNReanimated/apple (4.3.0):
|
||||
- RNReanimated/apple (4.3.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -2232,7 +2234,7 @@ PODS:
|
||||
- ReactNativeDependencies
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNReanimated/common (4.3.0):
|
||||
- RNReanimated/common (4.3.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -2256,7 +2258,7 @@ PODS:
|
||||
- ReactNativeDependencies
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNScreens (4.24.0):
|
||||
- RNScreens (4.25.2):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -2278,9 +2280,9 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNScreens/common (= 4.24.0)
|
||||
- RNScreens/common (= 4.25.2)
|
||||
- Yoga
|
||||
- RNScreens/common (4.24.0):
|
||||
- RNScreens/common (4.25.2):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -2325,7 +2327,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNSVG (15.15.4):
|
||||
- RNSVG (15.15.5):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -2346,9 +2348,9 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNSVG/common (= 15.15.4)
|
||||
- RNSVG/common (= 15.15.5)
|
||||
- Yoga
|
||||
- RNSVG/common (15.15.4):
|
||||
- RNSVG/common (15.15.5):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@ -2543,6 +2545,7 @@ 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`)
|
||||
@ -2762,6 +2765,8 @@ 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:
|
||||
@ -2802,13 +2807,13 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
BugsnagReactNative: bee770e3f497a8571feb1579bdc083a070bee1f3
|
||||
BugsnagReactNative: 73ce58aac04585e7cba3081c0abba06d848d62fc
|
||||
BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70
|
||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
||||
FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d
|
||||
hermes-engine: 4ed74710a31e8e31f20356c641eab1d8f7d54595
|
||||
lottie-ios: 8f959969761e9c45d70353667d00af0e5b9cadb3
|
||||
lottie-react-native: 615e5f4651bee144ea991ad8e900630b6b3daf5d
|
||||
lottie-react-native: ee142214581f3bb68fbda7efcf07b835a189eeda
|
||||
RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12
|
||||
RCTRequired: 9f3a7e5645d4bc3f551593de7550bb66ab6e42bc
|
||||
RCTSwiftUI: 239ed2eb9e73de5a6f518810630f0c95e01c8702
|
||||
@ -2855,7 +2860,7 @@ SPEC CHECKSUMS:
|
||||
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
|
||||
react-native-image-picker: 23540feacc79c63c60857f318fdfa8477c26e70a
|
||||
react-native-notifications: e2d3c022d6077de7e420ba5c01b4bd9464f3941d
|
||||
react-native-safe-area-context: 6b4966397ada0f7dd481e4486a2ef936834861a1
|
||||
react-native-safe-area-context: fb5c8ee9f6dd62ef710611b3d370c501f42a4ac0
|
||||
react-native-secure-key-store: eb45b44bdec3f48e9be5cdfca0f49ddf64892ea6
|
||||
react-native-tcp-socket: 7c7e53a07f122ecf00fb3626684bc0ca82c4f044
|
||||
react-native-vector-icons-entypo: f9de1c24005da510dde0de27caf0d2f5471bd433
|
||||
@ -2900,22 +2905,23 @@ SPEC CHECKSUMS:
|
||||
ReactNativeCameraKit: 5974256fc608631c1c812710cd98abe95dae0f88
|
||||
ReactNativeDependencies: 75299c281f422106c723e79dc1f6ce7ef03241be
|
||||
RealmJS: 1c37c6bdfe060f4caa0f9175aa0eedb962622ee1
|
||||
RNBackgroundFetch: 64b1215fbb8ec58afba877ca0ce177e009ce12b7
|
||||
RNCAsyncStorage: 2ad919e88b8bc2cd80e8697ce66d04d006743283
|
||||
RNCClipboard: 715fa7c6c8366f17d00f05a439ee7488f390fa5f
|
||||
RNDefaultPreference: 8a089ee8ce829a66c5453e3c5434f0785499d1c3
|
||||
RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c
|
||||
RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8
|
||||
RNGestureHandler: 187c5c7936abf427bc4d22d6c3b1ac80ad1f63c0
|
||||
RNGestureHandler: 2ff61eac036eaf89f6818bf4ed9c39771a17d134
|
||||
RNHandoff: bc8af5a86853ff13b033e7ba1114c3c5b38e6385
|
||||
RNKeychain: 6778b35b5bd067c322f8479526ac09b1d61f31d0
|
||||
RNLocalize: f370284ea42c48f29f0d8dd3a7bcc28a04f82155
|
||||
RNPermissions: dfbe915a8ee532bc55018cf5e387407847713b02
|
||||
RNQuickAction: c2c8f379e614428be0babe4d53a575739667744d
|
||||
RNReactNativeHapticFeedback: 576e23c1ad2d800ded4502be3f66b767308b63a1
|
||||
RNReanimated: c4e6659e58b793885ae6da476cb514fc913e7b85
|
||||
RNScreens: 01b065ded2dfe7987bcce770ff3a196be417ff41
|
||||
RNReanimated: f735b1747a7a93bda7ca102c6d37a3cf54b6d5e8
|
||||
RNScreens: 991cc417cd396602a6cf59a42139e5a9d91462a9
|
||||
RNShare: 2afdc1739d80ac140b2870ae81e8b2098f4599d9
|
||||
RNSVG: 04044c3abcf177fd674a1a3d13097efa1adebcbe
|
||||
RNSVG: 0e52210d4d43165e7e2cf9c890a9848b27e513ac
|
||||
RNWatch: 28fe1f5e0c6410d45fd20925f4796fce05522e3f
|
||||
RNWorklets: dd3b2cb0750090d78d85cd3b3ec0fdbeab5ce118
|
||||
Yoga: 77dfa8673de2874e1855002ae59c68b8be9b007b
|
||||
|
||||
@ -11,6 +11,13 @@ 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'],
|
||||
|
||||
88
loc/ar.json
88
loc/ar.json
@ -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": "يُرجى أخذ لحظة من وقتك لكتابة هذه العبارة التذكيرية على ورقة. إنها وسيلتك الاحتياطية لاستعادة المحفظة على جهاز آخر.",
|
||||
"text_lnd": "يُرجى حفظ هذه النسخة الاحتياطية للمحفظة. لكي تتتمكن من استعادة المحفظة في حالة فقدها.",
|
||||
"text": "يُرجى أخذ لحظة من وقتك لكتابة هذه العبارة التذكيرية على ورقة.\nإنها وسيلتك الاحتياطية لاستعادة المحفظة على جهاز آخر.",
|
||||
"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,11 +329,10 @@
|
||||
"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؛ أي التحكم بالعملية الرئيسية بمعاملة فرعية.",
|
||||
@ -345,7 +344,6 @@
|
||||
"details_copy_block_explorer_link": "نسخ رابط متصفح الكتل",
|
||||
"details_copy_note": "نسخ الملاحظة",
|
||||
"details_copy_txid": "نسخ معرّف المعاملة",
|
||||
"details_from": "من",
|
||||
"details_inputs": "المدخلات",
|
||||
"details_outputs": "المخرجات",
|
||||
"date": "التاريخ",
|
||||
@ -360,7 +358,6 @@
|
||||
"eta_10m": "الوقت المقدر للتأكيد: في حوالي 10 دقائق",
|
||||
"eta_3h": "الوقت المقدر للتأكيد: في حوالي 3 ساعات",
|
||||
"eta_1d": "الوقت المقدر للتأكيد: في حوالي يوم واحد",
|
||||
"view_wallet": "عرض {walletLabel}",
|
||||
"list_title": "العمليات",
|
||||
"list_title_received": "التاريخ",
|
||||
"transaction": "العملية",
|
||||
@ -380,8 +377,8 @@
|
||||
"outgoing_transaction": "معاملة صادرة",
|
||||
"expired_transaction": "معاملة منتهية الصلاحية",
|
||||
"pending_transaction": "معاملة قيد الانتظار",
|
||||
"offchain": "أوف-تشين",
|
||||
"onchain": "أون-تشين",
|
||||
"offchain": "خارج السلسلة",
|
||||
"onchain": "على السلسلة",
|
||||
"list_title_sent": "مرسلة",
|
||||
"watchOnlyWarningTitle": "تحذير أمني",
|
||||
"custom_fee_warning_title": "تحذير",
|
||||
@ -407,7 +404,7 @@
|
||||
"add_bitcoin": "بتكوين",
|
||||
"add_bitcoin_explain": "محفظة بتكوين بسيطة وقوية",
|
||||
"add_create": "إنشاء",
|
||||
"total_balance": "الرصيد الاجمالي",
|
||||
"total_balance": "الرصيد الإجمالي",
|
||||
"add_entropy": "الإنتروبيا (العشوائية)",
|
||||
"add_entropy_generated": "{gen} بايت من الإنتروبيا (العشوائية) المحققة",
|
||||
"add_entropy_provide": "توفير الإنتروبيا (العشوائية) باستخدام النرد",
|
||||
@ -426,12 +423,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 للمحفظة",
|
||||
@ -474,12 +471,11 @@
|
||||
"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",
|
||||
@ -524,7 +520,7 @@
|
||||
"details_delete_anyway": "احذف على أي حال"
|
||||
},
|
||||
"total_balance_view": {
|
||||
"title": "الرصيد الاجمالي",
|
||||
"title": "الرصيد الإجمالي",
|
||||
"display_in_bitcoin": "العرض بـ Bitcoin",
|
||||
"hide": "إخفاء",
|
||||
"display_in_sats": "العرض بالساتوشي",
|
||||
@ -534,21 +530,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": "إنشاء",
|
||||
@ -559,11 +555,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": "لارسال اي معاملة ومفتاح ثالث يمكنك استخدامه كاحتياطي.",
|
||||
"what_is_vault_description_to_spend_other": "لارسال اي معاملة",
|
||||
"what_is_vault_description_to_spend": "لإرسال أي معاملة ومفتاح ثالث\nيمكنك استخدامه كاحتياطي.",
|
||||
"what_is_vault_description_to_spend_other": "لإرسال أي معاملة",
|
||||
"quorum": "العدد {m} من {n}",
|
||||
"quorum_header": "العدد",
|
||||
"of": "من",
|
||||
@ -573,27 +569,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": "أدخل بصمة الإصبع",
|
||||
"input_fp": "أدخل البصمة (fingerprint)",
|
||||
"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": "مسح أو استيراد معاملة موقعة",
|
||||
@ -685,7 +681,7 @@
|
||||
},
|
||||
"bip47": {
|
||||
"payment_code": "كود الدفع",
|
||||
"purpose": "أكواد المشاركة التي يمكن أعادة استخدامها (BIP47)",
|
||||
"purpose": "أكواد المشاركة التي يمكن إعادة استخدامها (BIP47)",
|
||||
"not_found": "لم يتم العثور على كود الدفع",
|
||||
"contacts": "جهات الاتصال",
|
||||
"bip47_explain": "رمز قابل لإعادة الاستخدام والمشاركة",
|
||||
@ -701,7 +697,7 @@
|
||||
"invalid_pc": "رمز دفع غير صالح",
|
||||
"notification_tx_unconfirmed": "معاملة الإشعار غير مؤكدة بعد، يُرجى الانتظار",
|
||||
"failed_create_notif_tx": "فشل إنشاء المعاملة على السلسلة",
|
||||
"onchain_tx_needed": "مطلوب معاملة على السلسلة",
|
||||
"onchain_tx_needed": "معاملة على السلسلة مطلوبة",
|
||||
"notif_tx_sent": "تم إرسال معاملة الإشعار. يُرجى الانتظار حتى يتم تأكيدها",
|
||||
"notif_tx": "معاملة الإشعار"
|
||||
}
|
||||
|
||||
@ -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} satoshis.",
|
||||
"details_del_wb_q": "У гэтага кашалька ёсьць баланс. Перад тым, як працягнуць, зьвярніце ўвагу, што вы ня зможаце аднавіць сродкі без сід-фразы гэтага кашалька. Каб пазьбегнуць выпадковага выдаленьня, увядзіце баланс кашалька — {balance} сатошы.",
|
||||
"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": "У наладах кашалька вы знойдзеце рэзервовую копію сэйфу і рэзервовую копію толькі для прагляду. Гэтая рэзервовая копія — як карта да вашага кашалька. Яна неабходна для аднаўленьня кашалька ў выпадку страты аднаго з вашых семя.",
|
||||
"ms_help_4": "Каб імпартаваць мультыподпіс, выкарыстайце ваш файл рэзервовай копіі і функцыю Імпарт. Калі ў вас ёсьць толькі семя і XPUB, вы можаце выкарыстаць асобную кнопку Імпарт пры стварэньні ключоў сэйфу.",
|
||||
"ms_help_3": "У наладах кашалька вы знойдзеце рэзэрвовую копію сэйфа і watch-only копію. Гэтая рэзэрвовая копія — як мапа да вашага кашалька. Яна вельмі важная для аднаўленьня кашалька, калі вы згубіце адну з сід-фразаў.",
|
||||
"ms_help_4": "Каб імпартаваць multisig, выкарыстайце файл рэзэрвовай копіі і функцыю «Імпарт». Калі ў вас ёсьць толькі сід-фразы і 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": {
|
||||
|
||||
@ -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": "Plausible Deniability"
|
||||
"title": "Правдоподобно отричане"
|
||||
},
|
||||
"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": "Continuity",
|
||||
"general_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": "Tx Hex",
|
||||
"details_tx_hex": "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",
|
||||
|
||||
323
loc/bqi.json
323
loc/bqi.json
@ -27,7 +27,9 @@
|
||||
"unlock": "گۊشیڌن چفت",
|
||||
"port": "پورت",
|
||||
"ssl_port": "پورت SSL",
|
||||
"suggested": "پؽشنهاڌی"
|
||||
"suggested": "پؽشنهاڌی",
|
||||
"copied": "لف گیری وابی!",
|
||||
"discard_changes_explain": "آلشتکاریا زفت نوابیڌه دارین. اخۊی هونا ن نیڌه گری ۉ ز ای بلگه و در بئری؟"
|
||||
},
|
||||
"azteco": {
|
||||
"codeIs": "کوڌ تخفیف ایسا",
|
||||
@ -36,7 +38,8 @@
|
||||
"redeem": "ازاف کردن و کیف پیل",
|
||||
"redeemButton": "فعال کردن",
|
||||
"success": "سر ٱنجوم گرهڌ",
|
||||
"title": "فعال کردن کوڌ تخفیف Azte.co"
|
||||
"title": "فعال کردن کوڌ تخفیف Azte.co",
|
||||
"successMessage": "ووچر وا مووفقیت فعال وابی! دارایی ایسا و زۊڌی و کیف پیل بیت کوین ایسا اوݩ."
|
||||
},
|
||||
"entropy": {
|
||||
"save": "زفت کردن",
|
||||
@ -80,14 +83,18 @@
|
||||
"create_password_explanation": "رزمی ک سی جاگه زفت کردنی جعلی هڌ، نوا وا رزم جاگه زفت کردنی ٱلسی ی جۊر بۊ.",
|
||||
"help2": "جاگه زفت کردنی نۊ قلوه و کار ایا وو ایسا ترین یتی ز دارایی خوتۉݩ ن اۊچنا واڌارنین تا ب تؽ بیا ک هونی زس استفاڌه اکۊنین.",
|
||||
"password_should_not_match": "رزم هونی ب کار اروه. ی رزم دیری ن ب کار بگر.",
|
||||
"title": "انکار مووجه"
|
||||
"title": "انکار مووجه",
|
||||
"help": "تو هالاتی، شاید ایسا ن مجبۊر کۊنن رزم خوته فاش کۊنی. سی ایکه کوینا خوته امن داری، BlueWallet اتره ی جاگه زفت کردنی دؽ ریس رزم وا رزم دیر وورکل کونه. د هالت فشار، تری ای رزم ن وا ی نفر ی غیر فاش کۊنی. ٱر منه BlueWallet زیڌه بۊ، جاگه زفت کردنی نۊ «جعلی» گۊشیڌه ابۊ. یۊ سی ی نفر ی غیر، واقعی ب نظر اونه، اما د دل، جاگه زفت کردنی ٱلسی ایسا وا کوینا امن میمونه."
|
||||
},
|
||||
"pleasebackup": {
|
||||
"ask_no": "ن، مو نڌاروم.",
|
||||
"ask_yes": "هری، مو داروم.",
|
||||
"ok": "خا، هو ن هؽل کردوم.",
|
||||
"ok_lnd": "خا، هو ن زفت کردوم.",
|
||||
"title": "کیف پیل ایسا وورکل وابی."
|
||||
"title": "کیف پیل ایسا وورکل وابی.",
|
||||
"ask": "عبارت بازیابی کیف پیلته زفت کردیه؟ ای عبارت بازیابی سی دسرسی و دارایی خوت لازمه، ٱر ای دسگاهن گوم کۊنی. بؽ ای عبارت بازیابی، دارایی خوت سی همیشه گوم اونه.",
|
||||
"text": "ی دیقه ویرگار بنا تا ای عبارت بازیابی ن ری ی بلگه کاغذ بنویسی.\nیۊ نوسخه لادرار خوته هڌ ۉ تری وا یۊ کیف پیلته بازیابی کۊنی.",
|
||||
"text_lnd": "تی کۊن ای نوسخه لادرار کیف پیل ن زفت کۊنی. یۊ ایلیه ٱر گوم وابی، کیف پیل ته بازیابی کۊنی."
|
||||
},
|
||||
"receive": {
|
||||
"details_create": "وورکل",
|
||||
@ -101,7 +108,8 @@
|
||||
"maxSatsFull": "بیشترین مقدار {max} ساتۊشی یا {currency} هڌ.",
|
||||
"minSats": "کمترین مقدار {min} ساتۊشی هڌ.",
|
||||
"minSatsFull": "کمترین مقدار {min} ساتۊشی یا {currency} هڌ.",
|
||||
"qrcode_for_the_address": "QR کود سی ای نشۊوی"
|
||||
"qrcode_for_the_address": "QR کود سی ای نشۊوی",
|
||||
"bip47_explanation": "کود پرداخت ی آدرس عمومی هڌ ک بؽ فاش کردن آدرسا کیف پیل ایسا کار اکونه. هومه خدماتا تی پشتؽوانی نکونن."
|
||||
},
|
||||
"send": {
|
||||
"provided_address_is_invoice": "منی ای آدرس ی سۊرت هساو لایتنینگ هڌ. سی پرداخت ای سۊرت هساو، و کیف پیل لایتنینگ خوتۉݩ ریوین.",
|
||||
@ -147,7 +155,6 @@
|
||||
"details_next": "نیایی",
|
||||
"details_no_signed_tx": "فایل پسند بیڌه، تراکونشی منس نؽڌ ک ترسته بویم ب من یاریمس.",
|
||||
"details_note_placeholder": "ویرداشت و خوت",
|
||||
"counterparty_label_placeholder": "آلشت نوم هومدنگ",
|
||||
"details_scan": "اسکن",
|
||||
"details_scan_hint": "سی اسکن یا و من ٱووردن مقسد، دو کرت بزن ریس",
|
||||
"details_scan_error": "ختا اسکن",
|
||||
@ -189,7 +196,17 @@
|
||||
"success_done": "ٱنجوم وابی",
|
||||
"txSaved": "فایل تراکونش ({filePath}) زفت وابیڌه.",
|
||||
"file_saved_at_path": "فایل ({filePath}) زفت وابیڌه.",
|
||||
"problem_with_psbt": "موشکل وا تراکونش ناقس امزا بیڌه PSBT"
|
||||
"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": "ٱول ای کود پرداخت ن و هومدنگا اضاف کۊنی"
|
||||
},
|
||||
"settings": {
|
||||
"about": "زبار",
|
||||
@ -214,7 +231,7 @@
|
||||
"default_title": "موقه ره وندن",
|
||||
"electrum_connected": "منپیز",
|
||||
"electrum_connected_not": "بؽ منپیز",
|
||||
"electrum_error_connect": "نتره و سرور الکتروم داڌه وابیڌه منپیز بۊوه",
|
||||
"electrum_error_connect": "نتره و سرور الکترام داڌه وابیڌه منپیز بۊوه",
|
||||
"lndhub_uri": "سی نمووه، {example}",
|
||||
"electrum_host": "سی نمووه، {example}",
|
||||
"electrum_offline_mode": "هالت آفلاین",
|
||||
@ -242,7 +259,7 @@
|
||||
"header": "سامووا",
|
||||
"language": "زووݩ",
|
||||
"last_updated": "ورۊ رسۊوی دیندایی",
|
||||
"language_isRTL": "ر وندن دووارته BlueWallet سی انجوم آلشت کاریا ری زووݩ الن وا انجوم بۊ.",
|
||||
"language_isRTL": "ره وندن دۊوارته BlueWallet سی انجوم آلشت کاریا ری زووݩ الن وا انجوم بۊ.",
|
||||
"license": "موجوز",
|
||||
"lightning_error_lndhub_uri": "یۊ آر آی LNDhub زبال نؽ",
|
||||
"lightning_saved": "آلشت کاریا ایسا و خۊوی زفت وابین.",
|
||||
@ -266,12 +283,48 @@
|
||||
"saved": "زفت وابی",
|
||||
"total_balance": "پوی مۉجۊدی",
|
||||
"widgets": "اوزارکا",
|
||||
"tools": "اوزارا"
|
||||
"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": "پوی مۉجۊڌی کیف پیلا خوته من اوزارکا بلگه ٱلسی نشۉݩ بڌه."
|
||||
},
|
||||
"transactions": {
|
||||
"cancel_title": "ای تراکونشن لقو کوݩ (RBF)",
|
||||
"confirmations_lowercase": "{confirmations} تاییڌ",
|
||||
"copy_link": "لف گیری لینگ",
|
||||
"expand_note": "نشۉݩ داڌن کامل ویرداشت",
|
||||
"cpfp_create": "وورکل",
|
||||
"cpfp_no_bump": "ای تراکونش نتره کارمزدس بیشتر بۊ.",
|
||||
@ -309,7 +362,37 @@
|
||||
"pending": "مندیر سی زفت",
|
||||
"list_title_received": "گرؽڌه وابیڌه",
|
||||
"transaction": "تراکونش",
|
||||
"open_url_error": "نا مووفق منه گۊشیڌن لینگ وا گشت گر پؽش فرز. گشت گر پؽش فرز خوته آلشت کوݩ ۉ ز نۊ تفره کو."
|
||||
"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": "نرخ کارمزد"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "بیت کوین",
|
||||
@ -327,10 +410,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": "و در کشیڌن کیف پیل",
|
||||
@ -368,14 +451,75 @@
|
||||
"details_delete_anyway": "و هر هال پاک بۊ",
|
||||
"add_lightning": "لایتنینگ",
|
||||
"swipe_balance_hide": "بؽڌار",
|
||||
"details_delete": "پاک کردن"
|
||||
"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": "موشکلؽ منه تاییڌ پاک وابیڌن ای کیف پیل ز وارسۊویا پؽش ٱووڌ — یۊ شاید ز موشکل شبکه یا منپیز زبال نکوݩ بۊ. ٱر ادامه دی، شاید هنی سی تراکونشا پۊشمت وا ای کیف پیل وارسۊوی گری، حتا پس ز پاک وابیڌنس."
|
||||
},
|
||||
"total_balance_view": {
|
||||
"display_in_bitcoin": "نشووݩ داڌن من بیت کوین",
|
||||
"hide": "بؽڌار",
|
||||
"display_in_sats": "نشووݩ داڌن و ری ساتۊشی",
|
||||
"display_in_fiat": "نشووݩ داڌن من {currency}",
|
||||
"title": "پوی مۉجۊدی"
|
||||
"title": "پوی مۉجۊدی",
|
||||
"explanation": "پوی مۉجۊڌی کیف پیلا خوته من بلگه نمای کلی ووین."
|
||||
},
|
||||
"multisig": {
|
||||
"confirm": "تاییڌ",
|
||||
@ -394,26 +538,110 @@
|
||||
"quorum_header": "حد نصاب",
|
||||
"of": "ز",
|
||||
"wallet_type": "نوع کیف پیل",
|
||||
"vault_advanced_customize": "سامووا گاوصندوق"
|
||||
"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 وورکل اکونه. سی وورکل ی حد نصاب دیر یا آلشت نوع آدرس، هالت پؽشرفته ن منه سامووا فعال کۊن."
|
||||
},
|
||||
"cc": {
|
||||
"sort_status": "وزیت",
|
||||
"change": "باقیمانده",
|
||||
"change": "آلشتکاری",
|
||||
"header": "مدیریت UTXO",
|
||||
"selected_summ": "{value} پسند بیڌه",
|
||||
"sort_label": "برچسب"
|
||||
"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": "ترتیو ری ٱر"
|
||||
},
|
||||
"addresses": {
|
||||
"sign_placeholder_address": "آدرس",
|
||||
"type_receive": "گرؽڌن",
|
||||
"type_change": "باقیمانده",
|
||||
"type_change": "آلشتکاری",
|
||||
"addresses_title": "آدرسا",
|
||||
"transactions": "تراکونشا"
|
||||
"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": "ب کار رئڌه وابی"
|
||||
},
|
||||
"units": {
|
||||
"BTC": "BTC",
|
||||
"sat_vbyte": "ساتۊشی سی هر بایت مجازی",
|
||||
"sats": "ساتۊشی"
|
||||
"sats": "ساتۊشی",
|
||||
"MAX": "بیشترین"
|
||||
},
|
||||
"bip47": {
|
||||
"payment_code": "کود پرداخت",
|
||||
@ -422,6 +650,55 @@
|
||||
"copy_payment_code": "لف گیری کود پرداخت",
|
||||
"add_contact": "ٱووردن هومدنگ",
|
||||
"invalid_pc": "کود پرداخت زبال نؽ",
|
||||
"not_found": "کود پرداخت نجۊرست"
|
||||
"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": "تاییڌ هۊویت"
|
||||
}
|
||||
}
|
||||
|
||||
44
loc/ca.json
44
loc/ca.json
@ -12,8 +12,8 @@
|
||||
"of": "{number} de {total}",
|
||||
"ok": "OK",
|
||||
"enter_url": "Entrar URL",
|
||||
"storage_is_encrypted": "L'informació està xifrada. Es requereix la contrasenya per a desxifrar-la.",
|
||||
"yes": "Si",
|
||||
"storage_is_encrypted": "La informació està xifrada. Es requereix la contrasenya per a desxifrar-la.",
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
"save": "Desar...",
|
||||
"seed": "Llavor",
|
||||
@ -34,12 +34,12 @@
|
||||
"azteco": {
|
||||
"codeIs": "El codi del teu val és",
|
||||
"errorBeforeRefeem": "Abans de canviar, primer heu d’afegir 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ó de Azte.co"
|
||||
"title": "Canviar cupó d'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 \"false\" é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 \"fals\" é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ó, firmada i llesta per ser enviada a la xarxa. ¿Continuar?",
|
||||
"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_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 invalida",
|
||||
"details_address_field_is_not_valid": "Adreça no vàlida",
|
||||
"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 invalida",
|
||||
"details_amount_field_is_not_valid": "Quantitat no vàlida",
|
||||
"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ó invalida",
|
||||
"details_fee_field_is_not_valid": "Comissió no vàlida",
|
||||
"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": "Conectat",
|
||||
"electrum_connected_not": "No conectat",
|
||||
"electrum_connected": "Connectat",
|
||||
"electrum_connected_not": "No connectat",
|
||||
"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,7 +336,6 @@
|
||||
"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ó.",
|
||||
@ -347,7 +346,6 @@
|
||||
"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",
|
||||
@ -369,7 +367,6 @@
|
||||
"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",
|
||||
@ -431,9 +428,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": "Direcció",
|
||||
"details_address": "Adreça",
|
||||
"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.",
|
||||
@ -443,9 +440,9 @@
|
||||
"details_display": "Mostrar a la pantalla d'inici",
|
||||
"details_export_backup": "Exportar / Guardar",
|
||||
"details_export_history": "Exportar historial a CSV",
|
||||
"details_master_fingerprint": "Petjada digital mestre",
|
||||
"details_master_fingerprint": "Empremta digital mestra",
|
||||
"details_multisig_type": "multisig",
|
||||
"details_show_xpub": "Mostrar wallet XPUB",
|
||||
"details_show_xpub": "Mostrar XPUB del moneder",
|
||||
"details_show_addresses": "Mostrar adreces",
|
||||
"details_title": "Detalls del moneder",
|
||||
"wallets": "moneders",
|
||||
@ -454,15 +451,15 @@
|
||||
"drag_to_reorder": "Arrossegueu per reordenar",
|
||||
"clear_search": "Esborrar cerca",
|
||||
"details_type": "Tipus",
|
||||
"details_use_with_hardware_wallet": "Usar amb un moneder hardware",
|
||||
"details_yes_delete": "Si, eliminar",
|
||||
"details_use_with_hardware_wallet": "Usar amb un moneder de maquinari",
|
||||
"details_yes_delete": "Sí, 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?",
|
||||
@ -520,8 +517,7 @@
|
||||
"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",
|
||||
"xpub_copiedToClipboard": "Copiat al porta-retalls."
|
||||
"details_delete_anyway": "Eliminar igualment"
|
||||
},
|
||||
"total_balance_view": {
|
||||
"display_in_bitcoin": "Mostrar en Bitcoin",
|
||||
|
||||
@ -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é"
|
||||
"suggested": "Doporučené",
|
||||
"port": "Port"
|
||||
},
|
||||
"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."
|
||||
"wasnt_paid_and_expired": "Tato faktura nebyla zaplacena a její platnost vypršela.",
|
||||
"sats": "sats."
|
||||
},
|
||||
"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,7 +157,6 @@
|
||||
"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í",
|
||||
@ -206,7 +205,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 teno platební kód přidejte mezi kontakty",
|
||||
"cant_find_bip47_notification": "Nejdříve tento platební kód přidejte mezi kontakty",
|
||||
"problem_with_psbt": "Problém s PSBT (částečně podepsanou bitcoinovou transakcí)"
|
||||
},
|
||||
"settings": {
|
||||
@ -228,7 +227,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ě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.",
|
||||
"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.",
|
||||
"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í.",
|
||||
@ -336,7 +335,6 @@
|
||||
"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).",
|
||||
@ -348,7 +346,6 @@
|
||||
"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",
|
||||
@ -370,7 +367,6 @@
|
||||
"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",
|
||||
@ -382,8 +378,6 @@
|
||||
"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.",
|
||||
@ -392,10 +386,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é",
|
||||
@ -508,7 +502,6 @@
|
||||
"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.",
|
||||
@ -581,7 +574,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ý spolupodepsaný: toto není spolupodepsaný pro {format} formát.",
|
||||
"invalid_cosigner_format": "Nesprávný spolupodepisující: toto není spolupodepisující 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íč.",
|
||||
@ -589,7 +582,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? Vaš mnemotechnický seed bude ztracen, pokud nemáte zálohu.",
|
||||
"are_you_sure_seed_will_be_lost": "Jste si jisti? Váš 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.",
|
||||
@ -665,7 +658,7 @@
|
||||
"sign_placeholder_message": "Zpráva",
|
||||
"sign_placeholder_signature": "Podpis",
|
||||
"addresses_title": "Adresy",
|
||||
"type_change": "Change",
|
||||
"type_change": "Drobné",
|
||||
"type_receive": "Přijímací",
|
||||
"type_used": "Použitá",
|
||||
"transactions": "Transakce"
|
||||
|
||||
56
loc/cy.json
56
loc/cy.json
@ -12,11 +12,11 @@
|
||||
"of": "{number} o {total}",
|
||||
"ok": "Iawn",
|
||||
"enter_url": "Mewnosod URL",
|
||||
"storage_is_encrypted": "Mae'r storfa wedi encryptio. Mae angen Cyfrinair i'w ddad-gryptio. ",
|
||||
"storage_is_encrypted": "Mae'r storfa wedi'i hamgryptio. Mae angen cyfrinair i'w dadgryptio.",
|
||||
"yes": "Ie",
|
||||
"no": "Na",
|
||||
"save": "Safio...",
|
||||
"seed": "Hadyn",
|
||||
"seed": "Seed",
|
||||
"success": "Llwyddiant",
|
||||
"wallet_key": "Allwedd waled",
|
||||
"close": "Cau",
|
||||
@ -54,7 +54,7 @@
|
||||
},
|
||||
"lnd": {
|
||||
"errorInvoiceExpired": "Mae'r anfoneb wedi dod i ben.",
|
||||
"expired": "Gorffen",
|
||||
"expired": "Wedi dod i ben",
|
||||
"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": "Tâl rhwng {min} a {max} os gweli'n dda",
|
||||
"please_pay_between_and": "Tala rhwng {min} a {max}",
|
||||
"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."
|
||||
"wasnt_paid_and_expired": "Ni chafodd yr anfoneb ei thalu, ac mae wedi gorffen.",
|
||||
"sats": "sats."
|
||||
},
|
||||
"plausibledeniability": {
|
||||
"create_fake_storage": "Creu storfa wedi encryptio",
|
||||
"create_fake_storage": "Creu storfa wedi'i hamgryptio",
|
||||
"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 sicr dy fod eisiau defnyddio balans llawn dy walet ar gyfer y trafodyn hwn?",
|
||||
"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_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,8 +169,6 @@
|
||||
"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",
|
||||
@ -184,7 +182,7 @@
|
||||
"input_done": "Wedi Cwblhau",
|
||||
"input_paste": "Pastio",
|
||||
"input_total": "Cyfanswm:",
|
||||
"permission_camera_message": "Angen dy ganiatad i ddefnyddio dy gamera.",
|
||||
"permission_camera_message": "Angen dy ganiatâd i ddefnyddio dy gamera.",
|
||||
"psbt_sign": "Arwyddo trafodyn",
|
||||
"invalid_psbt": "PSBT annilys.",
|
||||
"open_settings": "Agor Gosodiadau",
|
||||
@ -206,7 +204,9 @@
|
||||
"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"
|
||||
"problem_with_psbt": "Problem efo'r PSBT",
|
||||
"fee_10m": "10m",
|
||||
"fee_1d": "1d"
|
||||
},
|
||||
"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 llwyddianus.",
|
||||
"lightning_saved": "Mae dy newidiadau wedi cael eu safio'n llwyddiannus.",
|
||||
"lightning_settings": "Gosodiadau Mellten",
|
||||
"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.",
|
||||
"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.",
|
||||
"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": "Hysbysebion",
|
||||
"notifications": "Hysbysiadau",
|
||||
"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": "Prefiatrwydd",
|
||||
"privacy": "Preifatrwydd",
|
||||
"privacy_read_clipboard": "Darllen Clipfwrdd",
|
||||
"privacy_system_settings": "Gosodiadau System",
|
||||
"privacy_quickactions": "Llwybrau Byr Waled",
|
||||
@ -389,7 +389,6 @@
|
||||
"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",
|
||||
@ -399,11 +398,11 @@
|
||||
"details_tx_hex": "Hex y Trafodyn",
|
||||
"details_inputs_count": "Mewnbynau ({count})",
|
||||
"details_outputs_count": "Allbynau ({count})",
|
||||
"details_from": "Mewnbwn"
|
||||
"details_id": "ID"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "Bitcoin",
|
||||
"add_bitcoin_explain": "Waled Bitcoin syml a cryf",
|
||||
"add_bitcoin_explain": "Waled Bitcoin syml a chryf",
|
||||
"add_create": "Creu",
|
||||
"total_balance": "Balans Llawn",
|
||||
"add_entropy_reset_title": "Ailosod Entropi",
|
||||
@ -417,13 +416,13 @@
|
||||
"add_lightning": "Mellten",
|
||||
"add_lightning_explain": "Er mwyn gwario yn syth",
|
||||
"add_lndhub": "Cysylltu â'th LNDhub",
|
||||
"add_lndhub_error": "Mae cyfeiriad y nôd a ddarparwyd yn nôd LNDhub annilys.",
|
||||
"add_lndhub_placeholder": "Cyfeiriad dy Nôd",
|
||||
"add_lndhub_error": "Mae cyfeiriad y nod a ddarparwyd yn nod LNDhub annilys.",
|
||||
"add_lndhub_placeholder": "Cyfeiriad dy Nod",
|
||||
"add_placeholder": "fy waled gyntaf",
|
||||
"add_title": "Adio waled",
|
||||
"add_wallet_name": "Enw",
|
||||
"add_wallet_type": "Math",
|
||||
"add_wallet_seed_length": "Hyd yr Hadyn",
|
||||
"add_wallet_seed_length": "Hyd y Seed",
|
||||
"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?",
|
||||
@ -431,7 +430,7 @@
|
||||
"clear_clipboard_on_import": "Clirio'r clipfwrdd ar ôl mewnforio",
|
||||
"details_address": "Cyfeiriad",
|
||||
"details_advanced": "Arbenigol",
|
||||
"details_are_you_sure": "Wyt ti'n siwr?",
|
||||
"details_are_you_sure": "Wyt ti'n siŵr?",
|
||||
"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.",
|
||||
@ -454,7 +453,7 @@
|
||||
"details_type": "Math",
|
||||
"details_use_with_hardware_wallet": "Defnyddio gyda Waled Caledwedd",
|
||||
"details_yes_delete": "Ia, dileu",
|
||||
"enter_bip38_password": "Angen cyfrinair i ddad-gryptio",
|
||||
"enter_bip38_password": "Angen cyfrinair i ddadgryptio",
|
||||
"export_title": "Waled Allfor",
|
||||
"import_do_import": "Mewnforio",
|
||||
"import_passphrase": "Geiriau pas",
|
||||
@ -464,7 +463,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 fewnforio'n llwyddiannus.",
|
||||
"import_success": "Mae dy waled wedi cael ei mewnforio'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",
|
||||
@ -518,8 +517,7 @@
|
||||
"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",
|
||||
"xpub_copiedToClipboard": "Wedi gopio i'r clipfwrdd."
|
||||
"details_delete_anyway": "Dileu beth bynnag"
|
||||
},
|
||||
"total_balance_view": {
|
||||
"display_in_bitcoin": "Dangos mewn Bitcoin",
|
||||
@ -637,9 +635,9 @@
|
||||
"sort_desc": "Disgynnol",
|
||||
"sort_height": "Uchder",
|
||||
"sort_value": "Gwerth",
|
||||
"sort_label": "Label",
|
||||
"sort_status": "Statws",
|
||||
"sort_by": "Trefnu yn ôl"
|
||||
"sort_by": "Trefnu yn ôl",
|
||||
"sort_label": "Label"
|
||||
},
|
||||
"units": {
|
||||
"BTC": "BTC",
|
||||
|
||||
376
loc/da_dk.json
376
loc/da_dk.json
@ -18,7 +18,7 @@
|
||||
"no": "Nej",
|
||||
"save": "Gem...",
|
||||
"seed": "Seed",
|
||||
"wallet_key": "Tegnebogsnøgle",
|
||||
"wallet_key": "Wallet-nøgle",
|
||||
"close": "Luk",
|
||||
"change_input_currency": "Skift inputvaluta",
|
||||
"refresh": "Opdater",
|
||||
@ -33,22 +33,22 @@
|
||||
},
|
||||
"azteco": {
|
||||
"success": "Succes",
|
||||
"codeIs": "Din voucherkode er",
|
||||
"errorBeforeRefeem": "Før du indløser, skal du først tilføje en Bitcoin-tegnebog.",
|
||||
"codeIs": "Din voucher-kode er",
|
||||
"errorBeforeRefeem": "Før du indløser, skal du først tilføje en Bitcoin-wallet.",
|
||||
"errorSomething": "Noget gik galt. Er denne voucher stadig gyldig?",
|
||||
"redeem": "Indløs til tegnebog",
|
||||
"redeem": "Indløs til wallet",
|
||||
"redeemButton": "Indløs",
|
||||
"successMessage": "Voucher indløst! Dine midler bør ankomme i din Bitcoin-tegnebog inden længe.",
|
||||
"successMessage": "Voucher indløst! Dine midler bør ankomme i din Bitcoin-wallet inden længe.",
|
||||
"title": "Indløs Azte.co-voucher"
|
||||
},
|
||||
"entropy": {
|
||||
"save": "save",
|
||||
"save": "Gem",
|
||||
"title": "Entropi",
|
||||
"undo": "Fortryd",
|
||||
"amountOfEntropy": "{bits} af {limit} bit"
|
||||
},
|
||||
"errors": {
|
||||
"broadcast": "Transmittering mislykkedes.",
|
||||
"broadcast": "Transmission 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-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."
|
||||
"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."
|
||||
},
|
||||
"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": "Preimage",
|
||||
"preimage": "Pre-image",
|
||||
"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 kryopteret lager",
|
||||
"create_fake_storage": "Opret falsk krypteret 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æ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.",
|
||||
"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.",
|
||||
"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 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": "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_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 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."
|
||||
"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."
|
||||
},
|
||||
"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 modtageadresse.",
|
||||
"address_not_found": "Kunne ikke generere modtageradresse.",
|
||||
"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": "Betalingskoder er en universel adresse, der undgår at afsløre dine tegnebogsadresser. Ikke alle tjenester understøtter dem."
|
||||
"bip47_explanation": "Payment Codes er en universel adresse, der undgår at afsløre dine wallet-adresser. Ikke alle tjenester understøtter dem."
|
||||
},
|
||||
"send": {
|
||||
"broadcastButton": "Transmitter",
|
||||
@ -124,21 +124,22 @@
|
||||
"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": "Adresse felt er ikke gyldigt",
|
||||
"details_amount_field_is_not_valid": "Beløbsfeltet er ikke gyldigt",
|
||||
"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_create": "Opret",
|
||||
"details_fee_field_is_not_valid": "Gebyr feltet er ikke gyldigt",
|
||||
"details_fee_field_is_not_valid": "Gebyret 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-tegnebog 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-wallet 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 per vByte",
|
||||
"create_satoshi_per_vbyte": "Satoshi pr. vByte",
|
||||
"create_verify": "Verificer på coinb.in",
|
||||
"details_insert_contact": "Indsæt kontakt",
|
||||
"details_add_rec_add": "Tilføj modtager",
|
||||
@ -151,8 +152,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 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_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_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.",
|
||||
@ -160,12 +161,11 @@
|
||||
"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 mønter er udelukket.",
|
||||
"details_total_exceeds_balance_frozen": "Sendebeløbet overstiger den tilgængelige saldo. Bemærk, at frosne coins 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-tegnebog.",
|
||||
"details_wallet_before_tx": "Før du opretter en transaktion, skal du først tilføje en Bitcoin-wallet.",
|
||||
"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": "Underskriv en transaktion",
|
||||
"psbt_sign": "Signer 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 delvist underskrevet Bitcoin-transaktion (PSBT). Færdiggør venligst underskrivelsen med din hardwaretegnebog.",
|
||||
"psbt_this_is_psbt": "Dette er en Partially Signed Bitcoin Transaction (PSBT). Færdiggør venligst signeringen med din hardware wallet.",
|
||||
"psbt_tx_export": "Eksportér til fil",
|
||||
"no_tx_signing_in_progress": "Der er ingen transaktionsunderskrivelse i gang.",
|
||||
"no_tx_signing_in_progress": "Der er ingen transaktionssignering i gang.",
|
||||
"outdated_rate": "Kurs blev sidst opdateret: {date}",
|
||||
"psbt_tx_open": "Åbn underskrevet transaktion",
|
||||
"psbt_tx_scan": "Scan underskrevet transaktion",
|
||||
"psbt_tx_open": "Åbn signeret transaktion",
|
||||
"psbt_tx_scan": "Scan signeret 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 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",
|
||||
"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",
|
||||
"problem_with_psbt": "Problem med PSBT"
|
||||
},
|
||||
"settings": {
|
||||
"about": "Andet",
|
||||
"currency": "Valuta",
|
||||
"header": "indstillinger",
|
||||
"header": "Indstillinger",
|
||||
"language": "Sprog",
|
||||
"lightning_settings": "Lightning settings",
|
||||
"lightning_settings": "Lightning-indstillinger",
|
||||
"password": "Adgangskode",
|
||||
"plausible_deniability": "Sandsynlig benægtelse...",
|
||||
"save": "save",
|
||||
"about_awesome": "Bygget med den fantastiske",
|
||||
"about_backup": "Sikkerhedskopier altid dine nøgler!",
|
||||
"save": "Gem",
|
||||
"about_awesome": "Bygget med det fantastiske",
|
||||
"about_backup": "Tag altid backup af 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. Tegnebogen fungerer fint.",
|
||||
"about_selftest_ok": "Alle interne tests er bestået. Wallet'en 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 tegnebøger 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 wallets 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 tegnebøger vil blive fjernet, og dit lager vil blive dekrypteret. Er du sikker på, at du vil fortsætte?",
|
||||
"biom_remove_decrypt": "Alle dine wallets 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-tegnebøger ikke forsøge at hente saldi eller transaktioner.",
|
||||
"electrum_offline_description": "Når aktiveret, vil dine Bitcoin-wallets 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 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_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_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 tegnebøger uden en adgangskode.",
|
||||
"encrypt_decrypt_q": "Er du sikker på, at du vil dekryptere dit lager? Dette vil tillade adgang til dine wallets 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 tegnebøger, der er gemt i enhedens nøglering. Den sætter ikke en adgangskode eller ekstra beskyttelse på selve tegnebøgerne.",
|
||||
"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.",
|
||||
"i_understand": "Jeg forstår",
|
||||
"block_explorer": "Blokudforsker",
|
||||
"block_explorer_preferred": "Brug foretrukken blokudforsker",
|
||||
"block_explorer_error_saving_custom": "Fejl ved lagring af foretrukken blokudforsker",
|
||||
"block_explorer": "Block Explorer",
|
||||
"block_explorer_preferred": "Brug foretrukken block explorer",
|
||||
"block_explorer_error_saving_custom": "Fejl ved lagring af foretrukken block explorer",
|
||||
"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 tegnebog.",
|
||||
"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.",
|
||||
"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 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.",
|
||||
"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.",
|
||||
"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 udforsker",
|
||||
"open_link_in_explorer": "Åbn link i explorer",
|
||||
"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": "Tegnebogsgenveje",
|
||||
"privacy_quickactions_explanation": "Tryk og hold på BlueWallet-appikonet for hurtigt at se din tegnebogs saldo.",
|
||||
"privacy_quickactions": "Wallet-genveje",
|
||||
"privacy_quickactions_explanation": "Tryk og hold på BlueWallet-appikonet for hurtigt at se din wallets 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 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.",
|
||||
"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.",
|
||||
"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 tegnebøger på din startskærms widgets.",
|
||||
"total_balance_explanation": "Vis den samlede saldo for alle dine wallets på din startskærms-widgets.",
|
||||
"widgets": "Widgets",
|
||||
"tools": "Værktøjer"
|
||||
},
|
||||
@ -331,10 +331,9 @@
|
||||
"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.",
|
||||
@ -348,7 +347,7 @@
|
||||
"cpfp_title": "Forøg gebyr (CPFP)",
|
||||
"details_balance_hide": "Skjul saldo",
|
||||
"details_balance_show": "Vis saldo",
|
||||
"details_copy_block_explorer_link": "Kopier blokudforsker-link",
|
||||
"details_copy_block_explorer_link": "Kopier block explorer-link",
|
||||
"details_copy_note": "Kopier note",
|
||||
"details_copy_txid": "Kopier transaktions-ID",
|
||||
"details_inputs": "Input",
|
||||
@ -360,10 +359,10 @@
|
||||
"outgoing_transaction": "Udgående transaktion",
|
||||
"expired_transaction": "Udløbet transaktion",
|
||||
"pending_transaction": "Afventende transaktion",
|
||||
"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}",
|
||||
"offchain": "Offchain",
|
||||
"onchain": "Onchain",
|
||||
"enable_offline_signing": "Denne wallet bruges ikke sammen med offline-signering. Vil du aktivere det nu?",
|
||||
"list_conf": "Bekr.: {number}",
|
||||
"pending": "Afventende",
|
||||
"pending_with_amount": "Afventende {amt1} ({amt2})",
|
||||
"received_with_amount": "+{amt1} ({amt2})",
|
||||
@ -381,13 +380,13 @@
|
||||
"txid": "Transaktions-ID",
|
||||
"updating": "Opdaterer...",
|
||||
"watchOnlyWarningTitle": "Sikkerhedsadvarsel",
|
||||
"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.",
|
||||
"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.",
|
||||
"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": "udforsker",
|
||||
"details_explorer": "explorer",
|
||||
"details_network_fee": "Netværksgebyr",
|
||||
"details_to_address": "Til",
|
||||
"details_id": "ID",
|
||||
@ -402,34 +401,38 @@
|
||||
"details_outputs_count": "Output ({count})"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin_explain": "Simple and powerful Bitcoin wallet",
|
||||
"add_bitcoin_explain": "Enkel og kraftfuld Bitcoin-wallet",
|
||||
"add_create": "Opret",
|
||||
"add_import_wallet": "Importer wallet",
|
||||
"add_title": "Tilføj wallet",
|
||||
"add_wallet_name": "wallet navn",
|
||||
"add_wallet_name": "Navn",
|
||||
"add_wallet_type": "Type",
|
||||
"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": "eller scan QR kode istedet?",
|
||||
"import_scan_qr": "Scan eller importer en fil",
|
||||
"import_success": "Succes",
|
||||
"import_title": "importer",
|
||||
"import_title": "Importer",
|
||||
"list_create_a_button": "Tilføj nu",
|
||||
"list_create_a_wallet": "Tilføj en tegnebog",
|
||||
"list_create_a_wallet": "Tilføj en wallet",
|
||||
"list_title": "Wallets",
|
||||
"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 tegnebogstypen vil nulstille den aktuelle entropi. Vil du fortsætte?",
|
||||
"add_entropy_reset_message": "Ændring af wallet-typen 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",
|
||||
@ -440,8 +443,7 @@
|
||||
"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 tegnebog",
|
||||
"add_wallet_type": "Type",
|
||||
"add_placeholder": "min første wallet",
|
||||
"add_wallet_seed_length": "Seed-længde",
|
||||
"add_wallet_seed_length_12": "12 ord",
|
||||
"add_wallet_seed_length_24": "24 ord",
|
||||
@ -450,75 +452,71 @@
|
||||
"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 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_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_derivation_path": "afledningssti",
|
||||
"details_display": "Vis på startskærm",
|
||||
"details_export_history": "Eksportér historik til CSV",
|
||||
"details_master_fingerprint": "Hovednøgle-fingeraftryk",
|
||||
"details_master_fingerprint": "Master fingerprint",
|
||||
"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_type": "Type",
|
||||
"details_use_with_hardware_wallet": "Brug med hardwaretegnebog",
|
||||
"details_use_with_hardware_wallet": "Brug med hardware wallet",
|
||||
"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 tegnebog.",
|
||||
"import_success_watchonly": "Din tegnebog er blevet importeret. ADVARSEL: Dette er en kun-kigge-tegnebog, 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 wallet.",
|
||||
"import_success_watchonly": "Din wallet er blevet importeret. ADVARSEL: Dette er en watch-only wallet, 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 tegnebog",
|
||||
"import_discovery_subtitle": "Vælg en opdaget wallet",
|
||||
"import_discovery_derivation": "Brug brugerdefineret afledningssti",
|
||||
"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_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_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 tegnebog.",
|
||||
"import_derivation_subtitle": "Indtast brugerdefineret afledningssti, og vi vil forsøge at opdage din wallet.",
|
||||
"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-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_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_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-tegnebog.",
|
||||
"no_ln_wallet_error": "Før du betaler en Lightning-faktura, skal du først tilføje en Lightning-wallet.",
|
||||
"looks_like_bip38": "Dette ligner en adgangskodebeskyttet privat nøgle (BIP38).",
|
||||
"manage_title": "Administrer tegnebøger",
|
||||
"manage_title": "Administrer wallets",
|
||||
"no_results_found": "Ingen resultater fundet.",
|
||||
"please_continue_scanning": "Fortsæt venligst med at scanne.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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 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}.",
|
||||
"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}.",
|
||||
"share_number": "Del {number}",
|
||||
"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.",
|
||||
"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.",
|
||||
"identity_pubkey": "Identitets-pubkey",
|
||||
"xpub_title": "Tegnebogs XPUB",
|
||||
"manage_wallets_search_placeholder": "Søg tegnebøger, adresser, transaktioner og notater",
|
||||
"xpub_title": "Wallet XPUB",
|
||||
"manage_wallets_search_placeholder": "Søg wallets, adresser, transaktioner og notater",
|
||||
"more_info": "Mere info",
|
||||
"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_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_anyway": "Slet alligevel"
|
||||
},
|
||||
"total_balance_view": {
|
||||
@ -527,118 +525,118 @@
|
||||
"display_in_sats": "Vis i sats",
|
||||
"display_in_fiat": "Vis i {currency}",
|
||||
"title": "Samlet saldo",
|
||||
"explanation": "Vis den samlede saldo for alle dine tegnebøger på oversigtsskærmen."
|
||||
"explanation": "Vis den samlede saldo for alle dine wallets på oversigtsskærmen."
|
||||
},
|
||||
"multisig": {
|
||||
"confirm": "Bekræft",
|
||||
"create": "Opret",
|
||||
"ms_help_title5": "Enable advanced mode",
|
||||
"multisig_vault": "Multisig-boks",
|
||||
"default_label": "Multisig-boks",
|
||||
"header": "Send",
|
||||
"multisig_vault": "Multisig Vault",
|
||||
"default_label": "Multisig Vault",
|
||||
"ms_help_title5": "Avanceret tilstand",
|
||||
"multisig_vault_explain": "Bedste sikkerhed til store beløb",
|
||||
"provide_signature": "Underskriv",
|
||||
"provide_signature_details": "Brug din enhed og tegnebog, hvor nøglen findes, til at underskrive denne transaktion",
|
||||
"provide_signature": "Signer",
|
||||
"provide_signature_details": "Brug din enhed og wallet, hvor nøglen findes, til at signere 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 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}",
|
||||
"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}",
|
||||
"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 medunderskriver",
|
||||
"shared_key_detected_question": "En medunderskriver er blevet delt med dig, vil du importere den?",
|
||||
"shared_key_detected": "Delt co-signer",
|
||||
"shared_key_detected_question": "En co-signer er blevet delt med dig, vil du importere den?",
|
||||
"manage_keys": "Administrer nøgler",
|
||||
"how_many_signatures_can_bluewallet_make": "hvor mange underskrifter kan BlueWallet lave",
|
||||
"signatures_required_to_spend": "Påkrævede underskrifter {number}",
|
||||
"how_many_signatures_can_bluewallet_make": "hvor mange signaturer kan BlueWallet lave",
|
||||
"signatures_required_to_spend": "Påkrævede signaturer {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": "Medunderskriv denne transaktion?",
|
||||
"cosign_this_transaction": "Co-sign denne transaktion?",
|
||||
"lets_start": "Lad os starte",
|
||||
"native_segwit_title": "Bedste praksis",
|
||||
"wrapped_segwit_title": "Bedste kompatibilitet",
|
||||
"legacy_title": "Legacy",
|
||||
"co_sign_transaction": "Underskriv en transaktion",
|
||||
"what_is_vault": "En boks er en",
|
||||
"co_sign_transaction": "Signer en transaktion",
|
||||
"what_is_vault": "En Vault er en",
|
||||
"what_is_vault_numberOfWallets": " {m}-af-{n} multisig ",
|
||||
"what_is_vault_wallet": "tegnebog.",
|
||||
"vault_advanced_customize": "Boks-indstillinger",
|
||||
"what_is_vault_wallet": "wallet.",
|
||||
"vault_advanced_customize": "Vault-indstillinger",
|
||||
"needs": "Den kræver",
|
||||
"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_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_to_spend_other": "til at sende.",
|
||||
"quorum": "{m} af {n} kvorum",
|
||||
"quorum_header": "Kvorum",
|
||||
"quorum": "{m} af {n} quorum",
|
||||
"quorum_header": "Quorum",
|
||||
"of": "af",
|
||||
"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.",
|
||||
"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.",
|
||||
"create_new_key": "Opret ny",
|
||||
"scan_or_open_file": "Scan eller åbn fil",
|
||||
"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.",
|
||||
"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.",
|
||||
"this_is_cosigners_xpub_airdrop": "Hvis du deler via AirDrop, skal modtagerne være på koordineringsskærmen.",
|
||||
"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",
|
||||
"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",
|
||||
"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-bokse: Tips og tricks",
|
||||
"ms_help_text": "En tegnebog med flere nøgler, til øget sikkerhed eller delt opbevaring",
|
||||
"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_title1": "Flere enheder anbefales.",
|
||||
"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_1": "Vault'en vil fungere med andre BlueWallet-apps og PSBT-kompatible wallets såsom Electrum, Specter, Coldcard, Cobo Vault osv.",
|
||||
"ms_help_title2": "Redigering af nøgler",
|
||||
"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."
|
||||
"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."
|
||||
},
|
||||
"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 tegnebøger ejer den angivne adresse.",
|
||||
"no_wallet_owns_address": "Ingen af de tilgængelige wallets ejer den angivne adresse.",
|
||||
"view_qrcode": "Vis QR-kode"
|
||||
},
|
||||
"autofill_word": {
|
||||
"title": "Sidste seed-ord",
|
||||
"enter": "Indtast din delvise mnemoniske frase",
|
||||
"enter": "Indtast din delvise mnemonic",
|
||||
"generate_word": "Generér det sidste ord",
|
||||
"error": "Inputtet er ikke en delvis mnemonisk frase med 11 eller 23 ord. Prøv venligst igen."
|
||||
"error": "Inputtet er ikke en delvis mnemonic med 11 eller 23 ord. Prøv venligst igen."
|
||||
},
|
||||
"cc": {
|
||||
"header": "Coin Control",
|
||||
"sort_label": "Etiket",
|
||||
"sort_status": "Status",
|
||||
"change": "Byttepenge",
|
||||
"coins_selected": "Mønter valgt ({number})",
|
||||
"coins_selected": "Coins valgt ({number})",
|
||||
"selected_summ": "{value} valgt",
|
||||
"empty": "Denne tegnebog har ingen mønter i øjeblikket.",
|
||||
"empty": "Denne wallet har ingen coins i øjeblikket.",
|
||||
"freeze": "Frys",
|
||||
"freezeLabel": "Frys",
|
||||
"freezeLabel_un": "Frigør",
|
||||
"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.",
|
||||
"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.",
|
||||
"sort_asc": "Stigende",
|
||||
"sort_desc": "Faldende",
|
||||
"sort_height": "Højde",
|
||||
"sort_value": "Værdi",
|
||||
"sort_status": "Status",
|
||||
"sort_by": "Sortér efter"
|
||||
},
|
||||
"units": {
|
||||
@ -648,14 +646,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": "Underskriv/verificer besked",
|
||||
"sign_title": "Signer/verificer besked",
|
||||
"sign_help": "Her kan du oprette eller verificere en kryptografisk signatur baseret på en Bitcoin-adresse.",
|
||||
"sign_sign": "Underskriv",
|
||||
"sign_sign": "Signer",
|
||||
"sign_verify": "Verificer",
|
||||
"sign_signature_correct": "Verificering lykkedes!",
|
||||
"sign_signature_incorrect": "Verificering mislykkedes!",
|
||||
@ -667,40 +665,40 @@
|
||||
},
|
||||
"lnurl_auth": {
|
||||
"register_question_part_1": "Vil du registrere en konto hos",
|
||||
"register_question_part_2": "ved hjælp af din Lightning-tegnebog?",
|
||||
"register_question_part_2": "ved hjælp af din Lightning-wallet?",
|
||||
"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-tegnebog?",
|
||||
"login_question_part_2": "ved hjælp af din Lightning-wallet?",
|
||||
"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-tegnebog?",
|
||||
"link_answer": "Din Lightning-tegnebog blev knyttet til din konto hos {hostname}!",
|
||||
"link_question_part_2": "til din Lightning-wallet?",
|
||||
"link_answer": "Din Lightning-wallet 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-tegnebog?",
|
||||
"auth_question_part_2": "ved hjælp af din Lightning-wallet?",
|
||||
"auth_answer": "Du er blevet godkendt hos {hostname}!",
|
||||
"could_not_auth": "Vi kunne ikke godkende dig hos {hostname}.",
|
||||
"authenticate": "Godkend"
|
||||
},
|
||||
"bip47": {
|
||||
"payment_code": "Betalingskode",
|
||||
"payment_code": "Payment Code",
|
||||
"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 betalingskode",
|
||||
"copy_payment_code": "Kopier Payment Code",
|
||||
"hide_contact": "Skjul kontakt",
|
||||
"rename": "Omdøb",
|
||||
"provide_name": "Angiv nyt navn til denne kontakt",
|
||||
"add_contact": "Tilføj kontakt",
|
||||
"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"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
239
loc/de_de.json
239
loc/de_de.json
@ -4,8 +4,9 @@
|
||||
"cancel": "Abbrechen",
|
||||
"continue": "Fortsetzen",
|
||||
"clipboard": "Zwischenablage",
|
||||
"copied": "Kopiert!",
|
||||
"discard_changes": "Änderungen verwerfen?",
|
||||
"discard_changes_explain": "Die ungespeicherten Änderungen verwerfen und den Bildschrim verlassen?",
|
||||
"discard_changes_explain": "Die ungespeicherten Änderungen verwerfen und den Bildschirm verlassen?",
|
||||
"enter_password": "Passwort eingeben",
|
||||
"never": "nie",
|
||||
"of": "{number} von {total}",
|
||||
@ -19,7 +20,7 @@
|
||||
"success": "Erfolg",
|
||||
"wallet_key": "Wallet Schlüssel",
|
||||
"close": "Schließen",
|
||||
"change_input_currency": "Eingangswährung ändern",
|
||||
"change_input_currency": "Eingabewährung ändern",
|
||||
"refresh": "Aktualisieren",
|
||||
"pick_image": "Aus der Bibliothek wählen",
|
||||
"pick_file": "Datei auswählen",
|
||||
@ -27,18 +28,17 @@
|
||||
"qr_custom_input_button": "10x antippen für individuelle Eingabe",
|
||||
"unlock": "Entsperren",
|
||||
"port": "Port",
|
||||
"ssl_port": "SSL Port",
|
||||
"suggested": "Vorgeschlagen",
|
||||
"copied": "Kopiert!"
|
||||
"ssl_port": "SSL-Port",
|
||||
"suggested": "Vorgeschlagen"
|
||||
},
|
||||
"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! Ihre Gelder sollten in Kürze in Ihrer Bitcoin-Wallet ankommen.",
|
||||
"successMessage": "Gutschein erfolgreich eingelöst! Deine Gelder sollten in Kürze in deiner 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 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.",
|
||||
"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.",
|
||||
"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": "Urbild",
|
||||
"preimage": "Pre-image",
|
||||
"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. 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.",
|
||||
"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.",
|
||||
"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 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": "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_no": "Nein, habe ich nicht.",
|
||||
"ask_yes": "Ja, habe ich.",
|
||||
"ok": "Ok, sie sind notiert.",
|
||||
"ok_lnd": "Die Sicherung 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."
|
||||
"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."
|
||||
},
|
||||
"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 ein Lightning Wallet zu verwenden.",
|
||||
"provided_address_is_invoice": "Diese Adresse ist für eine Lightning-Rechnung. Um diese Rechnung zu zahlen, ist eine 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 zu Übertragung ins Netzwerk.",
|
||||
"create_this_is_hex": "Dies ist die signierte Transaktion. Hexadezimal dargestellt und bereit zur Übertragung ins Netzwerk.",
|
||||
"create_to": "An",
|
||||
"create_tx_size": "Transaktionsgröße",
|
||||
"create_verify": "Verifiziere auf coinb.in",
|
||||
@ -157,7 +157,6 @@
|
||||
"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",
|
||||
@ -169,7 +168,7 @@
|
||||
"dynamic_next": "Nächste",
|
||||
"dynamic_prev": "Vorherige",
|
||||
"dynamic_start": "Start",
|
||||
"dynamic_stop": "Stop",
|
||||
"dynamic_stop": "Stopp",
|
||||
"fee_10m": "10min",
|
||||
"fee_1d": "1T",
|
||||
"fee_3h": "3h",
|
||||
@ -185,14 +184,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}",
|
||||
@ -204,14 +203,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 Stille Zahlung Adressen senden",
|
||||
"cant_send_to_bip47": "Dieses Wallet kann nicht an BIP47 Zahlungscodes senden",
|
||||
"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_find_bip47_notification": "Diesen Zahlungscode zuerst zu den Kontakten hinzufügen ",
|
||||
"problem_with_psbt": "PSBT-Problem"
|
||||
},
|
||||
"settings": {
|
||||
"about": "Über",
|
||||
"about_awesome": "Entwickelt mt dem eindrucksvollen",
|
||||
"about_awesome": "Entwickelt mit 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",
|
||||
@ -226,17 +225,19 @@
|
||||
"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 Sie BlueWallet schließen und erneut öffnen.",
|
||||
"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.",
|
||||
"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 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 und 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 Wallet 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 Wallets 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.",
|
||||
@ -248,33 +249,35 @@
|
||||
"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 setzten?",
|
||||
"set_electrum_server_as_default": "{server} als Standard-Electrum-Server festlegen?",
|
||||
"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 um Salden abzufragen, Transaktionen zu senden und Netzwerkdaten abzurufen ausschliesslich diesen Server verwenden. Prüfen sie 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 ausschließlich diesen Server verwenden, um Salden abzufragen, Transaktionen zu senden und Netzwerkdaten abzurufen. Prüfe 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ücksetzten",
|
||||
"electrum_reset": "Zurücksetzen",
|
||||
"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 Wallet ohne Passwortschutz direkt benutzbar. ",
|
||||
"encrypt_decrypt_q": "Die Speicherverschlüsselung wirklich aufheben? Hiermit werden die Wallets ohne Passwortschutz direkt benutzbar.",
|
||||
"encrypt_enc_and_pass": "Passwortgeschützt",
|
||||
"encrypt_storage_explanation_headline": "Speicherverschlüsselung aktivieren",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"biometrics_fail": "Wenn {type} nicht aktiviert ist oder entsperrt werden kann, alternativ Ihren Gerätepasscode verwenden.",
|
||||
"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.",
|
||||
"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.",
|
||||
@ -288,10 +291,11 @@
|
||||
"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 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.",
|
||||
"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",
|
||||
"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",
|
||||
@ -308,20 +312,15 @@
|
||||
"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 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.",
|
||||
"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.",
|
||||
"selfTest": "Selbsttest",
|
||||
"save": "Speichern",
|
||||
"saved": "Gespeichert",
|
||||
"success_transaction_broadcasted": "Erfolg! Diene Transaktion wurde übertragen.",
|
||||
"success_transaction_broadcasted": "Erfolg! Deine Transaktion wurde übertragen.",
|
||||
"total_balance": "Gesamtes Guthaben",
|
||||
"total_balance_explanation": "Zeigt das Wallet Guthaben auf dem Widget deiner Homepage",
|
||||
"total_balance_explanation": "Zeigt das Gesamtguthaben aller Wallets auf dem Widget deines Startbildschirms an.",
|
||||
"widgets": "Widgets",
|
||||
"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"
|
||||
"tools": "Werkzeuge"
|
||||
},
|
||||
"notifications": {
|
||||
"would_you_like_to_receive_notifications": "Soll bei Zahlungseingängen eine Benachrichtigung zugestellt werden?",
|
||||
@ -336,7 +335,6 @@
|
||||
"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).",
|
||||
@ -345,10 +343,9 @@
|
||||
"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",
|
||||
@ -370,8 +367,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.",
|
||||
@ -381,14 +378,11 @@
|
||||
"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 Wallet vorzutäsuchen. Sie können mit diesem Wallet keine Gelder kontrollieren oder senden, sondern nur den Kontostand einsehen.",
|
||||
"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.",
|
||||
"custom_fee_warning_title": "Warnung",
|
||||
"custom_fee_warning_description": "Gebühren unter 1 sat/vB sind gültig, aber werden ggf. nicht weitergeleitet.",
|
||||
"list_title_sent": "Gesendet",
|
||||
"custom_fee_warning_description": "Gebühren unter 1 sat/vB sind gültig, werden aber ggf. nicht weitergeleitet.",
|
||||
"details_eta_analyzing": "Analysiere...",
|
||||
"details_sent": "Gesendet",
|
||||
"details_section": "Details",
|
||||
@ -411,19 +405,19 @@
|
||||
"add_bitcoin_explain": "Einfache und leistungsstarke Bitcoin Wallet",
|
||||
"add_create": "Erstellen",
|
||||
"total_balance": "Gesamtes Guthaben",
|
||||
"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_reset_title": "Entropie zurücksetzen",
|
||||
"add_entropy_reset_message": "Ein Wechsel des Wallet-Typs wird die Entropie zurücksetzen. Wirklich fortfahren?",
|
||||
"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": "Ihren LNDhub verbinden",
|
||||
"add_lndhub": "Mit deinem LNDhub verbinden",
|
||||
"add_lndhub_error": "Die Adresse verweist auf einen ungültigen LNDhub-Knoten.",
|
||||
"add_lndhub_placeholder": "Bitcoin Knoten-Adresse",
|
||||
"add_lndhub_placeholder": "Knoten-Adresse",
|
||||
"add_placeholder": "Mein Wallet",
|
||||
"add_title": "Wallet hinzufügen",
|
||||
"add_wallet_name": "Wallet Name",
|
||||
@ -431,42 +425,46 @@
|
||||
"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 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_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_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": "Fingerabdruckkennung",
|
||||
"details_multisig_type": "Mehrfachsignatur",
|
||||
"details_master_fingerprint": "Master-Fingerabdruck",
|
||||
"details_multisig_type": "Multisig",
|
||||
"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üssellung eingeben",
|
||||
"enter_bip38_password": "Passwort zur Entschlüsselung 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 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_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_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 Wallet nur zum Anschauen, du kannst NICHT mit ihr ausgeben.",
|
||||
"import_success_watchonly": "Deine Wallet wurde erfolgreich importiert. WARNUNG: Dies ist eine reine Watch-only-Wallet, du kannst von ihr NICHT ausgeben.",
|
||||
"import_search_accounts": "Konten suchen",
|
||||
"import_title": "Importieren",
|
||||
"learn_more": "Mehr erfahren",
|
||||
@ -486,7 +484,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 das Lightning Wallet für Deine täglichen Bezahlungen. Lightning Transaktionen sind konkurrenzlos günstig und verblüffend schnell.",
|
||||
"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_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",
|
||||
@ -496,35 +494,30 @@
|
||||
"list_long_scan": "QR Code scannen",
|
||||
"list_title": "Wallets",
|
||||
"list_tryagain": "Nochmal versuchen",
|
||||
"no_ln_wallet_error": "Vor Bezahlung einer Lightning Rechnung zuerst ein Lightning Wallet eröffnen.",
|
||||
"no_ln_wallet_error": "Vor Bezahlung einer Lightning-Rechnung zuerst eine 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 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.",
|
||||
"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.",
|
||||
"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 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.",
|
||||
"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.",
|
||||
"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 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"
|
||||
"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"
|
||||
},
|
||||
"total_balance_view": {
|
||||
"display_in_bitcoin": "In bitcoin anzeigen",
|
||||
@ -539,20 +532,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 Ihrem Gerät und der Wallet, in der der Schlüssel ist, die Transaktion signieren.",
|
||||
"provide_signature_details": "Mit dem 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 Ihre 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 die 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": "Gebhür: {number}",
|
||||
"fee": "Gebühr: {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 Ihnen geteilt. Diesen jetzt importierten?",
|
||||
"shared_key_detected_question": "Ein Mitsignierer wurde mit dir geteilt. Diesen jetzt importieren?",
|
||||
"manage_keys": "Schlüssel verwalten",
|
||||
"how_many_signatures_can_bluewallet_make": "Anzahl Signaturen durch BlueWallet",
|
||||
"signatures_required_to_spend": "Benötigte Signaturen {number}",
|
||||
@ -567,7 +560,7 @@
|
||||
"legacy_title": "Altformat",
|
||||
"co_sign_transaction": "Transaktion signieren",
|
||||
"what_is_vault": "Ein Tresor ist ein ",
|
||||
"what_is_vault_numberOfWallets": "{m}-von-{n} Multisignatur",
|
||||
"what_is_vault_numberOfWallets": "{m}-von-{n} Multisig",
|
||||
"what_is_vault_wallet": "wallet",
|
||||
"vault_advanced_customize": "Tresor Einstellungen",
|
||||
"needs": "Es braucht",
|
||||
@ -578,39 +571,39 @@
|
||||
"quorum_header": "Signaturfähigkeit",
|
||||
"of": "von",
|
||||
"wallet_type": "Typ des Wallets",
|
||||
"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_mnemonics": "Ungültiger Seed.",
|
||||
"invalid_cosigner": "Die Mitsignierer-Daten sind ungültig",
|
||||
"not_a_multisignature_xpub": "Dies ist kein xPub einer Multisig-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 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.",
|
||||
"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.",
|
||||
"this_is_cosigners_xpub_airdrop": "Zur AirDrop-Freigabe müssen alle Empfänger auf dem Koordinierungsbildschirm sein.",
|
||||
"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!",
|
||||
"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!",
|
||||
"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": "Fingerabdruckkennung eingeben",
|
||||
"input_fp": "Fingerprint 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": "Ein Wallet mit mehreren Schlüsseln zur gemeinsamen Verwahrung oder zur Erhöhung der Sicherheit.",
|
||||
"ms_help_text": "Eine 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ä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_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_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 des 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 der Wallet essenziell. Es ist wie eine Karte zu deinem Vermögen.",
|
||||
"ms_help_title4": "Tresor importieren",
|
||||
"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_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_title5": "Erweiterte Optionen",
|
||||
"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."
|
||||
"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."
|
||||
},
|
||||
"is_it_my_address": {
|
||||
"title": "Ist dies meine Adresse?",
|
||||
@ -621,22 +614,22 @@
|
||||
"view_qrcode": "QR-Code anzeigen"
|
||||
},
|
||||
"autofill_word": {
|
||||
"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.",
|
||||
"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.",
|
||||
"title": "Letztes Seed-Wort"
|
||||
},
|
||||
"cc": {
|
||||
"change": "Ändern",
|
||||
"coins_selected": "Anz. gewählte Münzen ({number})",
|
||||
"selected_summ": "{value} ausgewählt",
|
||||
"empty": "Dieses Wallet hat aktuell keine Münzen.",
|
||||
"empty": "Diese Wallet hat aktuell keine Münzen.",
|
||||
"freeze": "einfrieren",
|
||||
"freezeLabel": "Einfrieren",
|
||||
"freezeLabel_un": "Entblocken",
|
||||
"freezeLabel_un": "Auftauen",
|
||||
"header": "Münzkontrolle",
|
||||
"use_coin": "Münzen benutzen",
|
||||
"use_coins": "Benutze Münzen",
|
||||
"use_coins": "Münzen benutzen",
|
||||
"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",
|
||||
@ -654,15 +647,15 @@
|
||||
},
|
||||
"addresses": {
|
||||
"copy_private_key": "Privaten Schlüssel kopieren",
|
||||
"sensitive_private_key": "Warnung: Private Schlüssel sind gefahrvoll. Weiterfahren?",
|
||||
"sign_title": "Meldung signieren/verifizieren",
|
||||
"sensitive_private_key": "Warnung: Private Schlüssel sind hochsensibel. Fortfahren?",
|
||||
"sign_title": "Nachricht 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": "Meldung",
|
||||
"sign_placeholder_message": "Nachricht",
|
||||
"sign_placeholder_signature": "Signatur",
|
||||
"addresses_title": "Adressen",
|
||||
"type_change": "Wechsel",
|
||||
@ -697,13 +690,13 @@
|
||||
"copy_payment_code": "Zahlungscode kopieren",
|
||||
"hide_contact": "Kontakt ausblenden",
|
||||
"rename": "Umbenennen",
|
||||
"provide_name": "Neuer Kontaktnahme eingeben",
|
||||
"provide_name": "Neuen Kontaktnamen 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 in erstellt werden",
|
||||
"onchain_tx_needed": "On-Chain Transaktion benötigt.",
|
||||
"failed_create_notif_tx": "On-Chain-Transaktion konnte nicht 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"
|
||||
|
||||
26
loc/el.json
26
loc/el.json
@ -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 Repository",
|
||||
"lndhub_github": "Αποθετήριο GitHub",
|
||||
"electrum_suggested_description": "Όταν δεν έχει οριστεί προτιμώμενος διακομιστής, ένας προτεινόμενος διακομιστής θα επιλέγεται για χρήση τυχαία.",
|
||||
"open_link_in_explorer": "Άνοιγμα συνδέσμου στον εξερευνητή",
|
||||
"password_explain": "Εισάγετε τον κωδικό που θα χρησιμοποιείτε για να ξεκλειδώνετε τον αποθηκευτικό σας χώρο.",
|
||||
@ -331,21 +331,18 @@
|
||||
"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": "Συναλλαγή",
|
||||
@ -411,7 +408,7 @@
|
||||
"add_entropy": "Εντροπία",
|
||||
"add_import_wallet": "Εισαγωγή πορτοφολιού",
|
||||
"add_lightning": "Lightning",
|
||||
"add_lndhub_placeholder": "Η διεύθυνση του κόμβου σας",
|
||||
"add_lndhub_placeholder": "Η διεύθυνση του κόμβου σας",
|
||||
"add_placeholder": "το πρώτο μου πορτοφόλι",
|
||||
"add_title": "Προσθήκη πορτοφολιού",
|
||||
"add_wallet_name": "Όνομα",
|
||||
@ -455,7 +452,6 @@
|
||||
"list_title": "Πορτοφόλια",
|
||||
"list_tryagain": "Προσπαθήστε ξανά",
|
||||
"select_wallet": "Επιλογή πορτοφολιού",
|
||||
"xpub_copiedToClipboard": "Αντιγράφηκε στο πρόχειρο",
|
||||
"xpub_title": "XPUB του πορτοφολιού",
|
||||
"add_entropy_reset_title": "Επαναφορά εντροπίας",
|
||||
"add_entropy_reset_message": "Η αλλαγή του τύπου πορτοφολιού θα επαναφέρει την τρέχουσα εντροπία. Θέλετε να προχωρήσετε;",
|
||||
@ -544,7 +540,7 @@
|
||||
"native_segwit_title": "Καλές πρακτικές",
|
||||
"co_sign_transaction": "Υπογραφή μιας συναλλαγής",
|
||||
"what_is_vault_wallet": "πορτοφόλι.",
|
||||
"wallet_type": "Τύπος πορτοφολιου",
|
||||
"wallet_type": "Τύπος πορτοφολιού",
|
||||
"create_new_key": "Δημιουργία νέου",
|
||||
"scan_or_open_file": "Σάρωση ή άνοιγμα αρχείου",
|
||||
"ms_help": "Βοήθεια",
|
||||
|
||||
18
loc/en.json
18
loc/en.json
@ -56,6 +56,7 @@
|
||||
"errorInvoiceExpired": "Invoice expired.",
|
||||
"expired": "Expired",
|
||||
"expiresIn": "Expires in {time} minutes",
|
||||
"network_fee": "Network fee: {fee}",
|
||||
"payButton": "Pay",
|
||||
"payment": "Payment",
|
||||
"placeholder": "Invoice or address",
|
||||
@ -76,7 +77,13 @@
|
||||
"preimage": "Pre-image",
|
||||
"sats": "sats.",
|
||||
"date_time": "Date and Time",
|
||||
"wasnt_paid_and_expired": "This invoice was not paid and has expired."
|
||||
"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."
|
||||
},
|
||||
"plausibledeniability": {
|
||||
"create_fake_storage": "Create Encrypted Storage",
|
||||
@ -281,7 +288,6 @@
|
||||
"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 BlueWallet’s infrastructure. Leave blank to use GroundControl’s default server.",
|
||||
"header": "Settings",
|
||||
"language": "Language",
|
||||
"last_updated": "Last Updated",
|
||||
@ -297,7 +303,6 @@
|
||||
"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",
|
||||
@ -315,7 +320,6 @@
|
||||
"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.",
|
||||
@ -376,7 +380,6 @@
|
||||
"rbf_title": "Speed Up (RBF)",
|
||||
"status_bump": "Speed Up",
|
||||
"status_cancel": "Cancel",
|
||||
"transactions_count": "Transactions Count",
|
||||
"txid": "Transaction ID",
|
||||
"updating": "Updating...",
|
||||
"watchOnlyWarningTitle": "Security warning",
|
||||
@ -438,13 +441,18 @@
|
||||
"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",
|
||||
|
||||
122
loc/es.json
122
loc/es.json
@ -2,10 +2,10 @@
|
||||
"_": {
|
||||
"bad_password": "Contraseña incorrecta. Por favor, inténtalo de nuevo.",
|
||||
"cancel": "Cancelar",
|
||||
"continue": "Continua",
|
||||
"continue": "Continuar",
|
||||
"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",
|
||||
"error": "Error",
|
||||
"network": "Error de red"
|
||||
"network": "Error de red",
|
||||
"error": "Error"
|
||||
},
|
||||
"lnd": {
|
||||
"errorInvoiceExpired": "Factura caducada.",
|
||||
@ -70,19 +70,19 @@
|
||||
"lndViewInvoice": {
|
||||
"additional_info": "Información adicional",
|
||||
"for": "Para:",
|
||||
"lightning_invoice": "Factura Lighting",
|
||||
"lightning_invoice": "Factura Lightning",
|
||||
"please_pay_between_and": "Paga entre {min} y {max}",
|
||||
"please_pay": "Por favor, pague",
|
||||
"please_pay": "Por favor, paga",
|
||||
"preimage": "Preimagen",
|
||||
"sats": "sats.",
|
||||
"date_time": "Fecha y hora",
|
||||
"wasnt_paid_and_expired": "Esta factura no fue pagada y ha expirado."
|
||||
"wasnt_paid_and_expired": "Esta factura no fue pagada y ha expirado.",
|
||||
"sats": "sats"
|
||||
},
|
||||
"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 almacen sera completamente funcional, y puedes almacenar cantidades minimas para que sea mas creible.",
|
||||
"help2": "El nuevo almacén será completamente funcional, y puedes almacenar cantidades mínimas para que sea más creíble.",
|
||||
"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. Será tu copia de seguridad y te permitirá restaurar la cartera en otro dispositivo.",
|
||||
"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_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": "Escanear código QR",
|
||||
"create_copy": "Copiar y emitir más tarde",
|
||||
"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 emitido a través de la red.",
|
||||
"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_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 wallet para 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_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, introduzca 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, introduce 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. Tenga en cuenta que las monedas congeladas están excluidas",
|
||||
"details_total_exceeds_balance_frozen": "La cantidad a enviar excede el balance disponible. Ten 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 de la tarifa de cambio: {date}",
|
||||
"outdated_rate": "Fecha de la última actualización del tipo 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 todos los destinatarios?",
|
||||
"details_add_recc_rem_all_alert_description": "¿Seguro que quieres eliminar a 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": "Built with the awesome",
|
||||
"about_awesome": "Construido con el increíble",
|
||||
"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 billetera 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 cartera 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 intentaran actualizar balances ni transacciones.",
|
||||
"electrum_offline_description": "Al habilitarlo, tus carteras de Bitcoin no intentarán 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á ver las transacciones y carteras seleccionadas usando cualquiera de sus dispositivos Apple conectados a iCloud.",
|
||||
"general_continuity_e": "Al activarlo, podrás ver las transacciones y carteras seleccionadas usando cualquiera de tus 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": "Desabilitar Analytics",
|
||||
"privacy_do_not_track": "Deshabilitar analíticas",
|
||||
"privacy_do_not_track_explanation": "Los datos sobre funcionamiento y fiabilidad no serán enviados para ser analizados.",
|
||||
"rate": "Tasa",
|
||||
"selfTest": "Self-Test",
|
||||
"selfTest": "Autodiagnóstico",
|
||||
"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": "¿Quires recibir notificaciones cuando detectemos transferencias entrantes?",
|
||||
"would_you_like_to_receive_notifications": "¿Quieres 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,7 +333,6 @@
|
||||
"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.",
|
||||
@ -345,33 +344,31 @@
|
||||
"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_from": "Origen",
|
||||
"details_inputs": "Inputs",
|
||||
"details_outputs": "Outputs",
|
||||
"details_inputs": "Entradas",
|
||||
"details_outputs": "Salidas",
|
||||
"date": "Fecha",
|
||||
"details_received": "Recibido",
|
||||
"details_title": "Transaccion",
|
||||
"details_title": "Transacción",
|
||||
"details_to": "Destino",
|
||||
"enable_offline_signing": "Esta billetera no se está usando en conjunción con una firma offline. ¿Quieres activarlo ahora? ",
|
||||
"list_conf": "Conf: {number}",
|
||||
"enable_offline_signing": "Esta cartera no se está usando en conjunción con una firma offline. ¿Quieres activarlo ahora?",
|
||||
"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",
|
||||
"view_wallet": "Ver {walletLabel}",
|
||||
"list_conf": "Conf: {number}",
|
||||
"list_title": "Transacciones",
|
||||
"list_title_received": "Recibido",
|
||||
"transaction": "Transaccion",
|
||||
"transaction": "Transacción",
|
||||
"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 comisón",
|
||||
"status_bump": "Aumentar comisió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",
|
||||
@ -392,7 +389,6 @@
|
||||
"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",
|
||||
@ -401,17 +397,18 @@
|
||||
"details_virtual_size": "Tamaño virtual",
|
||||
"details_tx_hex": "Hex de la tx",
|
||||
"details_inputs_count": "Entradas ({count})",
|
||||
"details_outputs_count": "Salidas ({count})"
|
||||
"details_outputs_count": "Salidas ({count})",
|
||||
"details_id": "ID"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "Bitcoin",
|
||||
"add_bitcoin_explain": "Una cartera de Bitcoin útil y facil de usar",
|
||||
"add_bitcoin_explain": "Una cartera de Bitcoin útil y fácil 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 of entropía generada. Los {rem} bytes restantes serán obtenidos del generador de números aleatorios.",
|
||||
"add_entropy_remain": "{gen} bytes de 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",
|
||||
@ -440,15 +437,15 @@
|
||||
"wallets": "Carteras",
|
||||
"details_type": "Tipo",
|
||||
"details_use_with_hardware_wallet": "Usar con cartera de hardware",
|
||||
"details_yes_delete": "Si, eliminar",
|
||||
"enter_bip38_password": "Introduce el password para descifrar",
|
||||
"details_yes_delete": "Sí, eliminar",
|
||||
"enter_bip38_password": "Introduce la contraseña para descifrar",
|
||||
"export_title": "Exportación de cartera",
|
||||
"import_do_import": "Importar",
|
||||
"import_passphrase": "Passphrase",
|
||||
"import_passphrase_title": "Passphrase",
|
||||
"import_passphrase": "Frase de contraseña",
|
||||
"import_passphrase_title": "Frase de contraseña",
|
||||
"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.",
|
||||
@ -472,16 +469,15 @@
|
||||
"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 debe agregar una cartera Lightning.",
|
||||
"no_ln_wallet_error": "Antes de pagar una factura Lightning, primero debes 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, cree o importe una.",
|
||||
"select_no_bitcoin_exp": "Una cartera de Bitcoin es necesaria para recargar una cartera Lightning. Por favor, crea o importa 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": "Identity Pubkey",
|
||||
"identity_pubkey": "Clave pública de identidad",
|
||||
"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?",
|
||||
@ -548,8 +544,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 coordinacion",
|
||||
"cosign_this_transaction": "Co-firmar esta transacción?",
|
||||
"export_coordination_setup": "Exportar coordinación",
|
||||
"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",
|
||||
@ -584,12 +580,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 multiples dispositivos.",
|
||||
"ms_help_title1": "Se recomienda usar múltiples 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",
|
||||
@ -663,16 +659,16 @@
|
||||
},
|
||||
"lnurl_auth": {
|
||||
"register_question_part_1": "¿Te gustaría registrar una cuenta en",
|
||||
"register_question_part_2": "usando tu billetera Lightning?",
|
||||
"register_question_part_2": "usando tu cartera 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 billetera Lightning?",
|
||||
"login_question_part_2": "usando tu cartera 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 billetera Lightning?",
|
||||
"auth_question_part_2": "usando tu cartera Lightning?",
|
||||
"auth_answer": "¡Te has autenticado con éxito en {hostname}!",
|
||||
"could_not_auth": "No pudimos autenticarte en {hostname}.",
|
||||
"authenticate": "Autenticar"
|
||||
|
||||
@ -2,19 +2,19 @@
|
||||
"_": {
|
||||
"bad_password": "Contraseña incorrecta. Intenta nuevamente.",
|
||||
"cancel": "Cancelar",
|
||||
"continue": "Continúa",
|
||||
"continue": "Continuar",
|
||||
"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": "Tasas potenciales: {fee}",
|
||||
"potentialFee": "Comisión potencial: {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": "Pagar por favor",
|
||||
"preimage": "Imagen previa",
|
||||
"please_pay": "Por favor paga",
|
||||
"preimage": "Preimagen",
|
||||
"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 mas creíble.",
|
||||
"help2": "El nuevo almacén será completamente funcional, y puedes almacenar cantidades mínimas para que sea más 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": "Transmitiendo",
|
||||
"broadcastButton": "Transmitir",
|
||||
"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": "Tasa",
|
||||
"create_fee": "Comisión",
|
||||
"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,12 +152,11 @@
|
||||
"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 tasa no es válida.",
|
||||
"details_fee_field_is_not_valid": "La comisión 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",
|
||||
@ -174,10 +173,10 @@
|
||||
"fee_1d": "1d",
|
||||
"fee_3h": "3h",
|
||||
"fee_custom": "Personalizado",
|
||||
"insert_custom_fee": "Insertar tasa",
|
||||
"insert_custom_fee": "Insertar comisión",
|
||||
"fee_fast": "Rápido",
|
||||
"fee_medium": "Medio",
|
||||
"fee_replace_minvb": "La tasa de tarifa total (satoshi por vByte) que deseas pagar debe ser superior a {min} sat/vByte.",
|
||||
"fee_replace_minvb": "La comisión total (satoshi por vByte) que deseas pagar debe ser superior a {min} sat/vByte.",
|
||||
"fee_satvbyte": "en sat/vByte",
|
||||
"fee_slow": "Lento",
|
||||
"header": "Enviar",
|
||||
@ -192,10 +191,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 hardware wallet.",
|
||||
"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_tx_export": "Exportar a archivo",
|
||||
"no_tx_signing_in_progress": "No hay ninguna transacción en curso.",
|
||||
"outdated_rate": "La tarifa se actualizó por última vez: {date}",
|
||||
"outdated_rate": "La cotización 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.",
|
||||
@ -206,7 +205,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": {
|
||||
@ -221,22 +220,23 @@
|
||||
"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 cierre y vuelva 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 cierres y vuelvas 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 tarifa se obtiene de",
|
||||
"currency_source": "La cotización 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,9 +279,8 @@
|
||||
"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 carteras seleccionadas y transacciones utilizando tus otros dispositivos conectados a Apple iCloud.",
|
||||
"general_continuity_e": "Cuando esté habilitado, podrás ver billeteras 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",
|
||||
@ -293,7 +292,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": "GitHub Repository",
|
||||
"lndhub_github": "Repositorio de GitHub",
|
||||
"network": "Red",
|
||||
"network_broadcast": "Publicar transacción",
|
||||
"network_electrum": "Servidor Electrum",
|
||||
@ -308,7 +307,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 cartera.",
|
||||
"privacy_quickactions_explanation": "Mantén pulsado el icono de la aplicación BlueWallet para ver rápidamente el saldo de tu billetera.",
|
||||
"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.",
|
||||
@ -320,8 +319,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.",
|
||||
"widgets": "Widgets",
|
||||
"tools": "Herramientas"
|
||||
"tools": "Herramientas",
|
||||
"widgets": "Widgets"
|
||||
},
|
||||
"notifications": {
|
||||
"would_you_like_to_receive_notifications": "¿Te gustaría recibir notificaciones cuando recibas pagos entrantes?",
|
||||
@ -332,11 +331,10 @@
|
||||
"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 ésta transacción (RBF)",
|
||||
"cancel_title": "Cancelar esta 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).",
|
||||
@ -348,7 +346,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_from": "Entrada",
|
||||
"details_id": "ID",
|
||||
"details_inputs": "Entradas",
|
||||
"details_outputs": "Salidas",
|
||||
"date": "Fecha",
|
||||
@ -363,14 +361,13 @@
|
||||
"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",
|
||||
"view_wallet": "Ver {walletLabel}",
|
||||
"list_conf": "Conf: {number}",
|
||||
"list_title": "Transacciones",
|
||||
"list_title_sent": "Enviado",
|
||||
"list_title_received": "Recibido",
|
||||
@ -382,8 +379,6 @@
|
||||
"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.",
|
||||
@ -395,7 +390,6 @@
|
||||
"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",
|
||||
@ -413,7 +407,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",
|
||||
@ -431,8 +425,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 usarlo para una transacción?",
|
||||
"clipboard_lightning": "Tienes una factura Lightning en tu portapapeles. ¿Te gustaría usarlo para una transacción?",
|
||||
"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?",
|
||||
"clear_clipboard_on_import": "Limpiar el portapapeles al importar",
|
||||
"details_address": "Dirección",
|
||||
"details_advanced": "Avanzado",
|
||||
@ -458,7 +452,7 @@
|
||||
"clear_search": "Limpiar búsqueda",
|
||||
"details_type": "Tipo",
|
||||
"details_use_with_hardware_wallet": "Usar con Billetera de Hardware",
|
||||
"details_yes_delete": "Si, eliminar",
|
||||
"details_yes_delete": "Sí, eliminar",
|
||||
"enter_bip38_password": "Ingresa la contraseña para descifrar",
|
||||
"export_title": "Exportación de Billetera",
|
||||
"import_do_import": "Importar",
|
||||
@ -470,7 +464,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 él.",
|
||||
"import_success_watchonly": "Tu billetera se ha importado correctamente. ADVERTENCIA: Esta es una billetera de \"solo ver\", NO puedes gastar desde ella.",
|
||||
"import_search_accounts": "Buscar cuentas",
|
||||
"import_title": "Importar",
|
||||
"learn_more": "Saber más",
|
||||
@ -482,7 +476,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 cartera.",
|
||||
"import_derivation_subtitle": "Introduce la ruta de derivación personalizada e intentaremos descubrir tu billetera.",
|
||||
"import_derivation_title": "Ruta de derivación",
|
||||
"import_derivation_unknown": "Desconocido",
|
||||
"import_wrong_path": "Ruta de derivación incorrecta",
|
||||
@ -508,7 +502,6 @@
|
||||
"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.",
|
||||
@ -555,7 +548,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 firmas {number}",
|
||||
"signatures_required_to_spend": "Se requieren {number} firmas",
|
||||
"signatures_we_can_make": "puede hacer {number}",
|
||||
"scan_or_import_file": "Escanear o importar archivo",
|
||||
"export_coordination_setup": "Exportar Configuración de Coordinación",
|
||||
@ -574,8 +567,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": "{m} of {n} quorum",
|
||||
"quorum_header": "Quorum",
|
||||
"quorum": "Quórum de {m} de {n}",
|
||||
"quorum_header": "Quórum",
|
||||
"of": "de",
|
||||
"wallet_type": "Tipo de Billetera",
|
||||
"invalid_mnemonics": "Esta frase mnemotécnica no parece válida.",
|
||||
@ -589,7 +582,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 segur@? Tu semilla mnemotécnica se perderá si no tienes una copia de seguridad.",
|
||||
"are_you_sure_seed_will_be_lost": "¿Estás seguro? 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.",
|
||||
@ -606,7 +599,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",
|
||||
@ -617,7 +610,7 @@
|
||||
"owns": "{label} posee {address}",
|
||||
"enter_address": "Ingresar dirección",
|
||||
"check_address": "Verificar dirección",
|
||||
"no_wallet_owns_address": "Ninguna de las carteras disponibles posee la dirección proporcionada.",
|
||||
"no_wallet_owns_address": "Ninguna de las billeteras disponibles posee la dirección proporcionada.",
|
||||
"view_qrcode": "Ver código QR"
|
||||
},
|
||||
"autofill_word": {
|
||||
@ -637,7 +630,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 cartera. 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 billetera. Puedes seleccionar varias monedas tocando los círculos de colores.",
|
||||
"sort_asc": "Ascendente",
|
||||
"sort_desc": "Descendente",
|
||||
"sort_height": "Altura",
|
||||
|
||||
@ -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": "Ülekanne nurjus.",
|
||||
"broadcast": "Leviedastus 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} 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}",
|
||||
"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}",
|
||||
"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 satid.",
|
||||
"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_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": "10m",
|
||||
"fee_10m": "10min",
|
||||
"fee_1d": "1p",
|
||||
"fee_3h": "3t",
|
||||
"fee_custom": "Kohandatud",
|
||||
@ -346,6 +346,7 @@
|
||||
"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",
|
||||
@ -389,7 +390,6 @@
|
||||
"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 autentitud saidil {hostname}!",
|
||||
"auth_answer": "Oled edukalt autenditud saidil {hostname}!",
|
||||
"could_not_auth": "Me ei suutnud sind autentida saidil {hostname}.",
|
||||
"authenticate": "Autendi"
|
||||
},
|
||||
|
||||
24
loc/fa.json
24
loc/fa.json
@ -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) را روی یک تکه کاغذ یادداشت کنید. این کلمهها نسخهٔ پشتیبان شما هستند، و میتوانید از آنها برای بازیابی کیف پول در دستگاه دیگری استفاده کنید.",
|
||||
"text": "لطفاً درنگ کرده و این عبارت یادیار (mnemonic phrase) را روی یک تکه کاغذ یادداشت کنید.\nاین کلمهها نسخهٔ پشتیبان شما هستند، و میتوانید از آنها برای بازیابی کیف پول در دستگاه دیگری استفاده کنید.",
|
||||
"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_error_connect": "اتصال به سرور Electrum ارائهشده ممکن نیست",
|
||||
"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": "مخزن گیتهاب",
|
||||
"lndhub_github": "مخزن GitHub",
|
||||
"network": "شبکه",
|
||||
"network_broadcast": "انتشار تراکنش",
|
||||
"network_electrum": "سرور الکترام",
|
||||
@ -335,7 +335,6 @@
|
||||
"transaction_loading_error": "در بارگذاری تراکنش مشکلی پیش آمد. لطفاً بعداً دوباره تلاش کنید.",
|
||||
"transaction_not_available": "تراکنش در دسترس نیست",
|
||||
"confirmations_lowercase": "{confirmations} تأیید",
|
||||
"copy_link": "کپی لینک",
|
||||
"expand_note": "نمایش کامل یادداشت",
|
||||
"cpfp_create": "ایجاد",
|
||||
"cpfp_exp": "ما تراکنش دیگری را ایجاد خواهیم کرد که تراکنش تأییدنشدهٔ شما را خرج میکند. کارمزد کل بیشتر از کارمزد تراکنش اولیه خواهد بود، بنابراین سریعتر تأیید میشود. این کار Child Pays for Parent (بهاختصار CPFP) نام دارد—فرزند بهجای والدین میپردازد.",
|
||||
@ -347,7 +346,6 @@
|
||||
"details_copy_block_explorer_link": "کپی لینک مرورگر بلاک",
|
||||
"details_copy_note": "کپی یادداشت",
|
||||
"details_copy_txid": "کپی شناسهٔ تراکنش",
|
||||
"details_from": "ورودی",
|
||||
"details_inputs": "ورودیها",
|
||||
"details_outputs": "خروجیها",
|
||||
"date": "تاریخ",
|
||||
@ -358,8 +356,8 @@
|
||||
"outgoing_transaction": "تراکنش خروجی",
|
||||
"expired_transaction": "تراکنش منقضیشده",
|
||||
"pending_transaction": "تراکنش در انتظار ثبت",
|
||||
"offchain": "آفچین",
|
||||
"onchain": "آنچین",
|
||||
"offchain": "خارج از زنجیره",
|
||||
"onchain": "روی زنجیره",
|
||||
"details_to": "خروجی",
|
||||
"enable_offline_signing": "این کیف پول در کنار امضای آفلاین استفاده نمیشود. آیا مایل به فعالکردن این امکان هستید؟",
|
||||
"list_conf": "تأییدها: {number}",
|
||||
@ -369,7 +367,6 @@
|
||||
"eta_10m": "زمان تقریبی رسیدن: حدود ده دقیقه",
|
||||
"eta_3h": "زمان تقریبی رسیدن: حدود سه ساعت",
|
||||
"eta_1d": "زمان تقریبی رسیدن: حدود یک روز",
|
||||
"view_wallet": "مشاهدهٔ {walletLabel}",
|
||||
"list_title": "تراکنشها",
|
||||
"list_title_sent": "ارسالشده",
|
||||
"list_title_received": "دریافتشده",
|
||||
@ -505,7 +502,6 @@
|
||||
"select_no_bitcoin": "هیچ کیف پول بیتکوینی درحالحاضر دردسترس نیست.",
|
||||
"select_no_bitcoin_exp": "یک کیف پول بیتکوین برای پرکردن کیف پولهای لایتنینگ نیاز است. لطفاً یکی بسازید یا وارد کنید.",
|
||||
"select_wallet": "انتخاب کیف پول",
|
||||
"xpub_copiedToClipboard": "در کلیپبورد کپی شد.",
|
||||
"pull_to_refresh": "برای بهروزسانی به پایین بکشید",
|
||||
"warning_do_not_disclose": "هرگز اطلاعات زیر را به اشتراک نگذارید",
|
||||
"scan_import": "این کد QR را اسکن کنید تا کیف پول خود را در برنامهٔ دیگری وارد کنید.",
|
||||
@ -688,10 +684,10 @@
|
||||
"contacts": "مخاطبان",
|
||||
"bip47_explain": "کد بازکاربردپذیر و قابل همرسانی",
|
||||
"bip47_explain_subtitle": "BIP47",
|
||||
"purpose": "کد بازکاربردپذیر و قابل همرسانی (بیپ47)",
|
||||
"purpose": "کد بازکاربردپذیر و قابل همرسانی (BIP47)",
|
||||
"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
Loading…
Reference in New Issue
Block a user